Skip to content

Content-Length header is missing in POST/DELETE/PUT/PATCH requests without body #987

Closed

Description

@senpos

Checklist

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!

Activity

  1. yeraydiazdiaz commented on May 24, 2020

    @yeraydiazdiaz
    Contributor

    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).

  2. florimondmanca commented on May 24, 2020

    @florimondmanca
    Contributor

    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.
  3. lovelydinosaur commented on May 24, 2020

    @lovelydinosaur
    Member

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

  4. florimondmanca commented on May 24, 2020

    @florimondmanca
    Contributor

    ^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.

  5. lovelydinosaur commented on May 25, 2020

    @lovelydinosaur
    Member

    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;"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions