http-2 0.7.0 → 0.8.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.
@@ -0,0 +1,191 @@
1
+ require_relative 'helper'
2
+ require 'http_parser'
3
+ require 'base64'
4
+
5
+ options = { port: 8080 }
6
+ OptionParser.new do |opts|
7
+ opts.banner = 'Usage: server.rb [options]'
8
+
9
+ opts.on('-s', '--secure', 'HTTPS mode') do |v|
10
+ options[:secure] = v
11
+ end
12
+
13
+ opts.on('-p', '--port [Integer]', 'listen port') do |v|
14
+ options[:port] = v
15
+ end
16
+ end.parse!
17
+
18
+ puts "Starting server on port #{options[:port]}"
19
+ server = TCPServer.new(options[:port])
20
+
21
+ if options[:secure]
22
+ ctx = OpenSSL::SSL::SSLContext.new
23
+ ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/mycert.pem'))
24
+ ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/mykey.pem'))
25
+ ctx.npn_protocols = [DRAFT]
26
+
27
+ server = OpenSSL::SSL::SSLServer.new(server, ctx)
28
+ end
29
+
30
+ class UpgradeHandler
31
+
32
+ VALID_UPGRADE_METHODS = %w[GET OPTIONS]
33
+ UPGRADE_RESPONSE = ("HTTP/1.1 101 Switching Protocols\n" +
34
+ "Connection: Upgrade\n" +
35
+ "Upgrade: h2c\n\n").freeze
36
+
37
+ attr_reader :complete, :headers, :body, :parsing
38
+
39
+ def initialize conn, sock
40
+ @conn, @sock = conn, sock
41
+ @complete, @parsing = false, false
42
+ @body = ''
43
+ @parser = ::HTTP::Parser.new(self)
44
+ end
45
+
46
+ def <<(data)
47
+ @parsing ||= true
48
+ @parser << data
49
+ if complete
50
+
51
+ @sock.write UPGRADE_RESPONSE
52
+
53
+ settings = headers['http2-settings']
54
+ request = {
55
+ ':scheme' => 'http',
56
+ ':method' => @parser.http_method,
57
+ ':authority' => headers['Host'],
58
+ ':path' => @parser.request_url
59
+ }.merge(headers)
60
+
61
+ @conn.upgrade(settings, request, @body)
62
+ end
63
+ end
64
+
65
+ def complete!; @complete = true; end
66
+
67
+ def on_headers_complete(headers)
68
+ @headers = headers
69
+ end
70
+
71
+ def on_body(chunk)
72
+ @body << chunk
73
+ end
74
+
75
+ def on_message_complete
76
+ raise unless VALID_UPGRADE_METHODS.include?(@parser.http_method)
77
+ @parsing = false
78
+ complete!
79
+ end
80
+
81
+ end
82
+
83
+ loop do
84
+ sock = server.accept
85
+ puts 'New TCP connection!'
86
+
87
+ conn = HTTP2::Server.new
88
+ conn.on(:frame) do |bytes|
89
+ # puts "Writing bytes: #{bytes.unpack("H*").first}"
90
+ sock.write bytes
91
+ end
92
+ conn.on(:frame_sent) do |frame|
93
+ puts "Sent frame: #{frame.inspect}"
94
+ end
95
+ conn.on(:frame_received) do |frame|
96
+ puts "Received frame: #{frame.inspect}"
97
+ end
98
+
99
+ conn.on(:stream) do |stream|
100
+ log = Logger.new(stream.id)
101
+ req, buffer = {}, ''
102
+
103
+ stream.on(:active) { log.info 'client opened new stream' }
104
+ stream.on(:close) do
105
+ log.info 'stream closed'
106
+ end
107
+
108
+ stream.on(:headers) do |h|
109
+ req = Hash[*h.flatten]
110
+ log.info "request headers: #{h}"
111
+ end
112
+
113
+ stream.on(:data) do |d|
114
+ log.info "payload chunk: <<#{d}>>"
115
+ buffer << d
116
+ end
117
+
118
+ stream.on(:half_close) do
119
+ log.info 'client closed its end of the stream'
120
+
121
+ if req['Upgrade']
122
+ log.info "Processing h2c Upgrade request: #{req}"
123
+
124
+ # Don't respond to OPTIONS...
125
+ if req[':method'] != "OPTIONS"
126
+ response = 'Hello h2c world!'
127
+ stream.headers({
128
+ ':status' => '200',
129
+ 'content-length' => response.bytesize.to_s,
130
+ 'content-type' => 'text/plain',
131
+ }, end_stream: false)
132
+ stream.data(response)
133
+ end
134
+ else
135
+
136
+ response = nil
137
+ if req[':method'] == 'POST'
138
+ log.info "Received POST request, payload: #{buffer}"
139
+ response = "Hello HTTP 2.0! POST payload: #{buffer}"
140
+ else
141
+ log.info 'Received GET request'
142
+ response = 'Hello HTTP 2.0! GET request'
143
+ end
144
+
145
+ stream.headers({
146
+ ':status' => '200',
147
+ 'content-length' => response.bytesize.to_s,
148
+ 'content-type' => 'text/plain',
149
+ }, end_stream: false)
150
+
151
+ # split response into multiple DATA frames
152
+ stream.data(response.slice!(0, 5), end_stream: false)
153
+ stream.data(response)
154
+ end
155
+ end
156
+ end
157
+
158
+ uh = UpgradeHandler.new(conn, sock)
159
+
160
+ while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
161
+ data = sock.readpartial(1024)
162
+ # puts "Received bytes: #{data.unpack("H*").first}"
163
+
164
+ begin
165
+ case
166
+ when !uh.parsing && !uh.complete
167
+
168
+ if data.start_with?(*UpgradeHandler::VALID_UPGRADE_METHODS)
169
+ uh << data
170
+ else
171
+ uh.complete!
172
+ conn << data
173
+ end
174
+
175
+ when uh.parsing && !uh.complete
176
+ uh << data
177
+
178
+ when uh.complete
179
+ conn << data
180
+ end
181
+
182
+ rescue => e
183
+ puts "Exception: #{e}, #{e.message} - closing socket."
184
+ puts e.backtrace.last(10).join("\n")
185
+ sock.close
186
+ end
187
+ end
188
+ end
189
+
190
+ # echo foo=bar | nghttp -d - -t 0 -vu http://127.0.0.1:8080/
191
+ # nghttp -vu http://127.0.0.1:8080/
@@ -4,19 +4,20 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'http/2/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "http-2"
7
+ spec.name = 'http-2'
8
8
  spec.version = HTTP2::VERSION
