http2 탐구

created : Wed, 03 Aug 2022 15:42:58 +0900
modified : Thu, 17 Apr 2025 20:37:07 +0900
http http2


RFC 7540 과 go 코드 뜯어보기

HTTP 버전별 분기

3.1.  HTTP/2 Version Identification

   The protocol defined in this document has two identifiers.

   o  The string "h2" identifies the protocol where HTTP/2 uses
      Transport Layer Security (TLS) [TLS12].  This identifier is used
      in the TLS application-layer protocol negotiation (ALPN) extension
      [TLS-ALPN] field and in any place where HTTP/2 over TLS is
      identified.

      The "h2" string is serialized into an ALPN protocol identifier as
      the two-octet sequence: 0x68, 0x32.
// golang.org/x/net/http2/http2.go#L53
const (
  /* skip */
  NextProtoTLS = "h2"
  /* skip */
)
// net/http/server.go#L2630
type Server struct {
  // skip
  TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
  // skip
}
// golang.org/x/net/http2/server.go#L304
  s.TLSNextProto[NextProtoTLS] = protoHandler

HTTP2 Multiplexed stream

5.1.  Stream States

   The lifecycle of a stream is shown in Figure 2.

                                +--------+
                        send PP |        | recv PP
                       ,--------|  idle  |--------.
                      /         |        |         \
                     v          +--------+          v
              +----------+          |           +----------+
              |          |          | send H /  |          |
       ,------| reserved |          | recv H    | reserved |------.
       |      | (local)  |          |           | (remote) |      |
       |      +----------+          v           +----------+      |
       |          |             +--------+             |          |
       |          |     recv ES |        | send ES     |          |
       |   send H |     ,-------|  open  |-------.     | recv H   |
       |          |    /        |        |        \    |          |
       |          v   v         +--------+         v   v          |
       |      +----------+          |           +----------+      |
       |      |   half   |          |           |   half   |      |
       |      |  closed  |          | send R /  |  closed  |      |
       |      | (remote) |          | recv R    | (local)  |      |
       |      +----------+          |           +----------+      |
       |           |                |                 |           |
       |           | send ES /      |       recv ES / |           |
       |           | send R /       v        send R / |           |
       |           | recv R     +--------+   recv R   |           |
       | send R /  `----------->|        |<-----------'  send R / |
       | recv R                 | closed |               recv R   |
       `----------------------->|        |<----------------------'
                                +--------+

          send:   endpoint sends this frame
          recv:   endpoint receives this frame

          H:  HEADERS frame (with implied CONTINUATIONs)
          PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
          ES: END_STREAM flag
          R:  RST_STREAM frame
// golang.org/x/net/http2/http2.go#L100
var stateName = [...]string{
  stateIdle:             "Idle",
	stateOpen:             "Open",
	stateHalfClosedLocal:  "HalfClosedLocal",
	stateHalfClosedRemote: "HalfClosedRemote",
	stateClosed:           "Closed",
}
// golang.org/x/net/http2/server.go#L1506
func (sc *serverConn) closeStream(st *stream, err error) {
	sc.serveG.check()
	if st.state == stateIdle || st.state == stateClosed {
4.1.  Frame Format

   All frames begin with a fixed 9-octet header followed by a variable-
   length payload.

    +-----------------------------------------------+
    |                 Length (24)                   |
    +---------------+---------------+---------------+
    |   Type (8)    |   Flags (8)   |
    +-+-------------+---------------+-------------------------------+
    |R|                 Stream Identifier (31)                      |
    +=+=============================================================+
    |                   Frame Payload (0...)                      ...
    +---------------------------------------------------------------+

                          Figure 1: Frame Layout
5.1.1.  Stream Identifiers

   Streams are identified with an unsigned 31-bit integer.  Streams
   initiated by a client MUST use odd-numbered stream identifiers; those
   initiated by the server MUST use even-numbered stream identifiers.  A
   stream identifier of zero (0x0) is used for connection control
   messages; the stream identifier of zero cannot be used to establish a
   new stream.
// golang.org/x/net/http2/server.go#L492
type serverConn struct {
  // skip
  streams map[uint32]*stream
  // skip
// golang.org/x/net/http2/server.go#L1455
func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error {
	sc.serveG.check()
	switch {
	case f.StreamID != 0: // stream-level flow control
		state, st := sc.state(f.StreamID)

Stream Prioritization

Stream Prioritization 은 둘러보다가 testcode랑 구현 내용을 발견하긴 했는데, 어떻게 구현했을지가 크게 궁금하지도 않고, 코드를 뜯어보는 비용에 비해서 크게 남는게 없을거 같아서 생략한다.


Server Push

8.2.  Server Push

   HTTP/2 allows a server to pre-emptively send (or "push") responses
   (along with corresponding "promised" requests) to a client in
   association with a previous client-initiated request.  This can be
   useful when the server knows the client will need to have those
   responses available in order to fully process the response to the
   original request.
// golang.org/x/net/http2/server.go#L807
func (sc *serverConn) serve() {
  // skip
  for {
    // skip
    select {
    // skip until #L879
    case msg := <-sc.serveMsgCh:
			switch v := msg.(type) {
			// skip until #L899
			  case *startPushRequest:
				sc.startPush(v)
   // skip

Header Compression

4.3.  Header Compression and Decompression

   Just as in HTTP/1, a header field in HTTP/2 is a name with one or
   more associated values.  Header fields are used within HTTP request
   and response messages as well as in server push operations (see
   Section 8.2).

   Header lists are collections of zero or more header fields.  When
   transmitted over a connection, a header list is serialized into a
   header block using HTTP header compression [COMPRESSION].  The
   serialized header block is then divided into one or more octet
   sequences, called header block fragments, and transmitted within the
   payload of HEADERS (Section 6.2), PUSH_PROMISE (Section 6.6), or
   CONTINUATION (Section 6.10) frames.
// golang.org/x/net/http2/server.go#L415
sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)

r := NewFramer(sc.bw, c)
fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
// golang.org/x/net/http2/frame.go#L487
func (fr *Framer) ReadFrame() (Frame, error) {
  // skip until L516
  if fh.Type == FrameHeaders && fr.ReadMetaHeaders != nil {
		return fr.readMetaFrame(f.(*HeadersFrame))
	}
  return f, nil
}

Graceful shutdown

6.8.  GOAWAY

   The GOAWAY frame (type=0x7) is used to initiate shutdown of a
   connection or to signal serious error conditions.  GOAWAY allows an
   endpoint to gracefully stop accepting new streams while still
   finishing processing of previously established streams.  This enables
   administrative actions, like server maintenance.

   There is an inherent race condition between an endpoint starting new
   streams and the remote sending a GOAWAY frame.  To deal with this
   case, the GOAWAY contains the stream identifier of the last peer-
   initiated stream that was or might be processed on the sending
   endpoint in this connection.  For instance, if the server sends a
   GOAWAY frame, the identified stream is the highest-numbered stream
   initiated by the client.

   Once sent, the sender will ignore frames sent on streams initiated by
   the receiver if the stream has an identifier higher than the included
   last stream identifier.  Receivers of a GOAWAY frame MUST NOT open
   additional streams on the connection, although a new connection can
   be established for new streams.

   If the receiver of the GOAWAY has sent data on streams with a higher
   stream identifier than what is indicated in the GOAWAY frame, those
   streams are not or will not be processed.  The receiver of the GOAWAY
   frame can treat the streams as though they had never been created at
   all, thereby allowing those streams to be retried later on a new
   connection.