http-2-next 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/http/2/next/base64.rb +8 -0
- data/lib/http/2/next/connection.rb +7 -5
- data/lib/http/2/next/extensions.rb +29 -0
- data/lib/http/2/next/framer.rb +36 -36
- data/lib/http/2/next/header/compressor.rb +37 -17
- data/lib/http/2/next/header/huffman.rb +1 -0
- data/lib/http/2/next/server.rb +8 -6
- data/lib/http/2/next/stream.rb +1 -1
- data/lib/http/2/next/version.rb +1 -1
- data/sig/extensions.rbs +5 -0
- data/sig/framer.rbs +4 -2
- data/sig/header/compressor.rbs +9 -5
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a97513d9cfdae554f85ed0cbd3d0818ae0c3c6dc5bdf418a126d3a1993f1c52
|
4
|
+
data.tar.gz: a8712f76a43721ca5d67f281d2689853f7d6cf4ca224704e500c34ae03e10a53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 303e98009c8a47229747659e4dfc4bd33636a52de907bc1164f1f33b4a4ba4a0e051459e30853cae3e88a5fdbb9b84d7e6fbbe387b51f8870e16d7923a28e757
|
7
|
+
data.tar.gz: c40a73c72fe006f2a371dda23adb7ba09dc11e791a4b7a7781a6e5e4593bbfd75e4a5a06e8e6b7755c1335857c84b3077430b6261337e039578f3978937f9e82
|
data/lib/http/2/next/base64.rb
CHANGED
@@ -16,6 +16,14 @@ elsif !defined?(Base64)
|
|
16
16
|
str.unpack1("m")
|
17
17
|
end
|
18
18
|
|
19
|
+
def strict_encode64(bin)
|
20
|
+
[bin].pack("m0")
|
21
|
+
end
|
22
|
+
|
23
|
+
def strict_decode64(str)
|
24
|
+
str.unpack1("m0")
|
25
|
+
end
|
26
|
+
|
19
27
|
def urlsafe_encode64(bin, padding: true)
|
20
28
|
str = strict_encode64(bin)
|
21
29
|
str.chomp!("==") or str.chomp!("=") unless padding
|
@@ -731,11 +731,13 @@ module HTTP2Next
|
|
731
731
|
# to any in-flight frames while close is registered on both sides.
|
732
732
|
# References to such streams will be purged whenever another stream
|
733
733
|
# is closed, with a minimum of 15s RTT time window.
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
734
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
735
|
+
|
736
|
+
# TODO: use a drop_while! variant whenever there is one.
|
737
|
+
@streams_recently_closed = @streams_recently_closed.drop_while do |_, v|
|
738
|
+
(now - v) > 15
|
739
|
+
end.to_h
|
740
|
+
|
739
741
|
@streams_recently_closed[id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
740
742
|
end
|
741
743
|
|
@@ -21,4 +21,33 @@ module HTTP2Next
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
# this mixin handles backwards-compatibility for the new packing options
|
26
|
+
# shipping with ruby 3.3 (see https://docs.ruby-lang.org/en/3.3/packed_data_rdoc.html)
|
27
|
+
module PackingExtensions
|
28
|
+
if RUBY_VERSION < "3.3.0"
|
29
|
+
def pack(array_to_pack, template, buffer:, offset: -1)
|
30
|
+
packed_str = array_to_pack.pack(template)
|
31
|
+
case offset
|
32
|
+
when -1
|
33
|
+
buffer << packed_str
|
34
|
+
when 0
|
35
|
+
buffer.prepend(packed_str)
|
36
|
+
else
|
37
|
+
buffer.insert(offset, packed_str)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
else
|
41
|
+
def pack(array_to_pack, template, buffer:, offset: -1)
|
42
|
+
case offset
|
43
|
+
when -1
|
44
|
+
array_to_pack.pack(template, buffer: buffer)
|
45
|
+
when 0
|
46
|
+
buffer.prepend(array_to_pack.pack(template))
|
47
|
+
else
|
48
|
+
buffer.insert(offset, array_to_pack.pack(template))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
24
53
|
end
|
data/lib/http/2/next/framer.rb
CHANGED
@@ -7,6 +7,7 @@ module HTTP2Next
|
|
7
7
|
using StringExtensions
|
8
8
|
|
9
9
|
include Error
|
10
|
+
include PackingExtensions
|
10
11
|
|
11
12
|
# Default value of max frame size (16384 bytes)
|
12
13
|
DEFAULT_MAX_FRAME_SIZE = 2 << 13
|
@@ -123,10 +124,9 @@ module HTTP2Next
|
|
123
124
|
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1
|
124
125
|
#
|
125
126
|
# @param frame [Hash]
|
127
|
+
# @param buffer [String] buffer to pack bytes into
|
126
128
|
# @return [String]
|
127
|
-
def common_header(frame)
|
128
|
-
header = []
|
129
|
-
|
129
|
+
def common_header(frame, buffer:)
|
130
130
|
raise CompressionError, "Invalid frame type (#{frame[:type]})" unless FRAME_TYPES[frame[:type]]
|
131
131
|
|
132
132
|
raise CompressionError, "Frame size is too large: #{frame[:length]}" if frame[:length] > @remote_max_frame_size
|
@@ -139,18 +139,18 @@ module HTTP2Next
|
|
139
139
|
raise CompressionError, "Window increment (#{frame[:increment]}) is too large"
|
140
140
|
end
|
141
141
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
142
|
+
pack([
|
143
|
+
(frame[:length] >> FRAME_LENGTH_HISHIFT),
|
144
|
+
(frame[:length] & FRAME_LENGTH_LOMASK),
|
145
|
+
FRAME_TYPES[frame[:type]],
|
146
|
+
frame[:flags].reduce(0) do |acc, f|
|
147
|
+
position = FRAME_FLAGS[frame[:type]][f]
|
148
|
+
raise CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}" unless position
|
149
|
+
|
150
|
+
acc | (1 << position)
|
151
|
+
end,
|
152
|
+
frame[:stream]
|
153
|
+
], HEADERPACK, buffer: buffer, offset: 0) # 8+16,8,8,32
|
154
154
|
end
|
155
155
|
|
156
156
|
# Decodes common 9-byte header.
|
@@ -200,8 +200,8 @@ module HTTP2Next
|
|
200
200
|
end
|
201
201
|
|
202
202
|
if frame[:flags].include? :priority
|
203
|
-
|
204
|
-
|
203
|
+
pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
|
204
|
+
pack([frame[:weight] - 1], UINT8, buffer: bytes)
|
205
205
|
length += 5
|
206
206
|
end
|
207
207
|
|
@@ -213,12 +213,12 @@ module HTTP2Next
|
|
213
213
|
raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
|
214
214
|
end
|
215
215
|
|
216
|
-
|
217
|
-
|
216
|
+
pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
|
217
|
+
pack([frame[:weight] - 1], UINT8, buffer: bytes)
|
218
218
|
length += 5
|
219
219
|
|
220
220
|
when :rst_stream
|
221
|
-
|
221
|
+
pack_error(frame[:error], buffer: bytes)
|
222
222
|
length += 4
|
223
223
|
|
224
224
|
when :settings
|
@@ -233,13 +233,13 @@ module HTTP2Next
|
|
233
233
|
raise CompressionError, "Unknown settings ID for #{k}" if k.nil?
|
234
234
|
end
|
235
235
|
|
236
|
-
|
237
|
-
|
236
|
+
pack([k], UINT16, buffer: bytes)
|
237
|
+
pack([v], UINT32, buffer: bytes)
|
238
238
|
length += 6
|
239
239
|
end
|
240
240
|
|
241
241
|
when :push_promise
|
242
|
-
|
242
|
+
pack([frame[:promise_stream] & RBIT], UINT32, buffer: bytes)
|
243
243
|
bytes << frame[:payload]
|
244
244
|
length += 4 + frame[:payload].bytesize
|
245
245
|
|
@@ -250,8 +250,8 @@ module HTTP2Next
|
|
250
250
|
length += 8
|
251
251
|
|
252
252
|
when :goaway
|
253
|
-
|
254
|
-
|
253
|
+
pack([frame[:last_stream] & RBIT], UINT32, buffer: bytes)
|
254
|
+
pack_error(frame[:error], buffer: bytes)
|
255
255
|
length += 8
|
256
256
|
|
257
257
|
if frame[:payload]
|
@@ -260,7 +260,7 @@ module HTTP2Next
|
|
260
260
|
end
|
261
261
|
|
262
262
|
when :window_update
|
263
|
-
|
263
|
+
pack([frame[:increment] & RBIT], UINT32, buffer: bytes)
|
264
264
|
length += 4
|
265
265
|
|
266
266
|
when :continuation
|
@@ -268,26 +268,26 @@ module HTTP2Next
|
|
268
268
|
length += frame[:payload].bytesize
|
269
269
|
|
270
270
|
when :altsvc
|
271
|
-
|
271
|
+
pack([frame[:max_age], frame[:port]], UINT32 + UINT16, buffer: bytes)
|
272
272
|
length += 6
|
273
273
|
if frame[:proto]
|
274
274
|
raise CompressionError, "Proto too long" if frame[:proto].bytesize > 255
|
275
275
|
|
276
|
-
|
276
|
+
pack([frame[:proto].bytesize], UINT8, buffer: bytes)
|
277
277
|
bytes << frame[:proto]
|
278
278
|
length += 1 + frame[:proto].bytesize
|
279
279
|
else
|
280
|
-
|
280
|
+
pack([0], UINT8, buffer: bytes)
|
281
281
|
length += 1
|
282
282
|
end
|
283
283
|
if frame[:host]
|
284
284
|
raise CompressionError, "Host too long" if frame[:host].bytesize > 255
|
285
285
|
|
286
|
-
|
286
|
+
pack([frame[:host].bytesize], UINT8, buffer: bytes)
|
287
287
|
bytes << frame[:host]
|
288
288
|
length += 1 + frame[:host].bytesize
|
289
289
|
else
|
290
|
-
|
290
|
+
pack([0], UINT8, buffer: bytes)
|
291
291
|
length += 1
|
292
292
|
end
|
293
293
|
if frame[:origin]
|
@@ -297,7 +297,7 @@ module HTTP2Next
|
|
297
297
|
|
298
298
|
when :origin
|
299
299
|
frame[:payload].each do |origin|
|
300
|
-
|
300
|
+
pack([origin.bytesize], UINT16, buffer: bytes)
|
301
301
|
bytes << origin
|
302
302
|
length += 2 + origin.bytesize
|
303
303
|
end
|
@@ -319,7 +319,7 @@ module HTTP2Next
|
|
319
319
|
end
|
320
320
|
|
321
321
|
length += padlen
|
322
|
-
|
322
|
+
pack([padlen -= 1], UINT8, buffer: bytes, offset: 0)
|
323
323
|
frame[:flags] << :padded
|
324
324
|
|
325
325
|
# Padding: Padding octets that contain no application semantic value.
|
@@ -329,7 +329,7 @@ module HTTP2Next
|
|
329
329
|
end
|
330
330
|
|
331
331
|
frame[:length] = length
|
332
|
-
|
332
|
+
common_header(frame, buffer: bytes)
|
333
333
|
end
|
334
334
|
|
335
335
|
# Decodes complete HTTP/2 frame from provided buffer. If the buffer
|
@@ -456,14 +456,14 @@ module HTTP2Next
|
|
456
456
|
|
457
457
|
private
|
458
458
|
|
459
|
-
def pack_error(error)
|
459
|
+
def pack_error(error, buffer:)
|
460
460
|
unless error.is_a? Integer
|
461
461
|
error = DEFINED_ERRORS[error]
|
462
462
|
|
463
463
|
raise CompressionError, "Unknown error ID for #{error}" unless error
|
464
464
|
end
|
465
465
|
|
466
|
-
[error]
|
466
|
+
pack([error], UINT32, buffer: buffer)
|
467
467
|
end
|
468
468
|
|
469
469
|
def unpack_error(error)
|
@@ -4,6 +4,8 @@ module HTTP2Next
|
|
4
4
|
module Header
|
5
5
|
# Responsible for encoding header key-value pairs using HPACK algorithm.
|
6
6
|
class Compressor
|
7
|
+
include PackingExtensions
|
8
|
+
|
7
9
|
# @param options [Hash] encoding options
|
8
10
|
def initialize(options = {})
|
9
11
|
@cc = EncodingContext.new(options)
|
@@ -29,10 +31,12 @@ module HTTP2Next
|
|
29
31
|
#
|
30
32
|
# @param i [Integer] value to encode
|
31
33
|
# @param n [Integer] number of available bits
|
34
|
+
# @param buffer [String] buffer to pack bytes into
|
35
|
+
# @param offset [Integer] offset to insert packed bytes in buffer
|
32
36
|
# @return [String] binary string
|
33
|
-
def integer(i, n)
|
37
|
+
def integer(i, n, buffer:, offset: 0)
|
34
38
|
limit = (2**n) - 1
|
35
|
-
return [i]
|
39
|
+
return pack([i], "C", buffer: buffer, offset: offset) if i < limit
|
36
40
|
|
37
41
|
bytes = []
|
38
42
|
bytes.push limit unless n.zero?
|
@@ -44,7 +48,7 @@ module HTTP2Next
|
|
44
48
|
end
|
45
49
|
|
46
50
|
bytes.push i
|
47
|
-
|
51
|
+
pack(bytes, "C*", buffer: buffer, offset: offset)
|
48
52
|
end
|
49
53
|
|
50
54
|
# Encodes provided value via string literal representation.
|
@@ -68,20 +72,16 @@ module HTTP2Next
|
|
68
72
|
# @param str [String]
|
69
73
|
# @return [String] binary string
|
70
74
|
def string(str)
|
71
|
-
plain = nil
|
72
|
-
huffman = nil
|
73
|
-
plain = integer(str.bytesize, 7) << str.dup.force_encoding(Encoding::BINARY) unless @cc.options[:huffman] == :always
|
74
|
-
unless @cc.options[:huffman] == :never
|
75
|
-
huffman = Huffman.new.encode(str)
|
76
|
-
huffman = integer(huffman.bytesize, 7) << huffman
|
77
|
-
huffman.setbyte(0, huffman.ord | 0x80)
|
78
|
-
end
|
79
75
|
case @cc.options[:huffman]
|
80
76
|
when :always
|
81
|
-
|
77
|
+
huffman_string(str)
|
82
78
|
when :never
|
83
|
-
|
79
|
+
plain_string(str)
|
84
80
|
else
|
81
|
+
huffman = huffman_string(str)
|
82
|
+
|
83
|
+
plain = plain_string(str)
|
84
|
+
|
85
85
|
huffman.bytesize < plain.bytesize ? huffman : plain
|
86
86
|
end
|
87
87
|
end
|
@@ -96,14 +96,14 @@ module HTTP2Next
|
|
96
96
|
|
97
97
|
case h[:type]
|
98
98
|
when :indexed
|
99
|
-
|
99
|
+
integer(h[:name] + 1, rep[:prefix], buffer: buffer)
|
100
100
|
when :changetablesize
|
101
|
-
|
101
|
+
integer(h[:value], rep[:prefix], buffer: buffer)
|
102
102
|
else
|
103
103
|
if h[:name].is_a? Integer
|
104
|
-
|
104
|
+
integer(h[:name] + 1, rep[:prefix], buffer: buffer)
|
105
105
|
else
|
106
|
-
|
106
|
+
integer(0, rep[:prefix], buffer: buffer)
|
107
107
|
buffer << string(h[:name])
|
108
108
|
end
|
109
109
|
|
@@ -132,6 +132,26 @@ module HTTP2Next
|
|
132
132
|
|
133
133
|
buffer
|
134
134
|
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# @param str [String]
|
139
|
+
# @return [String] binary string
|
140
|
+
def huffman_string(str)
|
141
|
+
huffman = Huffman.new.encode(str)
|
142
|
+
integer(huffman.bytesize, 7, buffer: huffman, offset: 0)
|
143
|
+
huffman.setbyte(0, huffman.ord | 0x80)
|
144
|
+
huffman
|
145
|
+
end
|
146
|
+
|
147
|
+
# @param str [String]
|
148
|
+
# @return [String] binary string
|
149
|
+
def plain_string(str)
|
150
|
+
plain = "".b
|
151
|
+
integer(str.bytesize, 7, buffer: plain)
|
152
|
+
plain << str.dup.force_encoding(Encoding::BINARY)
|
153
|
+
plain
|
154
|
+
end
|
135
155
|
end
|
136
156
|
end
|
137
157
|
end
|
data/lib/http/2/next/server.rb
CHANGED
@@ -80,13 +80,15 @@ module HTTP2Next
|
|
80
80
|
# Process received HTTP2-Settings payload
|
81
81
|
buf = "".b
|
82
82
|
buf << Base64.urlsafe_decode64(settings.to_s)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
83
|
+
@framer.common_header(
|
84
|
+
{
|
85
|
+
length: buf.bytesize,
|
86
|
+
type: :settings,
|
87
|
+
stream: 0,
|
88
|
+
flags: []
|
89
|
+
},
|
90
|
+
buffer: buf
|
88
91
|
)
|
89
|
-
buf.prepend(header)
|
90
92
|
receive(buf)
|
91
93
|
|
92
94
|
# Activate stream (id: 1) with on HTTP/1.1 request parameters
|
data/lib/http/2/next/stream.rb
CHANGED
@@ -170,7 +170,7 @@ module HTTP2Next
|
|
170
170
|
trailers = frame[:payload]
|
171
171
|
return unless trailers.respond_to?(:each)
|
172
172
|
|
173
|
-
trailers.each do |field, _|
|
173
|
+
trailers.each do |field, _| # rubocop:disable Style/HashEachMethods
|
174
174
|
@_trailers.delete(field)
|
175
175
|
break if @_trailers.empty?
|
176
176
|
end
|
data/lib/http/2/next/version.rb
CHANGED
data/sig/extensions.rbs
ADDED
data/sig/framer.rbs
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module HTTP2Next
|
2
2
|
class Framer
|
3
|
+
include PackingExtensions
|
4
|
+
|
3
5
|
DEFAULT_MAX_FRAME_SIZE: Integer
|
4
6
|
|
5
7
|
MAX_STREAM_ID: Integer
|
@@ -33,7 +35,7 @@ module HTTP2Next
|
|
33
35
|
|
34
36
|
attr_accessor remote_max_frame_size: Integer
|
35
37
|
|
36
|
-
def common_header: (frame) -> String
|
38
|
+
def common_header: (frame, buffer: String) -> String
|
37
39
|
|
38
40
|
def read_common_frame: (String) -> frame
|
39
41
|
|
@@ -45,7 +47,7 @@ module HTTP2Next
|
|
45
47
|
|
46
48
|
def initialize: (?Integer local_max_frame_size, ?Integer remote_max_frame_size) -> untyped
|
47
49
|
|
48
|
-
def pack_error: (Integer | Symbol error) -> String
|
50
|
+
def pack_error: (Integer | Symbol error, buffer: String) -> String
|
49
51
|
|
50
52
|
def unpack_error: (Integer) -> (Symbol | Integer)
|
51
53
|
end
|
data/sig/header/compressor.rbs
CHANGED
@@ -1,23 +1,27 @@
|
|
1
1
|
module HTTP2Next
|
2
2
|
module Header
|
3
3
|
class Compressor
|
4
|
+
include PackingExtensions
|
5
|
+
|
4
6
|
@cc: EncodingContext
|
5
7
|
|
6
8
|
def table_size=: (Integer) -> void
|
7
9
|
|
8
|
-
def integer: (Integer, Integer) -> String
|
10
|
+
def integer: (Integer, Integer, buffer: String, ?offset: Integer) -> String
|
9
11
|
|
10
12
|
def string: (String) -> String
|
11
13
|
|
12
|
-
def header: (header_command, String) -> String
|
13
|
-
| (header_command) -> String
|
14
|
+
def header: (header_command, ?String) -> String
|
14
15
|
|
15
16
|
def encode: (Enumerable[header_pair]) -> String
|
16
17
|
|
17
18
|
private
|
18
19
|
|
19
|
-
def initialize: (context_hash options) ->
|
20
|
-
|
20
|
+
def initialize: (?context_hash options) -> void
|
21
|
+
|
22
|
+
def huffman_string: (String str) -> String
|
23
|
+
|
24
|
+
def plain_string: (String str) -> String
|
21
25
|
end
|
22
26
|
end
|
23
27
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http-2-next
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2024-01-11 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description: Pure-ruby HTTP 2.0 protocol implementation
|
16
16
|
email:
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- sig/connection.rbs
|
44
44
|
- sig/emitter.rbs
|
45
45
|
- sig/error.rbs
|
46
|
+
- sig/extensions.rbs
|
46
47
|
- sig/flow_buffer.rbs
|
47
48
|
- sig/frame_buffer.rbs
|
48
49
|
- sig/framer.rbs
|