0%

When the Server Has News

Two Ways to Fake a Push

Polling every two seconds left the scoreboard about a second late and spent 1,800 requests an hour per viewer, nearly all of them answered with “nothing new”. Both costs come from the same place: the server has the news, but it may only answer when a request is waiting. There are two well-known ways to stretch so that something is waiting when the news arrives. Neither one is a real push, which is why this lesson’s title calls them fakes, but each removes one cost of polling.

Hold the request open

The first trick is long polling. The viewer sends a request as before, but the server does not answer at once. It holds the request open until a score changes, or until its own wait runs out, for example after 25 seconds. Then it answers, and the viewer sends the next request immediately.

client                                   server

GET /scores                    ---->
                                         holds the request
                                         a score changes
                               <----     200 OK, the new scores
GET /scores                    ---->
                                         holds the request
                                         25 seconds pass, no change
                               <----     200 OK, nothing new
GET /scores                    ---->
                                         holds the request

The viewer asks, and the server sits on the request. When a score changes, the server answers right away with the new scores, and the viewer asks again. When 25 quiet seconds pass, the server answers that nothing changed, and the viewer asks again.

The lateness almost disappears. A change no longer waits for the next poll, because a request is nearly always already waiting at the server; it usually waits only for the answer to cross the network. It waits a little longer when it lands while the previous answer is still on its way and the next request has not reached the server yet. The empty answers shrink too: one per 25 quiet seconds instead of one every 2 seconds.

There is one new rule for the client. In HTTP with Python you gave every request a timeout, and you learned that a timeout is your client deciding to stop waiting, not the server failing. A long poll is a request that is supposed to wait, so the client’s timeout must be longer than the server’s hold. Otherwise the client gives up on every quiet period just before the server’s honest “nothing new” arrives.

The server holds a long poll for up to 25 seconds. What client timeout makes sense?

One response that never ends

The second trick goes further: one request, and a response that never finishes. This is Server-Sent Events, often shortened to SSE. The viewer sends one GET, and the server answers with a head whose Content-Type is text/event-stream:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Transfer-Encoding: chunked

The head has no Content-Length, because the server does not know how long the body will be. It sends the body in chunks, one of the three ways a body can end that you met in HTTP with Python, and it keeps the connection open. Whenever a score changes, the server writes one more event into the body. Once the chunk sizes are taken out, the body the viewer reads looks like this:

data: Owls 12

data: Herons 9

Each event is a data: line followed by an empty line. The empty line is what ends an event; an event may have several data: lines before it. The data here is plain text, but it can be anything that fits on a line, included. The stream above carries two events. Two score changes later, there would be four.

Reading events out of that text takes the tools you already have. This block works on the same text, written as one literal, so nothing is received from anywhere. Run it:

Try it

It prints Owls 12 and then Herons 9. Splitting at "\n\n", the empty line between events, gives one piece per event, plus an empty string after the last one, because the stream ends with an empty line. The skips anything that does not start with data: , which includes that empty string, and the event[6:] drops the six characters of data: from the front.

A browser has this reading built in, and it also reopens a stream that drops. On the server side, an event stream is an ordinary HTTP response, so it needs nothing that HTTP does not already have.

In an event stream, what ends one event?

What each one keeps

Each trick removes one cost of polling and keeps one limit.

TechniqueWhat it removesWhat it keeps
Long pollingthe wait for the next poll, and most empty answersone request for every piece of news, and the client still starts every exchange
Server-Sent Eventsthe request for every piece of news: one response carries them allone direction only, from server to client

With long polling, the server can only answer a request, so every score change still ends one request and needs the viewer to send another. With SSE, the server writes whenever it likes, but only the server writes. The viewer cannot send anything back on the event stream.

That matters for the scoreboard, because the scorekeeper also has something to say. With SSE, a viewer receives scores on the stream, but the scorekeeper’s new score has to travel some other way.

With Server-Sent Events, how does the scorekeeper send a new score to the server?

So the pieces exist: a request the server can hold, and a response that keeps carrying news. What is missing is one connection on which both sides may speak whenever they want. The next lesson exactly what such a connection has to provide.