본문으로 건너뛰기

Pusher 를 이용한 실시간 채팅 웹앱 만들기 #3 (w/ Laravel, Nuxt)

변찬혁실시간 채팅 앱 만들기 (Pusher)PusherLaravelNuxtVueWeb

Pusher 를 이용한 실시간 채팅 웹앱 만들기 #3 (w/ Laravel, Nuxt)

이번 글에서는 본격적으로 채팅 서비스를 위한 백엔드 코드들을 Laravel로 작성해보면서 Laravel에서 Pusher와 연동하여 이벤트를 클라이언트들로 브로드캐스팅 해보겠습니다.

가장 기본적인 기능 구현하기

컨트롤러 생성

일단 먼저 어떤 사용자가 다른 사용자에게 보낼 메세지를 작성하고, 엔터를 누르거나 '보내기' 버튼을 누르면 그 메세지가 우리의 서버를 거쳐서 채팅에 참여하는 다른 사용자들에게 보내져야 할 것입니다.

그러기 위해서는 먼저 우리 서버에서 이렇게 사용자가 보낼 메세지를 담은 요청을 처리할 무언가가 필요합니다. 그 무언가를 우리는 ChatController 라고 이름 붙이고 이것을 만들어나가겠습니다.

프로젝트 루트(artisan 파일이 있는 디렉토리)에서 아래 artisan 명령을 사용하면 가장 기본적인 Laravel 컨트롤러의 뼈대를 가진 app/Http/Controllers/ChatController.php 가 생성됩니다. 직접 파일을 만들어도 되지만 artisan 명령을 사용하면 편리하게 만들 수 있습니다.

php artisan make:controller ChatController

만들어진 파일은 가장 기본적인 컨트롤러의 형태입니다. 여기에 사용자가 메세지를 보내려고 할 때의 요청을 처리할 send 메서드를 추가합니다. 내용은 아직 구현하지 않습니다.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ChatController extends Controller
{
    public function send()
    {
        // Todo..
    }
}

라우팅 정의

지금 만든 컨트롤러는 '어떤 요청을 누가 처리할 건가?' 에서 '누가' 부분입니다. 어떤 컨트롤러의 메서드들이 어떤 요청을 담당해서 처리할 건지 분기하는 부분에 대한 정의가 필요합니다.

이 부분을 '라우팅' 이라고 하며 라우팅에 대한 정의는 routes/ 디렉토리에 있는 파일들에 들어있습니다.

정보

기본 설정에서, routes/api.php의 라우트들과 routes/web.php의 라우트들은 서로 다른 속성(미들웨어, 접두사, 등등)을 갖습니다. 각각 어떤 속성들이 부여되는지는 app/Providers/RouteServiceProvider.phpboot 메서드에서 확인할 수 있습니다. 자세한 내용은 공식문서 참고open in new window.

정보

이 시리즈에서는 Laravel을 API 서버로 사용할 것이므로 쓸모없는 미들웨어는 제외하고 API에 필요한 미들웨어들만 모아서 이미 정의되어 있는 api 미들웨어 그룹을 사용하는 routes/api.php에서만 라우트들을 정의하겠습니다.

우리가 만든 ChatControllersend 메서드는 [POST] /chat/send 로 들어오는 요청을 처리하도록 라우트를 정의하겠습니다.

그 전에, 기본적으로 routes/api.php의 라우트들은 모두 /api라는 prefix를 갖도록 설정되어있는데, 우리는 routes/web.php를 사용하지 않을 것이므로 거추장스러운 접두사를 빼겠습니다.

app/Providers/RouteServiceProvider.phpboot 메서드로 가서 접두사를 붙이는 부분을 수정합니다. 메서드 체인에서 prefix('api') 메서드 호출 부분을 수정하면 됩니다.

public function boot()
{
    $this->configureRateLimiting();

    $this->routes(function () {
        // Route::prefix('api')
        //     ->middleware('api')
        //     ->namespace($this->namespace)
        //     ->group(base_path('routes/api.php'));
        
        Route::middleware('api')
            ->namespace($this->namespace)
            ->group(base_path('routes/api.php'));

        Route::middleware('web')
            ->namespace($this->namespace)
            ->group(base_path('routes/web.php'));
    });
}

이제 routes/api.php에서 [POST] /chat/send에 대한 라우트를 작성하겠습니다.

<?php

use App\Http\Controllers\ChatController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

// Route::middleware('auth:api')->get('/user', function (Request $request) {
//     return $request->user();
// });