9
- spec.authors = ["Ilya Grigorik", "Kaoru Maeda"]
10
- spec.email = ["ilya@igvita.com"]
11
- spec.description = "Pure-ruby HTTP 2.0 protocol implementation"
9
+ spec.authors = ['Ilya Grigorik', 'Kaoru Maeda']
10
+ spec.email = ['ilya@igvita.com']
11
+ spec.description = 'Pure-ruby HTTP 2.0 protocol implementation'
12
12
  spec.summary = spec.description
13
- spec.homepage = "https://github.com/igrigorik/http-2"
14
- spec.license = "MIT"
13
+ spec.homepage = 'https://github.com/igrigorik/http-2'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>=2.0.0'
15
16
 
16
- spec.files = `git ls-files`.split($/)
17
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
20
21
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency 'bundler', '~> 1.3'
22
23
  end
@@ -1,13 +1,13 @@
1
- require "http/2/version"
2
- require "http/2/error"
3
- require "http/2/emitter"
4
- require "http/2/buffer"
5
- require "http/2/flow_buffer"
6
- require "http/2/huffman"
7
- require "http/2/huffman_statemachine"
8
- require "http/2/compressor"
9
- require "http/2/framer"
10
- require "http/2/connection"
11
- require "http/2/client"
12
- require "http/2/server"
13
- require "http/2/stream"
1
+ require 'http/2/version'
2
+ require 'http/2/error'
3
+ require 'http/2/emitter'
4
+ require 'http/2/buffer'
5
+ require 'http/2/flow_buffer'
6
+ require 'http/2/huffman'
7
+ require 'http/2/huffman_statemachine'
8
+ require 'http/2/compressor'
9
+ require 'http/2/framer'
10
+ require 'http/2/connection'
11
+ require 'http/2/client'
12
+ require 'http/2/server'
13
+ require 'http/2/stream'
@@ -1,27 +1,24 @@
1
1
  module HTTP2
2
-
3
2
  # Simple binary buffer backed by string.
4
3
  #
5
4
  class Buffer < String
6
-
7
- UINT32 = "N"
8
- BINARY = "binary"
9
- private_constant :UINT32, :BINARY
5
+ UINT32 = 'N'.freeze
6
+ private_constant :UINT32
10
7
 
11
8
  # Forces binary encoding on the string
12
- def initialize(data = '')
13
- super(data.force_encoding(BINARY))
9
+ def initialize(*)
10
+ super.force_encoding(Encoding::BINARY)
14
11
  end
15
12
 
16
13
  # Emulate StringIO#read: slice first n bytes from the buffer.
17
14
  #
18
15
  # @param n [Integer] number of bytes to slice from the buffer
19
16
  def read(n)
20
- Buffer.new(slice!(0,n))
17
+ Buffer.new(slice!(0, n))
21
18
  end
22
19
 
23
20
  # Alias getbyte to readbyte
24
- alias :readbyte :getbyte
21
+ alias_method :readbyte, :getbyte
25
22
 
26
23
  # Emulate StringIO#getbyte: slice first byte from buffer.
27
24
  def getbyte
@@ -34,5 +31,4 @@ module HTTP2
34
31
  read(4).unpack(UINT32).first
35
32
  end
36
33
  end
37
-
38
34
  end
@@ -1,5 +1,4 @@
1
1
  module HTTP2
2
-
3
2
  # HTTP 2.0 client connection class that implements appropriate header
4
3
  # compression / decompression algorithms and stream management logic.
5
4
  #
@@ -18,7 +17,6 @@ module HTTP2
18
17
  # end
19
18
  #
20
19
  class Client < Connection
21
-
22
20
  # Initialize new HTTP 2.0 client object.
23
21
  def initialize(**settings)
24
22
  @stream_id = 1
@@ -42,15 +40,12 @@ module HTTP2
42
40
 
43
41
  # Emit the connection preface if not yet
44
42
  def send_connection_preface
45
- if @state == :waiting_connection_preface
46
- @state = :connected
47
- emit(:frame, CONNECTION_PREFACE_MAGIC)
43
+ return unless @state == :waiting_connection_preface
44
+ @state = :connected
45
+ emit(:frame, CONNECTION_PREFACE_MAGIC)
48
46
 
