http-2 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60d5e715ae277807ca53b015a14a0e8c7f6676b7
4
- data.tar.gz: 17c562bfb62b6db7209962ace7bd698a59fa2c91
3
+ metadata.gz: 2a5b4286258966b81acdfc845ae3dda4380c4f64
4
+ data.tar.gz: 7f61a80280bc7b85f220a6e6661d594d588f0969
5
5
  SHA512:
6
- metadata.gz: c8d072f9ac4302506d4770fd48fa153567812b2beaa2e0bc4e1f3d04b0cc7560ebf7bd6bc22335e0465814a08a70cdca9f52546b62ffbc362edea5ecfaf41103
7
- data.tar.gz: 950e2e78ff84257d62ddaf9d246b31e68689fd8125ee5fb180d239dbeaabe72d373c5a166353000a27d46f3f41c3924de19fa2287c195f7b75215204ae05e78f
6
+ metadata.gz: 90e4326062ff7d6ee5161ffe0509a99ac46f36c5aebe906b8ec6e044018d36fab1a8e17cf549a30272a5c6a1d7603362eb2fac42edbcc5a666f9b336cdf4bdc4
7
+ data.tar.gz: fc4ed18e276fbd8d27dbf090999f989d91450bd8a2dd59d9a1e77bdaa482e74217a4f7ecb0bf9033af99c40df29858d88c849a7cba36e19363c43718cc026aa4
@@ -8,25 +8,41 @@ AllCops:
8
8
  - 'vendor/**/*'
9
9
  - '**/huffman_statemachine.rb'
10
10
 
11
+ Layout/IndentHeredoc:
12
+ Exclude:
13
+ - 'lib/tasks/generate_huffman_table.rb'
14
+
11
15
  Metrics/LineLength:
12
16
  Max: 120
13
17
 
14
- Lint/EndAlignment:
15
- AlignWith: variable
18
+ Metrics/BlockLength:
19
+ Max: 700
20
+
21
+ Layout/EndAlignment:
22
+ EnforcedStyleAlignWith: variable
16
23
 
17
- Style/CaseIndentation:
18
- IndentWhenRelativeTo: end
24
+ Layout/CaseIndentation:
25
+ EnforcedStyle: end
19
26
 
20
- Style/IndentHash:
27
+ Layout/IndentHash:
21
28
  EnforcedStyle: consistent
22
29
 
23
- Style/TrailingCommaInLiteral:
30
+ Style/TrailingCommaInArrayLiteral:
24
31
  EnforcedStyleForMultiline: comma
25
32
 
26
- Style/SpaceAroundOperators:
33
+ Style/TrailingCommaInHashLiteral:
34
+ EnforcedStyleForMultiline: comma
35
+
36
+ Layout/SpaceAroundOperators:
27
37
  Enabled: false
28
38
 
29
- Style/ExtraSpacing:
39
+ Layout/ExtraSpacing:
40
+ Enabled: false
41
+
42
+ Layout/EmptyLinesAroundExceptionHandlingKeywords:
43
+ Enabled: false
44
+
45
+ Naming/UncommunicativeMethodParamName:
30
46
  Enabled: false
31
47
 
32
48
  Style/SignalException:
@@ -44,14 +60,33 @@ Style/ParenthesesAroundCondition:
44
60
  Style/IfInsideElse:
45
61
  Enabled: false
46
62
 
63
+ Style/IfUnlessModifier:
64
+ Enabled: false
65
+
66
+ Style/MultilineIfModifier:
67
+ Enabled: false
68
+
69
+ Lint/EmptyWhen:
70
+ Enabled: false
71
+
47
72
  Style/TrailingCommaInArguments:
48
73
  Enabled: false
49
74
 
50
75
  Style/TrailingUnderscoreVariable:
51
76
  Enabled: false
52
77
 
78
+ Style/SymbolArray:
79
+ Enabled: false
80
+
81
+ Style/CommentedKeyword:
82
+ Enabled: false
83
+
84
+ Style/PercentLiteralDelimiters:
85
+ Enabled: false
86
+
53
87
  Performance/TimesMap:
54
88
  Enabled: false
55
89
 
56
90
  Performance/RedundantBlockCall:
57
91
  Enabled: false
92
+
@@ -75,14 +75,14 @@ Style/GuardClause:
75
75
  # Cop supports --auto-correct.
76
76
  # Configuration parameters: SupportedStyles, IndentationWidth.
77
77
  # SupportedStyles: special_inside_parentheses, consistent, align_brackets
78
- Style/IndentArray:
78
+ Layout/IndentArray:
79
79
  EnforcedStyle: consistent
80
80
 
81
81
  # Offense count: 1
82
82
  # Cop supports --auto-correct.
83
83
  # Configuration parameters: EnforcedStyle, SupportedStyles.
84
84
  # SupportedStyles: symmetrical, new_line, same_line
85
- Style/MultilineArrayBraceLayout:
85
+ Layout/MultilineArrayBraceLayout:
86
86
  Exclude:
87
87
  - 'spec/compressor_spec.rb'
88
88
 
@@ -90,7 +90,7 @@ Style/MultilineArrayBraceLayout:
90
90
  # Cop supports --auto-correct.
91
91
  # Configuration parameters: EnforcedStyle, SupportedStyles.
92
92
  # SupportedStyles: symmetrical, new_line, same_line
93
- Style/MultilineHashBraceLayout:
93
+ Layout/MultilineHashBraceLayout:
94
94
  Exclude:
95
95
  - 'spec/compressor_spec.rb'
96
96
 
@@ -5,7 +5,7 @@ rvm:
5
5
  - 2.2
6
6
  - 2.3.0
7
7
  - 2.4.0
8
- - jruby-9.1.8.0
8
+ - jruby-9.2.0.0 # latest stable
9
9
  - jruby-head
10
10
  - rbx-2
11
11
  matrix:
data/Gemfile CHANGED
@@ -10,7 +10,7 @@ group :test do
10
10
  gem 'pry-byebug', platform: :mri
11
11
  gem 'rspec', '~> 3.4.0'
12
12
  gem 'rspec-autotest'
13
- gem 'rubocop', '0.43.0'
13
+ gem 'rubocop', '0.57.2'
14
14
  end
15
15
 
16
16
  gemspec
data/Guardfile CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  guard :process, name: 'HTTP/2 Server', command: 'ruby example/server.rb', stop_signal: 'TERM' do
3
2
  watch(%r{^example/(.+)\.rb$})
