개요
Laravel의 브로드캐스팅은 오랫동안 Reverb·Pusher·Ably라는 “WebSocket 네이티브” 드라이버를 제공해 왔지만,config/broadcasting.php의 driver에 새롭게 mercure가 추가되었습니다.
Mercure는 Server-Sent Events(SSE)를 기반으로 한 실시간 통신 프로토콜입니다. WebSocket과 같은 양방향 커넥션이 아니라 HTTP/1.1·HTTP/2 상의 롱 폴링에 가까운 방식으로 동작하기 때문에 다음과 같은 특징이 있습니다.
- 일반적인 HTTP 인프라(리버스 프록시, CDN, 로드 밸런서)를 그대로 통과할 수 있습니다
- 브라우저의
EventSourceAPI만으로 클라이언트를 구현할 수 있어, 전용 클라이언트 라이브러리 없이도 동작합니다 - FrankenPHP에는 내장 Mercure 허브가 탑재되어 있어 추가 인프라 없이 실행할 수 있습니다
driver 추가와 지원되는 값
config/broadcasting.php 첫머리 주석에 있는 지원 드라이버 목록에 mercure가 추가되었습니다.
url을 생략하면 FrankenPHP의 내장 Mercure 허브(mercure_publish() 함수)로 자동 폴백합니다. 이 판정은 CreatesMercureDrivers::mercure()에서 이루어집니다.
url에 관리 API(publish용) URL을, public_url에 브라우저에서 보이는 URL을 지정합니다. 두 값이 다른 이유는, Docker Compose 등 내부 네트워크를 경유하여 publish하면서 공개 URL만 브라우저에 노출하는 구성을 상정하고 있기 때문입니다.
토큰 발행 방식: publish용과 subscribe용을 분리
Mercure는 JWT(JSON Web Token)로 접근 제어를 수행하는 프로토콜입니다.CreatesMercureDrivers 트레이트는 publish(서버 → 허브)와 subscribe(브라우저 → 허브)에 대해 별도의 토큰 팩토리를 구성합니다.
secret/publish_secret/subscribe_secret을 개별적으로 지정할 수 있으며, 생략 시에는secret으로 폴백합니다algorithm/publish_algorithm/subscribe_algorithm도 마찬가지로 개별 지정이 가능합니다(기본값HS256)- HS256은 32바이트, HS384는 48바이트, HS512는 64바이트 이상의 시크릿 길이가 필수이며, 이를 만족하지 못하면
InvalidArgumentException이 발생합니다
Grant::ACTION_PUBLISH로 모든 토픽(*)에 대한 발행 권한을 가지며, CachingTokenProvider에 의해 메모리 캐시됩니다. 브로드캐스트할 때마다 JWT를 다시 생성하지 않도록 하기 위한 최적화입니다.
채널 인가와 Cookie 기반의 구독 모델
WebSocket 드라이버와의 가장 큰 차이는, 구독 측의 인가가 단일 Cookie로 이루어진다는 점입니다.MercureBroadcaster::auth()는 channel_names 배열(1회 요청당 최대 100건)을 받아 채널마다 인가를 판정한 뒤, 하나의 인가 Cookie로 묶어서 발행합니다.
denied: true로 응답에 포함될 뿐, 다른 허가된 채널의 구독은 계속 유지됩니다. Mercure는 하나의 EventSource로 여러 토픽을 다중화하기 때문에, 중간에 채널을 추가·제거해도 기존 커넥션을 끊지 않도록 하기 위함입니다.
Presence 채널은 일반적인 private/private-encrypted 채널과 다른 Grant를 발행하며, 구독 URL 패턴(Mercure의 Subscription API)에 대한 subscribe 권한으로 편입됩니다.
엔드투엔드 암호화 채널
private-encrypted-로 시작하는 채널 이름은, Mercure 허브 자체에도 페이로드를 보여주지 않는 “엔드투엔드 암호화(E2EE)” 채널로 취급됩니다. 이는 기존 Pusher/Reverb 드라이버에는 없는 기능입니다.
encryption_key를 base64 인코딩된 32바이트 키로 설정하면 ChannelEncrypter가 활성화되며, broadcast() 실행 시점에 이벤트 이름·페이로드·소켓 ID를 함께 JWE(JSON Web Encryption)로 암호화합니다. 허브가 중계하는 것은 암호화된 바이트열뿐입니다.
auth() 응답에 포함된 jwk(JSON Web Key) 필드를 사용하여 브라우저 측에서 복호화합니다. 키 교환은 Mercure 사양이 권장하는 “인가 응답에 실어 아웃오브밴드로 전달하는” 방식으로, 허브 자체는 키를 전혀 알지 못합니다.
Presence 채널은 암호화 대상이 아닙니다. 멤버 목록이 Mercure의 Subscription API를 경유하여 허브에 보이는 사양이므로 E2EE와는 양립할 수 없습니다.
Whisper(클라이언트 간 직접 메시지)
client_events가 활성화된 경우(기본값 true), 각 가드 채널에는 전용 “whisper 토픽”이 할당되며, 구독자는 그 토픽에 대해서만 publish 권한을 가집니다.
토픽 명명 규칙
Mercure는 여러 애플리케이션이 허브를 공유할 수 있도록 설계되어 있으며, 토픽 충돌을 피하기 위해topic_prefix라는 네임스페이스를 도입하고 있습니다. 기본값은 다음과 같습니다.
.alt는 DNS 상에서 예약된 미해결 도메인(RFC 9476)이므로 실제 도메인과 충돌할 걱정이 없습니다. 채널 이름은 RFC 3986에 따라 URL 인코딩되어 단일 경로 세그먼트에 담깁니다.
Cookie 도메인과 오류 메시지
Mercure 허브가 애플리케이션과 다른 서브도메인에서 동작하는 구성(예:mercure.example.com과 app.example.com)도 흔하기 때문에, 인가 Cookie의 도메인 해석에 실패하면 원인을 특정하기 쉬운 예외 메시지가 발생합니다.
__Secure-나 __Host- 접두사가 붙은 Cookie 이름을 사용하는 경우에는, public_url이 HTTPS인지도 기동 시에 검사됩니다.
Reverb·Pusher·Ably와의 사용 구분
Mercure는 WebSocket의 상시 양방향 커넥션이 필요 없는 유스케이스(알림, 진행 상황 표시, 채팅처럼 서버 → 클라이언트의 단방향 전송이 중심인 용도)에 적합합니다. FrankenPHP를 사용하고 있다면 추가 인프라 없이 동작하는 것도 장점입니다.
관련 페이지
- 브로드캐스트 기초
- Laravel Cloud(FrankenPHP 기반의 실행 환경)