http-2: Failing client requests

I’m using the latest published gem (0.9.0).

I’m working on an asynchronous implementation which has feature parity with HTTP1 (connection per request and sequential connections using keep-alive). I’m working on the HTTP2 implementation which for the most part appears to work except the functionality depends on when I start the read loop.

Here are two dumps from my client:

Sent frame: {:type=>:settings, :stream=>0, :payload=>[[:settings_max_concurrent_streams, 100]]}
Sent frame: {:type=>:headers, :flags=>[:end_headers, :end_stream], :payload=>{":method"=>"GET", ":path"=>"/", ":scheme"=>"https", ":authority"=>"www.codeotaku.com", "accept"=>"*/*", "user-agent"=>"nghttp2/1.30.0"}, :stream=>1}

-- start read loop here

Received frame: {:length=>18, :type=>:settings, :flags=>[], :stream=>0, :payload=>[[:settings_max_concurrent_streams, 128], [:settings_initial_window_size, 65536], [:settings_max_frame_size, 16777215]]}
Sent frame: {:type=>:settings, :stream=>0, :payload=>[], :flags=>[:ack]}
Received frame: {:length=>4, :type=>:window_update, :flags=>[], :stream=>0, :increment=>2147418112}
Received frame: {:length=>0, :type=>:settings, :flags=>[:ack], :stream=>0, :payload=>[]}

Received frame: {:length=>160, :type=>:headers, :flags=>[:end_headers], :stream=>1, :payload=>"H\x03301\\\x010\x00\x84BF\x9BQ\x90d\x01SA\xFB\x96E5\x96\xCAGQjM\x1E\xBF\x00\x86\xA0\xE4\x1ALz\xBF\x85`\xD5H_?\x00\x89 \xC99V!\xEAM\x87\xA3\x8A\xA4~V\x1C\xC5\x81\xE7\x1A\x00?\x00\x83\x90i/\x96\xDFi~\x94\x10\x14\xD0;\x14\x10\x02\xF2\x80f\xE3-\xDCi\xE51h\xDF\x00\x89\xF2\xB5g\xF0[\v\"\xD1\xFA\x91\xD7=\xA81\xEASX\xD0\x82\xD51lQ\xB5\xC2\xB8\x7F\x00\x85Al\xEE[?\x9C\xAAcU\xE5\x80\xAE\x10\xAE\xFA\x9F\xEDMs\xDA\x83\x1E\xA55\x8D\b-S\x16\xC5\e\\+\x87"}
Received frame: {:length=>0, :type=>:data, :flags=>[:end_stream], :stream=>1, :payload=>""}

It appears that depending on when the read loop is started (i.e. before new_stream or after) changes the behaviour of the client connection, and in this case it fails:

-- start read loop here

Received frame: {:length=>18, :type=>:settings, :flags=>[], :stream=>0, :payload=>[[:settings_max_concurrent_streams, 128], [:settings_initial_window_size, 65536], [:settings_max_frame_size, 16777215]]}
Sent frame: {:type=>:settings, :stream=>0, :payload=>[], :flags=>[:ack]}
Received frame: {:length=>4, :type=>:window_update, :flags=>[], :stream=>0, :increment=>2147418112}
Sent frame: {:type=>:headers, :flags=>[:end_headers, :end_stream], :payload=>{":scheme"=>"https", ":method"=>"GET", ":path"=>"/", ":authority"=>"www.codeotaku.com", "accept"=>"*/*", "user-agent"=>"spider"}, :stream=>1}
Received frame: {:length=>8, :type=>:goaway, :flags=>[], :stream=>0, :last_stream=>0, :error=>:protocol_error}

I’m not absolutely certain what the issue is yet but I thought I’d report my initial findings. I’ll follow up with more details as they become available.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 1
  • Comments: 30 (2 by maintainers)

Most upvoted comments

I had some issues with this before, it might mean that the headers frame was crafted before the handshake was performed and might be in an invalid state, but hard to say without a small repro code.

On another note, I’ve created this gem, which aims at supporting HTTP/2 and HTTP/1 and multiple concurrent requests, if you want to have a look. Maybe it solves your problem already.

No worries 😃 .

It’s kind of by design. Most of the examples here craft the frames from the client fully before doing network stuff (nghttp works the other way round, I suppose). This gem also doesn’t control what you do regarding network, so all bets are off there. A higher level abstraction is also better, which is why I created httpx.

If you followed the README example, there’s your issue (@igrigorik , maybe consider changing it?). You should instead follow the example get, or as a quick fix, apply the thing I suggested in my last comment.

meaning:

connection = HTTP2::Client.new
# set up all your callbacks here
connection.send_connection_preface

Again, I’d have to see that small repro script. I see two different dumps from your example, One sending two frames before the read loop, and and the second one without those. Did you omit those two frames from the second dump, or are they really not being generated?

The second example might mean that you’re reading frames from the socket and sending them to the connection before initializing your own. This gem expects you to initialize the request before exchanging frames. So, if you are not building frames from your request before exchanging, you should consider initializing it yourself