49
- payload = @local_settings.select {|k,v| v != SPEC_DEFAULT_CONNECTION_SETTINGS[k]}
50
- settings(payload)
51
- end
47
+ payload = @local_settings.select { |k, v| v != SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
48
+ settings(payload)
52
49
  end
53
-
54
50
  end
55
-
56
51
  end
@@ -1,101 +1,97 @@
1
1
  module HTTP2
2
-
3
2
  # Implementation of header compression for HTTP 2.0 (HPACK) format adapted
4
3
  # to efficiently represent HTTP headers in the context of HTTP 2.0.
5
4
  #
6
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09
5
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
7
6
  module Header
8
-
9
- BINARY = 'binary'
10
-
11
7
  # To decompress header blocks, a decoder only needs to maintain a
12
- # header table as a decoding context.
8
+ # dynamic table as a decoding context.
13
9
  # No other state information is needed.
14
10
  class EncodingContext
15
11
  include Error
16
12
 
17
13
  # @private
18
14
  # Static table
19
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09#appendix-B
15
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#appendix-A
20
16
  STATIC_TABLE = [
21
- [':authority', '' ],
22
- [':method', 'GET' ],
23
- [':method', 'POST' ],
24
- [':path', '/' ],
25
- [':path', '/index.html' ],
26
- [':scheme', 'http' ],
27
- [':scheme', 'https' ],
28
- [':status', '200' ],
29
- [':status', '204' ],
30
- [':status', '206' ],
31
- [':status', '304' ],
32
- [':status', '400' ],
33
- [':status', '404' ],
34
- [':status', '500' ],
35
- ['accept-charset', '' ],
36
- ['accept-encoding', 'gzip, deflate' ],
37
- ['accept-language', '' ],
38
- ['accept-ranges', '' ],
39
- ['accept', '' ],
40
- ['access-control-allow-origin', '' ],
41
- ['age', '' ],
42
- ['allow', '' ],
43
- ['authorization', '' ],
44
- ['cache-control', '' ],
45
- ['content-disposition', '' ],
46
- ['content-encoding', '' ],
47
- ['content-language', '' ],
48
- ['content-length', '' ],
49
- ['content-location', '' ],
50
- ['content-range', '' ],
51
- ['content-type', '' ],
52
- ['cookie', '' ],
53
- ['date', '' ],
54
- ['etag', '' ],
55
- ['expect', '' ],
56
- ['expires', '' ],
57
- ['from', '' ],
58
- ['host', '' ],
59
- ['if-match', '' ],
60
- ['if-modified-since', '' ],
61
- ['if-none-match', '' ],
62
- ['if-range', '' ],
63
- ['if-unmodified-since', '' ],
64
- ['last-modified', '' ],
65
- ['link', '' ],
66
- ['location', '' ],
67
- ['max-forwards', '' ],
68
- ['proxy-authenticate', '' ],
69
- ['proxy-authorization', '' ],
70
- ['range', '' ],
71
- ['referer', '' ],
72
- ['refresh', '' ],
73
- ['retry-after', '' ],
74
- ['server', '' ],
75
- ['set-cookie', '' ],
76
- ['strict-transport-security', '' ],
77
- ['transfer-encoding', '' ],
78
- ['user-agent', '' ],
79
- ['vary', '' ],
80
- ['via', '' ],
81
- ['www-authenticate', '' ],
82
- ].freeze
17
+ [':authority', ''],
18
+ [':method', 'GET'],
19
+ [':method', 'POST'],
20
+ [':path', '/'],
21
+ [':path', '/index.html'],
22
+ [':scheme', 'http'],
23
+ [':scheme', 'https'],
24
+ [':status', '200'],
25
+ [':status', '204'],
26
+ [':status', '206'],
27
+ [':status', '304'],
28
+ [':status', '400'],
29
+ [':status', '404'],
30
+ [':status', '500'],
31
+ ['accept-charset', ''],
32
+ ['accept-encoding', 'gzip, deflate'],
33
+ ['accept-language', ''],
34
+ ['accept-ranges', ''],
35
+ ['accept', ''],
36
+ ['access-control-allow-origin', ''],
37
+ ['age', ''],
38
+ ['allow', ''],
39
+ ['authorization', ''],
40
+ ['cache-control', ''],
41
+ ['content-disposition', ''],
42
+ ['content-encoding', ''],
43
+ ['content-language', ''],
44
+ ['content-length', ''],
45
+ ['content-location', ''],
46
+ ['content-range', ''],
47
+ ['content-type', ''],
48
+ ['cookie', ''],
49
+ ['date', ''],
50
+ ['etag', ''],
51
+ ['expect', ''],
52
+ ['expires', ''],
53
+ ['from', ''],
54
+ ['host', ''],
55
+ ['if-match', ''],
56
+ ['if-modified-since', ''],
57
+ ['if-none-match', ''],
58
+ ['if-range', ''],
59
+ ['if-unmodified-since', ''],
60
+ ['last-modified', ''],
61
+ ['link', ''],
62
+ ['location', ''],
63
+ ['max-forwards', ''],
64
+ ['proxy-authenticate', ''],
65
+ ['proxy-authorization', ''],
66
+ ['range', ''],
67
+ ['referer', ''],
68
+ ['refresh', ''],
69
+ ['retry-after', ''],
70
+ ['server', ''],
71
+ ['set-cookie', ''],
72
+ ['strict-transport-security', ''],
73
+ ['transfer-encoding', ''],
74
+ ['user-agent', ''],
75
+ ['vary', ''],
76
+ ['via', ''],
77
+ ['www-authenticate', ''],
78
+ ].each { |pair| pair.each(&:freeze).freeze }.freeze
83
79
 
