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?
answer
- nothing at the transport says why
- no framing signals on this path
- end of body is the close
- a cut looks like a completion
- missing trailer frame is the only tell
basics
~20 sgRPC-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.
solid answer
~50 sThe variant is carried by an ordinary HTTP response, and deliberately depends on nothing from HTTP/2 framing: there are no stream identifiers, no stream-reset frame, no connection-closing frame and no transport ping available to a browser client. **End of body is the close.** So a response cut by an intermediary's idle timeout on a long-lived response, by a dropped connection, or by a backend that died mid-stream arrives at the page looking exactly like one that finished - the status line said success long ago, the messages already delivered are valid, and the body simply stops. The single distinguishing fact is that the **trailer frame never arrived**. A conforming client must therefore treat a body that ends without that frame as a failed call, not a completed one, and the application - not the transport - owns noticing the stall and reopening the call.
code
http · 6 linesHTTP/1.1 200 OK
content-type: application/grpc-web+proto
[ 00 ][ 00 00 00 1C ] <28 bytes: stock update>
[ 00 ][ 00 00 00 1C ] <28 bytes: stock update>
<body ends - no frame flagged 0x80 ever arrives>go deeper
The point to hold on to: a gRPC-Web response finishes when its body finishes, and a call that ended properly has a final trailer frame carrying the status. No frame, no outcome.
Explain why the client sees no error - the variant uses no HTTP/2 framing, so no reset, connection-close or ping signal is available - and state the check a conforming client runs when the body ends.
Diagnose it end to end: name a mechanism that cut the response, show that a truncated body and a complete one differ only by the trailer frame, and describe stall detection, backoff on reopen and a resume position as application responsibilities.
The trade-off to articulate is what browser reach costs operationally - a long-lived path whose close carries no reason - and whether a feed that matters this much should be served over a transport with its own liveness and resumption instead.
## Why there is nothing to catch On a native gRPC call the transport is full of things that announce trouble. A stream can be reset with an error code. A connection can be closed with a frame that names how far the peer got. Pings can go unanswered. gRPC-Web has none of that available to a browser client - by design, because the variant is defined so that it does not depend on HTTP/2 framing and can therefore run over HTTP/1.1 as well. What is left is an HTTP response with a body. The response is over when the body is over. That is the entire close mechanism. ## What a cut looks like from the page Consider the dispensary console holding a server-streaming call open for live stock levels. Three things can go wrong in the path: - **An intermediary's idle timeout on a long-lived response** fires because the service emitted nothing for a few minutes, and the intermediary closes the response. - **The connection drops** - a network change, a device sleeping, a restart somewhere in the path. - **The serving process dies** mid-stream, or is drained during a deploy. In all three the page sees the same thing: a response that had a success status line, a run of perfectly valid message frames, and then nothing more. Every layer the page can inspect says the exchange completed normally. There is no error event carrying a reason, because no protocol element carrying a reason ever existed on this path. ## The one fact that distinguishes them A response that genuinely finished ends with the **trailer frame** - the frame whose first byte has its most significant bit set, carrying the trailing status. A response that was cut does not have it. That is the whole detection mechanism, and it only works if the client is written to use it: 1. Read frames from the body until the body ends. 2. If the last frame read was the trailer frame, complete the call with the status it carried. 3. If the body ended and no trailer frame was seen, **fail the call**. The messages already delivered were real, but the call has no outcome. The failure mode to watch for in a client library or a hand-rolled reader is step 3 collapsed into step 2 - treating end of body as success. That turns every cut stream into a silently truncated success, which is exactly the bug the console exhibits: the stock list stops updating and nothing anywhere reports an error. ## What the application has to own Because the transport offers no liveness signal on this path, staying live is application work: - **Detecting a stall.** Only the application knows what "too quiet" means for this feed. A client that expects an update at least every so often can time that gap itself; a feed with no natural rhythm needs the service to emit something periodically so silence becomes meaningful. - **Reopening.** There is no automatic reconnect in this protocol. Reopening the call, with backoff so a service restart does not draw every console at once, is the application's job. - **Resuming without gaps.** A reopened call is a new call with no memory of the old one. If the console must not miss an update, the request needs to carry a position - a cursor, a version, a last-seen identifier - so the service can resume rather than restart. Otherwise the fix for the stall quietly introduces a hole in the data. ## How to answer this in an interview Name the mechanism, not the symptom. "It times out" is not an answer. The answer is: *the variant has no HTTP/2 framing signals, the body's end is the close, so a cut response is indistinguishable from a completed one except that the trailing-status frame is missing; a conforming client fails the call when that frame is absent, and the application owns stall detection, backoff and a resume position.* That is also the sharpest illustration of what this variant trades away. It buys browser reach, and it pays for it with two call shapes and with a close that carries no reason.
- The console reopens the call after a stall. What must the request carry so no updates are missed?A position the service can resume from - a cursor, a version number or the identifier of the last update the console applied. A reopened gRPC-Web call is an entirely new call with no relationship to the old one, so without a position in the request the service restarts from wherever it likes and the gap becomes permanent.
- How would you make silence on the feed meaningful to the client?Have the service emit something on a known cadence when there is nothing to report, so an absence longer than that cadence is evidence rather than ambiguity. The client then times the gap and reopens. Without a rhythm, a quiet feed and a dead feed look identical, and no timeout value is defensible.
- Why does a client that treats end of body as success hide this failure completely?Because every other observable says the exchange went well: a success status line, valid messages, a body that ended cleanly. Failing the call on a missing trailer frame is the only place the truth is available, so collapsing that check turns every cut stream into a silent truncation with no error anywhere.
saying these in an interview costs you the question
- Says the client will get an error event explaining the cut
- Expects a stream-reset or connection-closing frame to reach the page
- Treats end of body as a successfully completed call
- Answers "it times out" without naming what fired
- Assumes the protocol reconnects the call automatically
- Reopens the call without carrying a resume position