When and why to use range 1xx status?

Asked

Viewed 228 times

19

The range 1xx of HTTP response status codes are informative, the description of the Wikipedia, translated from rfc2068:

"Request received, continuing the process. This status code class indicates a provisional response, consisting only of Status-Line and optional headers, and is terminated by an empty line. Since the HTTP/1.0 version is not defined for all 1xx status codes, servers should not send a 1xx response to an HTTP/1.0 client, except under experimental conditions."

What do you mean "Since the HTTP/1.0 version not all 1xx status codes are defined"? Does that mean it’s obsolete? I’m not clear to myself.

What is (or was) the utility of these status? Could exemplify with some use cases?

What are the official status? Wikipedia’s list is 100, 101, 102 and 122. On MDN only the 100, 101 and 102. On the website of Iana has 100, 101, 102 and 103

  • I guess I owe the rest of the hahaha Oops answer

  • @Andersoncarloswoss no problem, your answer, as it is, already deserves a reward

1 answer

24


Status 1xx are obsolete?

Not, there is a translation error in the excerpt you quoted from Wikipedia.

The original passage is:

Since HTTP/1.0 Did not define any 1xx status codes, Servers MUST NOT send a 1xx Response to an HTTP/1.0 client except under experimental conditions.

Where the term "Since" was translated as "from the", but in this context it would be better translated as "since", "as the" or something of the kind, that is, subordinative locution, of that which expresses the cause.

A better translation would be:

Since HTTP/1.0 does not define 1xx status codes, servers MUST NOT send a 1xx response to an HTTP/1.0 client unless under experimental conditions.

That is, this does not mean that the 1xx status code is obsolete, but on the contrary, that it was started to use from HTTP/1.1 onwards.

What is the purpose of these responses?

Allow the client to send headers and body of the request separately. This is because, in some situations, the server will be able to determine whether to accept a request or not only with the header information. In cases where the body of the request is too large there would be an unnecessary loss of resource when sending it to the server and having the request denied. Two main features that would be directly impacted by this would be the response time, which by moving more information is expected to take longer to deliver the message, and the bandwidth consumption of both the client and the server, by transmitting data that will not be used.

A fairly common situation that would take advantage of these responses are file uploads to the server. It is common for the server or application to have validations regarding the type (mimetype) of the file and its size. Two information that would already be present in the request header, so the server could refuse it without actually receiving the file.

Imagine the two applications (browser and server) chatting:

  • Browser: Good morning, server, I would like to send you a file;
  • Browser: It is a video file, MKV format;
  • Browser: It is 1.21 GB in size;
  • Browser: Please confirm if I can send it;
  • Server: Hello, browser, do not send, I only accept PDF files with less than 5 MB;

In this case, the browser didn’t even have to send 1.21 GB of data for no reason (imagine sending it by 4G), only to know that the server would refuse the request. In a valid case something quite similar would happen:

  • Browser: Good morning, server, I would like to send you a file;
  • Browser: It is a PDF file format;
  • Browser: It is 1.21 MB in size;
  • Browser: Please confirm if I can send it;
  • Server: Hello, browser, everything looks ok, continue;
  • Browser: Thank you, here is the file...

That is, if with the information present in the headers is not enough to refuse the request, will be returned to the customer to continue and send the body of the same. Note that this is not a guarantee of success, as the data sent by the body may be corrupted, from another file, etc.

Shattering expectations

If you notice the above dialogs in both cases the browser has asked for confirmation from the server to continue with the request. By default HTTP does not work like this, so you need to explicitly state when the client will wait for confirmation. This generates an expectation in the client, which is sent to the server through the header Expect, informing what is the expectation.

POST /upload HTTP/1.1
Content-Type: application/pdf
Content-Length: 1210000
Expect: 100-Continue
...

When informing 100-Continue in the header Expect the client is informing the server that it will wait for its confirmation to continue the process. If the server authorizes the request, it must respond with 100 Continue, but if you refuse, you will answer with 417 Expectation Failed.

Considerations on servers and proxies

