Purpose: The Go HTTP server is the source of truth for "what clients should expect." If the server doesn't set Content-Type on responses, clients cannot reliably determine the response format. If the server rejects unknown content types on response serialization, it creates an asymmetry with request parsing (which defaults to binary). Both issues must be fixed to establish a correct server baseline before auditing clients.
Output: Corrected server generator code with proper Content-Type response headers and consistent JSON default behavior. Updated golden files.
<execution_context> @/Users/sebastienmelki/.claude/get-shit-done/workflows/execute-plan.md @/Users/sebastienmelki/.claude/get-shit-done/templates/summary.md </execution_context>
Fix 1: Set Content-Type on all responses
In generateWriteProtoMessageResponseFunc (around line 860-893), the generated writeProtoMessageResponse function writes response bytes but never sets the Content-Type header. Fix this by adding w.Header().Set("Content-Type", ...) BEFORE w.WriteHeader(statusCode) (headers must be set before WriteHeader is called in Go's net/http).
The Content-Type should match the serialization format:
- JSON path:
w.Header().Set("Content-Type", "application/json") - Binary/Proto path:
w.Header().Set("Content-Type", "application/x-protobuf") - Default path (already defaults to JSON):
w.Header().Set("Content-Type", "application/json")
Restructure the generated code so the Content-Type header is set inside the switch cases (or set a variable and set it after the switch but before WriteHeader). The cleanest approach:
var responseBytes []byte
var err error
var respContentType string
switch filterFlags(contentType) {
case JSONContentType:
responseBytes, err = protojson.Marshal(msg)
respContentType = "application/json"
case BinaryContentType, ProtoContentType:
responseBytes, err = proto.Marshal(msg)
respContentType = "application/x-protobuf"
default:
responseBytes, err = protojson.Marshal(msg)
respContentType = "application/json"
}
if err != nil {
http.Error(w, fallbackMsg, statusCode)
return
}
w.Header().Set("Content-Type", respContentType)
w.WriteHeader(statusCode)
_, _ = w.Write(responseBytes)Fix 2: marshalResponse should default to JSON for unknown content types
In generateBindingHelpers (around line 629-653), the generated marshalResponse function has a default case that returns an error: return nil, fmt.Errorf("unsupported content type: %s", contentType). This is inconsistent with the writeProtoMessageResponse function which already defaults to JSON.
Change the default case in marshalResponse to default to JSON (matching protojson as source of truth):
default:
// Default to JSON for unrecognized content types
if marshaler, ok := response.(json.Marshaler); ok {
return marshaler.MarshalJSON()
}
return protojson.Marshal(msg)Also set the response Content-Type header in the handler function that calls marshalResponse. Look for where marshalResponse is called (in the generated handler functions) and add w.Header().Set("Content-Type", ...) there too. Find the generateMethodHandler or equivalent function that calls marshalResponse and writes the response. The Content-Type should be determined from the request Content-Type (matching the serialization format used) and set on the response.
Actually, check the full flow: marshalResponse returns bytes but doesn't have access to w. The caller writes the bytes. Find where the caller writes and add Content-Type there. The pattern is likely:
responseBytes, err := marshalResponse(r, response)
// ... error handling ...
w.WriteHeader(http.StatusOK)
w.Write(responseBytes)Add Content-Type setting before WriteHeader. Determine the Content-Type by checking r.Header.Get("Content-Type") (same logic as marshalResponse) and set it on the response:
respContentType := "application/json"
if ct := filterFlags(r.Header.Get("Content-Type")); ct == BinaryContentType || ct == ProtoContentType {
respContentType = "application/x-protobuf"
}
w.Header().Set("Content-Type", respContentType)IMPORTANT: Do NOT change field names, function signatures, or break any public API. These are internal generated helper functions. The fix only adds Content-Type header setting and changes the default case from error to JSON.
make build to verify compilation. Run UPDATE_GOLDEN=1 go test ./internal/httpgen/ -run TestExhaustiveGoldenFiles -count=1 to update golden files. Run go test ./internal/httpgen/ -count=1 to confirm all tests pass. Inspect the golden file diff to verify Content-Type headers are set and the default case is changed.
writeValidationErrorResponse(around line 896) -- This should callwriteProtoMessageResponsewhich now sets Content-Type. Verify.writeErrorResponse-- Same, should delegate towriteProtoMessageResponse. Verify.writeResponseBody-- This is the main response writing function. Verify it sets Content-Type.writeErrorWithHandler-- Custom error handler path. Verify Content-Type is set.- Any direct
http.Error()calls -- These usetext/plainby default which is correct for fallback errors. - Header validation error responses -- Check
generateHeaderValidationFunctionsfor Content-Type setting.
For each function:
- If it delegates to
writeProtoMessageResponse, it's already fixed (Task 1). - If it writes bytes directly, add Content-Type setting.
- If it uses
http.Error(), that's fine (sets text/plain automatically).
Also verify that the generateWriteResponseBodyFunc (the main happy-path response writer) sets Content-Type before writing. This is the function called for successful RPC responses.
If any path is missing Content-Type, fix it following the same pattern as Task 1.
After fixes, run make lint-fix to clean up any formatting issues.
go test ./internal/httpgen/ -count=1 to confirm all tests pass. Grep the generated golden files for Content-Type to verify it appears in all response paths: grep -n "Content-Type" internal/httpgen/testdata/golden/*_binding.pb.go. Every function that writes an HTTP response should set Content-Type.
<success_criteria>
- Every generated HTTP server response includes a correct Content-Type header
- Unknown/empty request Content-Type defaults to JSON for both request parsing and response serialization
- The server behavior is internally consistent (no asymmetry between request and response content-type handling)
- All httpgen tests pass with updated golden files </success_criteria>