84
80
  # Current table of header key-value pairs.
85
81
  attr_reader :table
86
82
 
87
83
  # Current encoding options
88
84
  #
89
- # :table_size Integer maximum header table size in bytes
85
+ # :table_size Integer maximum dynamic table size in bytes
90
86
  # :huffman Symbol :always, :never, :shorter
91
87
  # :index Symbol :all, :static, :never
92
88
  attr_reader :options
93
89
 
94
90
  # Initializes compression context with appropriate client/server
95
- # defaults and maximum size of the header table.
91
+ # defaults and maximum size of the dynamic table.
96
92
  #
97
93
  # @param options [Hash] encoding options
98
- # :table_size Integer maximum header table size in bytes
94
+ # :table_size Integer maximum dynamic table size in bytes
99
95
  # :huffman Symbol :always, :never, :shorter
100
96
  # :index Symbol :all, :static, :never
101
97
  def initialize(**options)
@@ -115,32 +111,32 @@ module HTTP2
115
111
  other = EncodingContext.new(@options)
116
112
  t = @table
117
113
  l = @limit
118
- other.instance_eval {
114
+ other.instance_eval do
119
115
  @table = t.dup # shallow copy
120
116
  @limit = l
121
- }
117
+ end
122
118
  other
123
119
  end
124
120
 
125
- # Finds an entry in current header table by index.
121
+ # Finds an entry in current dynamic table by index.
126
122
  # Note that index is zero-based in this module.
127
123
  #
128
124
  # If the index is greater than the last index in the static table,
129
- # an entry in the header table is dereferenced.
125
+ # an entry in the dynamic table is dereferenced.
130
126
  #
131
127
  # If the index is greater than the last header index, an error is raised.
132
128
  #
133
- # @param index [Integer] zero-based index in the header table.
129
+ # @param index [Integer] zero-based index in the dynamic table.
134
130
  # @return [Array] +[key, value]+
135
131
  def dereference(index)
136
132
  # NOTE: index is zero-based in this module.
137
- STATIC_TABLE[index] or
138
- @table[index - STATIC_TABLE.size] or
139
- raise CompressionError.new("Index too large")
133
+ value = STATIC_TABLE[index] || @table[index - STATIC_TABLE.size]
134
+ fail CompressionError, 'Index too large' unless value
135
+ value
140
136
  end
141
137
 
142
138
  # Header Block Processing
143
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09#section-4.1
139
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-4.1
144
140
  #
145
141
  # @param cmd [Hash] { type:, name:, value:, index: }
146
142
  # @return [Array] +[name, value]+ header field that is added to the decoded header list
@@ -149,13 +145,13 @@ module HTTP2
149
145
 
150
146
  case cmd[:type]
151
147
  when :changetablesize
152
- set_table_size(cmd[:value])
148
+ self.table_size = cmd[:value]
153
149
 
154
150
  when :indexed
155
151
  # Indexed Representation
156
152
  # An _indexed representation_ entails the following actions:
157
153
  # o The header field corresponding to the referenced entry in either
158
- # the static table or header table is added to the decoded header
154
+ # the static table or dynamic table is added to the decoded header
159
155
  # list.
160
156
  idx = cmd[:name]
161
157
 
@@ -163,14 +159,14 @@ module HTTP2
163
159
  emit = [k, v]
164
160
 
165
161
  when :incremental, :noindex, :neverindexed
166
- # A _literal representation_ that is _not added_ to the header table
162
+ # A _literal representation_ that is _not added_ to the dynamic table
167
163
  # entails the following action:
168
164
  # o The header field is added to the decoded header list.
169
165
 
