http-2 0.8.4 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|