Series MapLesson 06 / 35
Focus mode active/Press Alt+Shift+R to toggle/Esc to exit
Start HereOrdered learning track

HTTP Semantics at the Proxy Layer

HTTP Semantics and Protocol Behavior

Menilai bagaimana NGINX dapat mempertahankan atau merusak HTTP semantics, compatibility, dan protocol negotiation.

33 min read6599 words
PrevNext
Lesson 0635 lesson track01–06 Start Here
#http#headers#status-codes#cors+2 more

Part 006 — HTTP Semantics at the Proxy Layer

Depth level: Advanced
Primary audience: Senior Java/JAX-RS backend engineer yang perlu memastikan API contract tetap semantically correct ketika melewati NGINX, ingress controller, load balancer, dan protocol conversion.
Prerequisite: Part 002 dan 005; pemahaman HTTP request/response, media type, caching dasar, serta Java/JAX-RS endpoint behavior.

Learning outcomes

Setelah menyelesaikan part ini, Anda seharusnya mampu:

  1. mereview konfigurasi NGINX terhadap semantics method, status, header, representation, dan message framing;
  2. membedakan HTTP semantics, HTTP message syntax/framing, dan transport protocol version;
  3. mengidentifikasi hop-by-hop fields yang tidak boleh diteruskan secara buta;
  4. mempertahankan conditional requests, validators, content negotiation, CORS, Range, HEAD, dan error contracts;
  5. menjelaskan bahwa HTTP/2 atau HTTP/3 pada client-facing hop tidak berarti upstream Java menggunakan protocol yang sama;
  6. mendiagnosis compatibility failure akibat method filtering, header rewriting, compression, protocol downgrade, buffering, atau malformed framing;
  7. menemukan request-smuggling risk yang muncul karena parser disagreement pada proxy chain;
  8. membangun compatibility matrix dan end-to-end protocol test untuk Java/JAX-RS APIs.

Daftar isi

  1. Ringkasan eksekutif
  2. Tiga layer yang tidak boleh dicampur
  3. NGINX sebagai HTTP intermediary
  4. Semantic invariants
  5. Method semantics overview
  6. Safe versus idempotent
  7. Method filtering dan 405 versus 501
  8. HEAD semantics
  9. OPTIONS dan CORS preflight
  10. TRACE dan CONNECT
  11. Status-code provenance dan preservation
  12. Status/body invariants
  13. Header field model
  14. Hop-by-hop versus end-to-end
  15. Connection header stripping
  16. Header normalization dan duplicate fields
  17. Request and response framing
  18. Content-Length versus Transfer-Encoding
  19. Chunked transfer coding
  20. Trailers
  21. Expect: 100-continue dan informational responses
  22. Representation metadata
  23. Content negotiation
  24. Compression negotiation
  25. Vary dan cache correctness
  26. Validators: ETag dan Last-Modified
  27. Conditional requests
  28. Optimistic concurrency untuk mutating methods
  29. Range requests dan 206
  30. CORS semantics di edge
  31. HTTP/1.0 dan HTTP/1.1 upstream behavior
  32. HTTP/2 client-facing behavior
  33. HTTP/3 and QUIC considerations
  34. Protocol conversion matrix
  35. Java/JAX-RS implications
  36. Kubernetes ingress implications
  37. AWS, Azure, dan on-prem implications
  38. Compatibility-risk patterns
  39. Failure model
  40. Systematic debugging playbook
  41. Protocol and contract test matrix
  42. Security concerns
  43. Performance concerns
  44. Observability concerns
  45. PR review checklist
  46. Internal verification checklist
  47. Exercises
  48. Ringkasan
  49. Referensi resmi

Ringkasan eksekutif

NGINX dapat mempertahankan HTTP semantics, tetapi tidak menjamin bahwa setiap request dan response diteruskan byte-for-byte. Sebagai intermediary, NGINX:

  • mem-parse message;
  • memilih virtual server/location;
  • menghapus atau menyintesis connection-specific fields;
  • dapat mengganti method, URI, Host, dan headers;
  • dapat mengubah message framing;
  • dapat melakukan compression/decompression-related handling;
  • dapat cache, buffer, intercept, redirect, atau retry;
  • dapat menerima satu protocol version dan berbicara version lain ke upstream.

Mental model yang benar:

semantic intent
≠ wire representation
≠ transport connection

Contoh:

Client sends HTTP/2 GET
→ NGINX receives pseudo-headers and DATA frames
→ NGINX constructs HTTP/1.1 request to Java
→ Java returns HTTP/1.1 response
→ NGINX emits HTTP/2 response frames

Semantics GET dapat tetap sama walaupun framing dan protocol version berubah. Sebaliknya, satu directive kecil dapat merusak semantics walaupun semua hop tetap “HTTP”.


Tiga layer yang tidak boleh dicampur

1. Semantics

Menjawab:

  • apa arti method?
  • apa arti status?
  • representation mana yang dipilih?
  • apakah operation safe/idempotent?
  • apakah precondition terpenuhi?

2. Message representation and framing

Menjawab:

  • headers apa yang dikirim?
  • bagaimana body length ditentukan?
  • apakah chunked?
  • apakah ada trailers?
  • bagaimana content dikodekan?

3. Transport/protocol mapping

Menjawab:

  • TCP atau QUIC?
  • HTTP/1.1, HTTP/2, atau HTTP/3?
  • satu request per connection atau multiplexed streams?
  • TLS/ALPN bagaimana?
flowchart TB S[HTTP semantics] M[Message fields + content + framing] P[Protocol mapping H1/H2/H3] T[TCP/TLS or QUIC] S --> M --> P --> T

Bug sering muncul saat konfigurasi memperlakukan layer berbeda sebagai setara. Contoh: mengaktifkan HTTP/2 di listener tidak otomatis membuat Java backend menerima HTTP/2.


NGINX sebagai HTTP intermediary

Sebagai proxy/gateway, NGINX memiliki kewajiban protocol-level:

  • memahami connection-specific fields;
  • tidak meneruskan hop-by-hop controls secara buta;
  • menghasilkan message yang valid untuk next hop;
  • menjaga target URI dan method semantics kecuali transformasi memang disengaja;
  • menangani protocol conversion;
  • mengatribusikan failures yang tidak dapat diteruskan sebagai upstream response.

