Ok, so after trying a bunch of things, I have it working, but I have no idea why. That's going to be my next question.
What didn't work:
- varying the position/presence of
force-output
calls (unless both thesubscribe!
and thepublish!
messages are forced, no events fire on the client side at all) - using
babel
to encode the SSE events to octets before sending them (this failed;socket-stream
s aren'tbinary-stream
s - Re-writing the server using
cl-async
, which has its own write routines. The result of that effort can be seen here, but it didn't help at all. Firefox/Iceweasel/Conkeror perform as expected, but Chrom(?:e|ium)
still fails the same way. That is, the event stream is open as normal, theonopen
event fires, but whenever an actual event is sent,onerror
triggers instead ofonmessage
- Outputting the
bom
as specified in the "Parsing an event stream" section of the SSE spec. Doing(write-char (code-char #xfeff) s)
before starting up the stream had no effect. The stream would still be accepted by FF et al, and still be rejected by Safari engine browsers.
The only thing left at this point was busting out the packet sniffer. Using sniffit
, I discovered that there was in fact a difference between what the nginx PushStream module was emitting, and what was being emitted by my implementation.
Mine (yes, I pretended to be nginx/1.2.0
just to absolutely minimize the differences between responses):
HTTP/1.1 200 OK
Server: nginx/1.2.0
Date: Sun, 15 Oct 2013 10:29:38 GMT-5
Content-Type: text/event-stream; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Cache-Control: no-cache, no-store, must-revalidate
data: message goes here
The nginx Push Stream module:
HTTP/1.1 200 OK
Server: nginx/1.2.0
Date: Sun, 15 Sep 2013 14:40:12 GMT
Content-Type: text/event-stream; charset=utf-8
Connection: close
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Cache-Control: no-cache, no-store, must-revalidate
Transfer-Encoding: chunked
6d
data: message goes here
Adding that "6d" line to my implementation made it work properly. I have no idea why, unless this is some convention for bom
s in UTF-8 that I'm unfamiliar with. In other words, re-writing subscribe!
as
(defun subscribe! (sock)
(let ((s (socket-stream sock)))
(http-write s (list "HTTP/1.1 200 OK"
"Content-Type: text/event-stream; charset=utf-8"
"Transfer-Encoding: chunked"
"Connection: keep-alive"
"Expires: Thu, 01 Jan 1970 00:00:01 GMT"
"Cache-Control: no-cache, no-store, must-revalidate" :crlf
"6d"))
(force-output s)
(push sock *channel*)))
does the trick. Chrom(?:e|ium)
now properly accept these event streams, and don't error on message sends.
Now I need to understand exactly what the hell happened there...