http-2 1.2.2 → 1.2.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/LICENSE +21 -0
- data/lib/http/2/base64.rb +8 -8
- data/lib/http/2/client.rb +1 -1
- data/lib/http/2/connection.rb +14 -32
- data/lib/http/2/framer.rb +3 -3
- data/lib/http/2/header/compressor.rb +9 -37
- data/lib/http/2/header/decompressor.rb +9 -28
- data/lib/http/2/header/encoding_context.rb +21 -59
- data/lib/http/2/header/huffman.rb +3 -10
- data/lib/http/2/server.rb +2 -2
- data/lib/http/2/settings.rb +20 -4
- data/lib/http/2/version.rb +1 -1
- data/lib/http/2.rb +1 -0
- data/sig/2.rbs +6 -13
- data/sig/connection.rbs +3 -13
- data/sig/header/compressor.rbs +1 -1
- data/sig/header/decompressor.rbs +1 -1
- data/sig/header/encoding_context.rbs +2 -4
- data/sig/server.rbs +2 -0
- data/sig/settings.rbs +71 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa40c026d7345ba9879c03ce22a9ca6c8ec981482381fd0f89c2da5f4d814214
|
|
4
|
+
data.tar.gz: 9cf43f76f7aed43428b636dca605c05bc8c882bfd2f56cabbadaeff71c42648b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56813420638e4b6b8e310d64fef3a284ae0c597ce77e8fc83ae316f6cf3070a216fe943c5fd8b0cb9741e8103cf64ec17317e6af45163d355489f55d8f17379b
|
|
7
|
+
data.tar.gz: c6af72766c395fb4ef21ba2c6de0ef86dc1432421b4c8b0cdc486e4f006891d8e8f104225f98b7950d2ca31d111f97a2a1f42ba6633aeb62845c712da0d139e5
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013 Ilya Grigorik
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/lib/http/2/base64.rb
CHANGED
|
@@ -30,16 +30,16 @@ elsif !defined?(Base64)
|
|
|
30
30
|
str.tr!("+/", "-_")
|
|
31
31
|
str
|
|
32
32
|
end
|
|
33
|
-
end
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
def urlsafe_decode64(str)
|
|
35
|
+
if !str.end_with?("=") && str.length % 4 != 0
|
|
36
|
+
str = str.ljust((str.length + 3) & ~3, "=")
|
|
37
|
+
str.tr!("-_", "+/")
|
|
38
|
+
else
|
|
39
|
+
str = str.tr("-_", "+/")
|
|
40
|
+
end
|
|
41
|
+
strict_decode64(str)
|
|
41
42
|
end
|
|
42
|
-
strict_decode64(str)
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
data/lib/http/2/client.rb
CHANGED
|
@@ -64,7 +64,7 @@ module HTTP2
|
|
|
64
64
|
@state = :connected
|
|
65
65
|
emit(:frame, CONNECTION_PREFACE_MAGIC)
|
|
66
66
|
|
|
67
|
-
payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
|
67
|
+
payload = @local_settings.connection_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
|
68
68
|
settings(payload)
|
|
69
69
|
end
|
|
70
70
|
|
data/lib/http/2/connection.rb
CHANGED
|
@@ -10,25 +10,6 @@ module HTTP2
|
|
|
10
10
|
# Default stream_limit
|
|
11
11
|
DEFAULT_MAX_CONCURRENT_STREAMS = 100
|
|
12
12
|
|
|
13
|
-
# Default values for SETTINGS frame, as defined by the spec.
|
|
14
|
-
SPEC_DEFAULT_CONNECTION_SETTINGS = {
|
|
15
|
-
settings_header_table_size: 4096,
|
|
16
|
-
settings_enable_push: 1, # enabled for servers
|
|
17
|
-
settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
|
|
18
|
-
settings_initial_window_size: 65_535,
|
|
19
|
-
settings_max_frame_size: 16_384,
|
|
20
|
-
settings_max_header_list_size: (2 << 30) - 1 # unlimited
|
|
21
|
-
}.freeze
|
|
22
|
-
|
|
23
|
-
DEFAULT_CONNECTION_SETTINGS = {
|
|
24
|
-
settings_header_table_size: 4096,
|
|
25
|
-
settings_enable_push: 1, # enabled for servers
|
|
26
|
-
settings_max_concurrent_streams: 100,
|
|
27
|
-
settings_initial_window_size: 65_535,
|
|
28
|
-
settings_max_frame_size: 16_384,
|
|
29
|
-
settings_max_header_list_size: (2 << 30) - 1 # unlimited
|
|
30
|
-
}.freeze
|
|
31
|
-
|
|
32
13
|
# Default stream priority (lower values are higher priority).
|
|
33
14
|
DEFAULT_WEIGHT = 16
|
|
34
15
|
|
|
@@ -78,13 +59,12 @@ module HTTP2
|
|
|
78
59
|
attr_accessor :active_stream_count
|
|
79
60
|
|
|
80
61
|
# Initializes new connection object.
|
|
81
|
-
#
|
|
82
62
|
def initialize(settings = {})
|
|
83
|
-
@local_settings =
|
|
84
|
-
@remote_settings =
|
|
63
|
+
@local_settings = Settings.new(**settings)
|
|
64
|
+
@remote_settings = Settings.new(settings_max_concurrent_streams: Framer::MAX_STREAM_ID)
|
|
85
65
|
|
|
86
|
-
@compressor = Header::Compressor.new(
|
|
87
|
-
@decompressor = Header::Decompressor.new(
|
|
66
|
+
@compressor = Header::Compressor.new(@local_settings)
|
|
67
|
+
@decompressor = Header::Decompressor.new(@local_settings)
|
|
88
68
|
|
|
89
69
|
@active_stream_count = 0
|
|
90
70
|
@last_stream_id = 0
|
|
@@ -93,11 +73,11 @@ module HTTP2
|
|
|
93
73
|
@oldest_stream_recently_closed = nil
|
|
94
74
|
@pending_settings = []
|
|
95
75
|
|
|
96
|
-
@framer = Framer.new(@local_settings
|
|
76
|
+
@framer = Framer.new(@local_settings.settings_max_frame_size)
|
|
97
77
|
|
|
98
|
-
@local_window_limit = @local_settings
|
|
78
|
+
@local_window_limit = @local_settings.settings_initial_window_size
|
|
99
79
|
@local_window = @local_window_limit
|
|
100
|
-
@remote_window_limit = @remote_settings
|
|
80
|
+
@remote_window_limit = @remote_settings.settings_initial_window_size
|
|
101
81
|
@remote_window = @remote_window_limit
|
|
102
82
|
|
|
103
83
|
@recv_buffer = "".b
|
|
@@ -137,7 +117,7 @@ module HTTP2
|
|
|
137
117
|
|
|
138
118
|
stream = activate_stream(
|
|
139
119
|
id: @stream_id,
|
|
140
|
-
max_concurrent_streams: @remote_settings
|
|
120
|
+
max_concurrent_streams: @remote_settings.settings_max_concurrent_streams,
|
|
141
121
|
**args
|
|
142
122
|
)
|
|
143
123
|
@last_stream_id = stream.id
|
|
@@ -213,7 +193,7 @@ module HTTP2
|
|
|
213
193
|
elsif read_str(@recv_buffer, 24) == CONNECTION_PREFACE_MAGIC
|
|
214
194
|
# MAGIC is OK. Send our settings
|
|
215
195
|
@state = :waiting_connection_preface
|
|
216
|
-
payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
|
196
|
+
payload = @local_settings.connection_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
|
217
197
|
settings(payload)
|
|
218
198
|
else
|
|
219
199
|
raise HandshakeError
|
|
@@ -254,7 +234,7 @@ module HTTP2
|
|
|
254
234
|
# prevent HTTP/2 CONTINUATION FLOOD
|
|
255
235
|
# same heuristic as the one from HAProxy: https://www.haproxy.com/blog/haproxy-is-resilient-to-the-http-2-continuation-flood
|
|
256
236
|
# different mitigation (connection closed, instead of 400 response)
|
|
257
|
-
unless @continuation_size < @local_settings
|
|
237
|
+
unless @continuation_size < @local_settings.settings_max_frame_size
|
|
258
238
|
connection_error(:protocol_error,
|
|
259
239
|
msg: "too many continuations received")
|
|
260
240
|
end
|
|
@@ -752,7 +732,7 @@ module HTTP2
|
|
|
752
732
|
#: @type var payload: String
|
|
753
733
|
headers_frame[:payload] = payload
|
|
754
734
|
|
|
755
|
-
max_frame_size = @remote_settings
|
|
735
|
+
max_frame_size = @remote_settings.settings_max_frame_size
|
|
756
736
|
|
|
757
737
|
# if single frame, return immediately
|
|
758
738
|
if payload.bytesize <= max_frame_size
|
|
@@ -796,7 +776,7 @@ module HTTP2
|
|
|
796
776
|
# @param priority [Integer]
|
|
797
777
|
# @param window [Integer]
|
|
798
778
|
# @param parent [Stream]
|
|
799
|
-
def activate_stream(id:, max_concurrent_streams: @local_settings
|
|
779
|
+
def activate_stream(id:, max_concurrent_streams: @local_settings.settings_max_concurrent_streams, **args)
|
|
800
780
|
connection_error(msg: "Stream ID already exists") if @streams.key?(id)
|
|
801
781
|
|
|
802
782
|
# SETTINGS_MAX_CONCURRENT_STREAMS limits the number of concurrent streams that the sender
|
|
@@ -835,6 +815,8 @@ module HTTP2
|
|
|
835
815
|
true
|
|
836
816
|
end
|
|
837
817
|
@oldest_stream_recently_closed = new_oldest
|
|
818
|
+
else
|
|
819
|
+
@oldest_stream_recently_closed ||= now
|
|
838
820
|
end
|
|
839
821
|
|
|
840
822
|
@streams_recently_closed[id] = now
|
data/lib/http/2/framer.rb
CHANGED
|
@@ -130,10 +130,10 @@ module HTTP2
|
|
|
130
130
|
|
|
131
131
|
stream_id = frame.fetch(:stream, 0)
|
|
132
132
|
|
|
133
|
-
raise CompressionError, "Stream ID (#{stream_id}) is
|
|
133
|
+
raise CompressionError, "Stream ID (#{stream_id}) is outside the valid range" unless stream_id.between?(0, MAX_STREAM_ID)
|
|
134
134
|
|
|
135
|
-
if type == :window_update && frame[:increment]
|
|
136
|
-
raise CompressionError, "Window increment (#{frame[:increment]}) is
|
|
135
|
+
if type == :window_update && !frame[:increment].between?(1, MAX_WINDOWINC)
|
|
136
|
+
raise CompressionError, "Window increment (#{frame[:increment]}) is outside the valid range"
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
flags = frame[:flags]
|
|
@@ -7,18 +7,16 @@ module HTTP2
|
|
|
7
7
|
include PackingExtensions
|
|
8
8
|
include BufferUtils
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@cc = EncodingContext.new(options)
|
|
10
|
+
def initialize(settings = Settings.new)
|
|
11
|
+
@cc = EncodingContext.new(settings)
|
|
13
12
|
end
|
|
14
13
|
|
|
15
|
-
# Set dynamic table size in EncodingContext
|
|
16
|
-
# @param size [Integer] new dynamic table size
|
|
14
|
+
# Set dynamic table +size+ in EncodingContext
|
|
17
15
|
def table_size=(size)
|
|
18
16
|
@cc.table_size = size
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
# Encodes
|
|
19
|
+
# Encodes +i+ via integer representation into +buffer+ at the offset set by +offset+.
|
|
22
20
|
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.1
|
|
23
21
|
#
|
|
24
22
|
# If I < 2^N - 1, encode I on N bits
|
|
@@ -30,11 +28,6 @@ module HTTP2
|
|
|
30
28
|
# I = I / 128
|
|
31
29
|
# encode (I) on 8 bits
|
|
32
30
|
#
|
|
33
|
-
# @param i [Integer] value to encode
|
|
34
|
-
# @param n [Integer] number of available bits
|
|
35
|
-
# @param buffer [String] buffer to pack bytes into
|
|
36
|
-
# @param offset [Integer] offset to insert packed bytes in buffer
|
|
37
|
-
# @return [String] binary string
|
|
38
31
|
def integer(i, n, buffer:, offset: buffer.size)
|
|
39
32
|
limit = (1 << n) - 1
|
|
40
33
|
return pack([i], "C", buffer: buffer, offset: offset) if i < limit
|
|
@@ -65,16 +58,8 @@ module HTTP2
|
|
|
65
58
|
# * If the bit 7 of the first byte is 0, the string value is
|
|
66
59
|
# represented as a list of UTF-8 encoded octets.
|
|
67
60
|
#
|
|
68
|
-
# +@options [:huffman]+ controls whether to use Huffman encoding:
|
|
69
|
-
# :never Do not use Huffman encoding
|
|
70
|
-
# :always Always use Huffman encoding
|
|
71
|
-
# :shorter Use Huffman when the result is strictly shorter
|
|
72
|
-
#
|
|
73
|
-
# @param str [String]
|
|
74
|
-
# @param buffer [String]
|
|
75
|
-
# @return [String] binary string
|
|
76
61
|
def string(str, buffer = "".b)
|
|
77
|
-
case @cc.
|
|
62
|
+
case @cc.settings.huffman
|
|
78
63
|
when :always
|
|
79
64
|
huffman_string(str, buffer)
|
|
80
65
|
when :never
|
|
@@ -93,11 +78,7 @@ module HTTP2
|
|
|
93
78
|
end
|
|
94
79
|
end
|
|
95
80
|
|
|
96
|
-
# Encodes header command with appropriate header representation
|
|
97
|
-
#
|
|
98
|
-
# @param h [Hash] header command
|
|
99
|
-
# @param buffer [String]
|
|
100
|
-
# @return [Buffer]
|
|
81
|
+
# Encodes +h+ header command with appropriate header representation into +buffer+.
|
|
101
82
|
def header(h, buffer = "".b)
|
|
102
83
|
type = h[:type]
|
|
103
84
|
rep = HEADREP[type]
|
|
@@ -128,9 +109,6 @@ module HTTP2
|
|
|
128
109
|
end
|
|
129
110
|
|
|
130
111
|
# Encodes provided list of HTTP headers.
|
|
131
|
-
#
|
|
132
|
-
# @param headers [Array] +[[name, value], ...]+
|
|
133
|
-
# @return [Buffer]
|
|
134
112
|
def encode(headers)
|
|
135
113
|
buffer = "".b
|
|
136
114
|
headers.partition { |f, _| f.start_with? ":" }.each do |hs|
|
|
@@ -144,9 +122,7 @@ module HTTP2
|
|
|
144
122
|
|
|
145
123
|
private
|
|
146
124
|
|
|
147
|
-
#
|
|
148
|
-
# @param buffer [String]
|
|
149
|
-
# @return [String] binary string
|
|
125
|
+
# encodes +str+ into +buffer+ using Huffman encoding.
|
|
150
126
|
def huffman_string(str, buffer = "".b)
|
|
151
127
|
huffman_offset = buffer.bytesize
|
|
152
128
|
buffer << "\x00".b
|
|
@@ -162,18 +138,14 @@ module HTTP2
|
|
|
162
138
|
buffer
|
|
163
139
|
end
|
|
164
140
|
|
|
165
|
-
#
|
|
166
|
-
# @param buffer [String]
|
|
167
|
-
# @return [String] binary string
|
|
141
|
+
# encodes +str+ into +buffer+.
|
|
168
142
|
def plain_string(str, plain = "".b)
|
|
169
143
|
integer(str.bytesize, 7, buffer: plain)
|
|
170
144
|
append_str(plain, str)
|
|
171
145
|
plain
|
|
172
146
|
end
|
|
173
147
|
|
|
174
|
-
#
|
|
175
|
-
# @param huffman_offset [Integer] buffer offset where huffman string was introduced
|
|
176
|
-
# @return [String] binary string
|
|
148
|
+
# encodes the huffman string size from +buffer+ into the string at the offset indicated by +huffman_offset+
|
|
177
149
|
def set_huffman_size(buffer, huffman_offset)
|
|
178
150
|
integer(buffer.bytesize - huffman_offset, 7, buffer: buffer, offset: huffman_offset)
|
|
179
151
|
buffer.setbyte(huffman_offset, buffer.getbyte(huffman_offset) | 0x80)
|
|
@@ -5,32 +5,22 @@ module HTTP2
|
|
|
5
5
|
# Responsible for decoding received headers and maintaining compression
|
|
6
6
|
# context of the opposing peer. Decompressor must be initialized with
|
|
7
7
|
# appropriate starting context based on local role: client or server.
|
|
8
|
-
#
|
|
9
|
-
# @example
|
|
10
|
-
# server_role = Decompressor.new(:request)
|
|
11
|
-
# client_role = Decompressor.new(:response)
|
|
12
8
|
class Decompressor
|
|
13
9
|
include Error
|
|
14
10
|
include BufferUtils
|
|
15
11
|
|
|
16
12
|
FORBIDDEN_HEADERS = %w[connection te].freeze
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@cc = EncodingContext.new(options)
|
|
14
|
+
def initialize(settings = Settings.new)
|
|
15
|
+
@cc = EncodingContext.new(settings)
|
|
21
16
|
end
|
|
22
17
|
|
|
23
|
-
# Set dynamic table size in EncodingContext
|
|
24
|
-
# @param size [Integer] new dynamic table size
|
|
18
|
+
# Set dynamic table +size+ in EncodingContext
|
|
25
19
|
def table_size=(size)
|
|
26
20
|
@cc.table_size = size
|
|
27
21
|
end
|
|
28
22
|
|
|
29
|
-
# Decodes integer value from provided buffer
|
|
30
|
-
#
|
|
31
|
-
# @param buf [String]
|
|
32
|
-
# @param n [Integer] number of available bits
|
|
33
|
-
# @return [Integer]
|
|
23
|
+
# Decodes integer value from provided +buffer+.
|
|
34
24
|
def integer(buf, n)
|
|
35
25
|
limit = (1 << n) - 1
|
|
36
26
|
if n.zero?
|
|
@@ -55,11 +45,9 @@ module HTTP2
|
|
|
55
45
|
i
|
|
56
46
|
end
|
|
57
47
|
|
|
58
|
-
# Decodes string value from provided
|
|
48
|
+
# Decodes string value from provided +buf+.
|
|
59
49
|
#
|
|
60
|
-
#
|
|
61
|
-
# @return [String] UTF-8 encoded string
|
|
62
|
-
# @raise [CompressionError] when input is malformed
|
|
50
|
+
# raises CompressionError when input is malformed
|
|
63
51
|
def string(buf)
|
|
64
52
|
raise CompressionError, "invalid header block fragment" if buf.empty?
|
|
65
53
|
|
|
@@ -72,10 +60,7 @@ module HTTP2
|
|
|
72
60
|
str.force_encoding(Encoding::UTF_8)
|
|
73
61
|
end
|
|
74
62
|
|
|
75
|
-
# Decodes header command from provided
|
|
76
|
-
#
|
|
77
|
-
# @param buf [Buffer]
|
|
78
|
-
# @return [Hash] command
|
|
63
|
+
# Decodes header command from provided +buf+.
|
|
79
64
|
def header(buf)
|
|
80
65
|
peek = buf.getbyte(0)
|
|
81
66
|
|
|
@@ -109,13 +94,9 @@ module HTTP2
|
|
|
109
94
|
end
|
|
110
95
|
end
|
|
111
96
|
|
|
112
|
-
# Decodes and processes header commands within provided
|
|
113
|
-
#
|
|
114
|
-
# @param buf [Buffer]
|
|
115
|
-
# @param frame [HTTP2::Frame, nil]
|
|
116
|
-
# @return [Array] +[[name, value], ...]
|
|
97
|
+
# Decodes and processes header commands within provided +buf+.
|
|
117
98
|
def decode(buf, frame = nil)
|
|
118
|
-
list = []
|
|
99
|
+
list = [] #: Array[header_pair]
|
|
119
100
|
decoding_pseudo_headers = true
|
|
120
101
|
@cc.listen_on_table do
|
|
121
102
|
until buf.empty?
|
|
@@ -86,12 +86,6 @@ module HTTP2
|
|
|
86
86
|
|
|
87
87
|
STATIC_TABLE_SIZE = STATIC_TABLE.size
|
|
88
88
|
|
|
89
|
-
DEFAULT_OPTIONS = {
|
|
90
|
-
huffman: :shorter,
|
|
91
|
-
index: :all,
|
|
92
|
-
table_size: 4096
|
|
93
|
-
}.freeze
|
|
94
|
-
|
|
95
89
|
STATIC_ALL = %i[all static].freeze
|
|
96
90
|
|
|
97
91
|
STATIC_NEVER = %i[never static].freeze
|
|
@@ -99,37 +93,27 @@ module HTTP2
|
|
|
99
93
|
# Current table of header key-value pairs.
|
|
100
94
|
attr_reader :table
|
|
101
95
|
|
|
102
|
-
# Current encoding
|
|
103
|
-
|
|
104
|
-
# :table_size Integer maximum dynamic table size in bytes
|
|
105
|
-
# :huffman Symbol :always, :never, :shorter
|
|
106
|
-
# :index Symbol :all, :static, :never
|
|
107
|
-
attr_reader :options
|
|
96
|
+
# Current encoding settings
|
|
97
|
+
attr_reader :settings
|
|
108
98
|
|
|
109
99
|
# Current table size in octets
|
|
110
100
|
attr_reader :current_table_size
|
|
111
101
|
|
|
112
102
|
# Initializes compression context with appropriate client/server
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
# @param options [Hash] encoding options
|
|
116
|
-
# :table_size Integer maximum dynamic table size in bytes
|
|
117
|
-
# :huffman Symbol :always, :never, :shorter
|
|
118
|
-
# :index Symbol :all, :static, :never
|
|
119
|
-
def initialize(options = {})
|
|
103
|
+
# +settings+ and maximum size of the dynamic table.
|
|
104
|
+
def initialize(settings = Settings.new)
|
|
120
105
|
@table = []
|
|
121
106
|
@table_by_field = Hash.new { |hs, k| hs[k] = [] }
|
|
122
107
|
@unshifts = 0
|
|
123
|
-
@
|
|
124
|
-
@limit =
|
|
108
|
+
@settings = settings
|
|
109
|
+
@limit = settings.table_size
|
|
125
110
|
@_table_updated = false
|
|
126
111
|
@current_table_size = 0
|
|
127
112
|
end
|
|
128
113
|
|
|
129
114
|
# Duplicates current compression context
|
|
130
|
-
# @return [EncodingContext]
|
|
131
115
|
def dup
|
|
132
|
-
other = EncodingContext.new(@
|
|
116
|
+
other = EncodingContext.new(@settings)
|
|
133
117
|
t = @table
|
|
134
118
|
tbf = @table_by_field.transform_values(&:dup)
|
|
135
119
|
unshifts = @unshifts
|
|
@@ -143,16 +127,13 @@ module HTTP2
|
|
|
143
127
|
other
|
|
144
128
|
end
|
|
145
129
|
|
|
146
|
-
# Finds an entry in current dynamic table by index
|
|
147
|
-
# Note that index is zero-based in this module.
|
|
130
|
+
# Finds an entry in current dynamic table by +index+.
|
|
131
|
+
# Note that +index+ is zero-based in this module.
|
|
148
132
|
#
|
|
149
|
-
# If the index is greater than the last index in the static table,
|
|
133
|
+
# If the +index+ is greater than the last index in the static table,
|
|
150
134
|
# an entry in the dynamic table is dereferenced.
|
|
151
135
|
#
|
|
152
|
-
# If the index is greater than the last header index, an error is raised.
|
|
153
|
-
#
|
|
154
|
-
# @param index [Integer] zero-based index in the dynamic table.
|
|
155
|
-
# @return [Array] +[key, value]+
|
|
136
|
+
# If the +index+ is greater than the last header index, an error is raised.
|
|
156
137
|
def dereference(index)
|
|
157
138
|
# NOTE: index is zero-based in this module.
|
|
158
139
|
return STATIC_TABLE[index] if index < STATIC_TABLE_SIZE
|
|
@@ -166,10 +147,6 @@ module HTTP2
|
|
|
166
147
|
|
|
167
148
|
# Header Block Processing
|
|
168
149
|
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-4.1
|
|
169
|
-
#
|
|
170
|
-
# @param cmd [Hash] { type:, name:, value:, index: }
|
|
171
|
-
# @return [Array, nil] +[name, value]+ header field that is added to the decoded header list,
|
|
172
|
-
# or nil if +cmd[:type]+ is +:changetablesize+
|
|
173
150
|
def process(cmd)
|
|
174
151
|
type = cmd[:type]
|
|
175
152
|
name = cmd[:name]
|
|
@@ -181,7 +158,7 @@ module HTTP2
|
|
|
181
158
|
|
|
182
159
|
# we can receive multiple table size change commands inside a header frame. However,
|
|
183
160
|
# we should blow up if we receive another frame where the new table size is bigger.
|
|
184
|
-
table_size_updated = @limit != @
|
|
161
|
+
table_size_updated = @limit != @settings.table_size
|
|
185
162
|
|
|
186
163
|
raise CompressionError, "dynamic table size update exceed limit" if !table_size_updated && value > @limit
|
|
187
164
|
|
|
@@ -217,11 +194,12 @@ module HTTP2
|
|
|
217
194
|
emit = [name, value]
|
|
218
195
|
|
|
219
196
|
# add to table
|
|
220
|
-
|
|
197
|
+
cmdsize = name.bytesize + value.bytesize + 32
|
|
198
|
+
if type == :incremental && size_check?(cmdsize)
|
|
221
199
|
@table.unshift(emit)
|
|
222
200
|
@unshifts += 1
|
|
223
201
|
@table_by_field[name].unshift([value, @unshifts])
|
|
224
|
-
@current_table_size +=
|
|
202
|
+
@current_table_size += cmdsize
|
|
225
203
|
@_table_updated = true
|
|
226
204
|
end
|
|
227
205
|
|
|
@@ -231,16 +209,10 @@ module HTTP2
|
|
|
231
209
|
end
|
|
232
210
|
end
|
|
233
211
|
|
|
234
|
-
# Plan
|
|
235
|
-
# :never Do not use dynamic table or static table reference at all.
|
|
236
|
-
# :static Use static table only.
|
|
237
|
-
# :all Use all of them.
|
|
238
|
-
#
|
|
239
|
-
# @param headers [Array] +[[name, value], ...]+
|
|
240
|
-
# @return [Array] array of commands
|
|
212
|
+
# Plan +headers+ compression.
|
|
241
213
|
def encode(headers)
|
|
242
214
|
# Literals commands are marked with :noindex when index is not used
|
|
243
|
-
noindex = STATIC_NEVER.include?(@
|
|
215
|
+
noindex = STATIC_NEVER.include?(@settings.index)
|
|
244
216
|
|
|
245
217
|
headers.each do |field, value|
|
|
246
218
|
# Literal header names MUST be translated to lowercase before
|
|
@@ -254,26 +226,17 @@ module HTTP2
|
|
|
254
226
|
end
|
|
255
227
|
end
|
|
256
228
|
|
|
257
|
-
# Emits command for a header.
|
|
229
|
+
# Emits command for a +field+/+value+ header.
|
|
258
230
|
# Prefer static table over dynamic table.
|
|
259
231
|
# Prefer exact match over name-only match.
|
|
260
232
|
#
|
|
261
|
-
# +@options [:index]+ controls whether to use the dynamic table,
|
|
262
|
-
# static table, or both.
|
|
263
|
-
# :never Do not use dynamic table or static table reference at all.
|
|
264
|
-
# :static Use static table only.
|
|
265
|
-
# :all Use all of them.
|
|
266
|
-
#
|
|
267
|
-
# @param field [String] the header field
|
|
268
|
-
# @param value [String] the header value
|
|
269
|
-
# @return [Hash] command
|
|
270
233
|
def addcmd(field, value)
|
|
271
234
|
# @type var exact: Integer?
|
|
272
235
|
exact = nil
|
|
273
236
|
# @type var name_only: Integer?
|
|
274
237
|
name_only = nil
|
|
275
238
|
|
|
276
|
-
index_type = @
|
|
239
|
+
index_type = @settings.index
|
|
277
240
|
|
|
278
241
|
if STATIC_ALL.include?(index_type) &&
|
|
279
242
|
STATIC_TABLE_BY_FIELD.key?(field)
|
|
@@ -337,11 +300,10 @@ module HTTP2
|
|
|
337
300
|
end
|
|
338
301
|
end
|
|
339
302
|
|
|
303
|
+
# whether +cmd+ fits in the dynamic table.
|
|
304
|
+
#
|
|
340
305
|
# To keep the dynamic table size lower than or equal to @limit,
|
|
341
306
|
# remove one or more entries at the end of the dynamic table.
|
|
342
|
-
#
|
|
343
|
-
# @param cmdsize [Integer]
|
|
344
|
-
# @return [Boolean] whether +cmd+ fits in the dynamic table.
|
|
345
307
|
def size_check?(cmdsize)
|
|
346
308
|
resize_table(cmdsize)
|
|
347
309
|
cmdsize <= @limit
|
|
@@ -22,12 +22,7 @@ module HTTP2
|
|
|
22
22
|
|
|
23
23
|
EOS_PADDING = (0..7).map { |n| ("1" * n).b.freeze }.freeze
|
|
24
24
|
|
|
25
|
-
# Encodes provided
|
|
26
|
-
# Length is not encoded in this method.
|
|
27
|
-
#
|
|
28
|
-
# @param str [String]
|
|
29
|
-
# @param buffer [String]
|
|
30
|
-
# @return [String] binary string
|
|
25
|
+
# Encodes provided +str+ via huffman encoding into +buffer+ .
|
|
31
26
|
def encode(str, buffer = "".b)
|
|
32
27
|
bitstring = String.new("", encoding: Encoding::BINARY, capacity: (str.bytesize * 30) + ((8 - str.size) % 8))
|
|
33
28
|
str.each_byte { |chr| append_str(bitstring, ENCODE_TABLE[chr]) }
|
|
@@ -35,11 +30,9 @@ module HTTP2
|
|
|
35
30
|
pack([bitstring], "B*", buffer: buffer)
|
|
36
31
|
end
|
|
37
32
|
|
|
38
|
-
# Decodes provided Huffman coded string
|
|
33
|
+
# Decodes provided Huffman coded string in +buf+.
|
|
39
34
|
#
|
|
40
|
-
#
|
|
41
|
-
# @return [String] binary string
|
|
42
|
-
# @raise [CompressionError] when Huffman coded string is malformed
|
|
35
|
+
# raises CompressionError when Huffman coded string is malformed
|
|
43
36
|
def decode(buf)
|
|
44
37
|
emit = "".b
|
|
45
38
|
state = 0 # start state
|
data/lib/http/2/server.rb
CHANGED
|
@@ -97,10 +97,10 @@ module HTTP2
|
|
|
97
97
|
dependency: 0,
|
|
98
98
|
exclusive: false,
|
|
99
99
|
payload: headers
|
|
100
|
-
}
|
|
100
|
+
} #: headers_frame
|
|
101
101
|
|
|
102
102
|
if body.empty?
|
|
103
|
-
headers_frame[:flags] |=
|
|
103
|
+
headers_frame[:flags] |= END_STREAM
|
|
104
104
|
stream << headers_frame
|
|
105
105
|
else
|
|
106
106
|
stream << headers_frame
|
data/lib/http/2/settings.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HTTP2
|
|
4
|
+
MAX_HEADER_LIST_SIZE = (2 << 30) - 1
|
|
5
|
+
|
|
4
6
|
# Default values for SETTINGS frame, as defined by the spec.
|
|
5
7
|
SPEC_DEFAULT_CONNECTION_SETTINGS = {
|
|
6
8
|
settings_header_table_size: 4096,
|
|
@@ -8,32 +10,46 @@ module HTTP2
|
|
|
8
10
|
settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
|
|
9
11
|
settings_initial_window_size: 65_535,
|
|
10
12
|
settings_max_frame_size: 16_384,
|
|
11
|
-
settings_max_header_list_size:
|
|
13
|
+
settings_max_header_list_size: MAX_HEADER_LIST_SIZE # unlimited
|
|
12
14
|
}.freeze
|
|
13
15
|
|
|
16
|
+
ENCODING_CONTEXT_SETTINGS = %i[huffman index table_size].freeze
|
|
17
|
+
|
|
14
18
|
Settings = Struct.new(
|
|
19
|
+
# connection settings
|
|
15
20
|
:settings_header_table_size,
|
|
16
21
|
:settings_enable_push,
|
|
17
22
|
:settings_max_concurrent_streams,
|
|
18
23
|
:settings_initial_window_size,
|
|
19
24
|
:settings_max_frame_size,
|
|
20
25
|
:settings_max_header_list_size,
|
|
26
|
+
# encoding context settings
|
|
27
|
+
:huffman,
|
|
28
|
+
:index,
|
|
29
|
+
:table_size,
|
|
21
30
|
keyword_init: true
|
|
22
31
|
) do
|
|
23
32
|
def initialize(
|
|
33
|
+
# connection settings
|
|
24
34
|
settings_header_table_size: 4096,
|
|
25
35
|
settings_enable_push: 1,
|
|
26
36
|
settings_max_concurrent_streams: 100,
|
|
27
37
|
settings_initial_window_size: 65_535,
|
|
28
38
|
settings_max_frame_size: 16_384,
|
|
29
|
-
settings_max_header_list_size:
|
|
39
|
+
settings_max_header_list_size: MAX_HEADER_LIST_SIZE,
|
|
40
|
+
# encoding context settings
|
|
41
|
+
huffman: :shorter,
|
|
42
|
+
index: :all,
|
|
43
|
+
table_size: 4096
|
|
30
44
|
)
|
|
31
45
|
super
|
|
32
46
|
end
|
|
33
47
|
|
|
34
|
-
def
|
|
48
|
+
def connection_settings
|
|
49
|
+
return enum_for(__method__) unless block_given?
|
|
50
|
+
|
|
35
51
|
each_pair do |k, v|
|
|
36
|
-
next if
|
|
52
|
+
next if ENCODING_CONTEXT_SETTINGS.include?(k)
|
|
37
53
|
|
|
38
54
|
yield k, v
|
|
39
55
|
end
|
data/lib/http/2/version.rb
CHANGED
data/lib/http/2.rb
CHANGED
data/sig/2.rbs
CHANGED
|
@@ -11,23 +11,10 @@ module HTTP2
|
|
|
11
11
|
|
|
12
12
|
type connection_opts = Hash[Symbol, untyped]
|
|
13
13
|
|
|
14
|
-
type settings_hash = {
|
|
15
|
-
settings_header_table_size: Integer,
|
|
16
|
-
settings_enable_push: Integer,
|
|
17
|
-
settings_max_concurrent_streams: Integer,
|
|
18
|
-
settings_initial_window_size: Integer,
|
|
19
|
-
settings_max_frame_size: Integer,
|
|
20
|
-
settings_max_header_list_size: Integer
|
|
21
|
-
}
|
|
22
|
-
|
|
23
14
|
type settings_ary = Array[settings_enum]
|
|
24
15
|
|
|
25
16
|
type settings_enum = Enumerable[[Symbol, Integer]]
|
|
26
17
|
|
|
27
|
-
SPEC_DEFAULT_CONNECTION_SETTINGS: settings_hash
|
|
28
|
-
|
|
29
|
-
DEFAULT_CONNECTION_SETTINGS: settings_hash
|
|
30
|
-
|
|
31
18
|
DEFAULT_WEIGHT: Integer
|
|
32
19
|
|
|
33
20
|
CONNECTION_PREFACE_MAGIC: String
|
|
@@ -36,6 +23,12 @@ module HTTP2
|
|
|
36
23
|
|
|
37
24
|
RESPONSE_MANDATORY_HEADERS: Array[String]
|
|
38
25
|
|
|
26
|
+
CONNECTION_FRAME_TYPES: Array[Symbol]
|
|
27
|
+
|
|
28
|
+
STREAM_OPEN_STATES: Array[Symbol]
|
|
29
|
+
|
|
30
|
+
HEADERS_FRAME_TYPES: Array[Symbol]
|
|
31
|
+
|
|
39
32
|
# Frame flags
|
|
40
33
|
END_STREAM: Integer
|
|
41
34
|
ACK: Integer
|
data/sig/connection.rbs
CHANGED
|
@@ -4,16 +4,6 @@ module HTTP2
|
|
|
4
4
|
include Emitter
|
|
5
5
|
include BufferUtils
|
|
6
6
|
|
|
7
|
-
REQUEST_MANDATORY_HEADERS: Array[String]
|
|
8
|
-
|
|
9
|
-
RESPONSE_MANDATORY_HEADERS: Array[String]
|
|
10
|
-
|
|
11
|
-
CONNECTION_FRAME_TYPES: Array[Symbol]
|
|
12
|
-
|
|
13
|
-
HEADERS_FRAME_TYPES: Array[Symbol]
|
|
14
|
-
|
|
15
|
-
STREAM_OPEN_STATES: Array[Symbol]
|
|
16
|
-
|
|
17
7
|
attr_reader state: Symbol
|
|
18
8
|
|
|
19
9
|
attr_reader local_window: Integer
|
|
@@ -21,8 +11,8 @@ module HTTP2
|
|
|
21
11
|
|
|
22
12
|
alias window local_window
|
|
23
13
|
|
|
24
|
-
attr_reader remote_settings:
|
|
25
|
-
attr_reader local_settings:
|
|
14
|
+
attr_reader remote_settings: Settings
|
|
15
|
+
attr_reader local_settings: Settings
|
|
26
16
|
attr_reader pending_settings: settings_ary
|
|
27
17
|
|
|
28
18
|
attr_accessor active_stream_count: Integer
|
|
@@ -87,7 +77,7 @@ module HTTP2
|
|
|
87
77
|
|
|
88
78
|
def connection_frame?: (frame) -> bool
|
|
89
79
|
|
|
90
|
-
def connection_management: (
|
|
80
|
+
def connection_management: (frame frame) -> void
|
|
91
81
|
|
|
92
82
|
def ping_management: (frame) -> void
|
|
93
83
|
|
data/sig/header/compressor.rbs
CHANGED
data/sig/header/decompressor.rbs
CHANGED
|
@@ -11,14 +11,12 @@ module HTTP2
|
|
|
11
11
|
|
|
12
12
|
STATIC_NEVER: Array[Symbol]
|
|
13
13
|
|
|
14
|
-
DEFAULT_OPTIONS: context_hash
|
|
15
|
-
|
|
16
14
|
UPPER: Regexp
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
attr_reader table: Array[header_pair]
|
|
20
18
|
|
|
21
|
-
attr_reader
|
|
19
|
+
attr_reader settings: Settings
|
|
22
20
|
|
|
23
21
|
attr_reader current_table_size: Integer
|
|
24
22
|
|
|
@@ -46,7 +44,7 @@ module HTTP2
|
|
|
46
44
|
|
|
47
45
|
private
|
|
48
46
|
|
|
49
|
-
def initialize: (?
|
|
47
|
+
def initialize: (?Settings settings) -> void
|
|
50
48
|
|
|
51
49
|
def add_to_table: (string name, string value) -> void
|
|
52
50
|
|
data/sig/server.rbs
CHANGED
data/sig/settings.rbs
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module HTTP2
|
|
2
|
+
MAX_HEADER_LIST_SIZE: Integer
|
|
3
|
+
|
|
4
|
+
SPEC_DEFAULT_CONNECTION_SETTINGS: Hash[Symbol, Integer]
|
|
5
|
+
|
|
6
|
+
ENCODING_CONTEXT_SETTINGS: Array[Symbol]
|
|
7
|
+
|
|
8
|
+
type huffman_type = :always | :never | :shorter
|
|
9
|
+
|
|
10
|
+
type index_type = :all | :static | :never
|
|
11
|
+
|
|
12
|
+
# Container for all settings required for the management of an HTTP/2 connection. This
|
|
13
|
+
# includes the managemenet of the settings negotiated via the SETTINGS frame, as well as
|
|
14
|
+
# user-level configuration related with header compression.
|
|
15
|
+
class Settings # this is actually a Struct, but RBS does not support it yet
|
|
16
|
+
# connection settings (send and received in the SETTINGS frame)
|
|
17
|
+
|
|
18
|
+
# maximum size of the header compression table used to decode header blocks, in bytes.
|
|
19
|
+
attr_reader settings_header_table_size: Integer
|
|
20
|
+
|
|
21
|
+
# can be used to disable server push (set to 0)
|
|
22
|
+
attr_reader settings_enable_push: Integer
|
|
23
|
+
|
|
24
|
+
# maximum number of concurrent streams that the sender will allow.
|
|
25
|
+
attr_reader settings_max_concurrent_streams: Integer
|
|
26
|
+
|
|
27
|
+
# initial window size (in bytes) for stream-level flow control.
|
|
28
|
+
attr_reader settings_initial_window_size: Integer
|
|
29
|
+
|
|
30
|
+
# max size of for payload that the sender is willing to receive, in bytes.
|
|
31
|
+
attr_reader settings_max_frame_size: Integer
|
|
32
|
+
attr_reader settings_max_header_list_size: Integer
|
|
33
|
+
|
|
34
|
+
# encoding context settings
|
|
35
|
+
|
|
36
|
+
# controls how to use huffman encoding to encode strings. can be one of the following values:
|
|
37
|
+
#
|
|
38
|
+
# * `:always` - Always use Huffman encoding
|
|
39
|
+
# * `:never` - Do not use Huffman encoding
|
|
40
|
+
# * `:shorter` - Use Huffman when the result is strictly shorter
|
|
41
|
+
attr_reader huffman: huffman_type
|
|
42
|
+
|
|
43
|
+
# controls the configuration of the HPACK indexing tables:
|
|
44
|
+
#
|
|
45
|
+
# * `:static` - use the static table only
|
|
46
|
+
# * `:all` - use the static table and the dynamic table
|
|
47
|
+
# * `:never` - do not use any of the tables.
|
|
48
|
+
attr_reader index: index_type
|
|
49
|
+
|
|
50
|
+
# maximum dynamic table size in bytes
|
|
51
|
+
attr_reader table_size: Integer
|
|
52
|
+
|
|
53
|
+
def initialize: (
|
|
54
|
+
# connection settings
|
|
55
|
+
?settings_header_table_size: Integer,
|
|
56
|
+
?settings_enable_push: Integer,
|
|
57
|
+
?settings_max_concurrent_streams: Integer,
|
|
58
|
+
?settings_initial_window_size: Integer,
|
|
59
|
+
?settings_max_frame_size: Integer,
|
|
60
|
+
?settings_max_header_list_size: Integer,
|
|
61
|
+
# encoding context settings
|
|
62
|
+
?huffman: huffman_type,
|
|
63
|
+
?index: index_type,
|
|
64
|
+
?table_size: Integer
|
|
65
|
+
) -> void
|
|
66
|
+
|
|
67
|
+
# yields connection-level settings only.
|
|
68
|
+
def connection_settings: () { (Symbol, Integer) -> void } -> self
|
|
69
|
+
| () -> Enumerable[[Symbol, Integer]]
|
|
70
|
+
end
|
|
71
|
+
end
|
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: 1.2.
|
|
4
|
+
version: 1.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tiago Cardoso
|
|
@@ -19,6 +19,7 @@ executables: []
|
|
|
19
19
|
extensions: []
|
|
20
20
|
extra_rdoc_files: []
|
|
21
21
|
files:
|
|
22
|
+
- LICENSE
|
|
22
23
|
- README.md
|
|
23
24
|
- lib/http/2.rb
|
|
24
25
|
- lib/http/2/base64.rb
|
|
@@ -54,6 +55,7 @@ files:
|
|
|
54
55
|
- sig/header/encoding_context.rbs
|
|
55
56
|
- sig/header/huffman.rbs
|
|
56
57
|
- sig/server.rbs
|
|
58
|
+
- sig/settings.rbs
|
|
57
59
|
- sig/stream.rbs
|
|
58
60
|
homepage: https://github.com/igrigorik/http-2
|
|
59
61
|
licenses:
|