Search code examples
pythonhttp2

Any HTTP/2 Server support interleaving between HEADERS & CONTINUATION frame?


In RFC 9113, Section 6.10:

"Any number of CONTINUATION frames can be sent, as long as the preceding frame is on the same stream and is a HEADERS, PUSH_PROMISE, or CONTINUATION frame without the END_HEADERS flag set."

So, it means we can insert a frame from another stream between the HEADERS & CONTINUATION frame of one stream. But after testing with my code, I found that www.google.com does not support this spec.

With interleaving, google will reject requests. Without interleaving, Google will respond with two 404 pages.

Is there any other HTTP/2 server that supports HTTP/2 interleaving between HEADERS & CONTINUATION?

Thanks.

import hpack
import sys
import pdb
import h2.connection

def get_header_frame(sid=1, path="/", es=True):
    initial_headers = [
        (':method', 'GET'),
        (':path', path),
        (':scheme', 'https'),
        (':authority', 'www.google.com')
    ]
    encoder = hpack.Encoder()
    initial_headers_encoded = encoder.encode(initial_headers)
    res = b'\x00' + len(initial_headers_encoded).to_bytes(2, 'big') + b'\x01'
    res += b'\x05' if es else b'\x00'
    res += sid.to_bytes(length=4, byteorder="big")+initial_headers_encoded
    return res

def get_cont_frame(sid=1, c=1, eh=True):
    custom_headers = [
        ('x-%s' % (c), 'A'*1)
    ]
    encoder = hpack.Encoder()
    initial_headers_encoded = encoder.encode(custom_headers)
    res = b'\x00' + len(initial_headers_encoded).to_bytes(2, 'big') + b'\x09'
    res += b'\x04' if eh else b'\x00'
    res += sid.to_bytes(length=4, byteorder="big")+initial_headers_encoded
    return res

def get_data_frame(sid=1, es=True):
    data = b''
    res = b'\x00' + len(data).to_bytes(2, 'big') + b'\x00'
    res += b'\x01' if res else b'\x00'
    res += sid.to_bytes(length=4, byteorder="big")+data
    return res


def create_http2_request(interleave=False):
    conn = h2.connection.H2Connection()
    conn.initiate_connection()
    sys.stdout.buffer.write(conn.data_to_send())
    settings_frame = conn.data_to_send()
    sys.stdout.buffer.write(settings_frame)

    f1_1 = get_header_frame(1, "/1", False)
    f1_2 = get_cont_frame(1)
    f1_3 = get_data_frame(1)
    f3_1 = get_header_frame(3, "/3", False)
    f3_2 = get_cont_frame(3)
    f3_3 = get_data_frame(3)

    if interleave:
        frames = [f1_1, f3_1, f3_2, f1_2, f1_3, f3_3]
    else:
        frames = [f1_1, f1_2, f3_1, f3_2, f3_3, f1_3] 
    for x in frames:
        sys.stdout.buffer.write(x)
    sys.stdout.flush()
    

def main():
    '''
    Usage: 
      interleave: python3 good_h2_client.py 1 | openssl s_client -connect www.google.com:443 -alpn h2 -ign_eof
      non-interl: python3 good_h2_client.py 0 | openssl s_client -connect www.google.com:443 -alpn h2 -ign_eof
    '''
    if len(sys.argv) > 1 and int(sys.argv[1]) == 1:
        create_http2_request(True)
    else:
        create_http2_request(False)


if __name__ == '__main__':
    main()

Solution

  • So, it means we can insert a frame from another stream between the HEADERS & CONTINUATION frame of one stream.

    No you cannot. You misread the specification.

    It says:

    Any number of CONTINUATION frames can be sent, as long as the preceding frame is on the same stream

    Emphasis is mine.