Transparent versus transforming behavior

Proxy dapat dianggap lebih transparent bila:

method preserved
+ target resource mapping preserved
+ relevant headers preserved
+ representation preserved
+ status preserved

Ia menjadi transforming intermediary bila:

  • body/compression diubah;
  • method diubah;
  • URI ditulis ulang;
  • response di-cache atau dihasilkan ulang;
  • CORS/auth/error response dibuat di edge;
  • content dipotong atau diganti.

Transformasi bukan selalu buruk, tetapi harus dinyatakan sebagai bagian API contract.


Semantic invariants

Gunakan invariants berikut untuk review.

Request invariant

The upstream must receive an operation semantically equivalent
unless an intentional adapter contract says otherwise.

Response invariant

The client must receive a response whose status, fields, and content
retain the meaning promised by the public API contract.

Representation invariant

Content-Type, Content-Encoding, validators, Vary, and length/framing
must describe the actual transferred representation.

Retry invariant

Automatic replay must not create a semantic effect that the client did not request.

Cache invariant

A cached representation may only be reused for requests for which it is valid.

Method semantics overview

MethodDefined intentSafeIdempotentTypical JAX-RS annotation
GETretrieve representationyesyes@GET
HEADGET metadata without contentyesyesoften auto/@HEAD support varies
POSTresource-specific processingnono by default@POST
PUTreplace target statenoyes by semantics@PUT
DELETEremove target statenoyes by semantics@DELETE
PATCHpartial modificationnonot guaranteedcustom/@PATCH depending API/runtime
OPTIONScommunication optionsyesyes@OPTIONS or framework handling
TRACEloop-back diagnosticyes conceptuallyyesnormally disabled at public edge
CONNECTcreate tunnelnononormally not proxied to REST app

Important nuance

Idempotent does not mean:

  • response is identical;
  • no logs are created;
  • no metrics change;
  • no side effects whatsoever.

It means intended effect of repeated identical requests is equivalent to one request.


Safe versus idempotent

Safe

Safe methods are intended as read-only from the client's perspective. GET and HEAD should not trigger business mutations merely because a crawler, prefetcher, health checker, or cache invokes them.

Bad API:

GET /orders/123/cancel

A browser prefetch, scanner, or monitoring system could cancel an order.

Better:

POST /orders/123/cancellation

Idempotent

PUT and DELETE are defined as idempotent in their intended effect. But implementation must enforce this property.

Example:

PUT /quotes/Q-123

Repeated identical body should converge to equivalent state, not append duplicate items each time.

Proxy retry implications

NGINX upstream retry logic treats non-idempotent requests more conservatively after a request has been sent. Enabling retry of non-idempotent methods without application idempotency can duplicate business operations.

Detailed timeout/retry policy is covered in Part 009, tetapi method semantics harus menjadi input desainnya.


Method filtering dan 405 versus 501

405 Method Not Allowed

Method dikenal dan didukung server, tetapi tidak diizinkan untuk target resource.

Expected response:

HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, PUT

501 Not Implemented

Method tidak dikenali atau tidak diimplementasikan oleh server.

Edge filtering

limit_except GET HEAD POST {
    deny all;
}

Potential issue: deny all biasanya menghasilkan 403, bukan 405 dengan correct Allow. Ini mungkin acceptable sebagai security policy, tetapi bukan semantic equivalent dari application method dispatch.

Better decision

  • block universally dangerous methods at edge bila policy jelas;
  • biarkan application menghasilkan resource-specific 405/Allow bila diperlukan;
  • jangan mengklaim method tidak tersedia secara global jika beberapa route membutuhkannya;
  • test custom methods seperti PATCH, WebDAV, atau domain-specific extensions bila ada.

Route mismatch example

NGINX allows POST only
application supports PUT
OpenAPI advertises PUT
client receives 403 at edge

Ini adalah public API contract contradiction.


HEAD semantics

HEAD meminta response headers yang setara dengan GET, tanpa response content.

Potential layers:

  • NGINX may convert/handle HEAD relative to GET behavior;
  • Java runtime may provide automatic HEAD based on GET;
  • custom JAX-RS @HEAD may override;
  • cache may use HEAD for metadata validation.

Invariants

  • no response body is transferred for HEAD;
  • representation metadata should correspond to GET where applicable;
  • Content-Length may describe size a GET representation would have;
  • edge must not generate body accidentally through custom error handling;
  • auth and conditional logic should remain consistent with GET.

Test

curl -svI https://quote.example.com/platform/api/quotes/Q-123
curl -sv  https://quote.example.com/platform/api/quotes/Q-123 -o /dev/null

Compare:

  • status;
  • Content-Type;
  • ETag;
  • Last-Modified;
  • Cache-Control;
  • Content-Length semantics.

OPTIONS dan CORS preflight

OPTIONS describes communication options. Browser CORS preflight is a specialized OPTIONS request.

Example:

OPTIONS /platform/api/quotes HTTP/1.1
Origin: https://portal.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

Possible owners:

  1. NGINX generates preflight response;
  2. API gateway handles it;
  3. Java/JAX-RS CORS filter handles it;
  4. multiple layers all add headers.

Single-owner principle

Choose one primary owner for CORS policy. Duplicate ownership causes:

  • duplicate Access-Control-Allow-Origin;
  • wildcard plus credentials conflict;
  • preflight says method allowed but app rejects it;
  • app response lacks headers that preflight had;
  • inconsistent allowed headers across routes.

Max-Forwards

OPTIONS and TRACE can include Max-Forwards for proxy-chain diagnostics. Most APIs do not depend on this, tetapi edge-generated OPTIONS should not silently masquerade as application capability if the route has different behavior.


TRACE dan CONNECT

TRACE

TRACE echoes request information and can expose credentials or internal headers. Public enterprise endpoints commonly disable it.

if ($request_method = TRACE) {
    return 405;
}

Use a server/location policy appropriate to the configuration style; avoid complex if logic.

CONNECT

CONNECT establishes a tunnel, usually for forward proxies. Standard reverse-proxy deployment in front of JAX-RS services generally should not expose arbitrary CONNECT.

