HandleFrame processes an HTTP/2 frame isOutgoing=false => incoming (client->server) => REQUEST side isOutgoing=true => outgoing (server->client) => RESPONSE side
(frame http2.Frame, isOutgoing bool, frameTime time.Time)
| 174 | // isOutgoing=false => incoming (client->server) => REQUEST side |
| 175 | // isOutgoing=true => outgoing (server->client) => RESPONSE side |
| 176 | func (sm *DefaultStreamManager) HandleFrame(frame http2.Frame, isOutgoing bool, frameTime time.Time) error { |
| 177 | sm.mutex.Lock() |
| 178 | defer sm.mutex.Unlock() |
| 179 | |
| 180 | streamID := frame.Header().StreamID |
| 181 | if streamID == 0 { |
| 182 | // Handle SETTINGS frames to keep HPACK decoders in sync with |
| 183 | // the negotiated dynamic table size. Without this, the decoders |
| 184 | // stay at the initial MaxDynamicTableSize (8192) and fail to |
| 185 | // decode headers when the peer negotiates a larger table via |
| 186 | // SETTINGS_HEADER_TABLE_SIZE. |
| 187 | if sf, ok := frame.(*http2.SettingsFrame); ok && !sf.IsAck() { |
| 188 | sm.updateDecoderTableSize(sf, isOutgoing) |
| 189 | } |
| 190 | return nil |
| 191 | } |
| 192 | |
| 193 | // Init stream |
| 194 | if _, exists := sm.streams[streamID]; !exists { |
| 195 | prefix := "Incoming_" |
| 196 | if isOutgoing { |
| 197 | prefix = "Outgoing_" |
| 198 | } |
| 199 | requestID := fmt.Sprintf(prefix+"%d", streamID) |
| 200 | sm.streams[streamID] = &HTTP2StreamState{ |
| 201 | ID: streamID, |
| 202 | RequestID: requestID, |
| 203 | } |
| 204 | } |
| 205 | s := sm.streams[streamID] |
| 206 | if !isOutgoing && s.startTime.IsZero() { |
| 207 | s.startTime = frameTime |
| 208 | } |
| 209 | |
| 210 | switch f := frame.(type) { |
| 211 | |
| 212 | case *http2.HeadersFrame: |
| 213 | // Copy the header block fragment — http2.Framer reuses its internal |
| 214 | // buffer, so f.HeaderBlockFragment() becomes invalid after the next |
| 215 | // ReadFrame() call. |
| 216 | fragCopy := make([]byte, len(f.HeaderBlockFragment())) |
| 217 | copy(fragCopy, f.HeaderBlockFragment()) |
| 218 | |
| 219 | if isOutgoing { |
| 220 | s.respHeaderFrags = append(s.respHeaderFrags, fragCopy) |
| 221 | if f.HeadersEnded() { |
| 222 | if err := sm.processHeaderBlock(s /*isOutgoing=*/, true); err != nil { |
| 223 | return err |
| 224 | } |
| 225 | } |
| 226 | if f.StreamEnded() { |
| 227 | s.respEndStreamReceived = true |
| 228 | // gRPC error responses send a single HEADERS frame with END_STREAM |
| 229 | // that merges initial headers and trailers (grpc-status, grpc-message). |
| 230 | // processHeaderBlock treated it as initial headers, so respTrailersReceived |
| 231 | // is still false. Promote the initial headers to trailers so that |
| 232 | // checkStreamCompletion can mark the stream complete. |
| 233 | if s.respHeadersReceived && !s.respTrailersReceived { |