Errors & troubleshooting
An exception and an HTTP error response are different things. Your logs would appreciate being invited to this distinction.
Exceptions Wred can raise
| Exception | Typical trigger | What to do |
|---|---|---|
orjson.JSONDecodeError | Invalid JSON in await request.json(). | Catch at the input boundary and return a 400 response. |
UnicodeDecodeError | Invalid UTF-8 in the raw or percent-decoded query string. | Reject the query with 400 if it is part of your public input handling. |
ConnectionError | A body read receives a disconnect or another non-request event. | Handle cancellation/disconnection where relevant; do not assume a response can still reach the client. |
TypeError | An unsupported response body, or a serialization/type error. | Return bytes, a dict/list, a supported tuple, or a callable ASGI response. |
ValueError | For example, unpacking a tuple with fewer than three response fields. | Use exactly the documented three- or four-item response tuple. |
UnicodeEncodeError | A custom header name is not ASCII or a value cannot be encoded as Latin-1. | Use HTTP-compatible headers and encode internationalized values with the appropriate header format. |
RuntimeError | A non-HTTP scope reaches a bare Wred app. | Route that protocol to an outer app or configure lifespan appropriately. |
Wred lets these exceptions propagate. Your middleware or ASGI server decides how to log and respond to an unhandled exception. Do not depend on a bare Wred app to generate a JSON 500 response.
Return expected errors explicitly
import orjson
@app.route('/api/items', methods=['POST'])
async def create_item(request):
try:
data = await request.json()
except orjson.JSONDecodeError:
return {'error': 'Invalid JSON'}, 'application/json', 400
if not isinstance(data, dict):
return {'error': 'Send a JSON object'}, 'application/json', 400
return {'accepted': True}Catch the exception you expect at the boundary you understand. A blanket except Exception: return {"ok": True} makes dashboards look lovely and everything else substantially worse.
Where HTTP statuses come from
| Status | Wred behavior |
|---|---|
| 200 | Default for supported handler return values. |
| 400 / 401 / 403 / 409 / 429 | Application or middleware decisions. The notebook provides examples. |
| 404 | Wred’s fallback for an unmatched path or method. |
| 405 | Not generated automatically. Register a handler or use another routing layer if you need it. |
| 500 | Unhandled-error behavior is supplied by the server or middleware. See the error middleware example. |
HTTP status is part of the response, not a declaration made by throwing a Starlette exception. Wred does not install Starlette’s exception handlers for you.
A short debugging sequence
- Call the backend directly from its host and inspect the exact status, headers, and body with
curl -i. - Confirm the server imports the wrapper you intended, not just the inner Wred instance.
- Check the route path and method, then the proxy or mount prefix.
- Inspect the server traceback. Turn off public debug tracebacks before deployment.
- Repeat through the public proxy to isolate scheme, Host, cookie, and forwarding issues.
The operations troubleshooting table covers 502s, database startup failures, login problems, and buffered streams.
After a response has started
A callable response owns its send operations. If it raises after sending headers or part of a stream, Wred propagates the exception without attempting a second response. Validate inputs and permissions before sending the first message.
An error handler cannot change a status code already sent to the client. Time travel remains an optional dependency that nobody has successfully published.