Security invariant

Reverse proxy must not accidentally become an open forward proxy or tunnel endpoint.

Verify unusual modules, stream configuration, and generic proxy locations.


Status-code provenance dan preservation

Status may originate from:

  • client-facing load balancer;
  • WAF/auth layer;
  • NGINX parser/routing;
  • ingress default backend;
  • Kubernetes service/upstream connection;
  • Java container;
  • JAX-RS resource/filter/exception mapper;
  • downstream dependency surfaced by Java.

Preserve application semantics

If Java returns:

409 Conflict
Content-Type: application/problem+json

NGINX should not replace it with a generic HTML 500 unless policy explicitly requires transformation.

Separate external and upstream status in logs

$status              = status sent to downstream
$upstream_status     = status received from upstream attempt(s)

Examples:

$status$upstream_statusInterpretation
200200normal pass-through
504-edge timeout before valid upstream response or no upstream
502502upstream itself returned 502 or chain behavior; inspect logs
200404error interception/internal redirect transformed result
499- or partialclient aborted; application may still have processed

Status/body invariants

Certain statuses have special body semantics.

No response content

Common cases:

  • HEAD response;
  • 1xx informational responses;
  • 204 No Content;
  • 304 Not Modified.

Proxy/application must not attach an ordinary response body to these.

304 Not Modified

304 is not an empty 200. It tells a client/cache to reuse stored representation under validator semantics. Relevant metadata must remain coherent.

206 Partial Content

206 body represents only requested range(s), and Content-Range must match.

401 versus 403

  • 401 relates to authentication challenge and generally needs WWW-Authenticate;
  • 403 indicates request understood but refused.

Edge auth interception must preserve challenge semantics where clients rely on them.

429 and 503

Retry-After can communicate retry timing. Do not drop it when replacing error bodies.


Header field model

Header field names are case-insensitive, but HTTP/2 and HTTP/3 use lowercase field-name encoding on wire. Applications must not depend on original casing.

A field can be:

  • single-valued by semantics;
  • list-valued and combinable;
  • repeatable but not safely comma-combinable, notably Set-Cookie;
  • connection-specific;
  • representation metadata;
  • routing/control metadata.

Anti-pattern

Map<String, String> headers

This model may lose repeated fields or combine values incorrectly. Java/JAX-RS APIs usually expose multi-valued headers; filters and custom adapters should preserve multiplicity.

Header ordering

Most semantics should not depend on ordering, except protocol-specific requirements and list order where defined. Do not build authorization logic around arbitrary received header order.


Hop-by-hop versus end-to-end

End-to-end fields

Intended to describe request/response semantics across intermediaries, subject to their definitions:

  • Authorization;
  • Content-Type;
  • Content-Encoding;
  • Accept;
  • ETag;
  • Cache-Control;
  • If-None-Match;
  • Range.

Hop-by-hop / connection-specific fields

Examples commonly requiring per-hop handling:

  • Connection;
  • field names listed by Connection;
  • Keep-Alive;
  • Proxy-Authenticate;
  • Proxy-Authorization;
  • TE except carefully negotiated trailers semantics;
  • Trailer;
  • Transfer-Encoding;
  • Upgrade.

Core rule

A proxy must parse Connection, remove fields named by its connection options, and then remove or replace Connection for the next hop.

This is why “forward all headers exactly” is neither correct nor secure.


Connection header stripping

Malicious request:

Connection: X-Internal-Auth
X-Internal-Auth: trusted

If an intermediary fails to process Connection, downstream may see a field that should have been consumed at one hop.

Another example:

Connection: close, X-Remove-Me
X-Remove-Me: value

The intermediary must treat X-Remove-Me as connection-specific for that hop.

WebSocket exception pattern

Upgrade requires explicit reconstruction:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

proxy_set_header Upgrade    $http_upgrade;
proxy_set_header Connection $connection_upgrade;

This is intentional hop-by-hop synthesis, not blind forwarding. Real-time traffic is covered in Part 026.


Header normalization dan duplicate fields

Potential parser differences:

  • duplicate Content-Length;
  • comma-combined values;
  • whitespace around colon/value;
  • underscores in names;
  • invalid characters;
  • obs-fold/legacy line folding;
  • duplicate Host/authority;
  • conflicting Host and :authority;
  • oversized fields.

Parser-agreement security principle

Every hop must agree on request boundaries and routing-critical fields. Reject ambiguous requests rather than allowing different parsers to interpret them differently.

NGINX configuration concerns

  • underscores_in_headers changes acceptance behavior;
  • ignore_invalid_headers affects invalid field handling;
  • large header buffers affect rejection thresholds;
  • WAF, cloud LB, NGINX, and Java container may have different limits;
  • custom Lua/njs modules may parse values differently.

Compatibility test

Use valid and intentionally invalid requests in a controlled non-production environment. Do not rely only on curl, because it normalizes many inputs; lower-level tools may be needed for parser testing.


Request and response framing

Framing answers: where does message content end?

HTTP/1.1

Common mechanisms:

  • no body by method/status semantics;
  • Content-Length;
  • Transfer-Encoding: chunked;
  • connection close in limited cases.

HTTP/2 and HTTP/3

Messages use frames/streams; Transfer-Encoding: chunked is not used as HTTP/1.1 wire framing. The intermediary reconstructs semantics when converting to/from HTTP/1.1.

Key invariant

metadata describing content length and encoding
must match the actual content transmitted on that hop

Do not copy HTTP/1.1 framing fields blindly into HTTP/2/3 concepts.


Content-Length versus Transfer-Encoding

A message containing both can be ambiguous and is a classic request-smuggling signal.

Safe handling

  • sender must not send Content-Length together with Transfer-Encoding;
  • recipient/intermediary must follow protocol rules and reject or normalize safely;
  • if forwarding after receiving both, intermediary must not preserve contradictory framing;
  • Java container and NGINX must agree on message boundary.

Failure modes

InputFront parserBack parserRisk
CL + TEuses TEuses CLrequest desync/smuggling
duplicate CL differingrejectschooses firstambiguity
malformed chunktoleratesrejectsconnection desync
H2 fields converted to H1normalizedcustom parser differsboundary mismatch

Operational expectation

Ambiguous request should fail at the earliest trusted edge. A 400 at NGINX is safer than letting Java parse a different request stream.


Chunked transfer coding

Chunked transfer coding is HTTP/1.1 message framing, not content compression.

Transfer-Encoding: chunked

4
Wiki
5
pedia
0


Do not confuse

FieldPurpose
Transfer-Encoding: chunkedframing for one hop
Content-Encoding: gzipencoding of representation

A response can conceptually be gzip-compressed and chunked on HTTP/1.1:

representation → gzip content coding → chunks for transfer

Proxy conversion

Client H2 request may arrive as DATA frames. NGINX can forward it to H1 upstream using Content-Length or chunked behavior depending on buffering/protocol configuration. Backend should not assume client used the same framing.

Request buffering interaction

With buffering on, NGINX may read entire body before upstream transmission and know its size. With buffering off, streaming/framing behavior changes. Part 010 covers this in depth.


Trailers

Trailer fields are metadata sent after content.

Possible use cases:

  • integrity digest;
  • final streaming status;
  • late-generated metadata.

Compatibility reality

Trailer support varies across clients, proxies, frameworks, and protocol versions. Current NGINX proxying requires explicit trailer enablement; do not assume trailers pass automatically.

Conceptual configuration:

proxy_set_header Connection "te";
proxy_set_header TE "trailers";
proxy_pass_trailers on;

Exact support is version-dependent.

Review questions

  • Is trailer a contractual API feature or optional metadata?
  • Does Java runtime expose request/response trailers?
  • Are cloud LB and ingress controller transparent to them?
  • Does fallback exist for clients that drop trailers?
  • Are trailer fields safe to forward?

Avoid making critical business success dependent only on trailers unless every hop is tested.


Expect: 100-continue dan informational responses

A client can send:

Expect: 100-continue
Content-Length: 500000000

Purpose: allow server/proxy to reject based on headers before client uploads a large body.

Possible responses:

HTTP/1.1 100 Continue

followed by a final response, or an immediate final status such as 401/405/413.

Proxy concerns for informational responses

  • intermediary may generate or relay 100 behavior;
  • HTTP/1.0 hop cannot represent it the same way;
  • clients must not wait indefinitely;
  • buffering can make observed behavior different from direct application access;
  • auth/body-size policy should be evaluated as early as practical.

Other 1xx

Protocol support for 103 Early Hints or other informational responses must be verified end-to-end. Do not advertise optimization based on direct backend test only.


Representation metadata

Representation-related fields include:

  • Content-Type;
  • Content-Encoding;
  • Content-Language;
  • Content-Location;
  • Content-Length;
  • ETag;
  • Last-Modified.

Invariant

If proxy transforms content, metadata may also need transformation.

Example:

Java emits uncompressed JSON + strong ETag of uncompressed bytes
NGINX compresses response
client receives gzip bytes

Whether validator remains valid depends on representation model and implementation. Weak/strong validator semantics and cache key/Vary behavior must be tested.

Content-Type

Do not let generic NGINX error pages claim the API's application/problem+json contract. Conversely, do not force application/json onto HTML or binary content.

Charset

Avoid arbitrary charset injection into JSON or binary responses unless required and correct for the media type.


Content negotiation

Client preferences can include:

Accept: application/json
Accept-Language: en-SG, en;q=0.8
Accept-Encoding: gzip, br

JAX-RS may select resource methods/providers based on:

  • @Produces;
  • @Consumes;
  • Content-Type;
  • Accept quality values.

Failure statuses

  • 406 when no acceptable response representation is selected under policy;
  • 415 when request representation/content coding is unsupported;
  • 400 when syntax is invalid;
  • 422 when syntax is valid but semantic validation fails, according to API design.

Proxy risks

  • dropping Accept causes default representation selection;
  • forcing Accept: application/json hides client intent;
  • replacing Content-Type bypasses JAX-RS provider selection;
  • caching without Vary serves wrong language/encoding;
  • edge-generated errors use different media type from API contract.

Rule

NGINX should not perform domain-level content negotiation unless explicitly designed as an API transformation layer.


Compression negotiation

Compression involves:

client sends Accept-Encoding
→ server/proxy selects coding
→ response includes Content-Encoding
→ cache varies appropriately

Semantics checklist

  • Content-Encoding describes actual representation coding;
  • Vary: Accept-Encoding protects cache variants where needed;
  • compressed body length differs from source body length;
  • ETag behavior must remain coherent;
  • already-compressed media types may gain little;
  • clients that do not accept coding must receive usable response.

Double compression

Bad chain:

Java gzips
→ NGINX treats body as unencoded/misconfigured
→ compresses again

Possible result:

Content-Encoding: gzip

but body requires two decompressions or is otherwise inconsistent.

Boundary with Part 011

Part ini focuses on semantics and compatibility. Compression levels, CPU trade-offs, Brotli deployment, and cache tuning are covered later.


Vary dan cache correctness

Vary tells caches which request fields influenced representation selection.

Example:

Vary: Accept-Encoding, Accept-Language

Without correct Vary:

request A asks English + gzip
cache stores response
request B asks Indonesian + identity
cache reuses A incorrectly

CORS and Vary: Origin

When Access-Control-Allow-Origin echoes an allowed request Origin, response often needs:

Vary: Origin

Otherwise a shared cache can serve one origin's CORS response to another.

Proxy mutation risk

Multiple layers using add_header Vary ... may overwrite or duplicate values incorrectly. Treat Vary as a set-like semantic field but verify actual NGINX directive behavior and resulting wire value.


Validators: ETag dan Last-Modified

ETag

Entity tag identifies a selected representation version.

ETag: "quote-Q-123-v17"

Strong validator:

ETag: "abc"

Weak validator:

ETag: W/"abc"

Strong and weak validators have different applicability, especially for Range and concurrency.

Last-Modified

Last-Modified: Sat, 11 Jul 2026 10:00:00 GMT

Time precision and clock behavior can make it less precise than ETag.

Ownership

Validator should be generated by the component that understands representation version. NGINX static-file handling can generate validators for static resources; JAX-RS/domain API should usually define validators based on resource state.

Proxy transformation

If NGINX changes representation bytes through compression or transformation, test whether validator remains valid for the selected representation variants.


Conditional requests

Conditional GET

If-None-Match: "v17"

If unchanged:

304 Not Modified
ETag: "v17"

Date-based conditional GET

If-Modified-Since: Sat, 11 Jul 2026 10:00:00 GMT

Preconditions for update

If-Match: "v17"
PUT /quotes/Q-123

If current version differs:

412 Precondition Failed

NGINX cache interaction

When proxy caching is enabled, NGINX may treat conditional headers specially and can revalidate cached responses. Current proxy module behavior also changes which conditional/range headers are forwarded in some cache configurations. Therefore:

  • test cache-enabled and cache-disabled routes separately;
  • do not assume application sees all validators when NGINX cache is active;
  • inspect generated config and cache directives;
  • preserve public semantics even when edge answers 304 itself.

Optimistic concurrency untuk mutating methods

Conditional requests are not only performance optimization. They can protect state transitions.

Lost update scenario

Client A reads version 17
Client B reads version 17
Client A updates → version 18
Client B updates based on old version 17

With If-Match:

If-Match: "v17"

Client B receives 412 instead of overwriting version 18.

Proxy requirements

  • preserve If-Match and ETag;
  • do not retry mutation blindly;
  • do not rewrite 412 into generic 409/500 without API contract;
  • do not cache mutation responses incorrectly;
  • ensure WAF/header allowlist permits precondition fields.

JAX-RS note

Application remains authoritative for domain version and atomic check/update. NGINX cannot enforce business-level optimistic locking merely by forwarding headers.


Range requests dan 206

Client:

Range: bytes=0-999

Successful partial response:

HTTP/1.1 206 Partial Content
Content-Range: bytes 0-999/5000
Content-Length: 1000

Unsatisfiable:

HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */5000

Use cases

  • large document download;
  • resume interrupted transfer;
  • media/random access;
  • partial artifact retrieval.

Proxy concerns for range handling

  • cached versus uncached Range behavior;
  • compression changes byte offsets because ranges apply to selected encoded representation;
  • upstream Accept-Ranges handling;
  • If-Range validator;
  • multi-range response media type;
  • Java streaming endpoint support;
  • object storage redirect/proxy behavior.

Test invariant

Recombining received ranges must reproduce the same selected representation bytes as a full 200 response for that variant.


CORS semantics di edge

CORS is browser-enforced access policy, not server-side authorization.

Simple request response

Access-Control-Allow-Origin: https://portal.example.com
Access-Control-Allow-Credentials: true
Vary: Origin

Preflight response

Access-Control-Allow-Origin: https://portal.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 600

Invalid combination

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

This is not a valid credentialed cross-origin policy for browsers.

Edge risks

  • reflecting arbitrary Origin;
  • regex allowlist too broad;
  • adding CORS headers to error responses inconsistently;
  • preflight bypassing auth but revealing unsupported capability;
  • duplicate values from NGINX and Java;
  • missing always behavior on non-2xx where client needs readable errors;
  • cache missing Vary: Origin.

CORS security principle

CORS does not protect API from non-browser clients. Authentication and authorization remain required.


HTTP/1.0 dan HTTP/1.1 upstream behavior

proxy_http_version controls protocol used from NGINX to proxied server.

Current NGINX documentation indicates:

  • recent versions use HTTP/1.1 as default since 1.29.7;
  • older versions used HTTP/1.0 by default;
  • newer versions also introduce upstream HTTP/2 support under specific version/module conditions.

Production rule

Set protocol version explicitly when behavior matters:

proxy_http_version 1.1;

Reasons:

  • stable behavior across upgrades/distributions;
  • upstream keepalive;
  • streaming/chunking expectations;
  • upgrade protocols;
  • trailers;
  • compatibility with Java runtime.

Connection default

NGINX proxy module has its own default Host/Connection behavior. For persistent upstream connections, configuration commonly clears connection-close semantics:

proxy_set_header Connection "";

But route-specific protocols such as WebSocket require explicit Upgrade handling instead.

HTTP/1.0 risks

  • no chunked request transfer semantics;
  • different connection persistence behavior;
  • 100-continue limitations;
  • protocol downgrade may trigger buffering;
  • some modern backend features expect HTTP/1.1.

HTTP/2 client-facing behavior

HTTP/2 provides:

  • multiplexed streams over a connection;
  • binary framing;
  • header compression;
  • stream-level flow control;
  • pseudo-headers such as :method, :scheme, :authority, :path.

Semantic mapping

:method     → HTTP method
:scheme     → target scheme context
:authority  → authority/Host semantics
:path       → request target path/query

Important differences

  • no HTTP/1.1 chunked transfer coding on wire;
  • connection-specific headers such as Connection are prohibited in H2 messages;
  • field names are lowercase on wire;
  • one slow stream should not be equated with one connection, though transport-level loss can still affect connection;
  • upstream may still be HTTP/1.1.

Java/JAX-RS implication

Application often sees a container abstraction, not raw H2 frames. A request may be H2 externally and H1 internally. Log both client protocol and upstream protocol if protocol behavior matters.


HTTP/3 and QUIC considerations

HTTP/3 maps HTTP semantics over QUIC instead of TCP. It changes transport behavior, not REST resource semantics.

Characteristics

  • QUIC over UDP;
  • TLS integrated with QUIC;
  • stream multiplexing without TCP-level head-of-line blocking across independent streams;
  • connection migration capability;
  • QPACK header compression;
  • different observability/network-tooling requirements.

NGINX implementation caution

The NGINX HTTP/3 module remains explicitly documented as experimental in current official module documentation and is not built by default. Treat enablement as a platform decision with version/build verification, not as a generic directive addition.

Edge-only H3

Common topology:

Client H3/QUIC
→ cloud edge or NGINX
→ HTTP/1.1 or HTTP/2 upstream
→ Java service

Application behavior should not depend on H3 unless the API explicitly exposes transport-sensitive features.

