Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions nexios_contrib/etag/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,21 @@ async def __call__(
# Apply only to configured methods
stream = await call_next()
if request.method.upper() not in self.methods:
return None
return stream

# Compute/set ETag if missing (or override when requested)
has_existing = bool(response.headers.get("etag"))
print("has_existing",has_existing)
if not has_existing or self.override:
body = b""
async for chunk in stream.content_iterator:
body += chunk
compute_and_set_etag(response, body, weak=self.weak, override=True)

print("cnext header b4 isfresh",response.headers,has_existing)
# Handle If-None-Match freshness for conditional requests
if is_fresh(request, response, weak_compare=True):
# Per RFC 9110, a 304 response must not include a message body
# Ensure body is empty; BaseResponse avoids content-length for 304
response.make_response(BaseResponse(
status_code=304
))

response.status(304)


return None
return stream
16 changes: 6 additions & 10 deletions tests/etag/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,11 @@ def test_override_configuration(self):

@app1.get("/no-override")
async def no_override_handler(request, response):
response.set_header("etag", '"manual-etag"')
return {"data": "test"}
return response.json({"data": "test"}).set_header("etag", '"manual-etag"')

@app2.get("/with-override")
async def with_override_handler(request, response):
response.set_header("etag", '"manual-etag"')
return {"data": "test"}
return response.json({"data": "test"}).set_header("etag", '"manual-etag"')

client1 = TestClient(app1)
client2 = TestClient(app2)
Expand All @@ -146,13 +144,11 @@ def test_combined_configuration_options(self):

@app.get("/get-endpoint")
async def get_handler(request, response):
response.set_header("etag", '"manual-etag"')
return {"method": "GET"}
return response.json({"method": "GET"}).set_header("etag", '"manual-etag"')

@app.post("/post-endpoint")
async def post_handler(request, response):
response.set_header("etag", '"manual-etag"')
return {"method": "POST"}
return response.json({"method": "POST"}).set_header("etag", '"manual-etag"')

client = TestClient(app)

Expand All @@ -173,11 +169,11 @@ def test_case_insensitive_methods(self):

@app.get("/test")
async def get_handler(request, response):
return {"method": request.method}
return response.json({"method": request.method})

@app.post("/test")
async def post_handler(request, response):
return {"method": request.method}
return response.json({"method": request.method})

client = TestClient(app)

Expand Down
3 changes: 2 additions & 1 deletion tests/etag/test_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,10 @@ def test_etag_with_headers_and_cookies(self):

@app.get("/headers")
async def headers_handler(request, response):
response.json({"message": "Hello, World!"})
response.set_header("x-custom", "value")
response.set_cookie("session", "abc123")
return {"data": "test"}
return response

client = TestClient(app)

Expand Down
26 changes: 13 additions & 13 deletions tests/etag/test_helper_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,15 @@ def test_set_response_etag_new(self):
"""Test setting ETag on response without existing ETag."""
scope = create_mock_scope()
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
set_response_etag(response, '"abc123"')
assert response.headers.get("etag") == '"abc123"'

def test_set_response_etag_override_true(self):
"""Test setting ETag with override=True."""
scope = create_mock_scope()
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
response.set_header("etag", '"old123"')
set_response_etag(response, '"new123"', override=True)
assert response.headers.get("etag") == '"new123"'
Expand All @@ -161,7 +161,7 @@ def test_set_response_etag_normalize(self):
"""Test that ETag is normalized when set."""
scope = create_mock_scope()
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
set_response_etag(response, "abc123") # unquoted
assert response.headers.get("etag") == '"abc123"' # quoted

Expand All @@ -173,7 +173,7 @@ def test_compute_and_set_etag_new(self):
"""Test computing and setting ETag on response without existing."""
scope = create_mock_scope()
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
etag = compute_and_set_etag(response, body=b"Hello, World!")
assert etag.startswith('W/"')
assert response.headers.get("etag") == etag
Expand All @@ -182,7 +182,7 @@ def test_compute_and_set_etag_override_false(self):
"""Test not overriding existing ETag when override=False."""
scope = create_mock_scope()
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
response.set_header("etag", '"existing"')
etag = compute_and_set_etag(response, body=b"Hello, World!", override=False)
assert response.headers.get("etag") == '"existing"'
Expand All @@ -193,7 +193,7 @@ def test_compute_and_set_etag_override_true(self):
"""Test overriding existing ETag when override=True."""
scope = create_mock_scope()
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
response.set_header("etag", '"existing"')
etag = compute_and_set_etag(response, body=b"Hello, World!", override=True)
assert response.headers.get("etag") == etag
Expand All @@ -203,7 +203,7 @@ def test_compute_and_set_etag_weak_false(self):
"""Test computing strong ETag."""
scope = create_mock_scope()
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
etag = compute_and_set_etag(response, body=b"Hello, World!", weak=False)
assert not etag.startswith('W/')
assert response.headers.get("etag") == etag
Expand Down Expand Up @@ -317,45 +317,45 @@ def test_is_fresh_matching_etag(self):
"""Test freshness when ETag matches."""
scope = create_mock_scope(headers={"if-none-match": '"abc123"'})
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
response.set_header("etag", '"abc123"')
assert is_fresh(request, response) is True

def test_is_fresh_non_matching_etag(self):
"""Test freshness when ETag doesn't match."""
scope = create_mock_scope(headers={"if-none-match": '"def456"'})
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
response.set_header("etag", '"abc123"')
assert is_fresh(request, response) is False

def test_is_fresh_weak_compare(self):
"""Test freshness with weak comparison."""
scope = create_mock_scope(headers={"if-none-match": 'W/"abc123"'})
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
response.set_header("etag", '"abc123"')
assert is_fresh(request, response, weak_compare=True) is True

def test_is_fresh_no_etag(self):
"""Test freshness when response has no ETag."""
scope = create_mock_scope(headers={"if-none-match": '"abc123"'})
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
assert is_fresh(request, response) is False

def test_is_fresh_no_if_none_match(self):
"""Test freshness when request has no If-None-Match."""
scope = create_mock_scope()
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
response.set_header("etag", '"abc123"')
assert is_fresh(request, response) is False

def test_is_fresh_multiple_if_none_match(self):
"""Test freshness with multiple If-None-Match values."""
scope = create_mock_scope(headers={"if-none-match": '"def456", "abc123"'})
request = Request(scope, mock_receive, mock_send)
response = Response(request)
response = Response(request).empty()
response.set_header("etag", '"abc123"')
assert is_fresh(request, response) is True
3 changes: 1 addition & 2 deletions tests/etag/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,7 @@ def test_etag_override_behavior(self):

@app.get("/no-override")
async def handler_no_override(request, response):
response.set_header("etag", '"manual-etag"')
return {"data": "test"}
response.json({"data": "test"}).set_header("etag", '"manual-etag"')



Expand Down
9 changes: 3 additions & 6 deletions tests/etag/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ def test_middleware_does_not_override_existing_etag(self, test_client_factory):

@app.get("/test")
async def handler(request, response):
response.set_header("etag", '"custom-etag"')
return {"message": "Hello, World!"}
return response.json({"message": "Hello, World!"}).set_header("etag", '"custom-etag"')

with test_client_factory(app) as client:
resp = client.get("/test")
Expand All @@ -119,8 +118,7 @@ def test_middleware_overrides_existing_etag(self, test_client_factory):

@app.get("/test")
async def handler(request, response):
response.set_header("etag", '"custom-etag"')
return {"message": "Hello, World!"}
return response.json({"message": "Hello, World!"}).set_header("etag", '"custom-etag"')

with test_client_factory(app) as client:
resp = client.get("/test")
Expand Down Expand Up @@ -288,8 +286,7 @@ def test_conditional_request_no_etag_on_response(self, test_client_factory):
@app.get("/test")
async def handler(request, response):
# Manually remove any ETag that might be set
response.remove_header("etag")
return {"message": "Hello, World!"}
return response.json({"message":"hell world"}).remove_header("etag")

with test_client_factory(app) as client:
resp = client.get("/test", headers={"if-none-match": '"some-etag"'})
Expand Down
Loading