http-2 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +1 -1
- data/example/client.rb +4 -0
- data/example/server.rb +1 -3
- data/example/upgrade_client.rb +154 -0
- data/lib/http/2/client.rb +12 -0
- data/lib/http/2/compressor.rb +7 -7
- data/lib/http/2/connection.rb +6 -1
- data/lib/http/2/flow_buffer.rb +31 -0
- data/lib/http/2/stream.rb +13 -14
- data/lib/http/2/version.rb +1 -1
- data/spec/client_spec.rb +34 -1
- data/spec/compressor_spec.rb +12 -0
- data/spec/connection_spec.rb +22 -0
- data/spec/stream_spec.rb +23 -9
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f37bf7f07706136db469b5c188cf13b2995d0c16
|
4
|
+
data.tar.gz: e51aeb9218b11e3cada90485ba60b5342e89322f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45ca021e60dfe824fcc7bc6bf61d054ce37096a7f0f41ba7c1a5e45f89ad7848f37f4d6638f28d6a93bf6b41f28e8af66abfac28b4fe60d16556d82c1ec78ae3
|
7
|
+
data.tar.gz: c5bddd633904cd5ff9aef237debbdbcc78a6b901dad857cd5aa770e8d13ea28e26c158c902f9b840185b4a8d7dfe7c8e2acd9b380991426f0b5abdbfa4bc2f2f
|
data/.rubocop_todo.yml
CHANGED
data/example/client.rb
CHANGED
data/example/server.rb
CHANGED
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literals: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'http_parser'
|
5
|
+
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = 'Usage: upgrade_client.rb [options]'
|
8
|
+
end.parse!
|
9
|
+
|
10
|
+
uri = URI.parse(ARGV[0] || 'http://localhost:8080/')
|
11
|
+
sock = TCPSocket.new(uri.host, uri.port)
|
12
|
+
|
13
|
+
conn = HTTP2::Client.new
|
14
|
+
|
15
|
+
def request_header_hash
|
16
|
+
Hash.new do |hash, key|
|
17
|
+
k = key.to_s.downcase
|
18
|
+
k.tr! '_', '-'
|
19
|
+
_, value = hash.find { |header_key, _| header_key.downcase == k }
|
20
|
+
hash[key] = value if value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
conn.on(:frame) do |bytes|
|
25
|
+
sock.print bytes
|
26
|
+
sock.flush
|
27
|
+
end
|
28
|
+
conn.on(:frame_sent) do |frame|
|
29
|
+
puts "Sent frame: #{frame.inspect}"
|
30
|
+
end
|
31
|
+
conn.on(:frame_received) do |frame|
|
32
|
+
puts "Received frame: #{frame.inspect}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# upgrader module
|
36
|
+
class UpgradeHandler
|
37
|
+
UPGRADE_REQUEST = <<-RESP.freeze
|
38
|
+
GET %s HTTP/1.1
|
39
|
+
Connection: Upgrade, HTTP2-Settings
|
40
|
+
HTTP2-Settings: #{HTTP2::Client.settings_header(settings_max_concurrent_streams: 100)}
|
41
|
+
Upgrade: h2c
|
42
|
+
Host: %s
|
43
|
+
User-Agent: http-2 upgrade
|
44
|
+
Accept: */*
|
45
|
+
|
46
|
+
RESP
|
47
|
+
|
48
|
+
attr_reader :complete, :parsing
|
49
|
+
def initialize(conn, sock)
|
50
|
+
@conn = conn
|
51
|
+
@sock = sock
|
52
|
+
@headers = request_header_hash
|
53
|
+
@body = ''.b
|
54
|
+
@complete, @parsing = false, false
|
55
|
+
@parser = ::HTTP::Parser.new(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def request(uri)
|
59
|
+
host = "#{uri.hostname}#{":#{uri.port}" if uri.port != uri.default_port}"
|
60
|
+
req = format(UPGRADE_REQUEST, uri.request_uri, host)
|
61
|
+
puts req
|
62
|
+
@sock << req
|
63
|
+
end
|
64
|
+
|
65
|
+
def <<(data)
|
66
|
+
@parsing ||= true
|
67
|
+
@parser << data
|
68
|
+
return unless complete
|
69
|
+
upgrade
|
70
|
+
end
|
71
|
+
|
72
|
+
def complete!
|
73
|
+
@complete = true
|
74
|
+
end
|
75
|
+
|
76
|
+
def on_headers_complete(h)
|
77
|
+
@headers.merge!(h)
|
78
|
+
puts "received headers: #{h}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def on_body(chunk)
|
82
|
+
puts "received chunk: #{chunk}"
|
83
|
+
@body << chunk
|
84
|
+
end
|
85
|
+
|
86
|
+
def on_message_complete
|
87
|
+
fail 'could not upgrade to h2c' unless @parser.status_code == 101
|
88
|
+
@parsing = false
|
89
|
+
complete!
|
90
|
+
end
|
91
|
+
|
92
|
+
def upgrade
|
93
|
+
stream = @conn.upgrade
|
94
|
+
log = Logger.new(stream.id)
|
95
|
+
|
96
|
+
stream.on(:close) do
|
97
|
+
log.info 'stream closed'
|
98
|
+
end
|
99
|
+
|
100
|
+
stream.on(:half_close) do
|
101
|
+
log.info 'closing client-end of the stream'
|
102
|
+
end
|
103
|
+
|
104
|
+
stream.on(:headers) do |h|
|
105
|
+
log.info "response headers: #{h}"
|
106
|
+
end
|
107
|
+
|
108
|
+
stream.on(:data) do |d|
|
109
|
+
log.info "response data chunk: <<#{d}>>"
|
110
|
+
end
|
111
|
+
|
112
|
+
stream.on(:altsvc) do |f|
|
113
|
+
log.info "received ALTSVC #{f}"
|
114
|
+
end
|
115
|
+
|
116
|
+
@conn.on(:promise) do |promise|
|
117
|
+
promise.on(:headers) do |h|
|
118
|
+
log.info "promise headers: #{h}"
|
119
|
+
end
|
120
|
+
|
121
|
+
promise.on(:data) do |d|
|
122
|
+
log.info "promise data chunk: <<#{d.size}>>"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
@conn.on(:altsvc) do |f|
|
127
|
+
log.info "received ALTSVC #{f}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
uh = UpgradeHandler.new(conn, sock)
|
133
|
+
puts 'Sending HTTP/1.1 upgrade request'
|
134
|
+
uh.request(uri)
|
135
|
+
|
136
|
+
while !sock.closed? && !sock.eof?
|
137
|
+
data = sock.read_nonblock(1024)
|
138
|
+
|
139
|
+
begin
|
140
|
+
if !uh.parsing && !uh.complete
|
141
|
+
uh << data
|
142
|
+
elsif uh.parsing && !uh.complete
|
143
|
+
uh << data
|
144
|
+
elsif uh.complete
|
145
|
+
conn << data
|
146
|
+
end
|
147
|
+
|
148
|
+
rescue => e
|
149
|
+
puts "#{e.class} exception: #{e.message} - closing socket."
|
150
|
+
e.backtrace.each { |l| puts "\t" + l }
|
151
|
+
conn.close
|
152
|
+
sock.close
|
153
|
+
end
|
154
|
+
end
|
data/lib/http/2/client.rb
CHANGED
@@ -38,6 +38,13 @@ module HTTP2
|
|
38
38
|
super(frame)
|
39
39
|
end
|
40
40
|
|
41
|
+
# sends the preface and initializes the first stream in half-closed state
|
42
|
+
def upgrade
|
43
|
+
fail ProtocolError unless @stream_id == 1
|
44
|
+
send_connection_preface
|
45
|
+
new_stream(state: :half_closed_local)
|
46
|
+
end
|
47
|
+
|
41
48
|
# Emit the connection preface if not yet
|
42
49
|
def send_connection_preface
|
43
50
|
return unless @state == :waiting_connection_preface
|
@@ -47,5 +54,10 @@ module HTTP2
|
|
47
54
|
payload = @local_settings.select { |k, v| v != SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
48
55
|
settings(payload)
|
49
56
|
end
|
57
|
+
|
58
|
+
def self.settings_header(**settings)
|
59
|
+
frame = Framer.new.generate(type: :settings, stream: 0, payload: settings)
|
60
|
+
Base64.urlsafe_encode64(frame[9..-1])
|
61
|
+
end
|
50
62
|
end
|
51
63
|
end
|
data/lib/http/2/compressor.rb
CHANGED
@@ -199,8 +199,12 @@ module HTTP2
|
|
199
199
|
commands = []
|
200
200
|
# Literals commands are marked with :noindex when index is not used
|
201
201
|
noindex = [:static, :never].include?(@options[:index])
|
202
|
-
headers.each do |
|
203
|
-
|
202
|
+
headers.each do |field, value|
|
203
|
+
# Literal header names MUST be translated to lowercase before
|
204
|
+
# encoding and transmission.
|
205
|
+
field = field.downcase
|
206
|
+
value = '/' if field == ':path' && value.empty?
|
207
|
+
cmd = addcmd(field, value)
|
204
208
|
cmd[:type] = :noindex if noindex && cmd[:type] == :incremental
|
205
209
|
commands << cmd
|
206
210
|
process(cmd)
|
@@ -220,7 +224,7 @@ module HTTP2
|
|
220
224
|
#
|
221
225
|
# @param header [Array] +[name, value]+
|
222
226
|
# @return [Hash] command
|
223
|
-
def addcmd(header)
|
227
|
+
def addcmd(*header)
|
224
228
|
exact = nil
|
225
229
|
name_only = nil
|
226
230
|
|
@@ -442,10 +446,6 @@ module HTTP2
|
|
442
446
|
def encode(headers)
|
443
447
|
buffer = Buffer.new
|
444
448
|
|
445
|
-
# Literal header names MUST be translated to lowercase before
|
446
|
-
# encoding and transmission.
|
447
|
-
headers.map! { |hk, hv| [hk.downcase, hv] }
|
448
|
-
|
449
449
|
commands = @cc.encode(headers)
|
450
450
|
commands.each do |cmd|
|
451
451
|
buffer << header(cmd)
|
data/lib/http/2/connection.rb
CHANGED
@@ -147,6 +147,7 @@ module HTTP2
|
|
147
147
|
#
|
148
148
|
# @param increment [Integer]
|
149
149
|
def window_update(increment)
|
150
|
+
@local_window += increment
|
150
151
|
send(type: :window_update, stream: 0, increment: increment)
|
151
152
|
end
|
152
153
|
|
@@ -312,6 +313,10 @@ module HTTP2
|
|
312
313
|
else
|
313
314
|
if (stream = @streams[frame[:stream]])
|
314
315
|
stream << frame
|
316
|
+
if frame[:type] == :data
|
317
|
+
update_local_window(frame)
|
318
|
+
calculate_window_update(@local_window_limit)
|
319
|
+
end
|
315
320
|
else
|
316
321
|
case frame[:type]
|
317
322
|
# The PRIORITY frame can be sent for a stream in the "idle" or
|
@@ -664,7 +669,6 @@ module HTTP2
|
|
664
669
|
|
665
670
|
stream.on(:promise, &method(:promise)) if self.is_a? Server
|
666
671
|
stream.on(:frame, &method(:send))
|
667
|
-
stream.on(:window_update, &method(:window_update))
|
668
672
|
|
669
673
|
@streams[id] = stream
|
670
674
|
end
|
@@ -689,6 +693,7 @@ module HTTP2
|
|
689
693
|
backtrace = (e && e.backtrace) || []
|
690
694
|
fail Error.const_get(klass), msg, backtrace
|
691
695
|
end
|
696
|
+
alias error connection_error
|
692
697
|
|
693
698
|
def manage_state(_)
|
694
699
|
yield
|
data/lib/http/2/flow_buffer.rb
CHANGED
@@ -13,6 +13,37 @@ module HTTP2
|
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
+
def update_local_window(frame)
|
17
|
+
frame_size = frame[:payload].bytesize
|
18
|
+
frame_size += frame[:padding] || 0
|
19
|
+
@local_window -= frame_size
|
20
|
+
end
|
21
|
+
|
22
|
+
def calculate_window_update(window_max_size)
|
23
|
+
# If DATA frame is received with length > 0 and
|
24
|
+
# current received window size + delta length is strictly larger than
|
25
|
+
# local window size, it throws a flow control error.
|
26
|
+
#
|
27
|
+
error(:flow_control_error) if @local_window < 0
|
28
|
+
|
29
|
+
# Send WINDOW_UPDATE if the received window size goes over
|
30
|
+
# the local window size / 2.
|
31
|
+
#
|
32
|
+
# The HTTP/2 spec mandates that every DATA frame received
|
33
|
+
# generates a WINDOW_UPDATE to send. In some cases however,
|
34
|
+
# (ex: DATA frames with short payloads),
|
35
|
+
# the noise generated by flow control frames creates enough
|
36
|
+
# congestion for this to be deemed very inefficient.
|
37
|
+
#
|
38
|
+
# This heuristic was inherited from nghttp, which delays the
|
39
|
+
# WINDOW_UPDATE until at least half the window is exhausted.
|
40
|
+
# This works because the sender doesn't need those increments
|
41
|
+
# until the receiver window is exhausted, after which he'll be
|
42
|
+
# waiting for the WINDOW_UPDATE frame.
|
43
|
+
return unless @local_window <= (window_max_size / 2)
|
44
|
+
window_update(window_max_size - @local_window)
|
45
|
+
end
|
46
|
+
|
16
47
|
# Buffers outgoing DATA frames and applies flow control logic to split
|
17
48
|
# and emit DATA frames based on current flow control window. If the
|
18
49
|
# window is large enough, the data is sent immediately. Otherwise, the
|
data/lib/http/2/stream.rb
CHANGED
@@ -71,22 +71,24 @@ module HTTP2
|
|
71
71
|
# @param exclusive [Boolean]
|
72
72
|
# @param window [Integer]
|
73
73
|
# @param parent [Stream]
|
74
|
-
|
74
|
+
# @param state [Symbol]
|
75
|
+
def initialize(connection:, id:, weight: 16, dependency: 0, exclusive: false, parent: nil, state: :idle)
|
75
76
|
@connection = connection
|
76
77
|
@id = id
|
77
78
|
@weight = weight
|
78
79
|
@dependency = dependency
|
79
80
|
process_priority(weight: weight, stream_dependency: dependency, exclusive: exclusive)
|
81
|
+
@local_window_max_size = connection.local_settings[:settings_initial_window_size]
|
80
82
|
@local_window = connection.local_settings[:settings_initial_window_size]
|
81
83
|
@remote_window = connection.remote_settings[:settings_initial_window_size]
|
82
84
|
@parent = parent
|
83
|
-
@state =
|
85
|
+
@state = state
|
84
86
|
@error = false
|
85
87
|
@closed = false
|
86
88
|
@send_buffer = []
|
87
89
|
|
88
90
|
on(:window) { |v| @remote_window = v }
|
89
|
-
on(:local_window) { |v| @local_window = v }
|
91
|
+
on(:local_window) { |v| @local_window_max_size = @local_window = v }
|
90
92
|
end
|
91
93
|
|
92
94
|
# Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
|
@@ -97,16 +99,14 @@ module HTTP2
|
|
97
99
|
|
98
100
|
case frame[:type]
|
99
101
|
when :data
|
100
|
-
|
101
|
-
|
102
|
-
@local_window -= window_size
|
102
|
+
update_local_window(frame)
|
103
|
+
# Emit DATA frame
|
103
104
|
emit(:data, frame[:payload]) unless frame[:ignore]
|
104
|
-
|
105
|
-
|
106
|
-
# assuming that emit(:data) can now receive next data
|
107
|
-
window_update(window_size) if window_size > 0
|
108
|
-
when :headers, :push_promise
|
105
|
+
calculate_window_update(@local_window_max_size)
|
106
|
+
when :headers
|
109
107
|
emit(:headers, frame[:payload]) unless frame[:ignore]
|
108
|
+
when :push_promise
|
109
|
+
emit(:promise_headers, frame[:payload]) unless frame[:ignore]
|
110
110
|
when :priority
|
111
111
|
process_priority(frame)
|
112
112
|
when :window_update
|
@@ -153,7 +153,7 @@ module HTTP2
|
|
153
153
|
flags << :end_headers if end_headers
|
154
154
|
flags << :end_stream if end_stream
|
155
155
|
|
156
|
-
send(type: :headers, flags: flags, payload: headers
|
156
|
+
send(type: :headers, flags: flags, payload: headers)
|
157
157
|
end
|
158
158
|
|
159
159
|
def promise(headers, end_headers: true, &block)
|
@@ -228,8 +228,6 @@ module HTTP2
|
|
228
228
|
#
|
229
229
|
# @param increment [Integer]
|
230
230
|
def window_update(increment)
|
231
|
-
# always emit connection-level WINDOW_UPDATE
|
232
|
-
emit(:window_update, increment)
|
233
231
|
# emit stream-level WINDOW_UPDATE unless stream is closed
|
234
232
|
return if @state == :closed || @state == :remote_closed
|
235
233
|
send(type: :window_update, increment: increment)
|
@@ -602,6 +600,7 @@ module HTTP2
|
|
602
600
|
klass = error.to_s.split('_').map(&:capitalize).join
|
603
601
|
fail Error.const_get(klass), msg
|
604
602
|
end
|
603
|
+
alias error stream_error
|
605
604
|
|
606
605
|
def manage_state(frame)
|
607
606
|
transition(frame, true)
|
data/lib/http/2/version.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -34,6 +34,23 @@ RSpec.describe HTTP2::Client do
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
context 'upgrade' do
|
38
|
+
it 'fails when client has already created streams' do
|
39
|
+
@client.new_stream
|
40
|
+
expect { @client.upgrade }.to raise_error(HTTP2::Error::ProtocolError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'sends the preface' do
|
44
|
+
expect(@client).to receive(:send_connection_preface)
|
45
|
+
@client.upgrade
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'initializes the first stream in the half-closed state' do
|
49
|
+
stream = @client.upgrade
|
50
|
+
expect(stream.state).to be(:half_closed_local)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
37
54
|
context 'push' do
|
38
55
|
it 'should disallow client initiated push' do
|
39
56
|
expect do
|
@@ -69,12 +86,28 @@ RSpec.describe HTTP2::Client do
|
|
69
86
|
|
70
87
|
promise = nil
|
71
88
|
@client.on(:promise) { |stream| promise = stream }
|
72
|
-
@client << set_stream_id(f.generate(PUSH_PROMISE.
|
89
|
+
@client << set_stream_id(f.generate(PUSH_PROMISE.deep_dup), s.id)
|
73
90
|
|
74
91
|
expect(promise.id).to eq 2
|
75
92
|
expect(promise.state).to eq :reserved_remote
|
76
93
|
end
|
77
94
|
|
95
|
+
it 'should emit promise headers for received PUSH_PROMISE' do
|
96
|
+
header = nil
|
97
|
+
s = @client.new_stream
|
98
|
+
s.send HEADERS.deep_dup
|
99
|
+
|
100
|
+
@client.on(:promise) do |stream|
|
101
|
+
stream.on(:promise_headers) do |h|
|
102
|
+
header = h
|
103
|
+
end
|
104
|
+
end
|
105
|
+
@client << set_stream_id(f.generate(PUSH_PROMISE.deep_dup), s.id)
|
106
|
+
|
107
|
+
expect(header).to be_a(Array)
|
108
|
+
# expect(header).to eq([%w(a b)])
|
109
|
+
end
|
110
|
+
|
78
111
|
it 'should auto RST_STREAM promises against locally-RST stream' do
|
79
112
|
s = @client.new_stream
|
80
113
|
s.send HEADERS.deep_dup
|
data/spec/compressor_spec.rb
CHANGED
@@ -237,6 +237,18 @@ RSpec.describe HTTP2::Header do
|
|
237
237
|
expect(cc.table.first[0]).to eq 'test2'
|
238
238
|
end
|
239
239
|
end
|
240
|
+
|
241
|
+
context 'encode' do
|
242
|
+
it 'downcases the field' do
|
243
|
+
expect(EncodingContext.new.encode([['Content-Length', '5']]))
|
244
|
+
.to eq(EncodingContext.new.encode([['content-length', '5']]))
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'fills :path if empty' do
|
248
|
+
expect(EncodingContext.new.encode([[':path', '']]))
|
249
|
+
.to eq(EncodingContext.new.encode([[':path', '/']]))
|
250
|
+
end
|
251
|
+
end
|
240
252
|
end
|
241
253
|
|
242
254
|
spec_examples = [
|
data/spec/connection_spec.rb
CHANGED
@@ -237,6 +237,28 @@ RSpec.describe HTTP2::Connection do
|
|
237
237
|
expect(@conn.buffered_amount).to eq 0
|
238
238
|
expect(@conn.remote_window).to eq 900
|
239
239
|
end
|
240
|
+
|
241
|
+
it 'should update window when data received is over half of the maximum local window size' do
|
242
|
+
settings, data = SETTINGS.dup, DATA.dup
|
243
|
+
conn = Client.new(settings_initial_window_size: 500)
|
244
|
+
|
245
|
+
conn.receive f.generate(settings)
|
246
|
+
s1 = conn.new_stream
|
247
|
+
s2 = conn.new_stream
|
248
|
+
|
249
|
+
s1.send HEADERS.deep_dup
|
250
|
+
s2.send HEADERS.deep_dup
|
251
|
+
expect(conn).to receive(:send) do |frame|
|
252
|
+
expect(frame[:type]).to eq :window_update
|
253
|
+
expect(frame[:stream]).to eq 0
|
254
|
+
expect(frame[:increment]).to eq 400
|
255
|
+
end
|
256
|
+
conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s1.id))
|
257
|
+
conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s2.id))
|
258
|
+
expect(s1.local_window).to eq 300
|
259
|
+
expect(s2.local_window).to eq 300
|
260
|
+
expect(conn.local_window).to eq 500
|
261
|
+
end
|
240
262
|
end
|
241
263
|
|
242
264
|
context 'framing' do
|
data/spec/stream_spec.rb
CHANGED
@@ -598,19 +598,28 @@ RSpec.describe HTTP2::Stream do
|
|
598
598
|
expect(s1.remote_window).to eq 900
|
599
599
|
end
|
600
600
|
|
601
|
-
it 'should
|
601
|
+
it 'should not update window when data received is less than half of maximum local window size' do
|
602
602
|
data = DATA.deep_dup
|
603
603
|
datalen = data[:payload].bytesize
|
604
|
-
expect(@stream).
|
604
|
+
expect(@stream).not_to receive(:send) do |frame|
|
605
605
|
expect(frame[:type]).to eq :window_update
|
606
606
|
expect(frame[:increment]).to eq datalen
|
607
607
|
end
|
608
|
-
|
608
|
+
@stream.receive HEADERS.deep_dup
|
609
|
+
@stream.receive data
|
610
|
+
end
|
611
|
+
|
612
|
+
it 'should update window when data received is over half of the maximum local window size' do
|
613
|
+
data1 = DATA.merge(payload: 'a'*16_384, flags: [])
|
614
|
+
data2 = DATA.merge(payload: 'a'*16_384)
|
615
|
+
datalen = 16_384 * 2
|
616
|
+
expect(@stream).to receive(:send) do |frame|
|
609
617
|
expect(frame[:type]).to eq :window_update
|
610
618
|
expect(frame[:increment]).to eq datalen
|
611
619
|
end
|
612
620
|
@stream.receive HEADERS.deep_dup
|
613
|
-
@stream.receive
|
621
|
+
@stream.receive data1
|
622
|
+
@stream.receive data2
|
614
623
|
end
|
615
624
|
end
|
616
625
|
|
@@ -642,7 +651,7 @@ RSpec.describe HTTP2::Stream do
|
|
642
651
|
|
643
652
|
expect(@stream).to receive(:send) do |frame|
|
644
653
|
expect(frame[:type]).to eq :headers
|
645
|
-
expect(frame[:payload]).to eq payload
|
654
|
+
expect(frame[:payload]).to eq payload
|
646
655
|
expect(frame[:flags]).to eq [:end_headers]
|
647
656
|
end
|
648
657
|
|
@@ -822,8 +831,8 @@ RSpec.describe HTTP2::Stream do
|
|
822
831
|
expect(order).to eq [:reserved, :active, :half_close, :close]
|
823
832
|
end
|
824
833
|
|
825
|
-
it 'client:
|
826
|
-
order, headers = [], []
|
834
|
+
it 'client: promise_headers > active > headers > .. > data > close' do
|
835
|
+
order, headers, promise_headers = [], [], []
|
827
836
|
@client.on(:promise) do |push|
|
828
837
|
order << :reserved
|
829
838
|
|
@@ -832,6 +841,10 @@ RSpec.describe HTTP2::Stream do
|
|
832
841
|
push.on(:half_close) { order << :half_close }
|
833
842
|
push.on(:close) { order << :close }
|
834
843
|
|
844
|
+
push.on(:promise_headers) do |h|
|
845
|
+
order << :promise_headers
|
846
|
+
promise_headers += h
|
847
|
+
end
|
835
848
|
push.on(:headers) do |h|
|
836
849
|
order << :headers
|
837
850
|
headers += h
|
@@ -845,10 +858,11 @@ RSpec.describe HTTP2::Stream do
|
|
845
858
|
push.data('somedata')
|
846
859
|
end
|
847
860
|
|
848
|
-
expect(
|
861
|
+
expect(promise_headers).to eq([%w(key val)])
|
862
|
+
expect(headers).to eq([%w(key2 val2)])
|
849
863
|
expect(order).to eq [
|
850
864
|
:reserved,
|
851
|
-
:
|
865
|
+
:promise_headers,
|
852
866
|
:active,
|
853
867
|
:headers,
|
854
868
|
:half_close,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http-2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Grigorik
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2018-02-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -52,6 +52,7 @@ files:
|
|
52
52
|
- example/keys/server.crt
|
53
53
|
- example/keys/server.key
|
54
54
|
- example/server.rb
|
55
|
+
- example/upgrade_client.rb
|
55
56
|
- example/upgrade_server.rb
|
56
57
|
- http-2.gemspec
|
57
58
|
- lib/http/2.rb
|