http-2 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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