skip to content

HTTP/2 Transport Layer

The request shape a call puts on HTTP/2, length-prefixed messages inside DATA frames, and a status delivered in trailers. Interviewers ask because a proxy that drops trailers loses the error.

part ofAPI stylesoverview, primer and where to startread it →
on this pageshow

questions

6

What does a gRPC call look like as an HTTP/2 request, and how does the server know which method is wanted?

level: juniorimportance: must knowfreq 62%

answer

  1. always POST, never GET
  2. the method name is in the path
  3. package dot service, slash, method
  4. content type begins application/grpc
  5. te: trailers is an old-proxy tripwire

basics

~10 s

Every gRPC call is an HTTP/2 POST whose :path is /{package}.{Service}/{Method}, with content-type application/grpc and te: trailers. The request body carries length-prefixed messages; nothing about the call lives in a query string.

solid answer

~40 s

A gRPC call is one HTTP/2 stream carrying an ordinary POST. The pseudo-headers are `:method POST`, `:scheme http` or `https`, `:authority` for the host, and `:path`, which is `/` + the fully qualified service name + `/` + the method name — so `/customs.v1.DeclarationService/SubmitDeclaration` names both. Beside them the client sends `content-type: application/grpc` (optionally suffixed `+proto` or `+json` to say how the messages are encoded), `te: trailers` to announce it accepts a trailing status, `user-agent`, and optionally `grpc-encoding` and `grpc-accept-encoding` for per-message compression. There is no query string and no path parameter: every argument is a field of the request message in the body. A server that does not recognise the content type answers `415 (Unsupported Media Type)` and the call never starts.

code

http · 12 lines
http
HEADERS (stream 5, END_HEADERS)
:method = POST
:scheme = https
:path = /customs.v1.DeclarationService/SubmitDeclaration
:authority = filing.example
content-type = application/grpc+proto
te = trailers
grpc-accept-encoding = identity, gzip
user-agent = grpc-<impl>/<version>

DATA (stream 5, END_STREAM)
<length-prefixed request message>

go deeper

for a junior

Be able to sketch the request from memory: POST, a path of /package.Service/Method, content-type application/grpc, and messages in the body. That sketch alone answers most first-screen questions about the wire.

for a middle

Explain why the path is the routing key and what that buys an intermediary: per-method routing, authorisation and rate limiting with no message decoding. Know that te: trailers is a compatibility tripwire, not a negotiation.

for a senior

In production you will read this shape out of a capture. Know which failures are HTTP-shaped (415 for a wrong content type, a header block over the limit) and which are gRPC-shaped, because they are diagnosed with different tools.

for a principal

The design bet here is that the call's identity is plaintext and its arguments are opaque. That makes edge policy cheap per method and impossible per field, which is the trade-off to weigh when you set a boundary standard across teams.

## One POST per call gRPC does not invent a transport. A call is an ordinary **HTTP/2 request**: the client opens one stream, sends a `HEADERS` frame, then `DATA` frames holding the request messages, then closes its direction. Because the request shape is plain HTTP/2, anything that speaks HTTP/2 — a reverse proxy, an L7 load balancer, a packet capture — sees the call, which is exactly why this leaf matters: the parts that are *not* ordinary HTTP are the parts an intermediary can ruin. The method is **always POST**. There is no read-versus-write distinction on the wire, no `GET` for a lookup, and no way for an rpc declaration to choose something else. A cache keyed on HTTP method semantics therefore sees every call as unsafe and non-idempotent, which is the correct reading of it. ## The pseudo-header block Four pseudo-headers open the request: - **`:method`** — `POST`, without exception. - **`:scheme`** — `http` or `https`, following whether the connection is protected. - **`:authority`** — the host the caller believes it is talking to; an in-path hop that rewrites it changes which virtual service answers. - **`:path`** — the routing key for the whole call, described next. ## Naming the method in the path The specification builds the path from two names the schema already defines: 1. **Service-Name** is `{proto package name}` `"."` `{service name}` — so a `DeclarationService` declared in package `customs.v1` is `customs.v1.DeclarationService`. 2. The path is `"/"` + Service-Name + `"/"` + the **method name** exactly as the schema spells it. So a broker filing a declaration sends `:path = /customs.v1.DeclarationService/SubmitDeclaration`. This is the single most useful fact about the request shape in operations: an intermediary can route, rate-limit, authorise or log per method **without decoding a single message**, because the method identity is in a plain-text path. It is also why a route pattern written for a resource-oriented API — `/declarations/{id}` — never matches: gRPC paths are not resource paths, and the declaration's identifier is a field inside the request message, not a path segment. ## The call-definition fields Beside the pseudo-headers sit fields the specification defines for the call itself, distinct from any application metadata the caller attaches: | field | sent by | what it does | |---|---|---| | `content-type` | client | `application/grpc`, optionally `application/grpc+proto` or `application/grpc+json`, naming the message encoding | | `te` | client | the value `trailers`, announcing the client accepts a trailing status | | `grpc-encoding` | client | the per-message compression applied to messages in this request | | `grpc-accept-encoding` | client | the per-message compressions the client can decode in the response | | `user-agent` | client | identifies the calling implementation | Header field names are lowercase and drawn from a restricted charset (`0-9`, `a-z`, `_`, `-`, `.`), and the specification suggests an **8 KiB** default limit on the whole request header block, enforceable with `SETTINGS_MAX_HEADER_LIST_SIZE`. A caller that stuffs a large token or a long correlation value into metadata can exceed it, and the call fails before the server sees a byte of the message. ## Why `te: trailers` is there at all On HTTP/1.1 the `TE` field was how a client announced it would accept trailing fields after a chunked body. HTTP/2 needs no such negotiation — trailing fields are just another `HEADERS` frame. gRPC keeps the field anyway as a **tripwire**: the outcome of every call travels in the trailer section, so an intermediary too old to forward one is a fatal hop, and requiring the field on every request gives such a hop something it can recognise and refuse rather than silently swallow. ## What the server checks before anything else 1. **Content type.** If it does not begin with `application/grpc`, the server answers `415 (Unsupported Media Type)`. This is an HTTP-level rejection: there is no gRPC status because there is no gRPC call yet. 2. **Path.** An unrecognised service or method is a normal gRPC failure, answered with a status in the trailing metadata rather than an HTTP error. 3. **Message framing.** Everything after the header block is read as length-prefixed messages, never as a raw body. The practical consequence for a cross-border filing gateway is that the two failure classes look completely different on the wire: a misconfigured client gets an HTTP-shaped rejection it can read with any tool, while a real application failure gets a 200 response whose outcome is hidden in trailing metadata — the subject of the next question.

  • What does a server do when the request's content-type is not application/grpc?
    It answers `415 (Unsupported Media Type)` at the HTTP layer. No gRPC call was ever created, so there is no `grpc-status` to return — which is why this failure reads as an HTTP error in a capture while application failures read as a 200.
  • What do the +proto and +json suffixes on the content type mean?
    They name how each message in the body is encoded. `application/grpc+proto` is binary protobuf, `application/grpc+json` is JSON; bare `application/grpc` is accepted and treated as the default proto encoding. The suffix describes the messages, not the framing around them, which is identical either way.
  • Why is there no query string or path parameter on a gRPC request?
    Every argument is a field of the request message carried in the body, so the path only has to identify the method. That keeps routing trivially cheap for an intermediary but means nothing about the call's arguments is visible without decoding the message.

saying these in an interview costs you the question

  • Thinks gRPC defines its own transport underneath HTTP/2
  • Expects resource-style path segments carrying record identifiers
  • Says read-only methods are sent as GET with query parameters
  • Believes the method name travels inside the request message
  • Treats content-type: application/json as valid for a gRPC call
open as a page

A gRPC call fails, yet the HTTP :status on its response is 200 — where does the real outcome travel?

level: middleimportance: must knowfreq 74%

basics

~20 s

Once the server accepts a gRPC call, the response carries :status 200 whatever happens; the outcome rides in the trailing metadata as grpc-status, with grpc-message beside it. A call that fails immediately uses a Trailers-Only response.

open as a page

Inside a gRPC call's DATA frames, what precedes each message, and why can't one frame be read as one message?

level: middleimportance: should knowfreq 48%

basics

~10 s

Each message carries a five-byte prefix: one Compressed-Flag byte, then a four-byte big-endian Message-Length. HTTP/2 DATA frame boundaries are unrelated to message boundaries, so a receiver reads by that declared length, never by frame.

open as a page

A gRPC call through a new reverse proxy returns HTTP 200 but its status never arrives — what broke, and how do you confirm it?

level: seniorimportance: should knowfreq 52%

basics

~20 s

An in-path hop terminated the call and did not forward the trailer section, so grpc-status was erased. Confirm by capturing at both sides of the hop: the status is present leaving the server, absent arriving at the client.

open as a page

In gRPC, how do grpc-encoding, grpc-accept-encoding and the Compressed-Flag byte negotiate message compression, and what happens when a receiver cannot decode the encoding?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

grpc-encoding names the algorithm a sender uses, grpc-accept-encoding lists what it can decode, and each message's Compressed-Flag says whether that message is compressed. A server given an unsupported algorithm fails with UNIMPLEMENTED; a client fails with INTERNAL.

open as a page

A gRPC call ends with an HTTP/2 stream reset and no grpc-status — how does the client decide what to report?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

The client maps the reset's HTTP/2 error code onto a gRPC status. REFUSED_STREAM means the server never processed the call and becomes UNAVAILABLE; CANCEL becomes CANCELLED; most others become INTERNAL, since the call ended abnormally.

open as a page