-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Should EventSource and WebSocket be exposed in service workers? #947
Comments
FWIW just tested in html5workertest, and it seems Firefox 48 doesn't expose either Related: nolanlawson/html5workertest#14 |
Yeah, Gecko doesn't have EventSource in any workers. |
FWIW, if we get sub-workers in SW, these, including XHR, would become useable there too, if for nothing else, consistency. |
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. |
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. |
|
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.). |
You could also add "sendBeacon" to that list of obsolete APIs 😉 |
@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 " |
@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?) |
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 #947 (comment). We haven't had any real study on whether anyone has been utilizing this power, so not backed by data though. |
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 #947 (comment), that WebSocket may get closed when the Service Worker instance is shut down even if the server didn't intend to signal ring_end.
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 ( 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. |
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 whatwg/fetch#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? |
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. |
The other factor is lower per-message overhead, when messages are sent individually.
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.
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: mcmanus/draft-h2ws#1. |
@kentonv we got web sockets in the Cloudflare workers yet? |
@chick-fil-a-21 Currently CF Workers supports WebSocket pass-through -- that is, if all you do is rewrite requests, e.g. changing some headers or the URL, it will "just work" with a WebSocket request. We don't yet support examining or creating WebSocket content, only proxying. |
@unicomp21 Thanks! But this issue thread probably isn't the right place to discuss Cloudflare stuff. I suggest posting a thread on the Cloudflare Community; you can @ me there if you like. You can enable the Workers beta in your Cloudflare account settings (it's one of the boxes along the top). |
Hello everyone, I had already moved on to a quick reading on this issue some time ago. Today testing the APIs available on the global object of a ServiceWorker I was surprised to find WebSocket running on Chrome. I remember just coming to this discussion some time ago because trying to instantiate a socket in ServiceWorker would return an error. Soon I ran to see if there were any changes that I had not noticed but it seems to me that there were no changes. I searched for some information before I decided to share here, if there were any notes about Chrome | Chromium or even bugs reported but I did not find anything. So I do not really know if there were changes or if this could be an experimental feature or a bug.
PS: I forgot to mention but, after ServiceWorker gets control of the same page on "refresh" the socket connection is not terminated. This was my test: // sw.js
const WebSocketConnect = () => {
let ws = new WebSocket("ws://localhost/signaling", ['token', 'xxxxxxxxx'])
ws.onopen = function() {
setInterval(() => {
if ( 'readyState' in ws && ws.readyState === 1 ) {
ws.send('Hay')
}
}, 5000)
}
ws.onmessage = function(e) {
console.log('Message:', e.data)
}
ws.onclose = function(CloseEvent) {
ws.close()
console.log(`Socket is clossed, code: ${CloseEvent.code} - reason: ${CloseEvent.reason}`)
setTimeout(() => {
WebSocketConnect()
}, 3000)
}
ws.onerror = function(err) {
console.log(err)
//console.error('Socket encountered error: ', err.message, 'Closing socket')
ws.close()
}
}
WebSocketConnect() // main.js (Node)
const http = require('http')
const PORT = process.env.PORT || 3000
const WebSocketServer = require('uws').Server
// HTTP Server
const server = http.createServer((req, res) => {
// usually serve files by routes don't route "/signaling"
})
server.listen(PORT)
// Socket
const wss = new WebSocketServer({
server: server,
path: '/signaling'
})
/*
incremental value for test if ServiceWorker controler page close
socket connection after page refresh
*/
let increment = 1
// message from ServiceWorker is ignored
const sendMessage = (message, websocket) => {
increment++
websocket.send(`increment message - ${increment}`)
}
wss.on('connection', function(ws) {
ws.on('message', function(message) {
sendMessage(message, ws)
})
// await ServiceWorker close connection after page refresh
ws.on('close', function(code) {
console.log(`Socket has been closed. Code: ${code}`) // expect 1001
})
// first message from connection
ws.send('something')
}) The "onclose" event on the server does not receive an advertisement when the ServiceWorker is active and a "simple refresh" is done on the page, nor does the ServiceWorker report. Using DevTools (I know this has nothing to do with the specification) throting for offline does not enter the socket and neither does the socket connection shown in the |
Seems the reactivation of WebSocket in ServiceWorker started there for Chrome. Can you clarify a bit what's expected and what's not happening ? Also, what happens when the WebSocket server uses its native ping ? |
Hi @kael I followed the threaded thread but, I did not find exactly where in that version (path, release) Chrome came to support WebSocket within ServiceWorker. There are some versions back I could not stand. I hoped that:
This is not happening and the connection continues to be persisted. There may be some scenario that this is helpful, but I believe this is not the standard. Just out of curiosity: the socket connection started in the ServiceWorker is not shown in the I'm not using the ws module, I'm using the native API in frontend and uws in the backend. Edit: the socket connection is only shown on the On Firefex 61.0.1 when the user presses F5 or when I do not know exactly what the correct default behavior is, what the specification defines in these cases (if it defines) but, I believe what happens in Firefox is correct, is not it? |
As far as I know, Chrome has supported WebSockets in ServiceWorkers for as long as it has supported ServiceWorkers.
This seems odd, since a single ServiceWorker can have multiple pages as clients. Does navigating any of the pages terminate the connection, or is one specific page special in this regard? |
I do not know this, there are a few previous versions (I can not remember for sure) tried to use WebSocket in the ServiceWorker script in Chrome and received an error. I have two pages served by a ServiceWorker (localhost): index.html and about.html Firefox opens the WebSocket connection and maintains it for only 30 seconds (+/-) ... before reaching this limit (?) if the user presses the F5 key or if In Firefox navigate between cached pages (within this "30-second" limit) it also produces the same ... the open connection on the previous page is closed silently without sending a "close frame". In Chrome there is no time limit and browsing between different pages already cached does not end the connection. I'm not sure which of the two implementations follows the pattern defined by the WebSocket API (Chrome or Firefox) ... I tried to find reference in SPEC but I think this does not concern WebSocket's spec but rather that of ServiceWorker. I believe Firefox is closer to the ServiceWorker specification
Although I do not know if this is intentional since it is not sent a "close frame". I found interesting persistence in the connection obtained in Chrome but, after all, which of these two distinct implementations is correct (or about to be correct)? |
I know of one version where it was broken due to a bug. It wasn't intentional.
It sounds like they're trying to avoid people assuming that they can make long-term connections from a ServiceWorker. But having a different lifetime for the WebSocket and the variable that references it is really confusing, so I don't consider this a good idea.
This sounds like a bug, since it's a straightforward RFC6455 violation. See https://tools.ietf.org/html/rfc6455#page-44. |
@ricea ok
But this (the closing without sending a "close frame") would be the implementation of WebSockets that Firefox does in ServiceWorker or, this would originate from the ServiceWorker routine? I confess to having been confused ... I must assume that no implementation carried out by the mentioned browsers should be followed/used? |
Closing this since this can no longer be "fixed" now it's been shipping all over. |
I believe this is firefox implementing the idle timeout of the service worker. If there is no functional event waitUntil() holding the service worker alive it will be stopped to conserve system resources. The idle timeout is restarted when another functional event (like FetchEvent) is dispatched to the service worker.
Do you have devtools open while running this test? At least at one point chrome did not timeout service workers while devtools were open. Not sure if that is still the case or not. |
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.
The text was updated successfully, but these errors were encountered: