Skip to Content
Communication Protocols — HTTP, WebSockets, gRPC & SSE

Communication Protocols — HTTP, WebSockets, gRPC & SSE

  • system-design
  • http
  • websockets
  • grpc
  • sse
  • rest-api
  • distributed-systems
6 min read System Design · Part 9 of 13 Ritik Tiwari
Part 8System Design · Part 9 of 13Part 10

The Story: Ways to Communicate in a City

  • HTTP (Request-Response): You send a letter, wait for a reply. You initiate, server responds. Done.
  • WebSockets: You open a phone call. Both can speak at any time. The line stays open.
  • gRPC: A highly efficient internal memo system between departments — structured, typed, fast.
  • Server-Sent Events: A news broadcast. Server pushes updates. You only listen.
  • Long Polling: You call the office and wait on hold until they have an answer.

HTTP / HTTPS

HTTP (HyperText Transfer Protocol) is the protocol of the web. Every API call, every web page request, every REST call is HTTP.

Request-Response cycle:

Client → [HTTP Request]  → Server
Client ← [HTTP Response] ← Server
Connection closes (HTTP/1.1: may keep-alive, HTTP/2: multiplexed)

HTTP Versions

VersionKey improvementConnection
HTTP/1.0OriginalNew TCP connection per request
HTTP/1.1Keep-alive, pipeliningReuse connection, but HOL blocking
HTTP/2Multiplexing, header compression, server pushMultiple requests in parallel on one connection
HTTP/3Built on QUIC (UDP)Eliminates TCP HOL blocking, faster reconnection

Head-of-Line (HOL) blocking in HTTP/1.1: Requests are served in order on a connection. If request 1 is slow, requests 2–10 wait even if they’re ready. HTTP/2 multiplexing sends all in parallel on one connection.

HTTP Methods

MethodOperationIdempotent?Body?
GETReadYesNo
POSTCreateNoYes
PUTReplace entire resourceYesYes
PATCHPartial updateNoYes
DELETEDeleteYesNo
HEADLike GET, but no bodyYesNo

Idempotent: Calling the same request multiple times has the same effect as calling it once. Important for retry logic.

HTTP Status Codes

2xx — Success
  200 OK                     →  success
  201 Created                →  resource created (POST)
  204 No Content             →  success, no body (DELETE)

3xx — Redirection
  301 Moved Permanently      →  browser caches, SEO-safe
  302 Found                  →  temporary redirect
  304 Not Modified           →  browser uses cached version

4xx — Client Error
  400 Bad Request            →  malformed request
  401 Unauthorized           →  not authenticated
  403 Forbidden              →  authenticated but not allowed
  404 Not Found              →  resource doesn't exist
  409 Conflict               →  state conflict (e.g., duplicate username)
  422 Unprocessable          →  validation failure
  429 Too Many Requests      →  rate limited

5xx — Server Error
  500 Internal Server Error  →  generic server crash
  502 Bad Gateway            →  upstream service unreachable
  503 Service Unavailable    →  server overloaded or down
  504 Gateway Timeout        →  upstream too slow

REST Design Principles

REST (Representational State Transfer) is an architectural style for HTTP APIs.

Key principles:

  1. Resources are nouns (not verbs):
✅ GET    /users/42           → get user 42
✅ POST   /users              → create user
✅ PUT    /users/42           → update user 42
✅ DELETE /users/42           → delete user 42

❌ GET    /getUser?id=42
❌ POST   /createUser
❌ POST   /deleteUser/42
  1. Stateless: Every request contains all information needed. No server-side session state.

  2. Hierarchical URLs reflect relationships:

/users/42/orders           → orders belongs to user 42
/users/42/orders/101       → specific order 101 belongs to user 42
/users/42/orders/101/items → items in the order 101 belongs to user 42
  1. Use query params for filtering, sorting, pagination:
GET /products?category=electronics&sort=price_asc&page=2&limit=20
  1. Version your API:
GET /api/v1/users/42     ← current
GET /api/v2/users/42     ← new version with breaking changes

WebSockets: The Open Phone Line

What WebSockets solve

HTTP is request-response. The client always initiates. What if the server needs to push data to the client?

Problem scenarios:

  • Chat: other users’ messages appear without you refreshing
  • Live scores: score updates automatically
  • Stock prices: real-time price changes
  • Collaborative editing: see others’ cursor movements

Naive solution: polling

Client: every 1 second → "Any new messages?"
Server: "No"
Client: "Any new messages?"
Server: "No"
Client: "Any new messages?"
Server: "Yes! Here it is."

99% of requests return "No" — wasted bandwidth and latency

WebSocket solution: Persistent, full-duplex connection. Both sides can send at any time.

WebSocket Handshake

WebSockets upgrade from HTTP:

Client → Server: HTTP upgrade request
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Server → Client: 101 Switching Protocols
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

--- Connection is now a WebSocket ---

Server → Client: "{'type':'message','text':'Hello!'}"  (any time)
Client → Server: "{'type':'message','text':'Hi back!'}"  (any time)

WebSocket use cases

Use caseWhy WebSocket?
Chat applicationsBidirectional real-time messaging
Live sports scoresServer pushes score updates
Collaborative documentsReal-time cursor and edit sync
Online gamingLow latency bidirectional state updates
Live trading dashboardsSub-second price updates
IoT device monitoringContinuous sensor data streams

WebSocket scaling challenges

Problem: User opens WebSocket → connects to Server 1
         User A and User B must be on same server to chat directly

Solution options:
1. Sticky sessions: Load Balancer routes same user to same server always
   - Problem: server failures lose all connections

2. Pub/Sub broker (Redis Pub/Sub):
   Server 1 (User A) → publishes  → [Redis Pub/Sub channel]
   Server 2 (User B) ← subscribes ← [Redis Pub/Sub channel]
   Server 2 delivers to User B

3. Message broker (Kafka/RabbitMQ):
   Same as above but durable (messages survive crashes)

In production: Use Redis Pub/Sub or a dedicated WebSocket service (Pusher, Ably, AWS API Gateway WebSocket).


Server-Sent Events (SSE)

One-way push from server to client over HTTP. Simpler than WebSockets when you only need server → client.

// Server (Node.js)
app.get("/events", (req, res) => {
	res.setHeader("Content-Type", "text/event-stream");
	res.setHeader("Cache-Control", "no-cache");

	const interval = setInterval(() => {
		res.write(`data: ${JSON.stringify({ price: getStockPrice() })}\n\n`);
	}, 1000);

	req.on("close", () => clearInterval(interval));
});

// Client (JavaScript)
const es = new EventSource("/events");
es.onmessage = (e) => {
	console.log(JSON.parse(e.data));
};

SSE vs WebSockets:

SSEWebSockets
DirectionServer → Client onlyBidirectional
ProtocolHTTPWebSocket (upgraded HTTP)
ReconnectionAutomaticMust implement
Browser supportAll modern browsersAll modern browsers
Load balancerWorks with standard HTTP LBNeeds sticky sessions or pub/sub
Use whenNews feed, notifications, live updatesChat, gaming, collaborative

Long Polling

Before WebSockets: The trick to simulate server push with regular HTTP.

Client → "Any new messages?"
Server holds the request open... (up to 30s)
  → New message arrives → Server responds immediately
  → Timeout reached     → Server responds with empty

Client immediately makes another request:
Client → "Any new messages?"
Server holds... → responds when message arrives or timeout

Better than regular polling (requests only return when there’s data), but worse than WebSockets (still request-response, HTTP overhead per message).

Still used by: Some chat systems, webhooks with retry logic, systems where WebSocket infrastructure is unavailable.


gRPC: The High-Performance Internal Messenger

gRPC (Google Remote Procedure Call) is a high-performance RPC framework using Protocol Buffers for serialization and HTTP/2 for transport.

Think of it as calling a function on a remote server as if it were a local function call.

How gRPC works

  1. Define your service in a .proto file:
// service.proto
syntax = "proto3";

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (stream User);    // streaming!
  rpc CreateUser (stream CreateUserRequest) returns (User);  // client streaming
}

message GetUserRequest {
  int64 user_id = 1;
}

message User {
  int64 id = 1;
  string name = 2;
  string email = 3;
  google.protobuf.Timestamp created_at = 4;
}
  1. Generate client/server code in any language (Python, Go, Java, Rust…):
