Summary
The server-side codec lookup in _protocol_connect.codec_name_from_content_type strips only the application/ (or application/connect+) prefix and uses the rest verbatim as the codec name. When a client sends a Content-Type with parameters — e.g. application/json; charset=utf-8 — the lookup key becomes json; charset=utf-8, no codec matches, and _server_async.py:213 (and the _server_sync.py counterparts at :306/:437) raises HTTPException(HTTPStatus.UNSUPPORTED_MEDIA_TYPE).
Per RFC 9110 §8.3.2 the charset parameter is part of the standard media-type grammar and the value is case-insensitive so servers are expected to tolerate it.
Reproduction
# Works:
curl -X POST -H 'Content-Type: application/json' -d '{}' \
https://my-server/<service>/<method>
# → 401/200 — codec resolves
# Fails:
curl -X POST -H 'Content-Type: application/json; charset=utf-8' -d '{}' \
https://my-server/<service>/<method>
# → HTTP 415 Unsupported Media Type
Hits in production when a browser or proxy adds ; charset=utf-8 to the Connect-ES request (we observed this on a Connect-ES --> Connect-Python path even though @connectrpc/connect's request-header.js builds the Content-Type without parameters).
Expected behavior
Something matching the reference Go implementation, ie. canonicalizeContentType calls mime.ParseMediaType, discards/canonicalizes parameters, and matches on the bare media type.
Suggested fix
In _protocol_connect.codec_name_from_content_type, parse the input via Python's media-type parser before stripping the prefix:
from email.message import Message
def codec_name_from_content_type(content_type: str, *, stream: bool) -> str:
msg = Message()
msg["content-type"] = content_type
base = msg.get_content_type() # e.g. "application/json"
prefix = (
CONNECT_STREAMING_CONTENT_TYPE_PREFIX
if stream
else CONNECT_UNARY_CONTENT_TYPE_PREFIX
)
if base.startswith(prefix):
return base[len(prefix):]
return base
Thanks for all the work on the project. Happy to send a PR if it's useful. :)
Summary
The server-side codec lookup in
_protocol_connect.codec_name_from_content_typestrips only theapplication/(orapplication/connect+) prefix and uses the rest verbatim as the codec name. When a client sends a Content-Type with parameters — e.g.application/json; charset=utf-8— the lookup key becomesjson; charset=utf-8, no codec matches, and _server_async.py:213 (and the _server_sync.py counterparts at :306/:437) raisesHTTPException(HTTPStatus.UNSUPPORTED_MEDIA_TYPE).Per RFC 9110 §8.3.2 the charset parameter is part of the standard media-type grammar and the value is case-insensitive so servers are expected to tolerate it.
Reproduction
Hits in production when a browser or proxy adds
; charset=utf-8to the Connect-ES request (we observed this on a Connect-ES --> Connect-Python path even though @connectrpc/connect's request-header.js builds the Content-Type without parameters).Expected behavior
Something matching the reference Go implementation, ie. canonicalizeContentType calls mime.ParseMediaType, discards/canonicalizes parameters, and matches on the bare media type.
Suggested fix
In
_protocol_connect.codec_name_from_content_type, parse the input via Python's media-type parser before stripping the prefix:Thanks for all the work on the project. Happy to send a PR if it's useful. :)