When a server receives a request:

  • Upon receiving a request that includes the header Expect: 100-Continue, the server MUST respond with the status 100 Continue and continue reading the data input stream or sending a final response, denying the request. The server MUST NOT wait for the body of a request BEFORE sending the response 100 Continue. By submitting a final response rejecting the request, the connection to the client CAN be closed or CAN continue open, discarding any data arriving from the rejected request.

  • The server MUST NOT send the answer 100 Continue if the client request does not have the header Expect: 100-Continue and MUST NOT send it also if the client is using HTTP/1.0, as already mentioned. For RFC 2068 compatibility reasons, the server CAN respond with 100 Continue a request that does not have the header Expect of methods PUT or POST, HTTP/1.1 version only.

  • The server CAN omit the answer 100 Continue when you have already received the entire body of the request previously.

  • The server MUST send a final response after receiving and processing the body of the request.

When the request is handled by a proxy:

  • If a proxy receives a request that includes the header Expect with the value 100-Continue, and the proxy knows that the next entity works with HTTP/1.1 or higher, or does not know the version of HTTP that this entity accepts, the proxy MUST forward the message including the header Expect.

  • If the proxy, under the same conditions as the previous one, knows that the next entity only operates with HTTP/1.0, the proxy SHOULD NOT forward the message, instead it should already respond to the client with 417 Expectation Failed.

  • Proxy MUST keep cached the version the next entity uses to be possible for the two previous checks.

  • The proxy MUST NOT forward any response to the customer 100 Continue that eventually received if the client’s original request was by HTTP/1.0.

And how the server handles it?

I should start by saying that it depends on the server. I couldn’t find official information on how Apache and Nginx (I’ve never worked with anyone other than these) handle the header Expect, if anyone knows any reliable documentation please share in the comments or in another reply.

Life cycle of a request without Expect

First, we will analyze the life cycle of a "normal request":

Cliente                              Servidor                              Aplicação
 ->█                                    |                                      |
   █- Cabeçalhos da Requisição -------->█                                      |
   █                                    █                                      |
   █- Corpo da Requisição ------------->█                                      |
   |                                    █                                      |
   |                                    █- Requisição Completa --------------->█
   |                                    |                                      █-[Processamento]
   |                                    █<----------------- Resposta Completa -█
   |                                    █                                      |
   █<---------- Cabeçalhos da Resposta -█                                      |
   █                                    █                                      |
   █<--------------- Corpo da Resposta -█                                      |
 <-█                                    |                                      |

In this case, we have:

  1. Client sends to server the request headers;
  2. Client sends to server the body of the request;
  3. The server runs the application by providing an API to access the request information;
  4. The application is executed by generating a response that is sent to the server;
  5. The server sends the response headers to the client;
  6. The server sends the response body to the client;

As commented, in this configuration the client must send the complete request to the server, which will analyze and serve the application, the latter being responsible for defining whether the request will be processed or not. In many cases this decision can be made only based on the request headers and this is precisely what the header Expect search around.

Life cycle of a request with Expect

When the request has Expect is generated the possibility of action by the server whether or not to refuse the request only based on the headers.

Cliente                              Servidor                              Aplicação
 ->█                                    |                                      |
   █- Cabeçalhos da Requisição -------->█                                      |
   |                                    █-[Validação]                          |
   █<------------- Resposta 100 ou 417 -█                                      |
   █                                    |                                      |
 <-█-[Erro se 417]                      |                                      |
   █                                    |                                      |
   █- Corpo da Requisição ------------->█                                      |
   |                                    █                                      |
   |                                    █- Requisição Completa --------------->█
   |                                    |                                      █-[Processamento]
   |                                    █<----------------- Resposta Completa -█
   |                                    █                                      |
   █<---------- Cabeçalhos da Resposta -█                                      |
   █                                    █                                      |
   █<--------------- Corpo da Resposta -█                                      |
 <-█                                    |                                      |

In this case, we have:

  1. Client sends to server the request headers;
  2. The server performs a previous validation of the headers and defines whether to accept (100) or fail (417) the request;
  3. When server returns 417 the cycle ends by returning the error to the client;
  4. When server returns 100 the client continues sending to the server the body of the request;
  5. The server runs the application by providing an API to access the request information;
  6. The application is executed by generating a response that is sent to the server;
  7. The server sends the response headers to the client;
  8. The server sends the response body to the client;

