[HN Gopher] Mercure: A WebSocket alternative for server-sent events
___________________________________________________________________
Mercure: A WebSocket alternative for server-sent events
Author : porjo
Score : 137 points
Date : 2025-01-02 04:33 UTC (18 hours ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| panarky wrote:
| Please explain why this is better than websockets.
|
| I read the Github readme, but it doesn't say what's better.
| gorjusborg wrote:
| Along the same lines, what is the need for an alternative to
| SSE?
|
| There are some issues with SSE in HTTP/1.1, but HTTP/2 pretty
| much fixes those.
|
| I am curious what problem(s) are being targeted with this
| solution.
| afavour wrote:
| The site offers a little more detail:
|
| https://mercure.rocks/
|
| Basically its server sent events with some stuff on top:
|
| https://developer.mozilla.org/en-US/docs/Web/API/Server-sent...
|
| One big benefit of SSE is that you can put them behind a CDN,
| making scaling a lot easier. No idea about what Mercure brings
| there, though.
| conradfr wrote:
| It's targeted at PHP apps.
|
| You can use it with Symfony's copy of Phoenix LiveView for
| example.
| rbrtdrmpc- wrote:
| It is targeted to the PHP community, not the language, it has
| no dependency on that, it is written in go and is based on
| caddy. (I've contributed with one PR in my former job)
| archerx wrote:
| It's pretty easy to set up SSE in PHP and works very well.
| I don't see how adding this helps at all...
| withinboredom wrote:
| It's really not. Not properly anyway. You'd spend a week,
| probably, just getting all the synchronization stuff
| right. Unless of course, you don't care about losing
| messages during a disconnection. Or you could just
| install this in a couple of hours and be done with it;
| not have to worry about security, maintaining NIH
| libraries, etc.
| julik wrote:
| Fear of long-lived processes
| upghost wrote:
| They seem to handle a lot of nontrivial issues I have to deal
| with frequently like synchronization, but weirdly enough they
| do it with JWTs. For me the entire point of SSEs is that I can
| avoid using JWTs and use standard session logic which is very
| easy to reason about.
|
| By the way I'm sure JWTs are fine not trying to step on any
| toes I'm just not an expert with them and I know there are
| footguns so with security stuff I stick to the most boring
| technology I have access to.
| Timber-6539 wrote:
| SSE fixes some of the cargo cult practices formed around
| Websockets.
| whilenot-dev wrote:
| Care to explain a bit more? (otherwise I'd feel like being
| cargo cultured into SSEs)
| Timber-6539 wrote:
| Going off my personal experience I don't see SSE being
| mentioned anywhere at all. But I always come across some
| new shiny web framework/wsgi listing websockets as a
| feature.
|
| The reality is a lot of applications simply do not need bi-
| directional client-server communication but for some reason
| this abuse has been adopted as standard.
| gorjusborg wrote:
| > The reality is a lot of applications simply do not need
| bi-directional client-server communication but for some
| reason this abuse has been adopted as standard.
|
| I suspect the treatment as 'standard' is what thread OP
| is talking about. See my sibling comment in the thread.
| Unless you absolutely need websocket, SSE is simpler, and
| due to that, better.
|
| I suspect the reason many select websocket over SSE is
| due to its limitations (connection limit under HTTP/1.1,
| no client->server sends), but SSE for server->client
| events and normal HTTP requests for state fetching is
| better architecturally for bog standard apps in my
| experience.
| gorjusborg wrote:
| I have certainly seen people reach for websockets to just
| reverse the direction of data flow from server to client. On
| HTTP/1.1, the main issue people can hit is the max connection
| limit.
|
| This connection limit does not apply when using SSE over
| HTTP/2+, because server connections are multiplexed onto a
| single connection.
|
| I also prefer starting with SSE where possible, as it is
| simple conceptually, easy to implement (even from scratch),
| and doesn't introduce a secondary client to server path.
| Having that ability (even if unused) tends to create a
| temptation to use it, and when that happens it introduces a
| choice of whether to use normal HTTP requests or make an
| equivalent request over websocket.
|
| Using websocket for things HTTP can handle is a mistake, in
| my opinion, because HTTP is simpler than websocket to fetch
| state.
|
| Of course, if you need websocket, you need it, but there are
| some that just want to play with it in production or add it
| to their resume. I suspect that dynamic is what causes the
| 'cargo culting' you talk about.
| kdunglas wrote:
| Hi, Mercure author here.
|
| WebSockets are hard to secure (they totally bypass CORS as well
| as other browser built-in protections), don't work (yet) with
| HTTP/3 and for most use cases require to implement many
| features by yourself: reconnection in case of network failure,
| refetch of lost messages, authorization, topic mechanism...
|
| Mercure, which is built on top of SSE (it's more an extension
| to SSE than an alternative to it) fix these issues.
|
| However, SSE (as well as WebSockets) can be hard to use with
| stacks not designed to handle persistent connections such as
| PHP, serverless, most web servers proxying Ruby, Python etc
| apps. Even for languages designed to handle persistent
| connections, it's often more efficient and easier to manage
| persistent connections with ah-hoc software running on
| dedicated hardware.
|
| That's what Mercure allows. Mercure provides a "hub", a server
| that will maintain the persistent connections with the
| browsers, store events, and re-send them in case of network
| issues (or if asked for old messages by a client). To broadcast
| a message to all connected users, the server app (or even a
| client) can just send a single POST request to the hub. The hub
| will also check that clients are authorized to subscribe or
| publish to a given topic (that's why JWT is used).
|
| The reference implementation is written in Go, as a module for
| the Caddy web server, and his very efficient/optimized (it can
| handle thousands of persistent connections on very small
| machines).
|
| Install a Mercure hub and you have all these features available
| without having to write any code. Client-side, no SDK is
| required, you can embrace the built-in EventSource JavaScript
| class.
| Thorrez wrote:
| > they totally bypass CORS
|
| You can reimplement the Same Origin Policy serverside by
| checking that the Origin header equals the Host header. Even
| more secure would be to check both against an allowlist (this
| protects against DNS rebinding, which the Same Origin Policy
| doesn't protect against).
|
| >as well as other browser built-in protections
|
| I'm curious what those are.
|
| >for most use cases require to implement many features by
| yourself: [...] authorization
|
| Isn't auth of websockets generally the same as auth of any
| Javascript-initiated HTTP request (e.g. fetch())? Check that
| the cookie looks good? Now, in the cause of OAuth tokens,
| websockets are more difficult than fetch(), because you
| cannot attach an Authorization: Bearer header to a websocket.
| But OAuth is less common than cookies for websites.
| kdunglas wrote:
| Once the connection is upgraded, you loose all metadata
| included in the HTTP headers (because it's not HTTP) and
| all protections relying on it. Also CORS and SOP can be
| bypassed: https://dev.to/pssingh21/websockets-bypassing-
| sop-cors-5ajm
|
| Even very experienced teams make mistake with this kind of
| things. See for instance, this Kubernetes vulnerability:
| https://goteleport.com/blog/kubernetes-websocket-upgrade-
| sec...
|
| Of course you can reimplement everything by hand (and you
| must if you use WebSockets), but with SSE/Mercure you don't
| have to because it's plain old HTTP.
| whilenot-dev wrote:
| > Once the connection is upgraded, you loose all metadata
| included in the HTTP headers (because it's not HTTP) and
| all protections relying on it.
|
| The Upgrade request is HTTP and you can extract all
| needed metadata from there and store it server side as
| needed. Those metadata wouldn't change during an active
| WebSocket session anyway, would they?
| matharmin wrote:
| With your own native client: Yes, you can send arbitrary
| headers in the Upgrade request.
|
| In a browser however, you can't. It typically sets very
| little headers itself, and doesn't allow you to add
| custom headers.
| whilenot-dev wrote:
| The auth headers ( _Authorization_ , _Cookie_ ) are all
| passed along, and that's what I want to establish a
| secure connection from the browser.
|
| For more customized wishes there's always this
| "ticket"-based flow[0][1] that shouldn't be hard to
| implement. I might be a bit naive, but what needed
| metadata and custom headers are we talking about?
|
| [0]: https://devcenter.heroku.com/articles/websocket-
| security#aut...
|
| [1]: https://lucumr.pocoo.org/2012/9/24/websockets-101/#a
| uthoriza...
| jand wrote:
| > ... because you cannot attach an Authorization: Bearer
| header to a websocket.
|
| Well, not properly. You can abuse the Sec-Websocket-
| Protocol header to pass an initial token to the server.
| whilenot-dev wrote:
| It might be worth mentioning to the parent poster that SSEs
| (and Mercure) are unidirectional only (server -> client),
| whereas WebSockets are bidirectional.
| the_mitsuhiko wrote:
| Nowadays that's hardly an issue because SSE is multiplexed
| over HTTP/2 and HTTP/3 alongside regular requests. In some
| ways, SSE is today more efficient than websockets too
| because if you use it with HTTP/3 then you're avoiding head
| of line blocking which is still an issue with websockets.
|
| (Also in practice many places websockets are not even
| supporting HTTP/2 because on the server they were
| implemented on top of raw sockets which is no longer
| possible with HTTP/2)
| chris_pie wrote:
| Head-of-line still exists in HTTP/3, but on another
| level. A stream can still get blocked. With websockets
| the whole connection gets blocked (HTTP/1), but that
| connection isn't shared with anything else, right?
|
| Worth noting that HTTP/3 seems to recover faster than
| TCP-based protocols.
| archerx wrote:
| I have been using Server Sent Events with PHP since 2017 and
| it works very well. All you need is the right headers and a
| loop that breaks on user disconnect. Honestly it was super
| easy and had no issues setting it up and getting it to work.
| panarky wrote:
| This is helpful, thank you.
|
| You might consider updating the readme to make it clear what
| Mercure/SSE does that WebSockets doesn't.
|
| If it was me, I'd also put in the readme what WebSockets does
| that Mercure/SSE does not, such as bidirectional
| communication, low latency client-to-server messages, binary
| data transfer, etc.
| weitendorf wrote:
| It took a decent amount of digging, but it seems like this is
| trying to be a browser-compatible pubsub system more than a
| direct websocket replacement. The main thing they're doing is
| introducing a "mercure hub" between event producers and consumers
| (browsers) that handles delivery through SSE in a way that tracks
| subscribers and handles sporadic or interrupted connection.
|
| However what confuses me is that they seem to focus mostly on the
| protocol, hub, and publish implementation. At least based on a
| quick glance at https://mercure.rocks/docs/ecosystem/awesome and
| elsewhere it seems they expect you to use raw SSE clientside
| according to their protocol described in eg
| https://mercure.rocks/spec#active-subscriptions.
|
| Most of their clientside examples look pretty simple but they
| don't seem like they're fully implementing the logic described in
| "Reconnection, State Reconciliation and Event Sourcing" which
| seems rather complex. Maybe I'm missing something but it seems
| like that logic is the entire reason to use this over SSE alone.
| kdunglas wrote:
| Hi, Mercure author here.
|
| Indeed that's how it works. One of the key point of the
| solution is that you don't need anything client-side. The
| native EventSource class is all you need (but you can use more
| advanced SSE client libraries if wanted).
|
| Reconnection and state reconciliation are achieved
| automatically. The hub implements all the needed features: it
| stores sent events and automatically resend them at
| reconnection time if they have been lost. It's possible to do
| this transparently because browsers will automatically send the
| ID of the last received message in a Last-Event-ID header when
| reconnecting. This feature is just often not implemented by SSE
| servers (because it's not trivial to do).
|
| It's also possible to ask events received since a specific ID,
| matching one or several topics just by passing the query
| parameters defined in the protocol section.
|
| By the way, we are working on a new website that will make
| these things more clear.
| 9dev wrote:
| Hi Kevin! A while ago, I've built a hub implementation using
| Typescript and Deno, mainly for learning, but also to see if
| I could come up with a neat solution for distributed event
| storage in-memory, using Raft. It implements the full spec,
| so far, and is pretty easy to understand, if I may say so
| myself.
|
| Do you still accept entries for alternative hubs? It may be
| helpful for others to understand how the specification is
| supposed to work; some parts were a little opaque to me at
| first, and required digging into the reference implementation
| to fully grasp.
| benfortuna wrote:
| How is this different to Websub?
|
| https://websub.rocks/
|
| And it doesn't appear you have an associated working group
| (WG) for your (expired) draft publication, which could help
| you identify if you are reinventing an existing wheel..
| ramesh31 wrote:
| >The main thing they're doing is introducing a "mercure hub"
| between event producers and consumers (browsers) that handles
| delivery through SSE in a way that tracks subscribers and
| handles sporadic or interrupted connection.
|
| This is the singgle biggest issue with using SSE, so I'm
| intrigued.
| tka85 wrote:
| Does it play well behind a load balancer? For example
| Cloudflare's?
|
| And does it work well with non-browser consumers like native
| mobile apps?
| no_wizard wrote:
| These are the key questions.
|
| The biggest issue with SSE is that there is unfortunately alot
| of deployed software that will downgrade incoming connections
| to HTTP/1.1 so even if the client would have otherwise
| supported HTTP/2 or higher its not presented the ability to do
| so.
| kdunglas wrote:
| Yes, as it 's "just" SSE, this works pretty well behind
| Cloudflare and other similar solutions. Mercure also
| automatically sends the NGINX-specific headers required to
| allow unbuffered connections.
|
| Native mobile apps are also entirely supported. It's a very
| common use case. Most mobile languages have SSE client
| libraries.
| qwertox wrote:
| Reminds me of WAMP (Web Application Messaging Protocol) [0],
| which is a WebSocket subprotocol.
|
| I find the title odd, because, why would you want to replace
| server-sent events with WebSocket, if the great thing about SSE
| is the simplicity, both client- and server-side?
|
| [0]
| https://en.wikipedia.org/wiki/Web_Application_Messaging_Prot...
| h4ch1 wrote:
| Does it still have the limitation of SSE ie; it'll stop working
| after you open 6 tabs?
| kdunglas wrote:
| This limitation is gone with HTTP/2 and HTTP/3 and the Mercure
| hub automatically use the most recent protocol supported by the
| browser.
| no_wizard wrote:
| That is if the connection isn't downgraded by some other
| mechanism. Lots of corporate clients downgrade connections to
| HTTP/1.1 due to how their network is setup.
|
| There is also an issue with SSE I've noticed that websockets
| doesn't seem to have as much of a prevalent issue with, and
| that is being blocked by corporate firewalls.
| dunglas wrote:
| Most websites use TLS, HSTS and the like nowadays. It's
| hard for a corporate proxy to downgrade the connection.
|
| Also, the limitation is only applied by browsers, not by
| (reverse) proxies and other non-browsers clients.
|
| That being said, Mercure allow to subscribe to many
| "topics" using a single HTTP connection. This helps
| mitigating the limitation with HTTP/1 (which is very
| uncommon these days).
| anonzzzies wrote:
| I like it; too much stuff to write yourself with websockets.
| jand wrote:
| Was there a specific reason to use AGPL-3.0? Not critizing, just
| asking.
|
| Tried to read about the license and was greeted by a tl;dr
| summary of the AGPL-3.0 license [1]. I am no lawyer but my gut
| tells me that providing such a summary is an invitation to
| strange disputes. Take care.
|
| [1] https://mercure.rocks/docs/hub/license
| emacsen wrote:
| Also not a lawyer, but discussed this with multiple lawyers. As
| long as you make very clear that you're summarizing the legal
| document, but the legal document you're providing is the
| canonical truth, you're allowed to provide those kind of
| summaries.
|
| For example, Creative Commons has a visual/bullet point
| explanation of their licenses. That's entirely okay, as the
| legal text is the core license.
|
| I had a similar discussion with a lawyer once about a TOS that
| also included a summary. The lawyer told me that as long as you
| make it clear that your summary is just a summary, and is not
| the agreement, and you point out the actual agreement, you're
| okay.
|
| In this case, the OP is pointing to the legal text clearly and
| merely summarizing it's most salient points.
|
| Again, IANAL and this is not legal advice.
| hellojebus wrote:
| We've been using Mercure for 3+ years in production. It's been
| pretty much set it and forget it. Maybe once a year do we have to
| maintain the server e.g. clear up space, but overall has been
| great for our use case (which does not need full pub/sub).
| bvrmn wrote:
| Also I could recommend https://github.com/slact/nchan. It has the
| same idea: hide and abstract pubsub complexity for a backend
| service. nchan is built on top of nginx and could be more
| convenient (existing nginx configuration knowledge) to deploy.
| dagss wrote:
| The specification appears to be very similar to FeedAPI which I
| have been involved with:
|
| https://github.com/vippsas/feedapi-spec
|
| Our focus was kind of the opposite though: Remove the event
| broker instead of adding one.
|
| If I understand correctly, Mercure allows you to push events to
| the broker and clients pull log-based events.
|
| With FeedAPI we focused instead of removing the broker
| (RabbitMQ/Kafka/...) and let clients pull log-based events
| directly from backends.
|
| Very similar protocol though.
| alt227 wrote:
| I use PHP/Symfony/Websockets for a real time web app.
|
| I have been looking over at SSE/Mercure for a while, as Symfony
| recomends it for Server->Client traffic.
| https://symfony.com/doc/current/mercure.html
|
| However I am yet to see a reason to swap out my currently working
| websocket implementation other than SSE is the new shiny thing.
|
| Can anybody tell me any other benefits that I may get from making
| this switch?
| chris_pie wrote:
| SSE are old, hardly "the new shiny thing".
___________________________________________________________________
(page generated 2025-01-02 23:01 UTC)