Skip to content

Commit

Permalink
Fix API client compliance with HTTP/1.1
Browse files Browse the repository at this point in the history
In HTTP/1.1 Content-Length is not always required to be sent by the
server - if chunked transfer encoding is used, the Content-Length header
can be omitted, and length indicated as part of each chunk. Chunked
transfer encoding is always acceptable in HTTP/1.1 [1] so by effectively
requiring a Content-Length header we are not following the spec.

[1]: https://datatracker.ietf.org/doc/html/rfc9112#section-7.4-2
  • Loading branch information
sigmaris committed Nov 30, 2023
1 parent 99eeb51 commit 352e06f
Show file tree
Hide file tree
Showing 3 changed files with 11 additions and 4 deletions.
2 changes: 1 addition & 1 deletion aiven/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def verify(
time.sleep(0.2)

# Check API is actually returning data or not
if response.status_code == HTTPStatus.NO_CONTENT or response.headers.get("Content-Length", "0") == "0":
if response.status_code == HTTPStatus.NO_CONTENT or len(response.content) == 0:
return {}

result = response.json()
Expand Down
6 changes: 4 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def test_service_user_create() -> None:
def test_service_topic_create(command_line: str, expected_post_data: Mapping[str, str | int | None]) -> None:
client = AivenClient("")
session = MagicMock(spec=Session)
session.post.return_value = MagicMock(status_code=200, json=MagicMock(return_value={}))
session.post.return_value = MagicMock(status_code=200, json=MagicMock(return_value={}), content=b"{}", reason="OK")
client.session = session
cli = build_aiven_cli(client)
assert cli.run(args=command_line.split(" ")) is None
Expand Down Expand Up @@ -258,7 +258,9 @@ def get_service_topic(self, project: str, service: str, topic: str) -> Mapping:

client = TestAivenClient()
session = MagicMock(spec=Session)
session.put.return_value = MagicMock(status_code=200, json=MagicMock(return_value={"message": "updated"}))
session.put.return_value = MagicMock(
status_code=200, json=MagicMock(return_value={"message": "updated"}), content=b'{"message":"updated"}', reason="OK"
)
client.session = session
cli = build_aiven_cli(client)
assert cli.run(args=command_line.split(" ")) is None
Expand Down
7 changes: 6 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@
from typing import Any
from unittest import mock

import json
import pytest


class MockResponse:
def __init__(self, status_code: int, json_data: dict[str, Any] | None = None, headers: dict[str, str] | None = None):
self.status_code = status_code
self.json_data = json_data
if json_data is not None:
self.content = json.dumps(json_data).encode("utf-8")
else:
self.content = b""
self.headers = {} if headers is None else headers

def json(self) -> Any:
Expand All @@ -26,7 +31,7 @@ def json(self) -> Any:
"response",
[
MockResponse(status_code=HTTPStatus.NO_CONTENT),
MockResponse(status_code=HTTPStatus.CREATED, headers={"Content-Length": "0"}),
MockResponse(status_code=HTTPStatus.CREATED),
],
)
def test_no_content_returned_from_api(response: MockResponse) -> None:
Expand Down

0 comments on commit 352e06f

Please sign in to comment.