protoc --python_out=. --grpc_python_out=. service.proto
  1. Implement the server, call from client:
# Client code — looks like a local function call
user = stub.GetUser(GetUserRequest(user_id=42))
print(user.name)  # Ritik

gRPC vs REST comparison

REST/JSONgRPC/Protobuf
ProtocolHTTP/1.1 or 2HTTP/2
FormatJSON (text)Protobuf (binary)
SizeLarger (verbose)3–10× smaller
SpeedSlower~10× faster
SchemaOptional (OpenAPI)Required (.proto)
StreamingNot nativeBuilt-in
Browser supportNativeNeeds proxy (grpc-web)
ToolingMature, widespreadStrong in backend
Best forExternal/public APIsInternal microservices

gRPC streaming modes

Unary:          Client sends one request, gets one response
Server stream:  Client sends one request, server streams many responses
Client stream:  Client streams many requests, server sends one response
Bidirectional:  Both sides stream simultaneously

Bidirectional streaming example: Real-time audio transcription

Client streams: audio chunks
Server streams: transcript updates
Both happening simultaneously

Choosing the Right Protocol

Public API (browser/mobile clients)?
    → REST/HTTP — universal support, familiar, easy to debug

Internal microservice-to-microservice?
    → gRPC — faster, type-safe, streaming support

Real-time server → client updates (news, notifications, prices)?
    → Server-Sent Events — simple, works with HTTP

Real-time bidirectional (chat, gaming, collaborative)?
    → WebSockets — persistent connection, low latency

Need support across languages + existing HTTP infrastructure?
    → REST wins (pragmatic choice)

Need maximum performance + you control all clients?
    → gRPC wins

Webhooks: The Reverse API

Traditional API: you call the server to check for updates.
Webhook: the server calls you when something happens.

Traditional polling:
You → "Did the payment succeed?" (every 5 seconds)
Server: "No"... "No"... "No"... "Yes!"

Webhook:
You register: "POST https://your-server.com/webhook when payment completes"
Server: (when payment completes) → POST https://your-server.com/webhook { status: "success" }

Design considerations:

  • Webhooks can fail (your server might be down)
  • Implement retry with exponential backoff on the webhook sender side
  • Make your webhook endpoint idempotent (deduplication ID)
  • Always verify the webhook signature (HMAC-SHA256) to prevent spoofing
import hmac, hashlib

def verify_webhook(payload, signature, secret):
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Flashcards

Q: REST vs WebSocket vs gRPC — when to use each?

REST is my default for external APIs — universally supported, easy to debug, works everywhere. WebSockets for real-time bidirectional communication like chat or collaborative editing where the server needs to push data unprompted. gRPC for internal microservice communication where I need high throughput, type safety, and streaming — it’s 10× faster than REST due to binary Protobuf serialization over HTTP/2.

Q: How would you implement real-time notifications?

Server-Sent Events for one-way pushes (notifications, live feeds) — they’re simple, work over HTTP, and reconnect automatically. WebSockets if users need to interact in real time (chat, collaborative features). For scale, I’d put Redis Pub/Sub behind the WebSocket servers so messages route correctly regardless of which server a client is connected to.

Q: What is the difference between HTTP/1.1 and HTTP/2?

HTTP/2 adds multiplexing (multiple requests in parallel on one connection), header compression, and server push, eliminating the head-of-line blocking of HTTP/1.1.

Q: When should you use WebSockets vs SSE?

WebSockets for bidirectional communication (chat, gaming). SSE for server-to-client only (notifications, live feeds, prices). SSE is simpler and auto-reconnects.

Q: What makes gRPC faster than REST?

Binary Protobuf serialization (smaller payloads), HTTP/2 multiplexing (parallel requests), and generated typed code (no JSON parsing).

Q: What is a webhook and what’s the main reliability concern?

A webhook is a server calling your endpoint when an event occurs. Main concern: your endpoint might be down. Sender must implement retries with backoff. Your endpoint must be idempotent.

Q: What HTTP status code means the resource was created successfully?

201 Created.

Q: What is head-of-line blocking in HTTP/1.1?

Requests are served in order on a connection. A slow request blocks all subsequent requests. HTTP/2 multiplexing eliminates this.