170
- # A _literal representation_ that is _added_ to the header table
166
+ # A _literal representation_ that is _added_ to the dynamic table
171
167
  # entails the following actions:
172
168
  # o The header field is added to the decoded header list.
173
- # o The header field is inserted at the beginning of the header table.
169
+ # o The header field is inserted at the beginning of the dynamic table.
174
170
 
175
171
  if cmd[:name].is_a? Integer
176
172
  k, v = dereference(cmd[:name])
@@ -183,19 +179,17 @@ module HTTP2
183
179
 
184
180
  emit = [cmd[:name], cmd[:value]]
185
181
 
186
- if cmd[:type] == :incremental
187
- add_to_table(emit)
188
- end
182
+ add_to_table(emit) if cmd[:type] == :incremental
189
183
 
190
184
  else
191
- raise CompressionError.new("Invalid type: #{cmd[:type]}")
185
+ fail CompressionError, "Invalid type: #{cmd[:type]}"
192
186
  end
193
187
 
194
188
  emit
195
189
  end
196
190
 
197
191
  # Plan header compression according to +@options [:index]+
198
- # :never Do not use header table or static table reference at all.
192
+ # :never Do not use dynamic table or static table reference at all.
199
193
  # :static Use static table only.
200
194
  # :all Use all of them.
201
195
  #
@@ -207,9 +201,7 @@ module HTTP2
207
201
  noindex = [:static, :never].include?(@options[:index])
208
202
  headers.each do |h|
209
203
  cmd = addcmd(h)
210
- if noindex && cmd[:type] == :incremental
211
- cmd[:type] = :noindex
212
- end
204
+ cmd[:type] = :noindex if noindex && cmd[:type] == :incremental
213
205
  commands << cmd
214
206
  process(cmd)
215
207
  end
@@ -217,12 +209,12 @@ module HTTP2
217
209
  end
218
210
 
219
211
  # Emits command for a header.
220
- # Prefer static table over header table.
212
+ # Prefer static table over dynamic table.
221
213
  # Prefer exact match over name-only match.
222
214
  #
223
- # +@options [:index]+ controls whether to use the header table,
215
+ # +@options [:index]+ controls whether to use the dynamic table,
224
216
  # static table, or both.
225
- # :never Do not use header table or static table reference at all.
217
+ # :never Do not use dynamic table or static table reference at all.
226
218
  # :static Use static table only.
227
219
  # :all Use all of them.
228
220
  #
@@ -262,9 +254,9 @@ module HTTP2
262
254
  end
263
255
  end
264
256
 
265
- # Alter header table size.
257
+ # Alter dynamic table size.
266
258
  # When the size is reduced, some headers might be evicted.
267
- def set_table_size(size)
259
+ def table_size=(size)
268
260
  @limit = size
269
261
  size_check(nil)
270
262
  end
@@ -272,51 +264,49 @@ module HTTP2
272
264
  # Returns current table size in octets
273
265
  # @return [Integer]
274
266
  def current_table_size
275
- @table.inject(0){|r,(k,v)| r += k.bytesize + v.bytesize + 32 }
267
+ @table.inject(0) { |r, (k, v)| r + k.bytesize + v.bytesize + 32 }
276
268
  end
277
269
 
278
270
  private
279
271
 
280
- # Add a name-value pair to the header table.
272
+ # Add a name-value pair to the dynamic table.
281
273
  # Older entries might have been evicted so that
282
- # the new entry fits in the header table.
274
+ # the new entry fits in the dynamic table.
283
275
  #
284
276
  # @param cmd [Array] +[name, value]+
285
277
  def add_to_table(cmd)
286
- if size_check(cmd)
287
- @table.unshift(cmd)
288
- end
278
+ return unless size_check(cmd)
279
+ @table.unshift(cmd)
289
280
  end
290
281
 
291
- # To keep the header table size lower than or equal to @limit,
292
- # remove one or more entries at the end of the header table.
282
+ # To keep the dynamic table size lower than or equal to @limit,
283
+ # remove one or more entries at the end of the dynamic table.
293
284
  #
294
285
  # @param cmd [Hash]
