skip to content

Browser Clients and Trailers

A browser cannot speak native gRPC, so gRPC-Web moves the trailing status into the response body, in a shape a proxy can translate. Interviewers ask because it costs streaming call shapes.

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

questions

5

Why can a browser page not call a gRPC service directly, and what does gRPC-Web change so it can?

level: juniorimportance: must knowfreq 70%

answer

  1. the browser is missing something
  2. outcome arrives after the body
  3. trailing fields never reach script
  4. status re-encoded into the body
  5. two call shapes do not survive

basics

~20 s

gRPC-Web exists because browser code cannot control HTTP/2 framing or read trailing metadata. It carries a call as an ordinary HTTP request whose response body ends with the trailing status, and a translating layer converts to native gRPC.

solid answer

~50 s

Native gRPC delivers a call's outcome in trailing metadata, sent after the response body, and it assumes the caller drives HTTP/2 streams directly. Browser networking APIs give page script neither: they expose response headers but not trailing fields. gRPC-Web is a second wire protocol for the same calls, built only from what an ordinary HTTP client already has. The request is a plain `POST` with the encoded message in the body; the response carries the same status fields as a final frame *inside* the body, so the page can read them; the media type is `application/grpc-web`, which a receiver treats as `+proto` when no message-format suffix is given. The expected deployment puts a translating intermediary between the browser and a native gRPC service. The price is that only unary and server-streaming survive - client-streaming and bidirectional streaming are not available to a browser client.

code

http · 13 lines
http
POST /vet.dispensary.v1.StockService/GetItem HTTP/1.1
host: dispensary.example
content-type: application/grpc-web+proto
accept: application/grpc-web+proto
x-user-agent: grpc-web-javascript/0.1

[ 00 ][ 00 00 00 09 ] <9 bytes: encoded request message>

HTTP/1.1 200 OK
content-type: application/grpc-web+proto

[ 00 ][ 00 00 00 2A ] <42 bytes: encoded response message>
[ 80 ][ 00 00 00 0F ] grpc-status:0<CRLF>

go deeper

for a junior

Remember the one-line reason: the browser cannot read the trailing metadata where a native gRPC call puts its outcome, so the variant moves that outcome into the response body. Be able to name the media type application/grpc-web.

for a middle

Explain the mechanism rather than the headline: which part of the call moves, into what, and that the response becomes an ordinary HTTP message so the variant no longer depends on HTTP/2 framing. Name the two call shapes that are lost.

for a senior

Show you know where the translation lives in a real deployment and what follows from it - an extra hop in the path for browser traffic, a response whose end is the end of a body, and an HTTP status line that says nothing about whether the call succeeded.

for a principal

The angle worth arguing is whether browser reach should be bought with a second wire protocol at all, versus exposing a resource-oriented API for the browser and keeping the typed protocol strictly internal - a question about how many contracts the organisation maintains.