Operational concerns

  • UDP firewall/NAT paths;
  • Alt-Svc advertisement;
  • fallback to H2/H1;
  • load balancer support;
  • source address and connection migration logging;
  • 0-RTT replay considerations for unsafe methods;
  • tooling (curl --http3, packet capture, QUIC-aware metrics).

0-RTT warning

Early data may be replayable at transport/security level. Do not enable or accept unsafe mutation flows without explicit replay analysis.


Protocol conversion matrix

Downstream/client hopNGINX → upstreamSemantics possibleKey risk
H1.1H1.1normalframing/header ambiguity
H2H1.1normalpseudo-header conversion, chunking differences
H3H1.1normalQUIC observability, fallback, replay considerations
H2H2possible on supported current builds/configruntime compatibility, pool/multiplex behavior
H1.1H2possible only under supported proxy feature/configtranslation assumptions
H1.0H1.1limited legacy clientkeepalive/framing differences

Review rule

Document protocol independently for every hop:

client→edge
edge→cloud LB
LB→NGINX
NGINX→Java
Java→downstream dependency

“HTTP/2 enabled” without hop qualification is incomplete.


Java/JAX-RS implications

Method dispatch

Edge method restrictions must align with JAX-RS annotations and OpenAPI contract.

Providers and negotiation

JAX-RS message body readers/writers depend on Content-Type, Accept, @Consumes, and @Produces. Proxy mutation can change provider selection.

Exception mapping

Custom ExceptionMapper output can be replaced by NGINX error interception. Preserve Content-Type, status, and problem details.

Entity tags and preconditions

JAX-RS APIs provide types/builders for entity tags and precondition evaluation. Domain version check must still be atomic with update.

HEAD/OPTIONS

Runtime-specific automatic behavior should be verified. Do not assume every implementation generates identical Allow/OPTIONS/HEAD response.

Streaming

A JAX-RS StreamingOutput, SSE, or reactive publisher can be semantically altered by proxy buffering/timeouts. Covered in Part 010 and 026.

Header multiplicity

Use multi-valued APIs correctly, especially Set-Cookie, Warning, Link, WWW-Authenticate, and custom repeated fields.


Kubernetes ingress implications

Ingress abstraction does not standardize every HTTP mutation. Controller-specific annotations and ConfigMap settings can affect:

  • CORS;
  • method restrictions;
  • upstream protocol;
  • request/response headers;
  • compression;
  • buffering;
  • cache;
  • regex rewrites;
  • error interception;
  • gRPC/backend protocol;
  • HTTP/2 listener behavior.

Generated-config principle

Ingress YAML is intent
generated NGINX config is implementation
wire behavior is truth

Multi-ingress conflict

Two Ingress resources for same host/path may contribute conflicting annotations depending on controller behavior. Test effective route rather than assuming namespace isolation resolves semantic conflict.

Admission/governance

Policy should restrict annotations/snippets capable of:

  • overriding CORS globally;
  • enabling arbitrary header injection;
  • changing upstream protocol;
  • intercepting errors;
  • bypassing security headers;
  • creating unsafe regex routes.

AWS, Azure, dan on-prem implications

Cloud L7 load balancer

May:

  • terminate H2/H3 externally;
  • use H1/H2 to targets;
  • normalize headers;
  • impose method/header/body limits;
  • generate redirects/errors;
  • handle compression or WAF policies;
  • enforce idle timeout.

AWS examples to verify

  • ALB client-side and target-side protocol versions;
  • CloudFront/WAF cache and header policies;
  • API Gateway content transformation;
  • NLB as L4 path without HTTP semantics;
  • header limits and invalid-header handling.

Azure examples to verify

  • Front Door protocol and caching behavior;
  • Application Gateway header rewriting;
  • API Management policies;
  • Azure Load Balancer L4 behavior;
  • private routing and TLS re-encryption.

On-prem examples to verify

  • F5/HAProxy/proprietary ADC HTTP profiles;
  • legacy HTTP/1.0 or connection normalization;
  • WAF parser behavior;
  • corporate TLS inspection;
  • duplicate compression/caching;
  • header insertion/removal.

Do not infer internal CSG topology from provider examples. Capture actual behavior via configuration and wire tests.


Compatibility-risk patterns

1. Edge changes POST to GET

proxy_method GET;

Request body and application semantics can become meaningless.

2. Global JSON forcing

proxy_set_header Accept application/json;

Breaks clients requesting other representations and hides contract defects.

3. CORS generated at two layers

NGINX: ACAO=*
Java:  ACAO=https://portal.example.com

Browser rejects duplicate/conflicting policy.

4. Cache ignores Authorization/tenant variance

Potential cross-user data exposure.

5. Compression without Vary

Wrong encoded representation served from shared cache.

6. Intercepted 401

WWW-Authenticate lost, OAuth client behavior breaks.

7. Method allowlist excludes PATCH

OpenAPI says supported, edge returns 403.

8. H2 listener assumed to fix upstream keepalive

Upstream still uses separate H1 behavior and may close every connection.

9. Range through compression

Byte offsets do not match expected uncompressed artifact.

10. Conditional headers removed by cache path

Application optimistic concurrency or validation flow changes.


Failure model

SymptomPossible semantic/protocol causeFirst evidence
403 instead of 405edge method blockNGINX config, Allow header
415 only through proxyContent-Type/Encoding changedrequest diff
wrong language/formatAccept dropped or cache missing Varyheaders/cache status
browser CORS errorduplicate/missing ACAO or preflight mismatchbrowser network log
304 but stale/wrong datavalidator/cache variant defectETag, Age, Vary
206 corrupted downloadrange/compression mismatchContent-Range, hashes
Java sees empty bodymethod/body forwarding/framing issuecontent length, logs
intermittent 400malformed/duplicate header handlingNGINX error log
502 invalid headermalformed upstream response/framingupstream raw response
H2 works direct, fails via LBprotocol conversion/limitsper-hop protocol
trailers missingunsupported/not enabledTE/Trailer and config
large upload stalls before JavaExpect/buffering/body timeoutaccess/error timing
401 body readable in curl but not browserCORS headers absent on errorsresponse headers
duplicate mutationunsafe retry/replayupstream attempts, idempotency

Systematic debugging playbook

Step 1 — state the contract

