Real-Time Transports & RPC
Plain HTTP request/response works when the client always initiates. Several patterns exist for when that assumption breaks - the server needs to push data, both sides need to stream continuously, or a client needs to call a server function directly rather than fetch a resource. This page compares the options and when to reach for each; see the WebSockets page for the Upgrade handshake and frame mechanics, which are not repeated here.
The four options
- Long polling - the client sends a normal HTTP request, but the server holds it open without responding until it has new data (or a timeout elapses), then responds and the client immediately reopens a new request. It is plain HTTP underneath - no new protocol, works through any proxy - but every "push" costs a full request/response round trip and a fresh connection.
- Server-Sent Events (SSE) - the client opens one long-lived HTTP
connection (
Content-Type: text/event-stream) and the server writes a stream of text events down it indefinitely. One-way, server-to-client only; the browser's built-inEventSourceAPI auto-reconnects on drop and even replays missed events via aLast-Event-IDheader. - WebSocket - a persistent, full-duplex connection: either side sends at will, independent of the other. See the dedicated WebSockets page for how the connection is established and framed.
- gRPC - an RPC framework built on HTTP/2. Instead of "fetch a resource," the client calls a named method with typed (protobuf) arguments and gets a typed response. Built on HTTP/2 streams, it supports four call shapes: unary (one request, one response), server streaming, client streaming, and full bidirectional streaming - all multiplexed over one connection.
Comparing them
| Direction | Transport | Message shape | |
|---|---|---|---|
| Long polling | Server to client (via repeated requests) | Plain HTTP/1.1, one request per update | Whatever the app defines (usually JSON) |
| SSE | Server to client only | One long-lived HTTP connection | Text event stream, auto-reconnect built in |
| WebSocket | Full duplex | Upgraded persistent TCP connection | Binary or text frames, app-defined |
| gRPC | Unary, or streaming in either/both directions | HTTP/2 multiplexed streams | Typed protobuf messages, RPC methods |
When to pick each
Choose long polling. It is the least efficient option, but it is just HTTP, so nothing in the path needs to understand anything new.
Choose SSE. It is simpler to operate than WebSocket (still plain HTTP, no special proxy/load-balancer handling), and the browser gives you reconnect-and-resume for free.
Choose WebSocket. It is the only one of these four with true, low-overhead full duplex.
Choose gRPC. It fits service-to-service (and increasingly client-to-service) calls where "fetch a resource" is the wrong mental model and "invoke this method" is the right one.
Why gRPC leans on HTTP/2
gRPC could not offer streaming and multiplexing this cheaply on HTTP/1.1: one request per connection would mean one gRPC stream per TCP connection, with all the head-of-line blocking and connection overhead that HTTP/2 was built to remove (see the Modern HTTP page). HTTP/2's independent, multiplexed streams let gRPC open many concurrent calls - including long-running streaming calls - over a single connection, each call mapped to one HTTP/2 stream.
None of these four transports "replace" HTTP - long polling and SSE run entirely inside it, WebSocket starts as an HTTP request before upgrading, and gRPC is layered directly on HTTP/2. The choice is about which push/duplex model fits the problem, not about abandoning HTTP.