Httpx: Content-Length header is missing in POST/DELETE/PUT/PATCH requests without body

Created on 24 May 2020  Â·  5Comments  Â·  Source: encode/httpx

Checklist

  • [x] I searched the HTTPX documentation but couldn't find what I'm looking for.
  • [x] I looked through similar issues on GitHub, but didn't find anything.
  • [x] I looked up "How to do ... in HTTPX" on a search engine and didn't find any information.
  • [x] I asked the community chat for help but didn't get an answer. and decided to create an issue. :)

Environment

httpx version: 0.13.1
Python version: 3.7.3
OS: Windows 7

Question

I am not sure how to classify this issue: it could be a bug, since it is an incompatible with requests, on the other hand, it could be feature request, if you consider it is not that important. So, I decided to go with question. :D

Was replacing requests with httpx in my small script and received an error from the server httpx._exceptions.HTTPError: 411 Client Error: Length Required for url: ... while doing a POST request without a body (same behavior is applicable for PUT and PATCH requests too).

Steps to reproduce:

import requests
import httpx

for client in (requests, httpx):
    for method in ("get", "head", "delete", "post", "put", "patch"):
        r = client.request(method, f"https://httpbin.org/headers")
        print(
            f"[{client.__name__}] method={method.upper()} "
            f'Content-Length={r.request.headers.get("Content-Length")}'
        )

requests adds Content-Length header in every possible http method, except GET and HEAD.
httpx does not add Content-Length header at all, unless requests has a body.

866 added Content-Length to all methods, but since it is not needed in GET and HEAD methods, it was reverted.

I assume, it should be handled during the Request building.
httpx decides whether to add Content-Length depending on the steam type, which is handled in _content_streams.py#L314.

Thoughts on how to solve it

To be honest, I am not sure what is the proper way to deal with this. :-(

My first idea was to use method inside the stream:

class ByteStream(ContentStream):
    def __init__(self, body: typing.Union[str, bytes], method: str) -> None:
        self.body = body.encode("utf-8") if isinstance(body, str) else body
        self.method = method

    def get_headers(self) -> typing.Dict[str, str]:        
        if self.method in ("GET", "HEAD"):
            return {}
        content_length = str(len(self.body)) if self.body else "0"
        return {"Content-Length": content_length}

But this approach is not great, since it requires to pass method from Request to encode and then to ByteStream inside it. Also, it is not clear what method should I use when building stream in read methods.

Please, let me know what you think. Thanks!

bug

Most helpful comment

I believe this is a bug as we're not following the RFC:

A user agent SHOULD send a Content-Length in a request message when no Transfer-Encoding is sent and the request method defines a meaning for an enclosed payload body. For example, a Content-Length header field is normally sent in a POST request even when the value is 0 (indicating an empty payload body).

All 5 comments

I believe this is a bug as we're not following the RFC:

A user agent SHOULD send a Content-Length in a request message when no Transfer-Encoding is sent and the request method defines a meaning for an enclosed payload body. For example, a Content-Length header field is normally sent in a POST request even when the value is 0 (indicating an empty payload body).

Thanks!

As you mentioned I think it's best if content streams stay agnostic of the HTTP semantics here.

The .get_headers() method is called in Request.prepare(), so other options I can see as far as implementation goes…

  • Pass .get_headers(method=...); this would fix the "pass method down the content stream chain" item but streams would then have to be aware of HTTP semantics, not great.
  • Make streams include Content-Length in all cases, but ignore that header in Request.prepare() if method does not accept a body.

I’d suggest auto including it in Request.prepare, if the method requires one, and none is currently present.

^Sounds good to me too — let the content stream not return a Content-Length if there's no body, and add a default Content-Length: 0 in the request if it's required by the method.

We should cover POST, PUT, and PATCH here.

3.3.2 - "A user agent SHOULD send a Content-Length in a request message when no Transfer-Encoding is sent and the request method defines a meaning for an enclosed payload body"

And

4.3.5 - "A payload within a DELETE request message has no defined semantics;"

Was this page helpful?
0 / 5 - 0 ratings