method:
request Content-Type:
Accept:
expected status:
expected response Content-Type:
body allowed/required:
cacheability:
validator behavior:
range behavior:
CORS origin/method/header policy:
client protocol:
upstream protocol:

Step 2 — compare direct and proxied wire output

curl -sv --http1.1 http://quote-api:8080/api/quotes/Q-123
curl -sv --http1.1 https://quote.example.com/platform/api/quotes/Q-123
curl -sv --http2   https://quote.example.com/platform/api/quotes/Q-123

Where supported and approved:

curl -sv --http3 https://quote.example.com/platform/api/quotes/Q-123

Step 3 — dump headers without body noise

curl -skD - -o /dev/null \
  -H 'Accept: application/json' \
  https://quote.example.com/platform/api/quotes/Q-123

Step 4 — method matrix

for method in GET HEAD POST PUT PATCH DELETE OPTIONS TRACE; do
  printf '\n=== %s ===\n' "$method"
  curl -sk -X "$method" -D - -o /dev/null \
    https://quote.example.com/platform/api/quotes/Q-123
 done

Do not run mutating methods against production resources without safe test data and explicit approval.

Step 5 — conditional request

etag=$(curl -skD - -o /dev/null \
  https://quote.example.com/platform/api/quotes/Q-123 \
  | awk 'BEGIN{IGNORECASE=1} /^ETag:/{sub("\r$",""); print substr($0,7)}')

curl -skD - -o /dev/null \
  -H "If-None-Match: $etag" \
  https://quote.example.com/platform/api/quotes/Q-123

Step 6 — range test

curl -skD - -o part.bin \
  -H 'Range: bytes=0-999' \
  https://quote.example.com/platform/api/documents/D-123/content

Verify status, Content-Range, size, and hash.

Step 7 — CORS test

curl -skD - -o /dev/null -X OPTIONS \
  -H 'Origin: https://portal.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: authorization,content-type' \
  https://quote.example.com/platform/api/quotes

Step 8 — inspect per-hop protocols

Use:

  • load balancer access logs;
  • NGINX $server_protocol;
  • upstream connection/config;
  • Java container access logs;
  • TLS ALPN output;
  • curl -v negotiation;
  • packet capture only under approved operational procedures.

Protocol and contract test matrix

Core API matrix

CaseRequestExpected
GETvalid Accept200 + correct representation
HEADsame targetsame metadata, no content
unsupported methodknown method405 + Allow or explicit edge policy
unknown methodcustom unknown tokendefined 501/edge rejection
unsupported request mediawrong Content-Type415
unacceptable response mediaincompatible Acceptdefined 406 behavior
conditional unchangedIf-None-Match current304
stale updateIf-Match old412
range satisfiablevalid bytes range206 + Content-Range
range invalidbeyond end416 + Content-Range
CORS allowedapproved Origincorrect ACAO/Vary
CORS deniedunapproved Originno permissive ACAO

Protocol matrix

TestDirect appNGINX H1NGINX H2Full cloud path
GET/HEAD parity
POST body integrity
100-continue
large headers
compression
conditional GET
Range
repeated headers
error content type
trailers if required

Automate this in integration/environment smoke tests where feasible.


Security concerns

Request smuggling/desynchronization

Parser disagreement around Content-Length, Transfer-Encoding, duplicate fields, malformed chunks, or H2-to-H1 conversion can let one request be interpreted as multiple requests downstream.

Controls:

  • current patched components;
  • strict invalid-message rejection;
  • minimize heterogeneous parser chains;
  • align LB/WAF/NGINX/backend policies;
  • test known ambiguity classes in controlled security testing;
  • close connections after framing errors.

Host/authority ambiguity

Conflicting Host/:authority can affect routing and cache keys. Reject ambiguity.

CORS misconception

CORS is not authorization and does not stop server-to-server attackers.

Cache data leakage

Wrong cache key/Vary/Authorization handling can expose tenant or user data.

Compression side channels

Compressing secrets alongside attacker-controlled content can create side-channel risk in some contexts. Evaluate sensitive dynamic responses.

TRACE/internal headers

Disable TRACE when not required and prevent reflection of credentials.

0-RTT replay

HTTP/3/TLS early data may be replayed. Unsafe methods require explicit policy.

Error mutation

Replacing errors must not remove auth challenges, retry guidance, or security-relevant headers.


Performance concerns

Semantics correctness precedes optimization, tetapi protocol choices affect capacity:

  • H2/H3 multiplexing changes connection count and concurrency shape;
  • upstream H1 keepalive affects socket churn;
  • header compression reduces wire bytes but requires CPU/state;
  • giant cookies/JWTs increase every request cost;
  • compression consumes CPU and affects latency;
  • conditional requests and Range reduce bytes when correct;
  • CORS preflight cache can reduce requests but must not overstate policy duration;
  • buffering can smooth slow clients but increase disk/memory usage;
  • cache improves latency but expands correctness/security burden;
  • protocol conversion can shift head-of-line and flow-control behavior.

Do not measure only requests per second. Track latency, bytes, connection reuse, CPU, memory, and error semantics.


Observability concerns

request_method
server_protocol
status
upstream_status
request_length
bytes_sent
body_bytes_sent
content_type
content_encoding
http_accept
http_accept_encoding
http_origin
http_range
upstream_cache_status
request_time
upstream_response_time

Avoid logging full Authorization, cookies, sensitive query/body, or unbounded arbitrary headers.

Useful metrics

  • status by method and route;
  • 405/415/406/412/416 rate;
  • CORS preflight success/failure;
  • cache hit by representation variant;
  • H1/H2/H3 client distribution;
  • upstream protocol/connection reuse;
  • response compression ratio;
  • 304 ratio;
  • range traffic and bytes saved;
  • invalid-header/framing rejection;
  • external status versus upstream status.

Provenance

For edge-generated response, log a structured response_origin field internally:

nginx_parser
nginx_policy
nginx_cache
upstream_application
cloud_lb

Not every platform exposes this directly; derive it from upstream status/route logs where necessary.


PR review checklist

Methods and status

  • Allowed methods match OpenAPI/JAX-RS routes.
  • Edge block returns intentional status semantics.
  • HEAD and OPTIONS behavior tested.
  • Application status/body/header contract preserved.
  • 401 challenge, 405 Allow, 429/503 Retry-After remain correct.

Headers and framing

  • Hop-by-hop fields are not blindly forwarded.
  • No contradictory Content-Length/Transfer-Encoding manipulation.
  • Duplicate/repeated headers handled correctly.
  • Header limits align across LB, NGINX, and Java.
  • Trailer/100-continue behavior is explicit if used.

Negotiation and representation

  • Accept, Content-Type, and Content-Encoding are preserved.
  • Compression has correct Vary and metadata.
  • Error responses use expected media types.
  • No double compression.

Cache and validators

  • Cache policy is explicit for authenticated/tenant data.
  • Vary fields cover representation selection.
  • ETag/Last-Modified behavior tested through proxy.
  • Conditional update headers reach application.
  • Range and compression interaction tested.

Protocol

  • Client-facing and upstream protocol versions documented separately.
  • proxy_http_version explicit where behavior matters.
  • H2/H3 enablement includes fallback and compatibility tests.
  • Cloud LB protocol conversion is verified.

Security

  • Request-smuggling hardening considered.
  • TRACE/CONNECT policy explicit.
  • CORS does not substitute auth.
  • HTTP/3 0-RTT replay risk considered if enabled.

Internal verification checklist

Tidak ada detail arsitektur internal CSG yang diasumsikan. Verifikasi item berikut terhadap codebase, infrastructure repository, controller configuration, dan runtime evidence.

Java/JAX-RS codebase

  • Inventarisasi methods dan @Consumes/@Produces setiap API kritis.
  • Verifikasi HEAD/OPTIONS automatic behavior pada runtime yang digunakan.
  • Temukan custom filters/interceptors yang mengubah method atau headers.
  • Periksa ExceptionMapper dan error media type/status contracts.
  • Periksa ETag, Last-Modified, precondition, dan optimistic-lock handling.
  • Periksa Range/download implementation dan object-storage integration.
  • Periksa repeated header handling dan Set-Cookie generation.
  • Periksa CORS filter dan allowed origins per environment.
  • Periksa streaming/trailer/100-continue dependency bila ada.

NGINX and ingress

  • Catat exact NGINX/controller version dan build modules.
  • Temukan proxy_http_version, method restrictions, proxy_method, header rewrites, CORS annotations, cache, gzip/Brotli, dan error interception.
  • Periksa underscores_in_headers, ignore_invalid_headers, dan header-buffer settings.
  • Periksa all add_header inheritance/always behavior.
  • Periksa conditional/range header behavior pada cached routes.
  • Periksa proxy_pass_trailers atau special protocol features.
  • Inspect generated config with nginx -T or controller equivalent.

Cloud/on-prem network

  • Dokumentasikan protocol version pada setiap hop.
  • Verifikasi ALPN and TLS termination points.
  • Periksa WAF/LB invalid-header and method policies.
  • Periksa CDN/cache key and forwarded-header policies.
  • Periksa compression ownership pada CDN, LB, NGINX, dan Java.
  • Verifikasi UDP/QUIC path dan fallback bila HTTP/3 digunakan.
  • Verifikasi header/body limits across all hops.

Tests and observability

  • Bandingkan direct-app versus full-edge request/response headers.
  • Jalankan method/status/negotiation/conditional/range/CORS matrix.
  • Pastikan NGINX log memuat client protocol dan upstream status.
  • Temukan dashboard untuk 4xx semantic statuses, cache, compression, dan protocol distribution.
  • Cari incident notes terkait CORS, corrupted download, stale cache, 415, 502 invalid header, atau request smuggling.
  • Tambahkan regression tests sebelum mengubah protocol or header policies.

Exercises

Exercise 1 — classify methods

Untuk endpoint berikut, tentukan apakah design-nya sesuai:

GET  /quotes/Q-1/submit
POST /quotes/Q-1/search
PUT  /quotes/Q-1
DELETE /quotes/Q-1

Bahas safe/idempotent semantics dan retry implications.

Exercise 2 — find the hop-by-hop bug

proxy_set_header Connection $http_connection;

Jelaskan mengapa blind forwarding salah dan kapan NGINX harus menyintesis Connection sendiri.

Exercise 3 — conditional update

Design flow:

GET quote → ETag v17
PUT quote with If-Match v17
concurrent update changes version to v18

Tentukan expected status dan atomic application behavior.

Exercise 4 — protocol map

Gambar exact protocol chain untuk satu environment:

browser → CDN/LB → ingress → Java

Isi:

  • transport;
  • TLS termination;
  • ALPN;
  • HTTP version;
  • connection reuse;
  • source of errors.

Exercise 5 — CORS ownership

Cari CORS config di Java, NGINX, ingress annotations, API gateway, dan cloud edge. Tentukan satu owner dan rencana menghapus duplication.

Exercise 6 — range integrity

Download full file and two ranges, recombine them, then compare hashes. Repeat with and without Accept-Encoding: identity to reason about representation bytes.


Ringkasan

Mental model final:

HTTP semantics must survive intermediary translation,
not necessarily preserve the original wire bytes.

Senior engineer harus mampu memisahkan:

  1. operation semantics;
  2. representation metadata;
  3. message framing;
  4. connection-specific controls;
  5. protocol version per hop;
  6. cache/negotiation state;
  7. edge-generated versus application-generated responses.

Checklist inti:

  • methods tetap berarti sama;
  • status dan required headers tetap coherent;
  • hop-by-hop fields diproses per hop;
  • framing tidak ambiguous;
  • Content-Type/Encoding/Vary menggambarkan body yang nyata;
  • validators dan Range tetap correct;
  • CORS memiliki satu owner;
  • H2/H3 tidak diasumsikan end-to-end;
  • invalid messages fail early;
  • wire tests membuktikan behavior, bukan hanya konfigurasi.

NGINX yang “berhasil meneruskan traffic” belum tentu mempertahankan API contract. Correctness baru terbukti ketika semantics, security, compatibility, dan observability konsisten pada seluruh hop.


Referensi resmi

Lesson Recap

You just completed lesson 06 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.