295
- # @return [Boolean] whether +cmd+ fits in the header table.
286
+ # @return [Boolean] whether +cmd+ fits in the dynamic table.
296
287
  def size_check(cmd)
297
288
  cursize = current_table_size
298
289
  cmdsize = cmd.nil? ? 0 : cmd[0].bytesize + cmd[1].bytesize + 32
299
290
 
300
- while cursize + cmdsize > @limit do
291
+ while cursize + cmdsize > @limit
301
292
  break if @table.empty?
302
293
 
303
- last_index = @table.size - 1
304
294
  e = @table.pop
305
295
  cursize -= e[0].bytesize + e[1].bytesize + 32
306
296
  end
307
297
 
308
- return cmdsize <= @limit
298
+ cmdsize <= @limit
309
299
  end
310
300
  end
311
301
 
312
302
  # Header representation as defined by the spec.
313
303
  HEADREP = {
314
- indexed: {prefix: 7, pattern: 0x80},
315
- incremental: {prefix: 6, pattern: 0x40},
316
- noindex: {prefix: 4, pattern: 0x00},
317
- neverindexed: {prefix: 4, pattern: 0x10},
318
- changetablesize: {prefix: 5, pattern: 0x20},
319
- }
304
+ indexed: { prefix: 7, pattern: 0x80 },
305
+ incremental: { prefix: 6, pattern: 0x40 },
306
+ noindex: { prefix: 4, pattern: 0x00 },
307
+ neverindexed: { prefix: 4, pattern: 0x10 },
308
+ changetablesize: { prefix: 5, pattern: 0x20 },
309
+ }.each_value(&:freeze).freeze
320
310
 
321
311
  # Predefined options set for Compressor
322
312
  # http://mew.org/~kazu/material/2014-hpack.pdf
@@ -336,14 +326,14 @@ module HTTP2
336
326
  @cc = EncodingContext.new(options)
337
327
  end
338
328
 
339
- # Set header table size in EncodingContext
340
- # @param size [Integer] new header table size
341
- def set_table_size(size)
342
- @cc.set_table_size(size)
329
+ # Set dynamic table size in EncodingContext
330
+ # @param size [Integer] new dynamic table size
331
+ def table_size=(size)
332
+ @cc.table_size = size
343
333
  end
344
334
 
345
335
  # Encodes provided value via integer representation.
346
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09#section-6.1
336
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.1
347
337
  #
348
338
  # If I < 2^N - 1, encode I on N bits
349
339
  # Else
@@ -359,15 +349,15 @@ module HTTP2
359
349
  # @return [String] binary string
360
350
  def integer(i, n)
361
351
  limit = 2**n - 1
362
- return [i].pack('C') if (i < limit)
352
+ return [i].pack('C') if i < limit
363
353
 
364
354
  bytes = []
365
- bytes.push limit if !n.zero?
355
+ bytes.push limit unless n.zero?
366
356
 
367
357
  i -= limit
368
- while (i >= 128) do
358
+ while (i >= 128)
369
359
  bytes.push((i % 128) + 128)
370
- i = i / 128
360
+ i /= 128
371
361
  end
372
362
 
373
363
  bytes.push i
@@ -375,7 +365,7 @@ module HTTP2
375
365
  end
376
366
 
377
367
  # Encodes provided value via string literal representation.
378
- # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09#section-6.2
368
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.2
379
369
  #
380
370
  # * The string length, defined as the number of bytes needed to store
381
371
  # its UTF-8 representation, is represented as an integer with a seven
@@ -397,7 +387,7 @@ module HTTP2
397
387
  def string(str)
398
388
  plain, huffman = nil, nil
399
389
  unless @cc.options[:huffman] == :always
400
- plain = integer(str.bytesize, 7) << str.dup.force_encoding(BINARY)
390
+ plain = integer(str.bytesize, 7) << str.dup.force_encoding(Encoding::BINARY)
401
391
  end
402
392
  unless @cc.options[:huffman] == :never
403
393
  huffman = Huffman.new.encode(str)
