getsentry / responses

A utility for mocking out the Python Requests library.
Apache License 2.0
4.17k stars 356 forks source link

requests.head raises ChunkedEncodingError if header without content-length is sent #721

Open zepaz opened 5 months ago

zepaz commented 5 months ago

Describe the bug

When mocking requests.head() a ChunkedEncodingError is raised if you send something in the header but omit content-length in said header.

Additional context

No response

Version of responses

0.25.2

Steps to Reproduce

run pytest

import responses
import requests

@responses.activate
def test_get_head_success() -> None:
    """Tests a successful HEAD request."""
    responses.get(
        url="http://example.com/",
        body="hello world",
        match=[responses.matchers.header_matcher({"Accept": "text/plain"})],
    )

    responses.get(
        url="http://example.com/",
        json={"content": "hello world"},
        match=[responses.matchers.header_matcher({"Accept": "application/json"})],
    )
    responses.head(
        url="http://example.com/",
        json={"content": "hello world"},
        match=[responses.matchers.header_matcher({"Accept": "application/json"})],
    )
    responses.head(
        url="http://example.com/",
        body="hello world",
        match=[responses.matchers.header_matcher({"Accept": "text/plain"})],
    )
    responses.head(
        url="http://example.com/",
        match=[responses.matchers.header_matcher({"Accept": "text/html"})],
        status = 404,
    )
    # request in reverse order to how they were added!
    resp = requests.head("http://example.com/", headers={"Accept": "application/json"})
    assert resp.status_code == 200

    resp = request.head("http://example.com/", headers={"Accept": "text/html"})
    assert resp.status_code == 404

Expected Result

I would expect the test to succeed.

Actual Result

the test raises an ChunkedEncodingError

    def generate():
        # Special case for urllib3.
        if hasattr(self.raw, "stream"):
            try:
                yield from self.raw.stream(chunk_size, decode_content=True)
            except ProtocolError as e:
>               raise ChunkedEncodingError(e)
E               requests.exceptions.ChunkedEncodingError: ('Response may not contain content.', IncompleteRead(26 bytes read, -26 more expected))

..\..\..\..\venv\Lib\site-packages\requests\models.py:818: ChunkedEncodingError
HybridAU commented 1 week ago

Adding a comment here in the hopes it will help anyone else who ends up here. We had a bug in our code, we were using responses to return a 204 but we had content in the body.

import json

import responses
import requests

def call_me(_request):
    headers = {"Content-Type": "application/json"}
    body = {}

    # A bunch of logic to populate the body....

    if not body:
        # Bug is here. The body should be `None` not an empty object
        return 204, headers, json.dumps(body)
    else:
        return 200, headers, json.dumps(body)

@responses.activate
def test_post_204():
    responses.add_callback(responses.POST, "https://example.com", call_me)
    resp = requests.post("https://example.com")
    assert resp.status_code == 204

And it was producing the same ChunkedEncodingError so at first we thought it was the same root cause as this issue.

It had been working for years, but when we upgraded requests, urllib3 got upgraded to 2.2.3 and that broke all our mocks.

Pinning urllib3 = "^1.26.20" is the work around for us while we find and fix all the issues in our code.

beliaev-maksim commented 1 week ago

that is not a bug in responses but a changed functionality in the urllib3 in 2.0

see comment here: https://github.com/getsentry/responses/issues/635#issuecomment-1538862145