Skip to content

Conversation

C-Loftus
Copy link
Contributor

Overview

This PR makes it so http responses with a 204 status have no body in accordance with the http spec.

Background

Currently if you raise a ProviderNoDataError it will return a response with status 204.

class ProviderNoDataError(ProviderGenericError):
    """provider no data error"""
    ogc_exception_code = 'InvalidParameterValue'
    http_status_code = HTTPStatus.NO_CONTENT
    default_msg = 'No data found

However, currently it will include data in the response. This deviates from the spec which states that the response must not include any content

To fix this I make it return early and not add any data into the body.

Issue this solves

Currently in the starlette backend, if you raise a 204 but have data in the body, it will raise an error and fail.

2025-08-26 14:18:30.482
    raise ProviderNoDataError()
2025-08-26 14:18:30.502
[2025-08-26 18:18:30 +0000] [24] [ERROR] Exception in ASGI application
2025-08-26 14:18:30.502
Traceback (most recent call last):   File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi     result = await app(  # type: ignore[func-returns-value]
2025-08-26 14:18:30.502
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
2025-08-26 14:18:30.502
    return await self.app(scope, receive, send)
2025-08-26 14:18:30.502
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/applications.py", line 113, in __call__
2025-08-26 14:18:30.502
    await self.middleware_stack(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__
2025-08-26 14:18:30.502
    raise exc
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__
2025-08-26 14:18:30.502
    await self.app(scope, receive, _send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 93, in __call__
2025-08-26 14:18:30.502
    await self.simple_response(scope, receive, send, request_headers=headers)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 144, in simple_response
2025-08-26 14:18:30.502
    await self.app(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 63, in __call__
2025-08-26 14:18:30.502
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
2025-08-26 14:18:30.502
    raise exc
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
2025-08-26 14:18:30.502
    await app(scope, receive, sender)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/routing.py", line 716, in __call__
2025-08-26 14:18:30.502
    await self.middleware_stack(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/routing.py", line 736, in app
2025-08-26 14:18:30.502
    await route.handle(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/routing.py", line 462, in handle
2025-08-26 14:18:30.502
    await self.app(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/routing.py", line 716, in __call__
2025-08-26 14:18:30.502
    await self.middleware_stack(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/routing.py", line 736, in app
2025-08-26 14:18:30.502
    await route.handle(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/routing.py", line 290, in handle
2025-08-26 14:18:30.502
    await self.app(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/routing.py", line 78, in app
2025-08-26 14:18:30.502
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
2025-08-26 14:18:30.502
    raise exc
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
2025-08-26 14:18:30.502
    await app(scope, receive, sender)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/routing.py", line 76, in app
2025-08-26 14:18:30.502
    await response(scope, receive, send)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/responses.py", line 165, in __call__
2025-08-26 14:18:30.502
    await send({"type": prefix + "http.response.body", "body": self.body})
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 39, in sender
2025-08-26 14:18:30.502
    await send(message)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 39, in sender
2025-08-26 14:18:30.502
    await send(message)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 148, in send
2025-08-26 14:18:30.502
    await send(message)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 161, in _send
2025-08-26 14:18:30.502
    await send(message)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 500, in send
2025-08-26 14:18:30.502
    output = self.conn.send(event=h11.Data(data=data))
2025-08-26 14:18:30.502
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/h11/_connection.py", line 538, in send
2025-08-26 14:18:30.502
    data_list = self.send_with_data_passthrough(event)
2025-08-26 14:18:30.502
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/h11/_connection.py", line 571, in send_with_data_passthrough
2025-08-26 14:18:30.502
    writer(event, data_list.append)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/h11/_writers.py", line 65, in __call__
2025-08-26 14:18:30.502
    self.send_data(event.data, write)
2025-08-26 14:18:30.502
  File "/opt/pygeoapi/.venv/lib/python3.12/site-packages/h11/_writers.py", line 91, in send_data
2025-08-26 14:18:30.502
    raise LocalProtocolError("Too much data for declared Content-Length")
2025-08-26 14:18:31.591
pygeoapi.provider.base.ProviderNoDataError: None
2025-08-26 14:18:31.591
h11._util.LocalProtocolError: Too much data for declared Content-Length

Additional information

Dependency policy (RFC2)

  • I have ensured that this PR meets RFC2 requirements

Updates to public demo

Contributions and licensing

(as per https://github.com/geopython/pygeoapi/blob/master/CONTRIBUTING.md#contributions-and-licensing)

  • I'd like to contribute [feature X|bugfix Y|docs|something else] to pygeoapi. I confirm that my contributions to pygeoapi will be compatible with the pygeoapi license guidelines at the time of contribution
  • I have already previously agreed to the pygeoapi Contributions and Licensing Guidelines

@tomkralidis
Copy link
Member

Thanks @C-Loftus. As a first step, can you provide steps to reproduce the issue that this PR aims to address?

@C-Loftus
Copy link
Contributor Author

As a first step, can you provide steps to reproduce the issue that this PR aims to address?

Thanks for your reply @tomkralidis . To reproduce, make a custom provider that can potentially return no data, run pygeoapi with starlette, create a condition in the provider that throws a ProviderNoDataError within pygeoapi. You will then see the error in the logs.

I quickly made up a repo with a minimal example for you here: https://github.com/cgs-earth/pygeoapi-starlette-issue-reproduction

Copy link
Member

@tomkralidis tomkralidis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the test case @C-Loftus. Please see change requests. Thanks!

Comment on lines -136 to +140
# NOTE: that gzip currently doesn't work in starlette
# https://github.com/geopython/pygeoapi/issues/1591
content = apply_gzip(headers, content)
# 204 responses must have an empty body, but gzip
# encoding would add gzip metadata, thus we skip
if status != HTTPStatus.NO_CONTENT:
content = apply_gzip(headers, content)
Copy link
Contributor Author

@C-Loftus C-Loftus Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed a comment linking to an issue that was resolved and then added a clarifying comment on why we need to skip applying gzip when it is a 204 response. Without this, the apply_gzip function zips empty content, and empty content still will include a gzip header and thus raise an error

@tomkralidis tomkralidis added this to the 0.22.0 milestone Sep 15, 2025
@tomkralidis tomkralidis merged commit 579345d into geopython:master Sep 15, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants