Computer Networks #12: HTTP and HTTP/1.1

We have already seen how DNS helps us find the server for a domain.
But finding the server is only the beginning.
Once the client knows where the server is, it needs a way to communicate with it.
This is where HTTP comes in.
HTTP is the protocol that defines how a client and a server communicate on the web.
When your browser loads:
https://example.com
a lot happens behind the scenes, but at the application layer, HTTP is responsible for things like:
"I want this resource."
and the server responds:
"Here is the resource you asked for."
Let's understand how this actually works.
What is HTTP?
HTTP stands for HyperText Transfer Protocol.
It is an Application Layer protocol used for communication between clients and servers.
The client could be:
A web browser
A mobile application
A command-line tool like
curlAnother server
The server could be:
A web server
An API server
An application server
HTTP defines the structure of requests and responses and the rules for exchanging them.
A very simplified flow looks like this:
Client
|
| HTTP Request
↓
Server
|
| HTTP Response
↓
Client
For example:
GET /users HTTP/1.1
Host: example.com
The server might respond:
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"id": 1,
"name": "Alice"
}
]
The client and server do not need to be written in the same programming language.
A Node.js server can communicate with a Python client.
A Go server can communicate with a Java client.
A browser written using C++ can communicate with a server written in Rust.
As long as both sides understand HTTP, they can communicate.
HTTP is a Stateless Protocol
One important property of HTTP is that it is stateless.
This means that HTTP itself does not remember previous requests.
Suppose a client sends:
Request 1
GET /profile
and later:
Request 2
GET /orders
HTTP does not automatically know that both requests came from the same user.
Each request is treated independently.
This does not mean websites cannot maintain state.
Applications use mechanisms such as:
Cookies
Sessions
Authentication tokens
Server-side session stores
to maintain state between requests.
So:
HTTP is stateless, but applications built on HTTP can maintain state.
HTTP is Media Independent
HTTP is not limited to HTML.
It can transfer many different types of data.
For example:
HTML
CSS
JavaScript
JSON
Images
Videos
PDFs
Audio
Files
The Content-Type header tells the receiver what type of data is being transferred.
For example:
Content-Type: text/html
or:
Content-Type: application/json
or:
Content-Type: image/png
This is why HTTP can be used for websites, APIs, file downloads, and many other applications.
HTTP Does Not Encrypt Data
HTTP itself does not provide encryption.
If you send:
GET /login
over plain HTTP, the data can potentially be observed by someone who can intercept the connection.
This is why we normally use:
HTTPS
HTTPS is essentially HTTP running over a connection protected by TLS.
A simplified view is:
HTTP
↓
TLS
↓
TCP
↓
IP
TLS provides properties such as:
Encryption
Authentication of the server
Integrity protection
So it is more accurate to say:
HTTP defines the application-level communication. TLS provides the security when using HTTPS.
HTTP Does Not Handle Reliable Delivery
HTTP does not itself guarantee that packets will reach their destination.
Traditionally, HTTP has been used over TCP, which provides reliable, ordered byte-stream delivery.
The stack looks like:
HTTP
↓
TCP
↓
IP
TCP handles things such as:
Reliable delivery
Ordering
Retransmission
Flow control
Congestion control
HTTP focuses on the application-level request and response.
Modern HTTP versions can also use different transports. For example, HTTP/3 uses QUIC, which runs over UDP.
So we should avoid thinking of HTTP as being permanently tied to TCP.
HTTP/1.0
Now let's look at the evolution of HTTP.
One important limitation of early HTTP was how it handled TCP connections.
Consider a webpage containing:
index.html
style.css
script.js
logo.png
The browser needs to make several requests.
With the typical HTTP/1.0 behavior, a connection was closed after the response was completed.
The flow looked roughly like this:
Every new connection requires the TCP three-way handshake.
So if a page needs many resources, repeatedly creating and closing TCP connections adds overhead.
This means more:
TCP handshakes
Network round trips
CPU work
Connection management overhead
HTTP/1.0 could support persistent connections using mechanisms such as:
Connection: keep-alive
But persistent connections were not the default behavior in the same way they became in HTTP/1.1.
HTTP/1.1 and Persistent Connections
HTTP/1.1 introduced persistent connections as the default.
This was a major improvement.
Instead of creating a new TCP connection for every request, the same connection could be reused.
For example:
TCP Connection
|
+---- Request 1
| ↓
| Response 1
|
+---- Request 2
| ↓
| Response 2
|
+---- Request 3
↓
Response 3
This avoids performing a new TCP three-way handshake for every request.
The connection could remain open and be reused for multiple HTTP requests and responses.
This is called a persistent connection.
But HTTP/1.1 Still Had a Problem
Persistent connections solved one major problem, but another problem remained.
In normal HTTP/1.1 request/response usage, the client sends a request and waits for its response before sending the next request on that connection.
For example:
Request 1
↓
Response 1
↓
Request 2
↓
Response 2
↓
Request 3
↓
Response 3
Suppose the browser needs:
GET /style.css
GET /script.js
GET /logo.png
It cannot simply send all three requests at once on the same connection under the normal request/response behavior.
This leads to another HTTP/1.1 feature.
HTTP/1.1 Pipelining
HTTP/1.1 introduced request pipelining.
With pipelining, the client could send multiple requests without waiting for the previous response.
Instead of:
Request 1
↓
Response 1
↓
Request 2
↓
Response 2
it could send:
Request 1
Request 2
Request 3
↓
Response 1
Response 2
Response 3
This sounds like a great solution.
But there was a major problem.
Head-of-Line Blocking
HTTP/1.1 pipelining required responses to be returned in the same order as the requests.
Imagine the client sends:
Request 1 → /large-file
Request 2 → /small-file
Request 3 → /image
Suppose /large-file takes a long time to generate or transfer.
The server cannot simply return:
Response 2
Response 3
Response 1
It has to preserve the ordering.
So the small responses can get stuck behind the large response.
Request 1 ────────────────→
Request 2 ──→
Request 3 ──→
Response 1
████████████████████████████████
Response 2
Waiting...
Response 3
Waiting...
This is called Head-of-Line Blocking, or HOL blocking.
The request at the front of the line is blocking the requests behind it.
This made HTTP/1.1 pipelining difficult to use reliably in practice, and it was disabled or unsupported in many common client and server implementations.
Why HTTP/1.1 Still Mattered
Even with its limitations, HTTP/1.1 was a huge improvement over HTTP/1.0.
The most important improvements include:
Persistent connections
Connections could be reused instead of being created for every request.
Better caching
HTTP/1.1 provided stronger and more flexible mechanisms for controlling caches.
For example:
Cache-Control
ETag
If-None-Match
Last-Modified
These mechanisms can prevent unnecessary data from being transferred.
Host header
HTTP/1.1 made the Host header mandatory.
For example:
GET / HTTP/1.1
Host: example.com
This was extremely important for virtual hosting.
A single server IP can host many different domains:
example.com
example.org
example.net
The server can use the Host header to know which website the client wants.
Anatomy of an HTTP/1.1 Request
Let's look at a simple HTTP/1.1 request:
GET /users HTTP/1.1
Host: example.com
Accept: application/json
User-Agent: Mozilla/5.0
An HTTP request contains several important parts.
Request Method
GET
This tells the server what operation the client wants.
Common methods include:
GET
POST
PUT
PATCH
DELETE
Request Target
/users
This tells the server which resource is being requested.
HTTP Version
HTTP/1.1
This tells the server which HTTP version the client is speaking.
Headers
For example:
Host: example.com
Accept: application/json
Headers carry additional information about the request.
HTTP/1.1 Response
The server might respond:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 42
{"id":1,"name":"Alice"}
The first line contains:
HTTP/1.1 200 OK
This contains:
HTTP version
Status code
Reason phrase
The status code tells the client what happened.
Some common status codes are:
200 OK
201 Created
301 Moved Permanently
304 Not Modified
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error
After the status line come the response headers.
For example:
Content-Type: application/json
Content-Length: 42
Then comes the actual response body.
HTTP Request and Response in One Picture
Putting everything together:
HTTP/1.1
Client Server
| |
| GET /users HTTP/1.1 |
| Host: example.com |
|----------------------------------->|
| |
| HTTP/1.1 200 OK |
| Content-Type: application/json
| Content-Length: ... |
| { ... } |
|<-----------------------------------|
| |
The important thing is that HTTP defines the format and semantics of this communication.
TCP handles the reliable transport underneath it.
HTTP/1.1 Connection Lifecycle
A simplified HTTP/1.1 connection looks like:
DNS
↓
Find server IP
↓
TCP 3-Way Handshake
↓
HTTP Request
↓
HTTP Response
↓
Reuse TCP Connection
↓
HTTP Request
↓
HTTP Response
↓
...
This is much more efficient than creating a new TCP connection for every HTTP request.
However, HTTP/1.1 still has limitations when many requests need to be handled concurrently.
That limitation became one of the major motivations for HTTP/2.
HTTP/1.0 vs HTTP/1.1
| Feature | HTTP/1.0 | HTTP/1.1 |
|---|---|---|
| Persistent connections | Optional | Default |
| Connection reuse | Limited | Supported by default |
| Host header | Not required | Required |
| Pipelining | Not available | Supported |
| Caching | Basic | Improved |
| Multiple requests on one connection | Limited | Better support |
| Head-of-Line blocking | Less relevant | Major issue with pipelining |
The key idea to remember is:
HTTP/1.0 often created a new connection for each request, while HTTP/1.1 made connection reuse the normal behavior.
But HTTP/1.1 still processes responses in a way that can cause Head-of-Line Blocking, especially when pipelining is involved.
This is one of the problems that HTTP/2 was designed to address.