Steps 2, 3 and 4 are what differ this configuration from the previous one precisely at the moment when the server is given the possibility to accept or refuse the request only based on the headers. Apart from that, we can notice some peculiarities that are of utmost importance when implementing:

  • The client, when sending a request having the header Expect, must be able to receive an intermediate response (100 or 417) from the server before effectively sending the body of the request;

  • The client must define how it will handle the occasions when the server does not send the intermediate response (100 or 417); in such cases the client can (1) assume that the request was failed and abort or (2) send the body of the request anyway and check what will be the final response of the server. Option (2) is usually more common because it is naturally compatible with servers that do not handle the header Expect. In this case, the request life cycle is identical to the request cycle that does not have Expect;

  • The server must be able to receive the body of the request after sending the intermediate response to the client (100 or 417);

  • The server must be able to handle situations where the client will not send the body of the request after the intermediate response, possibly aborting the connection (timeout);

  • The server must be able to handle clients sending the request body even before receiving the intermediate response - in cases the client does not have header support Expect, the server must omit the intermediate response (100 or 417) and process the request normally; the request lifecycle is identical to that of the request that does not have Expect;

In this configuration you are, in a way, limited to do the validation exclusively on the server, that is, the validation that will define whether to accept a request or not be made based on the server settings or environment variables. There is also the possibility to transfer this validation to the application (remember that here we are talking about implementing, from scratch, a web server and we are not stuck to the settings of commercial servers).

Life cycle of a request with Expect validated by the application

The life cycle of a request that would be previously validated by the application would be:

Cliente                              Servidor                              Aplicação
 ->█                                    |                                      |
   █- Cabeçalhos da Requisição -------->█                                      |
   |                                    █- Cabeçalhos da Requisição ---------->█
   |                                    |                                      █-[Validação]
   |                                    █<--------------- Resposta 100 ou 417 -█
   █<------------- Resposta 100 ou 417 -█                                      |
   █                                    |                                      |
 <-█-[Erro se 417]                      |                                      |
   █                                    |                                      |
   █- Corpo da Requisição ------------->█                                      |
   |                                    █                                      |
   |                                    █- Requisição Completa --------------->█
   |                                    |                                      █-[Processamento]
   |                                    █<----------------- Resposta Completa -█
   |                                    █                                      |
   █<---------- Cabeçalhos da Resposta -█                                      |
   █                                    █                                      |
   █<--------------- Corpo da Resposta -█                                      |
 <-█                                    |                                      |

In this case we have basically the same life cycle as the previous one, where the server decided whether or not to accept the request, but now this responsibility is passed on to the application itself leaving the architecture considerably more versatile than the other settings. However, to be possible the application itself must support running only with the request headers, which makes the application not compatible with servers without support for the Expect, where the requisition would be delivered completely at once.

Implementation (In Python)

For all this to be possible, we will have to work at the level of socket, where we have control of the communication between the parties - both on the client and on the server. This way, a request made through a browser is out of our control and if it will use Expect or it will not depend exclusively on the development team when dealing with the connection between browser/server. A well-known customer who has support for Expect is Curl when set to property --expect100-timeout <seconds> (in fact Curl uses the Expect by default when it is a PUT request or a POST request over 1024 bytes in size).

For demonstration purposes, a fully functional Python server will be implemented using the library asyncio due to the ease of implementation. The server will be considered to respond with an echo of the request body as the response body when the request body does not exceed 42 bytes.

Example request sending less than 42 bytes

> GET / HTTP/1.1
> Host: localhost
> Content-Type: text/plain
> Content-Length: 35
> Expect: 100-continue

< HTTP/1.1 100 Continue

> Um texto com menos de 42 caracteres

< HTTP/1.1 200 Ok
< Content-Type: text/plain
< Content-Length: 35

< Um texto com menos de 42 caracteres

Example request sending more than 42 bytes

> GET / HTTP/1.1
> Host: localhost
> Content-Type: text/plain
> Content-Length: 45
> Expect: 100-continue

< HTTP/1.1 417 Expectation Failed
< Content-Length: 37

< Nao e permitido mais de 42 caracteres

X Ja este texto tem muito mais de 42 caracteres

Code time, baby!

Code written in Python 3.7, would work in 3.6 with minor adaptations.

import asyncio
from typing import Coroutine


