Backend Worker HTTP/1.1 Client
Use this guide when the backend fails to call a worker service even though DNS, service URL, and payload shape look correct.
Symptoms
- Backend calls to
chatbot-svc,grading-svc, or another Python worker fail immediately. - Worker logs show malformed or invalid HTTP requests rather than application validation errors.
- The same endpoint works with
curlor a simple HTTP/1.1 client. - The failure appears after changing a
RestClientbuilder or request factory.
Known Failure History
| Commit | Surface | Failure mode | Durable fix |
|---|---|---|---|
b8d0f278 | Chatbot worker | JDK client negotiated HTTP/2 over plaintext while uvicorn only accepted HTTP/1.1. | ChatbotClient builds HttpClient.Version.HTTP_1_1. |
9dcb463d | OMR/report worker calls | The same protocol risk existed for worker calls using shared RestClient wiring. | RestClientConfig and OmrServiceClient pin HTTP/1.1. |
Current Runtime Contract
The backend uses Java RestClient, but worker services are Python/FastAPI processes served by uvicorn-style HTTP/1.1 endpoints. Backend clients must therefore pin HTTP/1.1 explicitly.
Current source anchors:
| Source path | Contract |
|---|---|
modules/ai/src/main/java/com/lumie/ai/adapter/out/external/ChatbotClient.java | Builds a JDK HttpClient with HttpClient.Version.HTTP_1_1. |
modules/exam/src/main/java/com/lumie/exam/adapter/out/config/RestClientConfig.java | Shared exam RestClient.Builder uses an HTTP/1.1 request factory. |
modules/exam/src/main/java/com/lumie/exam/adapter/out/external/OmrServiceClient.java | OMR call overrides read timeout but keeps HTTP/1.1. |
Diagnosis
- Confirm the failing dependency is a backend-to-worker HTTP call, not RabbitMQ callback processing.
- Inspect worker logs for invalid HTTP request parsing, early connection close, or protocol mismatch language.
- Check the backend client source and verify the request factory uses a JDK
HttpClientpinned toHTTP_1_1. - Confirm the call still forwards
X-Tenant-Slug; protocol fixes should not remove tenant propagation. - Compare with a direct HTTP/1.1 probe to the same worker URL.
Source check:
cd lumie-backend
rg -n "HTTP_1_1|JdkClientHttpRequestFactory|X-Tenant-Slug" \
modules/ai/src/main/java modules/exam/src/main/java
Fix
Use a request factory built from an explicit HTTP/1.1 JDK client:
HttpClient http11 = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.build();
RestClient client = restClientBuilder
.requestFactory(new JdkClientHttpRequestFactory(http11))
.build();
For long-running image or report calls, keep the protocol pin and adjust read timeout separately. Do not replace the request factory with a default builder that may reintroduce protocol negotiation.
Verification Evidence
- worker logs no longer show invalid HTTP request parsing for the backend call,
- backend logs reach application-level success or validation failure,
- the request still includes
X-Tenant-Slug, - OMR grading and chatbot stream paths keep their separate timeout and streaming behavior,
- tests or source checks cover the client that was changed.
Prevention
- Treat backend-to-worker HTTP client configuration as part of the worker compatibility contract.
- When adding a new worker client, copy the HTTP/1.1 request factory pattern before tuning timeouts.
- Do not use the JDK default protocol negotiation for uvicorn-backed services.
- Keep protocol fixes independent from payload, tenant, and timeout fixes so regressions are easy to identify.