http2 탐구

created : Wed, 03 Aug 2022 15:42:58 +0900
modified : Wed, 03 Aug 2022 15:44:45 +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
}