async def echo_server(
    reader: asyncio.StreamReader, 
    writer: asyncio.StreamWriter
) -> Coroutine:

    # Read the status line of the request
    status_line: bytes = await reader.readline()
    method, resource, version = status_line.decode('ascii').split()

    # Read the request headers
    headers: list = []
    has_expect_header: bool = False
    content_length: int = 0

    while True:
        header_line: bytes = await reader.readline()

        # End of headers
        if not header_line.strip():
            break

        header_name: str
        header_value: str

        header_name, header_value = header_line.decode('ascii').split(':', 1)
        headers.append((header_name.strip(), header_value.strip()))

        if header_name.strip().lower() == 'expect':
            has_expect_header = True
        elif header_name.strip().lower() == 'content-length':
            content_length = int(header_value)

    # Has the Expect header?
    if has_expect_header:

        # Validation
        if content_length > 42:
            writer.write(b'HTTP/1.1 417 Expectation Failed\n')
            writer.write(b'Content-Length: 37\n\n')
            writer.write(b'Nao e permitido mais de 42 caracteres')
            await writer.drain()
            writer.close()
            return

        writer.write(b'HTTP/1.1 100 Continue\n\n')

    # Read the request body
    body: bytes = await reader.read(content_length)

    # Send the echo response
    writer.write(b'HTTP/1.1 200 Ok\n')
    writer.write(f'Content-Length: {content_length}\n'.encode('ascii'))
    writer.write(b'Content-Type: text/plain\n\n')

    writer.write(body)
    await writer.drain()
    writer.close()

async def main(host, port):
    server = await asyncio.start_server(echo_server, host, port)
    await server.serve_forever()

asyncio.run(main('127.0.0.1', 5000))

Code starts a server at 127.0.0.1:5000 receiving HTTP requests. If you have the header Expect the server will validate Content-Length; if less than or equal to 42 (limit stipulated for the example), the 100 Continue reply will be sent allowing the customer to send the body of the request; if Content-Length surpass the value 42 the answer 417 Failed Expectation is returned by terminating communication.

Starting the server

When executing the script, we can use Curl as client for validation.

$ python main.py

Requisition with Expect valid:

$ curl -v -H "Expect: 100-continue" -d "Um texto com menos de 42 caracteres" 127.0.0.1:5000

* Rebuilt URL to: 127.0.0.1:5000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
> POST / HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/7.58.0
> Accept: */*
> Expect: 100-continue
> Content-Length: 35
> Content-Type: application/x-www-form-urlencoded
> 
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 200 Ok
< Content-Length: 35
< Content-Type: text/plain
< 
* Connection #0 to host 127.0.0.1 left intact
Um texto com menos de 42 caracteres

See that Curl sends the headers, the server responds with 100 Continue, the Curl displays the log message We are completely uploaded and fine, sends the body of the request and finally receives the final response, 200, from the server with the echo of the request.

Image 1: Valid request made manually from telnet 127.0.0.1 5000 sending the header Expect to a server configured to receive a maximum of 5 bytes

inserir a descrição da imagem aqui

Requisition with Expect invalid:

$ curl -v -H "Expect: 100-continue" -d "Ja este texto tem muito mais de 42 caracteres" 127.0.0.1:5000

* Rebuilt URL to: 127.0.0.1:5000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
> POST / HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/7.58.0
> Accept: */*
> Expect: 100-continue
> Content-Length: 45
> Content-Type: application/x-www-form-urlencoded
> 
< HTTP/1.1 417 Expectation Failed
< Content-Length: 37
* HTTP error before end of send, stop sending
< 
* Closing connection 0
Nao e permitido mais de 42 caracteres

See that Curl sends headers, the server responds with 417 Expectation Failed, as we are sending 45 bytes to a server that supports at most 42, Curl displays the log message HTTP error before end of send, stop sending showing that an error occurred before closing the upload and therefore terminates the communication, not sending the body of the request to the server.

Image 2: Invalid request made manually from telnet 127.0.0.1 5000 sending the header Expect to a server configured to receive a maximum of 5 bytes

inserir a descrição da imagem aqui

Readings and references

  1. https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100
  2. https://tools.ietf.org/html/rfc7231#Section-6.2.1
  3. https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3
  4. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect
  5. https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/417
  6. https://tools.ietf.org/html/rfc7231#Section-5.1.1
  • What is the server like? Could you give an example?

  • @Costamilam Answer finalized

  • So if you wanted to implement in the browser there would have to be 2 pairs of request/reset?

  • 2

    @Costamilam No and yes. No, because the browser does not support communication with the Expect (actually did not find documentation that said it supports, so I think better to consider it not), which makes it impossible to use this in the browser; yes, because an alternative way is to just make two requests, using the OPTIONS method or making a request normal, passing the values to be validated through the body.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.