4
3
  watch(%r{^lib/http/2/(.+)\.rb$})
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # HTTP-2
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/http-2.png)](http://rubygems.org/gems/http-2)
4
- [![Build Status](https://travis-ci.org/igrigorik/http-2.png?branch=master)](https://travis-ci.org/igrigorik/http-2)
5
- [![Coverage Status](https://coveralls.io/repos/igrigorik/http-2/badge.png)](https://coveralls.io/r/igrigorik/http-2)
3
+ [![Gem Version](https://badge.fury.io/rb/http-2.svg)](http://rubygems.org/gems/http-2)
4
+ [![Build Status](https://travis-ci.org/igrigorik/http-2.svg?branch=master)](https://travis-ci.org/igrigorik/http-2)
5
+ [![Coverage Status](https://coveralls.io/repos/igrigorik/http-2/badge.svg)](https://coveralls.io/r/igrigorik/http-2)
6
6
  [![Analytics](https://ga-beacon.appspot.com/UA-71196-10/http-2/readme)](https://github.com/igrigorik/ga-beacon)
7
7
 
8
8
  Pure Ruby, framework and transport agnostic, implementation of HTTP/2 protocol and HPACK header compression with support for:
9
9
 
10
- * [Binary framing](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#_binary_framing_layer) parsing and encoding
11
- * [Stream multiplexing](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_STREAMS_MESSAGES_FRAMES) and [prioritization](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_PRIORITIZATION)
12
- * Connection and stream [flow control](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#_flow_control)
13
- * [Header compression](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_HEADER_COMPRESSION) and [server push](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_PUSH)
10
+ * [Binary framing](https://hpbn.co/http2/#binary-framing-layer) parsing and encoding
11
+ * [Stream multiplexing](https://hpbn.co/http2/#streams-messages-and-frames) and [prioritization](https://hpbn.co/http2/#stream-prioritization)
12
+ * Connection and stream [flow control](https://hpbn.co/http2/#flow-control)
13
+ * [Header compression](https://hpbn.co/http2/#header-compression) and [server push](https://hpbn.co/http2/#server-push)
14
14
  * Connection and stream management
15
15
  * And more... see [API docs](http://www.rubydoc.info/github/igrigorik/http-2/frames)
16
16
 
@@ -88,7 +88,7 @@ Events emitted by the connection object:
88
88
 
89
89
  ### Stream lifecycle management
90
90
 
91
- A single HTTP/2 connection can [multiplex multiple streams](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#REQUEST_RESPONSE_MULTIPLEXING) in parallel: multiple requests and responses can be in flight simultaneously and stream data can be interleaved and prioritized. Further, the specification provides a well-defined lifecycle for each stream (see below).
91
+ A single HTTP/2 connection can [multiplex multiple streams](https://hpbn.co/http2/#request-and-response-multiplexing) in parallel: multiple requests and responses can be in flight simultaneously and stream data can be interleaved and prioritized. Further, the specification provides a well-defined lifecycle for each stream (see below).
92
92
 
93
93
  The good news is, all of the stream management, and state transitions, and error checking is handled by the library. All you have to do is subscribe to appropriate events (marked with ":" prefix in diagram below) and provide your application logic to handle request and response processing.
94
94
 
@@ -186,7 +186,7 @@ Events emitted by the [Stream object](http://www.rubydoc.info/github/igrigorik/h
186
186
 
187
187
  ### Prioritization
188
188
 
189
- Each HTTP/2 [stream has a priority value](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_PRIORITIZATION) that can be sent when the new stream is initialized, and optionally reprioritized later:
189
+ Each HTTP/2 [stream has a priority value](https://hpbn.co/http2/#stream-prioritization) that can be sent when the new stream is initialized, and optionally reprioritized later:
190
190
 
191
191
  ```ruby
192
192
  client = HTTP2::Client.new
@@ -203,7 +203,7 @@ On the opposite side, the server can optimize its stream processing order or res
203
203
 
204
204
  ### Flow control
205
205
 
206
- Multiplexing multiple streams over the same TCP connection introduces contention for shared bandwidth resources. Stream priorities can help determine the relative order of delivery, but priorities alone are insufficient to control how the resource allocation is performed between multiple streams. To address this, HTTP/2 provides a simple mechanism for [stream and connection flow control](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#_flow_control).
206
+ Multiplexing multiple streams over the same TCP connection introduces contention for shared bandwidth resources. Stream priorities can help determine the relative order of delivery, but priorities alone are insufficient to control how the resource allocation is performed between multiple streams. To address this, HTTP/2 provides a simple mechanism for [stream and connection flow control](https://hpbn.co/http2/#flow-control).
207
207
 
208
208
  Connection and stream flow control is handled by the library: all streams are initialized with the default window size (64KB), and send/receive window updates are automatically processed - i.e. window is decremented on outgoing data transfers, and incremented on receipt of window frames. Similarly, if the window is exceeded, then data frames are automatically buffered until window is updated.
209
209
 
@@ -219,16 +219,10 @@ stream.window # check current window size
219
219
  stream.window_update(2048) # increment stream window by 2048 bytes
220
220
  ```
221
221
 
222
- Alternatively, flow control can be disabled by emitting an appropriate settings frame on the connection:
223
-
224
- ```ruby
225
- # limit number of concurrent streams to 100 and disable flow control
226
- conn.settings(streams: 100, window: Float::INFINITY)
227
- ```
228
222
 
229
223
  ### Server push
230
224
 
231
- An HTTP/2 server can [send multiple replies](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_PUSH) to a single client request. To do so, first it emits a "push promise" frame which contains the headers of the promised resource, followed by the response to the original request, as well as promised resource payloads (which may be interleaved). A simple example is in order:
225
+ An HTTP/2 server can [send multiple replies](https://hpbn.co/http2/#server-push) to a single client request. To do so, first it emits a "push promise" frame which contains the headers of the promised resource, followed by the response to the original request, as well as promised resource payloads (which may be interleaved). A simple example is in order:
232
226
 
233
227
  ```ruby
234
228
  conn = HTTP2::Server.new
@@ -276,10 +270,10 @@ conn.on(:promise) do |push|
276
270
  end
277
271
  ```
278
272
 
279
- The client can cancel any given push stream (via `.close`), or disable server push entirely by sending the appropriate settings frame (note that below setting only impacts server > client direction):
273
+ The client can cancel any given push stream (via `.close`), or disable server push entirely by sending the appropriate settings frame:
280
274
 
281
275
  ```ruby
282
- client.settings(streams: 0) # setting max limit to 0 disables server push
276
+ client.settings(settings_enable_push: 0)
283
277
  ```
284
278
  ### Specs
285
279
 
@@ -114,7 +114,7 @@ while !sock.closed? && !sock.eof?
114
114
 
115
115
  begin
116
116
  conn << data
117
- rescue => e
117
+ rescue StandardError => e
118
118
  puts "#{e.class} exception: #{e.message} - closing socket."
119
119
  e.backtrace.each { |l| puts "\t" + l }
120
120
  sock.close
@@ -130,7 +130,7 @@ loop do
130
130
 
131
131
  begin
132
132
  conn << data
133
- rescue => e
133
+ rescue StandardError => e
134
134
  puts "#{e.class} exception: #{e.message} - closing socket."
135
135
  e.backtrace.each { |l| puts "\t" + l }
136
136
  sock.close
@@ -34,16 +34,16 @@ end
34
34
 
35
35
  # upgrader module
36
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: */*
37
+ UPGRADE_REQUEST = <<-RESP.strip_heredoc.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
45
 
46
- RESP
46
+ RESP
47
47
 
48
48
  attr_reader :complete, :parsing
49
49
  def initialize(conn, sock)
@@ -73,9 +73,9 @@ RESP
73
73
  @complete = true
74
74
  end
75
75
 
76
- def on_headers_complete(h)
77
- @headers.merge!(h)
78
- puts "received headers: #{h}"
76
+ def on_headers_complete(headers)
77
+ @headers.merge!(headers)
78
+ puts "received headers: #{headers}"
79
79
  end
80
80
 
81
81
  def on_body(chunk)
@@ -144,8 +144,7 @@ while !sock.closed? && !sock.eof?
144
144
  elsif uh.complete
145
145
  conn << data
146
146
  end
147
-
148
- rescue => e
147
+ rescue StandardError => e
149
148
  puts "#{e.class} exception: #{e.message} - closing socket."
150
149
  e.backtrace.each { |l| puts "\t" + l }
151
150
  conn.close
@@ -39,12 +39,12 @@ end
39
39
 
40
40
  class UpgradeHandler
41
41
  VALID_UPGRADE_METHODS = %w(GET OPTIONS).freeze
42
- UPGRADE_RESPONSE = <<-RESP
43
- HTTP/1.1 101 Switching Protocols
44
- Connection: Upgrade
45
- Upgrade: h2c
42
+ UPGRADE_RESPONSE = <<-RESP.strip_heredoc.freeze
43
+ HTTP/1.1 101 Switching Protocols
44
+ Connection: Upgrade
45
+ Upgrade: h2c
46
46
 
47
- RESP
47
+ RESP
48
48
 
49
49
  attr_reader :complete, :headers, :body, :parsing
50
50
 
@@ -191,7 +191,7 @@ loop do
191
191
  conn << data
192
192
  end
193
193
 
194
- rescue => e
194
+ rescue StandardError => e
195
195
  puts "Exception: #{e}, #{e.message} - closing socket."
196
196
  puts e.backtrace.last(10).join("\n")
197
197
  sock.close
@@ -1,5 +1,4 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('./lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'http/2/version'
5
4
 
@@ -51,7 +51,7 @@ module HTTP2
51
51
  @state = :connected
52
52
  emit(:frame, CONNECTION_PREFACE_MAGIC)
53
53
 
54
- payload = @local_settings.select { |k, v| v != SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
54
+ payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
55
55
  settings(payload)
56
56
  end
57
57
 
@@ -445,7 +445,8 @@ module HTTP2
445
445
  # @return [Buffer]
446
446
  def encode(headers)
447
447
  buffer = Buffer.new
448
-
448
+ pseudo_headers, regular_headers = headers.partition { |f, _| f.start_with? ':' }
449
+ headers = [*pseudo_headers, *regular_headers]
449
450
  commands = @cc.encode(headers)
450
451
  commands.each do |cmd|
451
452
  buffer << header(cmd)
@@ -549,7 +550,16 @@ module HTTP2
549
550
  # @return [Array] +[[name, value], ...]+
550
551
  def decode(buf)
551
552
  list = []
552
- list << @cc.process(header(buf)) until buf.empty?
553
+ decoding_pseudo_headers = true
554
+ until buf.empty?
555
+ next_header = @cc.process(header(buf))
556
+ is_pseudo_header = next_header.first.start_with? ':'
557
+ if !decoding_pseudo_headers && is_pseudo_header
558
+ fail ProtocolError, 'one or more pseudo headers encountered after regular headers'
559
+ end
560
+ decoding_pseudo_headers = is_pseudo_header
561
+ list << next_header
562
+ end
553
563
  list.compact
554
564
  end
555
565
  end
@@ -20,9 +20,9 @@ module HTTP2
20
20
 
21
21
  DEFAULT_CONNECTION_SETTINGS = {
22
22
  settings_header_table_size: 4096,
23
- settings_enable_push: 1, # enabled for servers
23
+ settings_enable_push: 1, # enabled for servers
24
24
  settings_max_concurrent_streams: 100,
25
- settings_initial_window_size: 65_535, #
25
+ settings_initial_window_size: 65_535,
26
26
  settings_max_frame_size: 16_384,
27
27
  settings_max_header_list_size: 2**31 - 1, # unlimited
28
28
  }.freeze
@@ -96,6 +96,10 @@ module HTTP2
96
96
  @h2c_upgrade = nil
97
97
  end
98
98
 
99
+ def closed?
100
+ @state == :closed
101
+ end
102
+
99
103
  # Allocates new stream for current connection.
100
104
  #
101
105
  # @param priority [Integer]
@@ -350,7 +354,7 @@ module HTTP2
350
354
  end
351
355
  end
352
356
 
353
- rescue => e
357
+ rescue StandardError => e
354
358
  raise if e.is_a?(Error::Error)
355
359
  connection_error(e: e)
356
360
  end
@@ -492,7 +496,7 @@ module HTTP2
492
496
  # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
493
497
  # Values outside this range MUST be treated as a connection error
494
498
  # (Section 5.4.1) of type PROTOCOL_ERROR.
495
- unless 16_384 <= v && v <= 16_777_215
499
+ unless v >= 16_384 && v <= 16_777_215
496
500
  return ProtocolError.new("invalid #{key} value")
497
501
  end
498
502
  when :settings_max_header_list_size
@@ -602,8 +606,12 @@ module HTTP2
602
606
  frame[:payload] = @decompressor.decode(frame[:payload])
603
607
  end
604
608
 
605
- rescue => e
609
+ rescue CompressionError => e
606
610
  connection_error(:compression_error, e: e)
611
+ rescue ProtocolError => e
612
+ connection_error(:protocol_error, e: e)
613
+ rescue StandardError => e
614
+ connection_error(:internal_error, e: e)
607
615
  end
608
616
 
609
617
  # Encode headers payload and update connection compressor state.
@@ -633,7 +641,7 @@ module HTTP2
633
641
 
634
642
  frames
635
643
 
636
- rescue => e
644
+ rescue StandardError => e
637
645
  connection_error(:compression_error, e: e)
638
646
  nil
639
647
  end
@@ -701,4 +709,5 @@ module HTTP2
701
709
  yield
702
710
  end
703
711
  end
712
+ # rubocop:enable ClassLength
704
713
  end
@@ -359,14 +359,14 @@ module HTTP2
359
359
  if frame[:flags].include? :priority
360
360
  e_sd = payload.read_uint32
361
361
  frame[:stream_dependency] = e_sd & RBIT
362
- frame[:exclusive] = (e_sd & EBIT) != 0 # rubocop:disable Style/NumericPredicate
362
+ frame[:exclusive] = (e_sd & EBIT) != 0
363
363
  frame[:weight] = payload.getbyte + 1
364
364
  end
365
365
  frame[:payload] = payload.read(frame[:length])
366
366
  when :priority
367
367
  e_sd = payload.read_uint32
368
368
  frame[:stream_dependency] = e_sd & RBIT
369
- frame[:exclusive] = (e_sd & EBIT) != 0 # rubocop:disable Style/NumericPredicate
369
+ frame[:exclusive] = (e_sd & EBIT) != 0
370
370
  frame[:weight] = payload.getbyte + 1
371
371
  when :rst_stream
372
372
  frame[:error] = unpack_error payload.read_uint32
@@ -442,4 +442,5 @@ module HTTP2
442
442
  name || error
443
443
  end
444
444
  end
445
+ # rubocop:enable ClassLength
445
446
  end
@@ -91,6 +91,10 @@ module HTTP2
91
91
  on(:local_window) { |v| @local_window_max_size = @local_window = v }
92
92
  end
93
93
 
94
+ def closed?
95
+ @state == :closed
96
+ end
97
+
94
98
  # Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
95
99
  #
96
100
  # @param frame [Hash]
@@ -144,6 +148,7 @@ module HTTP2
144
148
  end
145
149
 
146
150
  # Sends a HEADERS frame containing HTTP response headers.
151
+ # All pseudo-header fields MUST appear in the header block before regular header fields.
147
152
  #
148
153
  # @param headers [Array or Hash] Array of key-value pairs or Hash
149
154
  # @param end_headers [Boolean] indicates that no more headers will be sent
@@ -1,3 +1,3 @@
1
1
  module HTTP2
2
- VERSION = '0.9.1'.freeze
2
+ VERSION = '0.10.0'.freeze
3
3
  end
@@ -351,6 +351,78 @@ RSpec.describe HTTP2::Header do
351
351
  },
352
352
  ],
353
353
  },
354
+ { title: 'D.4.a. Request Examples with Huffman - Client Handling of Improperly Ordered Headers',
355
+ type: :request,
356
+ table_size: 4096,
357
+ huffman: :always,
358
+ streams: [
359
+ { wire: '8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff',
360
+ emitted: [
361
+ [':method', 'GET'],
362
+ [':scheme', 'http'],
363
+ [':path', '/'],
364
+ [':authority', 'www.example.com'],
365
+ ],
366
+ table: [
367
+ [':authority', 'www.example.com'],
368
+ ],
369
+ table_size: 57,
370
+ },
371
+ { wire: '8286 84be 5886 a8eb 1064 9cbf',
372
+ emitted: [
373
+ [':method', 'GET'],
374
+ [':scheme', 'http'],
375
+ ['cache-control', 'no-cache'],
376
+ [':path', '/'],
377
+ [':authority', 'www.example.com'],
378
+ ],
379
+ table: [
380
+ ['cache-control', 'no-cache'],
381
+ [':authority', 'www.example.com'],
382
+ ],
383
+ table_size: 110,
384
+ },
385
+ { wire: "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925
386
+ a849 e95b b8e8 b4bf",
387
+ emitted: [
388
+ [':method', 'GET'],
389
+ [':scheme', 'https'],
390
+ ['custom-key', 'custom-value'],
391
+ [':path', '/index.html'],
392
+ [':authority', 'www.example.com'],
393
+ ],
394
+ table: [
395
+ ['custom-key', 'custom-value'],
396
+ ['cache-control', 'no-cache'],
397
+ [':authority', 'www.example.com'],
398
+ ],
399
+ table_size: 164,
400
+ },
401
+ ],
402
+ },
403
+ { title: 'D.4.b. Request Examples with Huffman - Server Handling of Improperly Ordered Headers',
404
+ type: :request,
405
+ bypass_encoder: true,
406
+ table_size: 4096,
407
+ huffman: :always,
408
+ streams: [
409
+ { wire: '8286408825a849e95ba97d7f8925a849e95bb8e8b4bf84418cf1e3c2e5f23a6ba0ab90f4ff',
410
+ emitted: [
411
+ [':method', 'GET'],
412
+ [':scheme', 'http'],
413
+ ['custom-key', 'custom-value'],
414
+ [':path', '/'],
415
+ [':authority', 'www.example.com'],
416
+ ],
417
+ table: [
418
+ ['custom-key', 'custom-value'],
419
+ [':authority', 'www.example.com'],
420
+ ],
421
+ table_size: 111,
422
+ has_bad_headers: true,
423
+ },
424
+ ],
425
+ },
354
426
  { title: 'D.5. Response Examples without Huffman',
355
427
  type: :response,
356
428
  table_size: 256,
@@ -485,25 +557,38 @@ RSpec.describe HTTP2::Header do
485
557
  before do
486
558
  (0...nth).each do |i|
487
559
  bytes = [ex[:streams][i][:wire].delete(" \n")].pack('H*')
488
- @dc.decode(HTTP2::Buffer.new(bytes))
560
+ if ex[:streams][i][:has_bad_headers]
561
+ expect { @dc.decode(HTTP2::Buffer.new(bytes)) }.to raise_error ProtocolError
562
+ else
563
+ @dc.decode(HTTP2::Buffer.new(bytes))
564
+ end
489
565
  end
490
566
  end
491
- subject do
492
- bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
493
- @emitted = @dc.decode(HTTP2::Buffer.new(bytes))
494
- end
495
- it 'should emit expected headers' do
496
- subject
497
- # order-perserving compare
498
- expect(@emitted).to eq ex[:streams][nth][:emitted]
499
- end
500
- it 'should update header table' do
501
- subject
502
- expect(@dc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
503
- end
504
- it 'should compute header table size' do
505
- subject
506
- expect(@dc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
567
+ if ex[:streams][nth][:has_bad_headers]
568
+ it 'should raise CompressionError' do
569
+ bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
570
+ expect { @dc.decode(HTTP2::Buffer.new(bytes)) }.to raise_error ProtocolError
571
+ end
572
+ else
573
+ subject do
574
+ bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
575
+ @emitted = @dc.decode(HTTP2::Buffer.new(bytes))
576
+ end
577
+ it 'should emit expected headers' do
578
+ subject
579
+ # partitioned compare
580
+ pseudo_headers, headers = ex[:streams][nth][:emitted].partition { |f, _| f.start_with? ':' }
581
+ partitioned_headers = pseudo_headers + headers
582
+ expect(@emitted).to eq partitioned_headers
583
+ end
584
+ it 'should update header table' do
585
+ subject
586
+ expect(@dc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
587
+ end
588
+ it 'should compute header table size' do
589
+ subject
590
+ expect(@dc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
591
+ end
507
592
  end
508
593
  end
509
594
  end
@@ -513,6 +598,7 @@ RSpec.describe HTTP2::Header do
513
598
 
514
599
  context 'encode' do
515
600
  spec_examples.each do |ex|
601
+ next if ex[:bypass_encoder]
516
602
  context "spec example #{ex[:title]}" do
517
603
  ex[:streams].size.times do |nth|
518
604
  context "request #{nth + 1}" do
@@ -522,22 +608,33 @@ RSpec.describe HTTP2::Header do
522
608
  end
523
609
  before do
524
610
  (0...nth).each do |i|
525
- @cc.encode(ex[:streams][i][:emitted])
611
+ if ex[:streams][i][:has_bad_headers]
612
+ @cc.encode(ex[:streams][i][:emitted], ensure_proper_ordering: false)
613
+ else
614
+ @cc.encode(ex[:streams][i][:emitted])
615
+ end
526
616
  end
527
617
  end
528
618
  subject do
529
- @cc.encode(ex[:streams][nth][:emitted])
619
+ if ex[:streams][nth][:has_bad_headers]
620
+ @cc.encode(ex[:streams][nth][:emitted], ensure_proper_ordering: false)
621
+ else
622
+ @cc.encode(ex[:streams][nth][:emitted])
623
+ end
530
624
  end
531
625
  it 'should emit expected bytes on wire' do
626
+ puts subject.unpack('H*').first
532
627
  expect(subject.unpack('H*').first).to eq ex[:streams][nth][:wire].delete(" \n")
533
628
  end
534
- it 'should update header table' do
535
- subject
536
- expect(@cc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
537
- end
538
- it 'should compute header table size' do
539
- subject
540
- expect(@cc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
629
+ unless ex[:streams][nth][:has_bad_headers]
630
+ it 'should update header table' do
631
+ subject
632
+ expect(@cc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
633
+ end
634
+ it 'should compute header table size' do
635
+ subject
636
+ expect(@cc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
637
+ end
541
638
  end
542
639
  end
543
640
  end
@@ -8,14 +8,18 @@ RSpec.describe HTTP2::Connection do
8
8
  let(:f) { Framer.new }
9
9
 
10
10
  context 'initialization and settings' do
11
- it 'should raise error if first frame is not SETTINGS' do
12
- (FRAME_TYPES - [SETTINGS]).each do |frame|
11
+ (FRAME_TYPES - [SETTINGS]).each do |frame|
12
+ it "should raise error if first frame is #{frame[:type]}" do
13
13
  frame = set_stream_id(f.generate(frame.deep_dup), 0x0)
14
- expect { @conn.dup << frame }.to raise_error(ProtocolError)
14
+ expect { @conn << frame }.to raise_error(ProtocolError)
15
+ expect(@conn).to be_closed
15
16
  end
17
+ end
16
18
 
19
+ it 'should not raise error if first frame is SETTINGS' do
17
20
  expect { @conn << f.generate(SETTINGS.dup) }.to_not raise_error
18
21
  expect(@conn.state).to eq :connected
22
+ expect(@conn).to_not be_closed
19
23
  end
20
24
 
21
25
  it 'should raise error if SETTINGS stream != 0' do
@@ -50,13 +54,19 @@ RSpec.describe HTTP2::Connection do
50
54
  settings = SETTINGS.dup
51
55
  settings[:payload] = [[:settings_header_table_size, 256]]
52
56
 
53
- expect(@conn).to receive(:send) do |frame|
54
- expect(frame[:type]).to eq :settings
55
- expect(frame[:flags]).to eq [:ack]
56
- expect(frame[:payload]).to eq []
57
+ # We should expect two frames here (append .twice) - one for the connection setup, and one for the settings ack.
58
+ frames = []
59
+ expect(@conn).to receive(:send).twice do |frame|
60
+ frames << frame
57
61
  end
58
62
 
63
+ @conn.send_connection_preface
59
64
  @conn << f.generate(settings)
65
+
66
+ frame = frames.last
67
+ expect(frame[:type]).to eq :settings
68
+ expect(frame[:flags]).to eq [:ack]
69
+ expect(frame[:payload]).to eq []
60
70
  end
61
71
  end
62
72
 
@@ -95,6 +105,9 @@ RSpec.describe HTTP2::Connection do
95
105
  s1.receive DATA
96
106
  s2.send DATA.dup
97
107
  expect(@conn.active_stream_count).to eq 0
108
+
109
+ expect(s1).to be_closed
110
+ expect(s2).to be_closed
98
111
  end
99
112
 
100
113
  it 'should not exceed stream limit set by peer' do
@@ -512,10 +525,14 @@ RSpec.describe HTTP2::Connection do
512
525
  expect(last_stream).to eq 17
513
526
  expect(error).to eq :no_error
514
527
  expect(payload).to eq 'test'
528
+
529
+ expect(@conn).to be_closed
515
530
  end
516
531
 
517
532
  it 'should raise error when opening new stream after sending GOAWAY' do
518
533
  @conn.goaway
534
+ expect(@conn).to be_closed
535
+
519
536
  expect { @conn.new_stream }.to raise_error(ConnectionClosed)
520
537
  end
521
538
 
@@ -341,11 +341,11 @@ RSpec.describe HTTP2::Framer do
341
341
  length: 44,
342
342
  type: :altsvc,
343
343
  stream: 1,
344
- max_age: 1_402_290_402, # 4
345
- port: 8080, # 2
346
- proto: 'h2-13', # 1 + 5
347
- host: 'www.example.com', # 1 + 15
348
- origin: 'www.example.com', # 15
344
+ max_age: 1_402_290_402, # 4
345
+ port: 8080, # 2
346
+ proto: 'h2-13', # 1 + 5
347
+ host: 'www.example.com', # 1 + 15
348
+ origin: 'www.example.com', # 15
349
349
  }
350
350
  bytes = f.generate(frame)
351
351
  expected = [0, 43, 0xa, 0, 1, 1_402_290_402, 8080].pack('CnCCNNn')
@@ -10,9 +10,11 @@ Coveralls.wear! if ENV['CI']
10
10
 
11
11
  require 'http/2'
12
12
 
13
+ # rubocop: disable Style/MixinUsage
13
14
  include HTTP2
14
15
  include HTTP2::Header
15
16
  include HTTP2::Error
17
+ # rubocop: enable Style/MixinUsage
16
18
 
17
19
  DATA = {
18
20
  type: :data,
@@ -194,6 +194,7 @@ RSpec.describe HTTP2::Stream do
194
194
  it 'should transition to closed if sending RST_STREAM' do
195
195
  @stream.close
196
196
  expect(@stream.state).to eq :closed
197
+ expect(@stream).to be_closed
197
198
  end
198
199
 
199
200
  it 'should transition to closed if receiving RST_STREAM' do
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.9.1
4
+ version: 0.10.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: 2018-05-06 00:00:00.000000000 Z
12
+ date: 2018-07-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler