Closed annevk closed 6 years ago
FWIW just tested in html5workertest, and it seems Firefox 48 doesn't expose either EventSource
or WebSocket
inside a ServiceWorker, but Chrome 52 exposes both. Interestingly Firefox 50 Dev Edition exposes WebSocket
.
Related: https://github.com/nolanlawson/html5workertest/pull/14
Yeah, Gecko doesn't have EventSource in any workers. https://bugzilla.mozilla.org/show_bug.cgi?id=1243942 fixed WebSocket on ServiceWorkers.
FWIW, if we get sub-workers in SW, these, including XHR, would become useable there too, if for nothing else, consistency. Unless we then want to create some new type of sub-worker which doesn't do any I/O.
I was wondering, is there any legit use case for having WebSockets in service workers? Considering the service worker isn't something long-running, and WebSockets are best utilized when the connection built up with the (relatively expensive) handshake lasts longer I couldn't come up with a good usecase.
(fwiw being able to host "sub-workers" and run socket connections in there does seem to solve this and sounds like a better usecase for me)
Basically, both WebSocket and EventSource will soon be obsolete due to Fetch + Streams. WebSocket has the additional sadness of not working with HTTP/2. That's the main reason to avoid exposing them in new places.
However, as @smaug---- said, if service workers get sub-workers, and we don't make those different from normal workers, all these restrictions are getting rather arbitrary.
Basically, both WebSocket and EventSource will soon be obsolete due to Fetch + Streams
FWIW, It is very unclear to me how Fetch+Streams let UA to do similar memory handling optimizations to Blob messages as what WebSocket (and XHR) let: (huge )Blobs can be stored in temporary files.
fetch(url).then((res)=>res.blob())
is as efficient. Fair point for streaming though. Do we have data as to whether that matters there?
I'm not aware of having data for streaming + blob. But if it is needed for non-streaming case, why wouldn't it be needed for streaming case (I could imagine some web file sharing service to want to use streaming-like API to pass many files, and not do that one fetch per file.). Gecko does not store right now WebSocket blobs in temporary files, but I consider that as a bug, since it means browser's memory usage may be way too high in certain cases, and XHR's and WebSocket should have similar blob handling.
both WebSocket and EventSource will soon be obsolete due to Fetch + Streams
You could also add "sendBeacon" to that list of obsolete APIs :wink:
@smaug---- well, with HTTP/2 requests are cheaper since the connection stays alive for longer, so if you already have an open connection for your bi-directional communication channel over a request-response pair, you might as well just send another request to get the actual file. That way it doesn't block the server from sending other messages meanwhile either.
While it's true that WebSockets do not fit well with the ServiceWorker model, it is not the case that WebSockets are supplanted by the fetch API. Their use cases are different. It is unfortunate that WebSocket over HTTP/2 is not a thing (yet?) but arguably for many things WebSockets are actually used for that is irrelevant.
We use service worker to indicate incoming call. It starts with push notification. Then ServiceWorker opens WebSocket where it receives message if the ringing is active. If yes then it shows a Notification and waits until it receives "ring_end
" message. Afterwards it closes the Notification and the connection. Without WebSocket we would need to use HTTP (long)polling.
@ricea how are they not supplanted by the Fetch API? I don't think we should do WebSocket over H/2. Not worth the effort.
@zdila What did you mean by "incoming call" and "If yes"? Is your application a voice chat?
Does the WebSocket receive only 1 message for ring start and 1 message for ring end? Or while the ringing is active, the server keeps sending a message to indicate the ringing is still active periodically? Or the received WebSocket messages are used for updating existing notification? Why can't the push notification be used for these purposes in addition to the first push notification?
@annevk It's off topic (or too much general topic), but I've recently started sharing an idea named WiSH at IETF. Here's the I-D https://tools.ietf.org/html/draft-yoshino-wish-01. There're also some real (ideas that uses some HTTP2 level stuff such as mapping WS frames to HTTP2 frames) WS/HTTP2 proposals. But WiSH just uses the Fetch API and the Streams to provide almost equivalent functionality as WebSocket for now, HTTP2 era, QUIC era, and the future while keeping taking care of migration of existing WebSocket API users from the WebSocket protocol over TCP.
I'm thinking that providing some message oriented API on the web platform makes some sense even after we finish the HTTP API powerful enough. We already have the WebSocket API, and it would be reasonable to keep providing almost the same interface and evolve its capability (e.g. make it able to benefit from HTTP2, QUIC). Until that evolution happens, the combination of the WebSocket API and the WebSocket protocol would stay there to satisfy the demands.
Possible simplifcation of the architecture may motivate disallowing WebSockets in SW, but we haven't done the evolution, yet. I think we should keep WebSockets available regardless of the context (window, worker, SW) for now if the use case is reasonable enough.
(I'd like to hear your general opinion on the WiSH idea somewhere. Should I file an issue at whatwg/fetch repo for this?)
Also with the lifetime of a service worker it's unlikely these APIs are useful.
Yeah. The nature of SW would make most of WS's key features useless. I'd like to understand zdila's use case more.
Regarding streamed efficient blob receiving, basically I agree with Anne's answer at https://github.com/w3c/ServiceWorker/issues/947#issuecomment-255664753. We haven't had any real study on whether anyone has been utilizing this power, so not backed by data though.
What did you mean by "incoming call" and "If yes"? Is your application a voice chat? @tyoshino yes, it is a video chat application.
Simplified, we receive only two messages via WebSocket as you described - start ringing with caller details and stop ringing. It can be reworked to use 2 push notifications as you described. It will just mean for us to not to reuse existing API which is now used in several other cases.
Thank you @zdila. Then, I recommend that you switch to use that method (two pushes) for this use case. As Anne said and flaki summarized at https://github.com/w3c/ServiceWorker/issues/947#issuecomment-255223776, that WebSocket may get closed when the Service Worker instance is shut down even if the server didn't intend to signal ring_end.
to reuse existing API which is now used in several other cases
I see.
So, if one has an existing WebSocket based service, it would be convenient if it also works in a service worker though it requires event.waitUntil() to work correctly and shouldn't last for long time. How about background sync + WebSocket in SW?
We could discuss WiSH over at whatwg/fetch, sure. I think the main problem with keeping WebSocket in is that it'll make it harder to remove in the future. Whereas the reverse will be easy to do once we have explored alternatives.
@tyoshino I realised that the problem with two-push method is that Chrome now requires to show notification for every push (userVisibleOnly
to be true). This means that "ring end" push which would be meant to hide the notification will have to actually show one.
We actually have this problem also now because after the first push we may also find out that there is actually no ringing outgoing already (it is just gone).
@zdila If you just need a single notification then a hanging GET will be more efficient than a WebSocket in terms of network bytes and browser CPU usage.
@ricea then I would need to do a ugly polling to find out when ring ended. Not an option.
@annevk WebSocket is more efficient for small messages. It provides a drop-in replacement for TCP when interfacing with legacy protocols. It is simple to implement on existing infrastructure.
How does a request body/response body channel established through H/2 rather than WebSocket not have the same benefits?
An H/2 frame has a 9 octet header, compared to 2 octets for a small WebSocket message. H/2 is a complex multiplexed beast which is not very much like a TCP connection. It is not simple to implement.
We use WebSockets in ServiceWorkers for syncing Indexed DB data. The site is capable of working in full offline mode, we have multiple input forms in our application and when the user saves a form the data is written to Indexed DB and the data is synced across clients using ServiceWorkers. This is a two way sync and all clients are kept updated of any changes to the data. The sync happens in the service worker as having the sync on page will cause issue when the user navigates away from the page and there should be a WebSocket connection open for each page load, as there is no way we can have a persistent connection open.
@annevk is EventSource being deprecated? I thought its auto-reconnection stuff made it a nice high-level API? I agree it isn't useful in a service worker though.
The reason I'm nervous about removing WebSockets from SW is it prevents use of a protocol. Removing XHR wasn't so bad, as the intention is for fetch to be able to do everything it does.
EventSource is not deprecated, but you can do everything it can do with Fetch, the same goes for WebSocket, though WebSocket has some framing advantages as pointed out above and maybe some connection advantages as long as HTTP sits on top of TCP.
Anyway, if everyone exposes these let's close this and hope we don't regret it later.
@annevk I'm not sure I understand the claim that Fetch can replace WebSocket. I Googled around a bit and couldn't find anything explaining how this could be done, so I hope you don't mind if I ask here.
Are you suggesting that people can replace a WebSocket with a single "full-duplex" HTTP request, relying on the ability to start reading the response while still sending the request body?
If that is what you mean, that could work over HTTP/2 but lots of HTTP/1 infrastructure is not designed to support bidirectional streaming (even though the protocol technically doesn't prevent it). Can/should apps be probing somehow to see if they have HTTP/2 all the way back to the origin server? In a world with proxies, CDNs, load balancers, etc., it's pretty common to have requests that bounce between protocol versions as they traverse the network, so this seems error-prone.
Or are you instead suggesting that apps should use old-fashion long polling patterns, while sending upstream messages as separate requests? This is problematic in use cases where the client and server want to maintain some shared state connected to the WebSocket. When polyfilling WebSocket using long polling, you end up needing to configure load balancers to support "sticky sessions" (so that messages intended for the same virtual-WebSocket land at the same server), which is tricky at best and often not supported at all.
On another note, does fetch streaming guarantee that HTTP chunk boundaries on the wire will be preserved through to Javascript? Or would the application need to provide its own framing? WebSocket's built-in framing is nice to have, though admittedly not that hard to implement manually.
Can/should apps be probing somehow to see if they have HTTP/2 all the way back to the origin server?
I guess so, just like they'd have to do with WebSocket today. But it should work with HTTP/1 too, in theory.
On another note, does fetch streaming guarantee that HTTP chunk boundaries on the wire will be preserved through to Javascript?
Chunked is only applicable to HTTP/1 and if you use it via the mechanism defined in HTTP it will end up decoded by the time the data reaches JavaScript.
I guess so, just like they'd have to do with WebSocket today. But it should work with HTTP/1 too, in theory.
Hmm, I would be pretty concerned that this is the kind of thing that will regularly and unexpectedly break.
Proxies have a lot of legitimate reasons for breaking full-duplex streaming. For example, nginx likes to buffer request bodies to disk in order to protect backends from slow-loris attacks and in order to avoid excessive memory buffering. But this feature assumes half-duplex (response-strictly-follows-request) operation.
I worry that even with HTTP/2 (and especially with HTTP/1), we're going to see trouble with infrastructure (proxies, CDNs, load-balancers, etc.) that assume half-duplex because "it seemed to work" and the developers didn't know better. These assumptions may even appear unexpectedly in production, when the CDN added a new "feature" or the load balancer admin changed a setting that looked harmless.
Thus it seems pretty risky for applications ever to rely on this.
The nice thing about WebSocket is that it's very explicitly intended to be full-duplex, which makes it much less likely that anyone would break it by accident.
Of course, these are human bugs, not spec bugs.
Chunked is only applicable to HTTP/1 and if you use it via the mechanism defined in HTTP it will end up decoded by the time the data reaches JavaScript.
Sorry, that's not quite what I was asking. What I was asking was, if the server sends two chunks (or HTTP/2 frames), will the client be guaranteed to receive two callbacks from a ReadableStream, or might it only receive one callback in which the chunks are concatenated? I'm assuming the latter, hence the application must define its own framing on top. (EDIT: Yeah this clearly must be the case because HTTP itself certainly doesn't specify that proxies must preserve chunk boundaries on entities passing through them, so it would be silly for an application to rely on chunk boundaries being preserved...)
Ah yes, no guarantees about how many bytes your callback contains.
No feedback on the infrastructure issues, I don't know enough about it, but it does sound like it needs to be deployed with care, once we get there.
EventSource is not deprecated, but you can do everything it can do with Fetch
This is not possible until Firefox does implement Readable Stream in Fetch API.
Hi all,
So I have a requirement where I need to get intermittent push messages from the server to inside a service worker. And it should work in flaky network conditions. I was thinking of creating a websocket inside the service worker and listen for any updates. But that does not seem to be the recommendation here.
I am wondering what is the ideal way to achieve this now. Using web push notifications is an option that I am considering. However it seems pretty convoluted to set up.
EventSource is not deprecated, but you can do everything it can do with Fetch
@annevk - can you explain how that will work when a server wants to push something to the client ? As far as I understand, its a successor to our beloved xhr. But its still a simple request-response API.
I am thinking of using EventSource in the main app code outside of service worker and have a message passing mechanism to trigger the service worker when I receive an event.
The same way EventSource works except at a lower level of abstraction. You keep the connection open and push more bytes into the response body.
WebSockets provide a reasonably low-latency persistent channel for things like games. I was looking into whether and how Service Workers could be utilized to allow a user to load (offline and/or quickly) an in-progress game from the last-known state, while still allowing low-latency updates when the network is available. It sounds like Service Workers may not be suitable for this use, if they are expected to be short-lived. It does however seem desirable to allow low-latency asynchronous messaging when it's available, and otherwise fall back to last-known state. This seems relevant to the conversation here, then; I'm wondering if there is some combination of proposals that could replace WebSocket's persistence and latency in this context? I'm not sure I am grokking such an option from the comments here...
@ricea I'd love to explore WebSocket vs Fetch further and since for now this seems to be the place, I'll keep asking here. Why are H/2 frames that large? If we did WebSocket over H/2, as some have suggested at various times, wouldn't we run into the same problem? Does QUIC have the same problem?
Also, you suggest that WebSocket is easier to setup, referencing TCP, but any realistic setup needs at least all the complexity of TLS. Is H/2 and QUIC in the future really that much worse?
FWIW, we are going to need to support WebSockets in Cloudflare Workers. A lot of existing applications use WebSockets and it would not be reasonable for us to ask them to rewrite to HTTP/2 fetch before they can use Workers.
Yeah, I suspect everyone has to support these now given that this issue never got fixed. The issue can be closed I suspect, but I'd still be interested in exploring the trade offs in depth. And then mostly on the theoretical side as I can appreciate that there's still lots of bugs and legacy deployments on the practical side.
On the theoretical side, I would make two arguments:
That said I don't feel super-strongly about this, from the theoretical angle.
Purely on the theoretical side,
https://tools.ietf.org/html/rfc7540#section-8.1
doesn't really spec out full-duplex (simplex bidi is never an issue, i.e. upload followed by a download). Rather, it clarifies early 2xx completion is allowed (as opposed to http/1.1 which states early error responses are to be expected). While "early 2xx completion" may enable full-duplex support, it's not quite the same thing. More specifically, early 2xx completion is about committing an OK response (i.e. generating all the headers) while request body is still in flight. When the server decides to produce an early response, any request body that has not been received is deemed "useless" data. This is not really the case for most full-duplex use cases, where response data is causally generated from the request data, albeit in a streaming fashion (e.g. speech translation).
===
For user-space protocols that use http/2 (framing) purely as a transport, HTTP/2 (being a transport to HTTP/1.1 semantics) can certainly be treated as a full-duplex bidi transport (i.e. multiplex TCP streams), subject to middlebox interpretation (which is a big unknown to me).
@annevk Part of the extra size of HTTP/2 frame headers is explained by needing stream IDs, which is an inescapable consequence of being multiplexed. I must confess I don't know what the rest is.
There were three things I think I was talking about when I said "HTTP/2 is not simple to implement":
@kentonv While lots of developers get surprisingly far using bare WebSockets, I couldn't recommend it for general-purpose applications over the open Internet[1]. There are far too many people stuck in environments where only HTTP/1.1 will get through. So, in practice, you need fallbacks, and to make that not be painful you need some kind of library.
[1] Games seem to be an exception. Game developers appear to be quite happy to say "if my game doesn't work with your ISP, get a new ISP".
So if we assume the cost for HTTP/2 approaches zero in the future, the main features WebSocket has that Fetch does not would be:
It feels like we should explore exposing those "primitives" to Fetch somehow if they are important.
It worries me a little bit that @wenbozhu claims that HTTP is not full-duplex whereas the discussion in https://github.com/whatwg/fetch/issues/229 concluded it pretty clearly is. I hope that's just a misunderstanding.
My position on the full-duplex issue is that HTTP/1.1 will never be full-duplex except in restricted environments. The interoperability issues are intractable.
I know of no first-order interoperability issues with HTTP/2, but what happens when you're in an environment where HTTP/2 doesn't work? Should the page behave differently depending on the transport protocol in use?
@annevk
- Smaller frames.
I think this argument is confused. HTTP/2 (AFAIK) doesn't have frames in the WebSocket sense. HTTP/2 frames are an internal protocol detail used for multiplexing but not revealed to the application. WebSocket frames are application-visible message segmentation that have nothing to do with multiplexing. So comparing their sizes doesn't really make sense; these are completely unrelated protocol features.
So I would rephrase as:
@ricea FWIW Sandstorm.io (my previous project / startup) had a number of longstanding bugs that would manifest when WebSockets weren't available. In practice I don't remember ever getting a complaint from a single user who turned out to be on a WebSocket-breaking network. We did get a number of reports from users where it turned out Chrome had incorrectly decided that WebSockets weren't available and so was fast-failing them without even trying -- this was always fixed by restarting Chrome.
I would agree that for a big cloud service, having a fallback from WebSocket is necessary. But there are plenty of smaller / private services where you can pretty safely skip the fallback these days.
So I would rephrase as:
- Built-in message segmentation.
- Dedicated connections (no multiplexing overhead).
The other factor is lower per-message overhead, when messages are sent individually.
@ricea FWIW Sandstorm.io (my previous project / startup) had a number of longstanding bugs that would manifest when WebSockets weren't available. In practice I don't remember ever getting a complaint from a single user who turned out to be on a WebSocket-breaking network. We did get a number of reports from users where it turned out Chrome had incorrectly decided that WebSockets weren't available and so was fast-failing them without even trying -- this was always fixed by restarting Chrome.
That's great news, thanks. We only have client-side metrics, so we don't have much insight into why things are failing in the wild.
The bad news is that Chrome has no logic to intentionally make WebSockets fail fast when they're not available. So if it's doing that then the cause is unknown.
@kentonv it's a bit confused, but as @ricea points out it's likely you end up packaging such messages in H2 frames, meaning you have more overhead per message.
Should the page behave differently depending on the transport protocol in use?
That's already the case (requiring secure contexts; I think various performance APIs might expose the protocol) and would definitely be the case if we ever expose a H2 server push API. It doesn't seem like a huge deal to me. If we always required everything to work over H1 we can't really make progress.
I guess I consider "Dedicated connections (no multiplexing overhead)" and "lower per-message overhead" to be the same issue, since the overhead is specifically there to allow for multiplexing.
Well, there's at least one proposal for WebSocket over H2 that addresses one, but not the other: https://github.com/mcmanus/draft-h2ws/issues/1.
It seems like
XMLHttpRequest
we best treat these as "legacy APIs" that you can usefetch()
instead for (at least, once we have upload streams).Also with the lifetime of a service worker it's unlikely these APIs are useful.