Route::post('/chat/send', [ChatController::class, 'send']);

기존에 파일에 존재하던 쓸모없는 라우트는 지우고, [POST] /chat/send 요청을 ChatControllersend 메서드가 처리하게 하는 라우트를 하나 작성했습니다.

이제 남은 것은 ChatControllersend 메서드의 내용을 작성하는 부분입니다.

이벤트 작성

send 메서드를 작성하기 전에, Laravel의 브로드캐스팅 기능은 특정 이벤트가 발생했을 때, Laravel Event 객체 데이터를 브로드캐스팅 채널을 구독하는 클라이언트들에게 전달하며 해당 이벤트가 발생했다는 것을 알려주는 기능입니다.

따라서 브로드캐스팅을 하려면 먼저 Event 객체를 정의해야합니다. 아래 명령으로 ChatSent라는 Event 클래스 파일을 만들어 정의합니다. 만들어진 파일은 app/Events 디렉토리에 생성됩니다.

php artisan make:event ChatSent

만들어진 파일의 모습은 다음과 같습니다.

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ChatSent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

ChatSent 객체는 사용자가 메세지를 적어 보내면, 그 보낸이와 보낸 메세지 데이터를 담아 클라이언트로 전달되어야 합니다.

그러므로 클래스 내부에 변수를 만들고, 객체를 생성할 때 생성자에서 데이터를 넣을 수 있게 합니다.

또, 이벤트를 브로드캐스트하려면 해당 이벤트가 ShouldBroadcast 인터페이스를 구현해야합니다. 기본적으로 상단에 use 구문으로 들어가 있으므로, 바로 상속만 시켜주면 됩니다.

broadcastOn 메서드는 이 이벤트가 브로드캐스트될 때, 어떤 채널을 통해 브로드캐스팅 될지 선택해주는 부분입니다.

여기서 채널의 종류로 Public, Private, Presence 세 가지가 있는데, 크게 클라이언트에서 해당 채널을 구독할 때 인증 절차를 거치느냐, 안 거치느냐의 두 분류로 나눌 수 있습니다.

우리의 가장 처음의 채팅 앱은 어떤 로그인도 없이 익명으로 대화할 수 있는 채팅 앱을 만들 예정입니다. 따라서 Public 채널을 사용하겠습니다.

// ShouldBroadcast 인터페이스
class ChatSent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    // 데이터 변수 추가
    public $sender;
    public $message;
    public $sentAt;

    // 생성자에서 값을 받아 할당
    public function __construct($sender, $message, $sentAt)
    {
        $this->sender = $sender;
        $this->message = $message;
        $this->sentAt = $sentAt;
    }

    // Laravel에서 Public 채널은 그냥 Channel 클래스로 표현합니다.
    public function broadcastOn()
    {
        return new Channel('chat');
    }
}

ChatControllersend 메서드 작성

이제 글의 가장 첫 부분에서 미뤄두었던 ChatControllersend 메서드를 작성하기 위한 모든 나머지 코드들을 작성했습니다. 이제 ChatControllersend 메서드만 마저 작성하면 됩니다.

send 메서드가 해야할 일은 간단합니다.

클라이언트에서 메세지와 보내는 사람의 이름을 간단하게 문자열의 형태로 요청에 담아 보낼것입니다. send 메서드는 단순히 이 데이터를 가지고 ChatSent 이벤트 객체를 하나 생성해 클라이언트들에게 브로드캐스팅하면 됩니다.

public function send()
{
    $sender = request('sender');
    $message = request('message');

    broadcast(new ChatSent($sender, $message, now()));
}

가장 간단한 형태의 채팅 앱을 위한 백엔드 코드 작성이 끝났습니다.

지금 코드로는 DB도 사용하지 않아 클라이언트에서 새로고침을 하거나 하면 기존 대화목록이 남아있지도 않고, 단순히 클라이언트에서 메세지를 담은 요청을 우리 서버에 보내면, chat 채널을 구독하는 클라이언트들에게 브로드캐스팅하는 기능만 담당할 수 있습니다.

다음 포스팅부터 간단한 채팅 페이지를 만들어보고, 이 간단한 기능이 잘 작동하는지 먼저 테스트해본 후에 로그인, DB 연동 등 여러 가지 기능을 추가해보면서 가장 간단한 형태부터 우리가 원하는 최종 형태까지 채팅 앱을 개선하겠습니다.

마지막 수정 일시:
기여자: Chanhyuk Byeon