@@ -424,12 +414,12 @@ module HTTP2
424
414
 
425
415
  case h[:type]
426
416
  when :indexed
427
- buffer << integer(h[:name]+1, rep[:prefix])
417
+ buffer << integer(h[:name] + 1, rep[:prefix])
428
418
  when :changetablesize
429
419
  buffer << integer(h[:value], rep[:prefix])
430
420
  else
431
421
  if h[:name].is_a? Integer
432
- buffer << integer(h[:name]+1, rep[:prefix])
422
+ buffer << integer(h[:name] + 1, rep[:prefix])
433
423
  else
434
424
  buffer << integer(0, rep[:prefix])
435
425
  buffer << string(h[:name])
@@ -454,7 +444,7 @@ module HTTP2
454
444
 
455
445
  # Literal header names MUST be translated to lowercase before
456
446
  # encoding and transmission.
457
- headers.map! {|hk,hv| [hk.downcase, hv] }
447
+ headers.map! { |hk, hv| [hk.downcase, hv] }
458
448
 
459
449
  commands = @cc.encode(headers)
460
450
  commands.each do |cmd|
@@ -478,10 +468,10 @@ module HTTP2
478
468
  @cc = EncodingContext.new(options)
479
469
  end
480
470
 
481
- # Set header table size in EncodingContext
482
- # @param size [Integer] new header table size
483
- def set_table_size(size)
484
- @cc.set_table_size(size)
471
+ # Set dynamic table size in EncodingContext
472
+ # @param size [Integer] new dynamic table size
473
+ def table_size=(size)
474
+ @cc.table_size = size
485
475
  end
486
476
 
487
477
  # Decodes integer value from provided buffer.
@@ -494,7 +484,7 @@ module HTTP2
494
484
  i = !n.zero? ? (buf.getbyte & limit) : 0
495
485
 
496
486
  m = 0
497
- while byte = buf.getbyte do
487
+ while (byte = buf.getbyte)
498
488
  i += ((byte & 127) << m)
499
489
  m += 7
500
490
 
@@ -513,10 +503,9 @@ module HTTP2
513
503
  huffman = (buf.readbyte(0) & 0x80) == 0x80
514
504
  len = integer(buf, 7)
515
505
  str = buf.read(len)
516
- str.bytesize == len or raise CompressionError.new("string too short")
517
- huffman and str = Huffman.new.decode(Buffer.new(str))
518
- str = str.force_encoding('utf-8')
519
- str
506
+ fail CompressionError, 'string too short' unless str.bytesize == len
507
+ str = Huffman.new.decode(Buffer.new(str)) if huffman
508
+ str.force_encoding(Encoding::UTF_8)
520
509
  end
521
510
 
522
511
  # Decodes header command from provided buffer.
@@ -527,18 +516,18 @@ module HTTP2
527
516
  peek = buf.readbyte(0)
528
517
 
529
518
  header = {}
530
- header[:type], type = HEADREP.select do |t, desc|
519
+ header[:type], type = HEADREP.find do |_t, desc|
531
520
  mask = (peek >> desc[:prefix]) << desc[:prefix]
532
521
  mask == desc[:pattern]
533
- end.first
522
+ end
534
523
 
535
- header[:type] or raise CompressionError
524
+ fail CompressionError unless header[:type]
536
525
 
537
526
  header[:name] = integer(buf, type[:prefix])
538
527
 
539
528
  case header[:type]
540
529
  when :indexed
541
- header[:name] == 0 and raise CompressionError.new
530
+ fail CompressionError if header[:name] == 0
542
531
  header[:name] -= 1
543
532
  when :changetablesize
544
533
  header[:value] = header[:name]
@@ -560,10 +549,9 @@ module HTTP2
560
549
  # @return [Array] +[[name, value], ...]+
561
550
  def decode(buf)
562
551
  list = []
563
- list << @cc.process(header(buf)) while !buf.empty?
552
+ list << @cc.process(header(buf)) until buf.empty?
564
553
  list.compact
565
554
  end
566
555
  end
567
-
568
556
  end
569
557
  end