## The problem gRPC-Web solves gRPC is defined over HTTP/2, and a native call depends on two things page script in a browser does not have. The first is the framing layer. The native protocol assumes the caller can drive HTTP/2 streams directly, and browser networking APIs deliberately hide that layer - script hands over a request and receives a response, and the connection underneath is the browser's business. The second is decisive: **trailing metadata**. Native gRPC announces a call's outcome *after* the response body, in a trailer section - a block of header fields sent at the end of the message rather than at the front. The browser's networking APIs surface response headers to script; they do not surface trailing fields. A call whose success or failure is announced only in trailers is, from the page's point of view, a call with no outcome at all. gRPC-Web is a **separate wire protocol for the same calls**, defined so that everything it needs is something an ordinary HTTP client already has: a request with a body, a response with a body, and headers at the front. ## What the variant changes - The call is an ordinary HTTP request: `POST`, a path built from the service and method, and the encoded request message in the body. - **The trailing status moves into the response body** as a final frame, so the outcome arrives as payload bytes the page can read rather than as trailing header fields it cannot. - **The response ends when the body ends.** No protocol frame closes it. - Nothing in the variant depends on HTTP/2 framing, so it also runs over HTTP/1.1 - and it therefore gives up the signals HTTP/2 framing would have carried: stream identifiers, a connection-closing frame, transport pings. - The client identifies itself in a field of the variant's own, `X-User-Agent` (for example `X-User-Agent: grpc-web-javascript/0.1`), because `user-agent` belongs to the browser and script cannot set it. - Two of the four call shapes do not survive. A browser client gets **unary** and **server-streaming**; **client-streaming** and **bidirectional streaming** are not available. ## The media types | Value | What it means | |---|---| | `application/grpc-web` | The variant with no message-format suffix; a receiver treats it as `+proto` | | `application/grpc-web+proto` | The variant, messages encoded with Protocol Buffers - the ordinary case | | `application/grpc-web-text` | The same protocol with the whole body base64-encoded, for environments that cannot carry binary bodies | A client asks for the text form with `Accept: application/grpc-web-text`. Note what is *not* here: the variant defines no cross-origin field names of its own. A browser's usual cross-origin rules apply to a gRPC-Web request exactly as they apply to any other request, and nothing in the protocol changes or replaces them. ## Where the translation happens The expected deployment is a **translating intermediary** sitting between the browser and a service that speaks native gRPC. In one direction it takes the gRPC-Web request and re-frames it as a native call; in the other it takes the native response, lifts the trailing status out of the trailer section and writes it back into the response body as the final frame. A server framework may instead implement the variant itself and serve both protocols on the same port - either way, *something* performs that translation, because a service that only speaks the native protocol will not accept a gRPC-Web request unchanged. One consequence is worth internalising early: the HTTP status of a gRPC-Web response is not the call's outcome. A response can carry an ordinary success status line and still describe a failed call, because the real status is in the trailer frame at the end of the body. ## What an interviewer is checking 1. Do you know that the browser limitation is about **trailing metadata and framing**, not about TLS, ports or performance? 2. Do you know that gRPC-Web is a **different wire protocol**, not gRPC with a flag flipped - so something in the path has to translate? 3. Do you know **what it costs**: two call shapes, and a stream whose end is the end of a body rather than a protocol event? A candidate who answers "the browser just can't do HTTP/2" is close but has the wrong mechanism - browsers speak HTTP/2 constantly. What they do not do is hand script the controls, or the trailers.

  • Which request field does a gRPC-Web client use to identify itself, and why not `user-agent`?
    The variant defines `X-User-Agent`, carrying a value such as `grpc-web-javascript/0.1`. The browser owns `user-agent` and page script cannot set it, so the variant reserves a field of its own for the client library's identity. A server or an intermediary that wants to know which client it is talking to reads that field instead.
  • A gRPC-Web response arrives with `content-type: application/grpc-web` and no suffix. What message format should the receiver assume?
    Protocol Buffers. The bare media type implies the `+proto` form, so a receiver decodes the frames in the body exactly as it would for `application/grpc-web+proto`. The suffix exists to let another message format be named explicitly; its absence is not ambiguity, it is the default.
  • Does a gRPC-Web deployment always need a separate translating process?
    No. A server framework may implement the variant directly and answer both protocols, in which case there is no separate hop. What is unavoidable is the translation itself: a service that speaks only the native protocol will not read a gRPC-Web request, because the framing of the trailing status differs between the two.

saying these in an interview costs you the question

  • Says gRPC-Web is native gRPC with a configuration flag switched on
  • Claims a browser cannot use HTTP/2 at all, so gRPC is impossible
  • Thinks page script can read trailing header fields if configured correctly
  • Believes all four gRPC call shapes work from a browser client
  • Reads the HTTP status line of the response as the call's outcome
  • Assumes a native-only gRPC server accepts a gRPC-Web request unchanged
open as a page

Which gRPC call shapes can a browser client use over gRPC-Web, and which two does it lose?

level: middleimportance: must knowfreq 60%

basics

~20 s

A browser client over gRPC-Web gets unary and server-streaming calls. Client-streaming and bidirectional streaming are not available, because the variant is carried by an ordinary HTTP request whose body is complete before the response begins.

open as a page

In a gRPC-Web response body, how does a client tell the trailer frame from a message frame?

level: middleimportance: should knowfreq 52%

basics

~20 s

Every frame in a gRPC-Web response body opens with a flag byte and a four-byte length. gRPC-Web sets the most significant bit of that flag byte to mark the trailer frame: 0x80 uncompressed, 0x81 compressed. It must be the last frame.

open as a page

A browser console's gRPC-Web server-streaming feed stops silently. Why is a cut response hard to detect, and what must the client check?

level: seniorimportance: should knowfreq 46%

basics

~20 s

gRPC-Web uses no HTTP/2 framing, so no stream-reset frame, connection-closing frame or transport ping tells the client anything. The response ends when its body ends, so a cut response looks complete - except the final trailer frame never arrived.

open as a page

What does gRPC-Web's text variant change about a response body, and what trips decoders up?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

The text variant, application/grpc-web-text, base64-encodes the whole body - frames, payloads and trailer block alike. A client asks for it with Accept: application/grpc-web-text. Its trap is that base64 padding does not line up with frame boundaries.

open as a page