The HTTP Protocol

comic about web servers

Server Attention Span by xkcd

  • Requires: a connection between client and server
  • Stateless: no login process, each request is independent
  • Simple format: request header, blank line, possible payload
  • Symmetrical: allows data to be sent and received
  • Very easy to implement but scales very well

The Hypertext Transfer Protocol (HTTP) is one of the core technologies of the web, invented by Tim Berners-Lee back in 1989. HTTP is a communication protocol - a language of requests and responses that is used to negotiate the transfer of information on the web.

The web is driven by requests that are sent by clients to a server. A client is usually a web browser but it could be a script, for example, a search engine's web crawler. When the server gets a request, it works out how to answer it and sends in return a response. The HTTP protocol defines how these requests and responses look and the way that client and server talk to each other.

The first thing that is required to make an HTTP request is a point-to-point connection between client and server. This is established on the internet via the TCP/IP protocol. Once in place, the client is able to send messages to the server and wait for a response.

In this chapter, we'll talk mainly about the older but still widely used 1.1 version of the HTTP protocol. The newer HTTP/2 and HTTP/3 protocols follow the same basic ideas but are more complicated in the way that they exchange messages. The final section will provide a summary.

The HTTP Request

The message that is sent in an HTTP request begins with a textual header. The first word in the request is always an HTTP Verb that defines what kind of request is being made. We'll begin by looking at the most common verb which is GET. A GET request asks for a particular resource to be sent back in the response. Following the word GET, the name of the resource to be returned is sent, this is the path part of the URL. Finally, the first line ends with the version of the HTTP protocol that is being used by the client. Here are some first lines for the URLs http://example.org/

GET / HTTP/1.1

http://example.org/style.css

GET /style.css HTTP/1.1

https://pwp.stevecassidy.net/web/http/

GET /web/http/ HTTP/1.1

Note how the path part of the URL (after the domain name, starting with the first / character), becomes the second word in the GET request.

The first line of the request is then followed by one or more header lines that contain additional information about the request. Here's a real example from the URL https://pwp.stevecassidy.net/web/http/

GET /web/http/ HTTP/1.1
Host: pwp.stevecassidy.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://pwp.stevecassidy.net/
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Pragma: no-cache
Cache-Control: no-cache

What do each of these headers mean? Which are required? Many are defined in the official HTTP Documentation maintained by the HTTP Working Group, but others can be defined via the HTTP extension framework.

To look at some examples. The Host header encodes the host part of the original URL. This is sent along with the request because one web server might be serving pages for many websites. The Host header is used to work out which response to return.

The User-Agent header includes details of your web browser. This used to be quite important when different web browsers had different capabilities. These days it is much less useful as most browsers implement the same set of web standards. However, it can be used by the server to return different results to different browsers.

The Accept header tells the server a little about what kind of response the browser can deal with. It can be used by the server to decide what kind of document to return. In this case the browser says it prefers images in avif or webp format but note the */* at the end which means it will take anything. The server can use this information to return particular formats of images if it has them.

The header ends with a blank line and in this case, that is the end of the request. For some requests, there can be more data sent after the header. For example, when we are uploading a file or sending form data. These requests would not be GET requests but would use POST or PUT. We'll look at these later.

Most of the header lines are optional. The simplest valid request would be, eg:

GET /web/http/ HTTP/1.1
Host: pwp.stevecassidy.net

Note - the newer HTTP/2 and HTTP/3 protocols require a little bit more setup so that the client knows that the server understands this more recent protocol. Lower level protocols are used when setting up the connection to allow the server to tell the client that it understands HTTP/2 or HTTP/3. In contrast, HTTP/1.1 is assumed for any web server without prior acknowledgement.

The HTTP Response

The web server receives the request and must decide how to handle it. In the simplest case, it will locate a file that corresponds to the resource being requested and will return that. The message sent as the HTTP response again begins with a header that starts with a status line. The status line begins with the version of HTTP being used and ends with a status code and message. If the request was successful, the most common status code is 200 OK. Here is an example:

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 29 Feb 2024 02:46:24 GMT
Content-Type: text/html
Last-Modified: Wed, 21 Feb 2024 10:17:10 GMT
Transfer-Encoding: chunked
Connection: keep-alive
ETag: W/"65d5cda6-4e39"
Content-Encoding: gzip

As with the request headers, there are more lines following the first and the whole header is terminated with a blank line. In this case the header is saying that the Content-Type of the returned document is text/html, telling the browser that this is an HTML page rather than, say, an image. The response will also be sent back using a gzip encoding, meaning that it has been compressed by the server to save network bandwidth. If you look at the request header above, you'll see that the browser indicated it could accept gzip responses, so the server has used this capability to save resources.

In a response, we expect there to be something returned - in this case an HTML file. The response payload is sent after the blank line that terminates the header.
If we were to look at the response in this case we'd just see binary data representing the gzip compressed HTML file.

Viewing HTTP Requests and Responses

Your browser provides tools to help developers see the requests and responses that it is sending. It can be very useful to be able to see these as developers so you should learn to use your browser tools. Viewing requests and responses can be useful in debugging your web application.

In most browsers, you can open the developer tools using the F12 key. Note that in Safari, you need to first enable them in the settings.

Here's an example of viewing the Network tab in the developer tools in Firefox for this page.

Firefox dev-tools screenshot

In the Network tab you will see a list of all of the requests that are being sent to render your page. Clicking on one of the requests will show the details of that request and the response that was received. Get used to using this tool to view requests and responses and explore what is happening behind the scenes in your browser.

GET Requests are Idempotent

GET is the simplest and most common HTTP request. It is intended to be a simple request for a resource and has the important property of being idempotent. This means that responding to the request has no side-effects and could be carried out many times without a problem. That should be the case for any GET request but will not be true for other requests as we'll now see.

POST Requests

The next most common kind of request is a POST request. Rather than just requesting a resource from the server, a POST request sends some data to the server to update a resource or carry out an action. POST requests are generally used to submit form data to a server and the request will generally be passed to a program/script on the server to do what has been asked. A POST request is not idempotent because it can affect the state of the world; for example, creating a new listing on Ebay or transferring money between bank accounts.

A POST request has the same general form as a GET request. The main difference is that it will have a payload - content that will be sent along with the request. The payload can be as simple as the values of some form variables or it could be a file to be uploaded to the server.

As with the GET request, the POST request has headers that are terminated with a blank line. The POST body then follows that blank line. Here is an example request that submits some form data:

POST /process HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 60
Origin: http://example.org
Connection: keep-alive
Referer: http://example.org/
Pragma: no-cache
Cache-Control: no-cache

name=Steve+Cassidy&age=33&message=This+is+my+message+to+you.

The first line follows the same pattern as the GET request with the action verb, POST, followed by the resource name /process and the version of HTTP. The following headers are also similar, with the addition of the Content-Type and Content-Length headers which relate to the content that is being sent along with the request. In this case we're sending 60 characters encoded in the x-www-form-urlencoded format.

You can see the content of the request in the last line above. It encodes three form variables: name, age and message. These are separated by the & character and any spaces in the values are replaced with +. This is a simple form of encoding text and as its name suggests, is the one used to encode data in a URL - for example if the resource you are requesting has spaces in it, they would be replaced by +.

Once the server gets this request it will do whatever it decides is the right thing to do and then return a response to us. That will often be a new HTML page, so the response could look just like the one we got from the GET request. The effect of the request though might be more involved since the server is probably adding something to a database or creating a new resource in response to this request.

GET Requests with Form Data

In the examples above we said that a GET request was for a simple resource and a POST request is often for submitting a form. However, it is possible to send form data along with a GET request. Here we'll discuss how this is done and then how you might decide whether to use a GET or POST request for your form.

In an HTML form the method attribute defines what HTTP request type will be used to send the form. If this is POST then the data will be URL encoded and sent in the request body as in the example above. If the method is GET, the form data is again URL encoded but this time it is appended to the URL defined in the action attribute.

Assuming that the following form is included in the page at http://example.org/, when the submit button is clicked a GET request will be generated.

<form method="GET" action="/search">
    <input name="q">
    <input type="submit" value="Submit Message">
</form>

The GET request will be sent to http://example.org/search - the URL is derived from the source page URL with the form action URL appended. The request will look something like:

GET /search?q=HTTP+Requests HTTP/1.1
Host: example.org

As you can see, the form data has been appended to the URL after a question mark ?. This whole request is then sent to the server which can respond as it sees fit. In this example, we seem to be sending a search term and so the response may be a list of documents matching that search.

Going back to the definition of the GET request we said that it should be idempotent - it should not change the state of the world. That would seem to be the case here; we are returned a list of the documents matching the search term and that would probably be the same whoever made the request.

However, it would be possible to send a GET request with our new message in the POST example above:

GET /process?name=Steve+Cassidy&age=33&message=This+is+my+message+to+you. HTTP/1.1
Host: example.org

If the effect of this request is to create a new message and send it to someone, that would not be an idempotent operation. Sending the request again would send a new message. So, even though we could use a GET request here, we should not because it violates the meaning of GET. Some parts of the web infrastructure such as caching rely on proper use of the request types.

Is GET more Secure?

In a GET request including form data, the data is shown in the URL. If the web server is keeping a log of requests, the data will be included in the log along with the IP address of the client machine. If a POST request was used instead, the form data would be part of the request body and would not appear in any logs.

In both cases, the same form data is included in the HTTP request being sent. If someone was able to snoop on the request, they could see the data either way.

The only case where POST might be considered more secure is if the web logs on the server are compromised. In this case however, it is likely that the databases on the server are compromised too and the attacker will have all the access they need to the data.

Ultimately, neither type of request is more secure.

Other Request Types

There are other kinds of request but all follow the pattern described above.

A HEAD request is like GET but the response will only contain the headers rather than any body content. It is used for checking whether a resource has been updated since it was last retrieved or for trying out (pre-flight) a request before sending it. The client will look at the response headers to work out what it should do next.

A PUT request can be used to upload a new resource and, like POST, will have a payload. The difference between PUT and POST is subtle and relates to the resource name that the request is sent to. As an example, if my web application stores a collection of books they might be represented by the URL http://example.org/books/. Sending a GET request to that URL would return a page listing all of the books. Sending a POST request might add a new book to the collection. One particular book might be given the URL http://example.org/books/a_tale_of_two_cities.html; I could send a PUT request to that URL to upload a new version of the book.

The final common request type is DELETE. I might send a DELETE request to the book URL to remove that book from the collection.

A web server won't automatically know how to respond to POST, PUT or DELETE requests. The author of the server would need to write code to handle those requests and do the right thing - adding or removing things from a database. A GET request is much simpler and the default might be to just return the contents of a file, however, the author of the server could also write code to respond to GET requests as they see fit.

Request like PUT and DELETE are often protected by authentication requirements. You might not be allowed to delete something unless you have admin permissions on the site. The HTTP request can include information about your identity and authorisation. See the Authentication chapter for more details and some examples.

HTTP Response Codes

In the above examples we saw a response code of 200 which means that the request succeeded. There are a different HTTP status codes that can be returned by the server in different situations.

Response codes starting with 2 are success codes; the request was a success and whatever you asked to be done was done. The most common is the 200 OK response code but there is also 201 Created, used when a new resource was created, or 204 No Content, meaning that everything was ok but the server has no content to return to the client.

If the response code starts with 3, then the server is saying that the client should look elsewhere for the resource they are asking for. A 301 response means Moved Permanently and the response headers will include a Location header containing a new URL that the client should request instead. Your browser would automatically follow this new URL and show you the page. A 303 response is similar but means that the resource has moved temporarily. The only difference is that the browser might remember the redirect for a 301 response and not go there next time, whereas for 303 it would try the same request again.

A response starting with 4 is an error response indicating that the client did something wrong. The most common is the 404 Not Found response meaning that the resource being requested is not present on the server. There is also 403 Forbidden, meaning you aren't allowed to see that resource and 402 Payment Required, meaning you need to organise payment somehow. A 405 response means you aren't allowed to use this HTTP request method on this resource (eg. you can GET but you can't POST).

A 5xx response means that something went wrong with the server. The most common one we see is 500 which means Internal Server Error - or the server crashed. You might also see 502 Bad Gateway meaning that the server is forwarding requests to another server and it didn't get a useable response.

All of these response codes give us information about the response that we get from the server and allow the client to act appropriately.

HTTP Caches

A cache is a bit of software that watches HTTP requests and decides whether to send them or just return the response that it got last time. Your browser does this but we can also set up a cache on the internet between the client and server; for example, the University could have a cache that intercepts all traffic going out of its network. If the cache can respond with a saved page to some requests then it would save time and network bandwidth. However, the cache needs to be careful as to what it can and can't store. It is usually safe to store GET requests but see the header line Cache-Control: no-cache in the sample response above which tells any cache not to store this page.