plum 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/Guardfile +7 -0
- data/LICENSE +21 -0
- data/README.md +14 -0
- data/Rakefile +12 -0
- data/bin/.gitkeep +0 -0
- data/examples/local_server.rb +206 -0
- data/examples/static_server.rb +157 -0
- data/lib/plum.rb +21 -0
- data/lib/plum/binary_string.rb +74 -0
- data/lib/plum/connection.rb +201 -0
- data/lib/plum/connection_utils.rb +38 -0
- data/lib/plum/errors.rb +35 -0
- data/lib/plum/event_emitter.rb +19 -0
- data/lib/plum/flow_control.rb +97 -0
- data/lib/plum/frame.rb +163 -0
- data/lib/plum/frame_factory.rb +53 -0
- data/lib/plum/frame_utils.rb +50 -0
- data/lib/plum/hpack/constants.rb +331 -0
- data/lib/plum/hpack/context.rb +55 -0
- data/lib/plum/hpack/decoder.rb +145 -0
- data/lib/plum/hpack/encoder.rb +105 -0
- data/lib/plum/hpack/huffman.rb +42 -0
- data/lib/plum/http_connection.rb +33 -0
- data/lib/plum/https_connection.rb +24 -0
- data/lib/plum/stream.rb +217 -0
- data/lib/plum/stream_utils.rb +58 -0
- data/lib/plum/version.rb +3 -0
- data/plum.gemspec +29 -0
- data/test/plum/connection/test_handle_frame.rb +70 -0
- data/test/plum/hpack/test_context.rb +63 -0
- data/test/plum/hpack/test_decoder.rb +291 -0
- data/test/plum/hpack/test_encoder.rb +49 -0
- data/test/plum/hpack/test_huffman.rb +36 -0
- data/test/plum/stream/test_handle_frame.rb +262 -0
- data/test/plum/test_binary_string.rb +64 -0
- data/test/plum/test_connection.rb +96 -0
- data/test/plum/test_connection_utils.rb +29 -0
- data/test/plum/test_error.rb +13 -0
- data/test/plum/test_flow_control.rb +167 -0
- data/test/plum/test_frame.rb +59 -0
- data/test/plum/test_frame_factory.rb +56 -0
- data/test/plum/test_frame_utils.rb +46 -0
- data/test/plum/test_https_connection.rb +37 -0
- data/test/plum/test_stream.rb +32 -0
- data/test/plum/test_stream_utils.rb +16 -0
- data/test/server.crt +19 -0
- data/test/server.csr +16 -0
- data/test/server.key +27 -0
- data/test/test_helper.rb +28 -0
- data/test/utils/assertions.rb +60 -0
- data/test/utils/server.rb +63 -0
- metadata +234 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
using Plum::BinaryString
|
2
|
+
|
3
|
+
module Plum
|
4
|
+
module StreamUtils
|
5
|
+
# Responds to HTTP request.
|
6
|
+
#
|
7
|
+
# @param headers [Hash<String, String>] The response headers.
|
8
|
+
# @param body [String, IO] The response body.
|
9
|
+
def respond(headers, body = nil, end_stream: true) # TODO: priority, padding
|
10
|
+
if body
|
11
|
+
send_headers(headers, end_stream: false)
|
12
|
+
send_data(body, end_stream: end_stream)
|
13
|
+
else
|
14
|
+
send_headers(headers, end_stream: end_stream)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Reserves a stream to server push. Sends PUSH_PROMISE and create new stream.
|
19
|
+
#
|
20
|
+
# @param headers [Hash<String, String>] The *request* headers. It must contain all of them: ':authority', ':method', ':scheme' and ':path'.
|
21
|
+
# @return [Stream] The stream to send push response.
|
22
|
+
def promise(headers)
|
23
|
+
stream = @connection.reserve_stream(weight: self.weight + 1, parent: self)
|
24
|
+
encoded = @connection.hpack_encoder.encode(headers)
|
25
|
+
original = Frame.push_promise(id, stream.id, encoded, :end_headers)
|
26
|
+
original.split_headers(@connection.remote_settings[:max_frame_size]).each do |frame|
|
27
|
+
send frame
|
28
|
+
end
|
29
|
+
stream
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def send_headers(headers, end_stream:)
|
34
|
+
max = @connection.remote_settings[:max_frame_size]
|
35
|
+
encoded = @connection.hpack_encoder.encode(headers)
|
36
|
+
original_frame = Frame.headers(id, encoded, :end_headers, (end_stream && :end_stream || nil))
|
37
|
+
original_frame.split_headers(max).each do |frame|
|
38
|
+
send frame
|
39
|
+
end
|
40
|
+
@state = :half_closed_local if end_stream
|
41
|
+
end
|
42
|
+
|
43
|
+
def send_data(data, end_stream: true)
|
44
|
+
max = @connection.remote_settings[:max_frame_size]
|
45
|
+
if data.is_a?(IO)
|
46
|
+
while !data.eof? && fragment = data.readpartial(max)
|
47
|
+
send Frame.data(id, fragment, (end_stream && data.eof? && :end_stream))
|
48
|
+
end
|
49
|
+
else
|
50
|
+
original = Frame.data(id, data, (end_stream && :end_stream))
|
51
|
+
original.split_data(max).each do |frame|
|
52
|
+
send frame
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@state = :half_closed_local if end_stream
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/plum/version.rb
ADDED
data/plum.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "plum/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "plum"
|
7
|
+
spec.version = Plum::VERSION
|
8
|
+
spec.authors = ["rhenium"]
|
9
|
+
spec.email = ["k@rhe.jp"]
|
10
|
+
|
11
|
+
spec.summary = %q{A minimal implementation of HTTP/2 server.}
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = "https://github.com/rhenium/plum"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x00")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/[^.]}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^test/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "yard"
|
24
|
+
spec.add_development_dependency "minitest", "~> 5.7.0"
|
25
|
+
spec.add_development_dependency "simplecov"
|
26
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
27
|
+
spec.add_development_dependency "guard"
|
28
|
+
spec.add_development_dependency "guard-minitest"
|
29
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
using Plum::BinaryString
|
4
|
+
|
5
|
+
class ServerConnectionHandleFrameTest < Minitest::Test
|
6
|
+
## SETTINGS
|
7
|
+
def test_server_handle_settings
|
8
|
+
open_server_connection {|con|
|
9
|
+
assert_equal(4096, con.remote_settings[:header_table_size])
|
10
|
+
con << Frame.new(type: :settings, stream_id: 0, payload: "\x00\x01\x00\x00\x10\x10").assemble
|
11
|
+
assert_equal(0x1010, con.remote_settings[:header_table_size])
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_server_handle_settings
|
16
|
+
open_server_connection {|con|
|
17
|
+
assert_no_error {
|
18
|
+
con << Frame.new(type: :settings, stream_id: 0, flags: [:ack], payload: "").assemble
|
19
|
+
}
|
20
|
+
assert_connection_error(:frame_size_error) {
|
21
|
+
con << Frame.new(type: :settings, stream_id: 0, flags: [:ack], payload: "\x00").assemble
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_server_handle_settings_invalid
|
27
|
+
open_server_connection {|con|
|
28
|
+
assert_no_error {
|
29
|
+
con << Frame.new(type: :settings, stream_id: 0, payload: "\xff\x01\x00\x00\x10\x10").assemble
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
## PING
|
35
|
+
def test_server_handle_ping
|
36
|
+
open_server_connection {|con|
|
37
|
+
con << Frame.new(type: :ping, flags: [], stream_id: 0, payload: "AAAAAAAA").assemble
|
38
|
+
last = sent_frames.last
|
39
|
+
assert_equal(:ping, last.type)
|
40
|
+
assert_equal([:ack], last.flags)
|
41
|
+
assert_equal("AAAAAAAA", last.payload)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_server_handle_ping_error
|
46
|
+
open_server_connection {|con|
|
47
|
+
assert_connection_error(:frame_size_error) {
|
48
|
+
con << Frame.new(type: :ping, stream_id: 0, payload: "A" * 7).assemble
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_server_handle_ping_ack
|
54
|
+
open_server_connection {|con|
|
55
|
+
con << Frame.new(type: :ping, flags: [:ack], stream_id: 0, payload: "A" * 8).assemble
|
56
|
+
last = sent_frames.last
|
57
|
+
refute_equal(:ping, last.type) if last
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
## GOAWAY
|
62
|
+
def test_server_handle_goaway_reply
|
63
|
+
open_server_connection {|con|
|
64
|
+
assert_no_error {
|
65
|
+
con << Frame.goaway(1234, :stream_closed).assemble
|
66
|
+
}
|
67
|
+
assert_equal(:goaway, sent_frames.last.type)
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class HPACKContextTest < Minitest::Test
|
4
|
+
def test_store
|
5
|
+
context = new_context
|
6
|
+
context.store("あああ", "いい")
|
7
|
+
assert_equal([["あああ", "いい"]], context.dynamic_table)
|
8
|
+
assert_equal("あああいい".bytesize + 32, context.size)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_store_eviction
|
12
|
+
context = new_context(1)
|
13
|
+
context.store("あああ", "いい")
|
14
|
+
assert_equal([], context.dynamic_table)
|
15
|
+
assert_equal(0, context.size)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_fetch_static
|
19
|
+
context = new_context
|
20
|
+
assert_equal([":method", "POST"], context.fetch(3))
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_fetch_dynamic
|
24
|
+
context = new_context
|
25
|
+
context.store("あああ", "いい")
|
26
|
+
assert_equal(["あああ", "いい"], context.fetch(62))
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_fetch_error
|
30
|
+
context = new_context
|
31
|
+
context.store("あああ", "いい")
|
32
|
+
assert_raises(Plum::HPACKError) {
|
33
|
+
context.fetch(64)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_search_static
|
38
|
+
context = new_context
|
39
|
+
i1 = context.search(":method", "POST")
|
40
|
+
assert_equal(3, i1)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_search_dynamic
|
44
|
+
context = new_context
|
45
|
+
context.store("あああ", "abc")
|
46
|
+
context.store("あああ", "いい")
|
47
|
+
i1 = context.search("あああ", "abc")
|
48
|
+
assert_equal(63, i1)
|
49
|
+
i2 = context.search("あああ", "AAA")
|
50
|
+
assert_equal(nil, i2)
|
51
|
+
i3 = context.search("あああ", nil)
|
52
|
+
assert_equal(62, i3)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def new_context(limit = 1 << 31)
|
57
|
+
@c ||= Class.new {
|
58
|
+
include Plum::HPACK::Context
|
59
|
+
public *Plum::HPACK::Context.private_instance_methods
|
60
|
+
}
|
61
|
+
@c.new(limit)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class HPACKDecoderTest < Minitest::Test
|
4
|
+
# C.1.1
|
5
|
+
def test_hpack_read_integer_small
|
6
|
+
buf = [0b11001010, 0b00001111].pack("C*")
|
7
|
+
result = new_decoder.__send__(:read_integer!, buf, 5)
|
8
|
+
assert_equal(10, result)
|
9
|
+
assert_equal([0b00001111].pack("C*"), buf)
|
10
|
+
end
|
11
|
+
|
12
|
+
# C.1.2
|
13
|
+
def test_hpack_read_integer_big
|
14
|
+
buf = [0b11011111, 0b10011010, 0b00001010, 0b00001111].pack("C*")
|
15
|
+
result = new_decoder.__send__(:read_integer!, buf, 5)
|
16
|
+
assert_equal(1337, result)
|
17
|
+
assert_equal([0b00001111].pack("C*"), buf)
|
18
|
+
end
|
19
|
+
|
20
|
+
# C.1.3
|
21
|
+
def test_hpack_read_integer_8prefix
|
22
|
+
buf = [0b00101010, 0b00001111].pack("C*")
|
23
|
+
result = new_decoder.__send__(:read_integer!, buf, 8)
|
24
|
+
assert_equal(42, result)
|
25
|
+
assert_equal([0b00001111].pack("C*"), buf)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_hpack_read_integer_too_big
|
29
|
+
buf = [0b11011111, 0b10011010, 0b10001010, 0b10001111, 0b11111111, 0b00000011].pack("C*")
|
30
|
+
assert_raises(HPACKError) {
|
31
|
+
new_decoder.__send__(:read_integer!, buf, 5)
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_hpack_read_integer_incomplete
|
36
|
+
buf = [0b11011111, 0b10011010].pack("C*")
|
37
|
+
assert_raises(HPACKError) {
|
38
|
+
new_decoder.__send__(:read_integer!, buf, 5)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
# C.2.1
|
43
|
+
def test_hpack_decode_indexing
|
44
|
+
encoded = "\x40\x0acustom-key\x0dcustom-header"
|
45
|
+
result = new_decoder.decode(encoded)
|
46
|
+
assert_equal([["custom-key", "custom-header"]], result)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_hpack_decode_indexing_imcomplete
|
50
|
+
encoded = "\x40\x0acus"
|
51
|
+
assert_raises(HTTPError) {
|
52
|
+
new_decoder.decode(encoded)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
# C.2.2
|
57
|
+
def test_hpack_decode_without_indexing
|
58
|
+
encoded = "\x04\x0c/sample/path"
|
59
|
+
result = new_decoder.decode(encoded)
|
60
|
+
assert_equal([[":path", "/sample/path"]], result)
|
61
|
+
end
|
62
|
+
|
63
|
+
# C.2.3
|
64
|
+
def test_hpack_decode_without_indexing2
|
65
|
+
encoded = "\x10\x08password\x06secret"
|
66
|
+
result = new_decoder.decode(encoded)
|
67
|
+
assert_equal([["password", "secret"]], result)
|
68
|
+
end
|
69
|
+
|
70
|
+
# C.2.4
|
71
|
+
def test_hpack_decode_index
|
72
|
+
encoded = "\x82"
|
73
|
+
result = new_decoder.decode(encoded)
|
74
|
+
assert_equal([[":method", "GET"]], result)
|
75
|
+
end
|
76
|
+
|
77
|
+
# C.3.1
|
78
|
+
def test_hpack_decode_headers_without_huffman
|
79
|
+
decoder = new_decoder
|
80
|
+
encoded = "\x82\x86\x84\x41\x0f\x77\x77\x77\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d"
|
81
|
+
result = decoder.decode(encoded)
|
82
|
+
expected = [
|
83
|
+
[":method", "GET"],
|
84
|
+
[":scheme", "http"],
|
85
|
+
[":path", "/"],
|
86
|
+
[":authority", "www.example.com"]
|
87
|
+
]
|
88
|
+
assert_equal(expected, result)
|
89
|
+
|
90
|
+
decoder # for C.3.2
|
91
|
+
end
|
92
|
+
|
93
|
+
# C.3.2
|
94
|
+
def test_hpack_decode_headers_without_huffman2
|
95
|
+
decoder = test_hpack_decode_headers_without_huffman
|
96
|
+
encoded = "\x82\x86\x84\xbe\x58\x08\x6e\x6f\x2d\x63\x61\x63\x68\x65"
|
97
|
+
result = decoder.decode(encoded)
|
98
|
+
expected = [
|
99
|
+
[":method", "GET"],
|
100
|
+
[":scheme", "http"],
|
101
|
+
[":path", "/"],
|
102
|
+
[":authority", "www.example.com"],
|
103
|
+
["cache-control", "no-cache"],
|
104
|
+
]
|
105
|
+
assert_equal(expected, result)
|
106
|
+
|
107
|
+
decoder # for C.3.3
|
108
|
+
end
|
109
|
+
|
110
|
+
# C.3.3
|
111
|
+
def test_hpack_decode_headers_without_huffman3
|
112
|
+
decoder = test_hpack_decode_headers_without_huffman2
|
113
|
+
encoded = "\x82\x87\x85\xbf\x40\x0a\x63\x75\x73\x74\x6f\x6d\x2d\x6b\x65\x79\x0c\x63\x75\x73\x74\x6f\x6d\x2d\x76\x61\x6c\x75\x65"
|
114
|
+
result = decoder.decode(encoded)
|
115
|
+
expected = [
|
116
|
+
[":method", "GET"],
|
117
|
+
[":scheme", "https"],
|
118
|
+
[":path", "/index.html"],
|
119
|
+
[":authority", "www.example.com"],
|
120
|
+
["custom-key", "custom-value"],
|
121
|
+
]
|
122
|
+
assert_equal(expected, result)
|
123
|
+
end
|
124
|
+
|
125
|
+
# C.4.1
|
126
|
+
def test_hpack_decode_headers_with_huffman
|
127
|
+
decoder = new_decoder
|
128
|
+
encoded = "\x82\x86\x84\x41\x8c\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff"
|
129
|
+
result = decoder.decode(encoded)
|
130
|
+
expected = [
|
131
|
+
[":method", "GET"],
|
132
|
+
[":scheme", "http"],
|
133
|
+
[":path", "/"],
|
134
|
+
[":authority", "www.example.com"]
|
135
|
+
]
|
136
|
+
assert_equal(expected, result)
|
137
|
+
|
138
|
+
decoder # for C.4.2
|
139
|
+
end
|
140
|
+
|
141
|
+
# C.4.2
|
142
|
+
def test_hpack_decode_headers_with_huffman2
|
143
|
+
decoder = test_hpack_decode_headers_with_huffman
|
144
|
+
encoded = "\x82\x86\x84\xbe\x58\x86\xa8\xeb\x10\x64\x9c\xbf"
|
145
|
+
result = decoder.decode(encoded)
|
146
|
+
expected = [
|
147
|
+
[":method", "GET"],
|
148
|
+
[":scheme", "http"],
|
149
|
+
[":path", "/"],
|
150
|
+
[":authority", "www.example.com"],
|
151
|
+
["cache-control", "no-cache"],
|
152
|
+
]
|
153
|
+
assert_equal(expected, result)
|
154
|
+
|
155
|
+
decoder # for C.4.3
|
156
|
+
end
|
157
|
+
|
158
|
+
# C.4.3
|
159
|
+
def test_hpack_decode_headers_with_huffman3
|
160
|
+
decoder = test_hpack_decode_headers_with_huffman2
|
161
|
+
encoded = "\x82\x87\x85\xbf\x40\x88\x25\xa8\x49\xe9\x5b\xa9\x7d\x7f\x89\x25\xa8\x49\xe9\x5b\xb8\xe8\xb4\xbf"
|
162
|
+
result = decoder.decode(encoded)
|
163
|
+
expected = [
|
164
|
+
[":method", "GET"],
|
165
|
+
[":scheme", "https"],
|
166
|
+
[":path", "/index.html"],
|
167
|
+
[":authority", "www.example.com"],
|
168
|
+
["custom-key", "custom-value"],
|
169
|
+
]
|
170
|
+
assert_equal(expected, result)
|
171
|
+
end
|
172
|
+
|
173
|
+
# C.5.1
|
174
|
+
def test_hpack_decode_response_without_huffman
|
175
|
+
decoder = new_decoder(256)
|
176
|
+
encoded = "\x48\x03\x33\x30\x32\x58\x07\x70\x72\x69\x76\x61\x74\x65\x61\x1d\x4d\x6f\x6e\x2c\x20\x32\x31\x20\x4f\x63\x74\x20\x32\x30\x31\x33\x20\x32\x30\x3a\x31\x33\x3a\x32\x31\x20\x47\x4d\x54\x6e\x17\x68\x74\x74\x70\x73\x3a\x2f\x2f\x77\x77\x77\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d"
|
177
|
+
result = decoder.decode(encoded)
|
178
|
+
expected = [
|
179
|
+
[":status", "302"],
|
180
|
+
["cache-control", "private"],
|
181
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
182
|
+
["location", "https://www.example.com"]
|
183
|
+
]
|
184
|
+
assert_equal(expected, result)
|
185
|
+
|
186
|
+
decoder # for C.5.2
|
187
|
+
end
|
188
|
+
|
189
|
+
# C.5.2
|
190
|
+
def test_hpack_decode_response_without_huffman2
|
191
|
+
decoder = test_hpack_decode_response_without_huffman
|
192
|
+
encoded = "\x48\x03\x33\x30\x37\xc1\xc0\xbf"
|
193
|
+
result = decoder.decode(encoded)
|
194
|
+
expected = [
|
195
|
+
[":status", "307"],
|
196
|
+
["cache-control", "private"],
|
197
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
198
|
+
["location", "https://www.example.com"]
|
199
|
+
]
|
200
|
+
assert_equal(expected, result)
|
201
|
+
refute_includes(decoder.dynamic_table, [":status", "302"]) # evicted
|
202
|
+
|
203
|
+
decoder # for C.5.3
|
204
|
+
end
|
205
|
+
|
206
|
+
# C.5.3
|
207
|
+
def test_hpack_decode_response_without_huffman3
|
208
|
+
decoder = test_hpack_decode_response_without_huffman2
|
209
|
+
encoded = "\x88\xc1\x61\x1d\x4d\x6f\x6e\x2c\x20\x32\x31\x20\x4f\x63\x74\x20\x32\x30\x31\x33\x20\x32\x30\x3a\x31\x33\x3a\x32\x32\x20\x47\x4d\x54\xc0\x5a\x04\x67\x7a\x69\x70\x77\x38\x66\x6f\x6f\x3d\x41\x53\x44\x4a\x4b\x48\x51\x4b\x42\x5a\x58\x4f\x51\x57\x45\x4f\x50\x49\x55\x41\x58\x51\x57\x45\x4f\x49\x55\x3b\x20\x6d\x61\x78\x2d\x61\x67\x65\x3d\x33\x36\x30\x30\x3b\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x31"
|
210
|
+
result = decoder.decode(encoded)
|
211
|
+
expected = [
|
212
|
+
[":status", "200"],
|
213
|
+
["cache-control", "private"],
|
214
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT"],
|
215
|
+
["location", "https://www.example.com"],
|
216
|
+
["content-encoding", "gzip"],
|
217
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"]
|
218
|
+
]
|
219
|
+
assert_equal(expected, result)
|
220
|
+
end
|
221
|
+
|
222
|
+
# C.6.1
|
223
|
+
def test_hpack_decode_response_with_huffman
|
224
|
+
decoder = new_decoder(256)
|
225
|
+
encoded = "\x48\x82\x64\x02\x58\x85\xae\xc3\x77\x1a\x4b\x61\x96\xd0\x7a\xbe\x94\x10\x54\xd4\x44\xa8\x20\x05\x95\x04\x0b\x81\x66\xe0\x82\xa6\x2d\x1b\xff\x6e\x91\x9d\x29\xad\x17\x18\x63\xc7\x8f\x0b\x97\xc8\xe9\xae\x82\xae\x43\xd3"
|
226
|
+
result = decoder.decode(encoded)
|
227
|
+
expected = [
|
228
|
+
[":status", "302"],
|
229
|
+
["cache-control", "private"],
|
230
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
231
|
+
["location", "https://www.example.com"]
|
232
|
+
]
|
233
|
+
assert_equal(expected, result)
|
234
|
+
|
235
|
+
decoder # for C.6.2
|
236
|
+
end
|
237
|
+
|
238
|
+
# C.6.2
|
239
|
+
def test_hpack_decode_response_with_huffman2
|
240
|
+
decoder = test_hpack_decode_response_with_huffman
|
241
|
+
encoded = "\x48\x83\x64\x0e\xff\xc1\xc0\xbf"
|
242
|
+
result = decoder.decode(encoded)
|
243
|
+
expected = [
|
244
|
+
[":status", "307"],
|
245
|
+
["cache-control", "private"],
|
246
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
247
|
+
["location", "https://www.example.com"]
|
248
|
+
]
|
249
|
+
assert_equal(expected, result)
|
250
|
+
refute_includes(decoder.dynamic_table, [":status", "302"]) # evicted
|
251
|
+
|
252
|
+
decoder # for C.6.3
|
253
|
+
end
|
254
|
+
|
255
|
+
# C.6.3
|
256
|
+
def test_hpack_decode_response_with_huffman3
|
257
|
+
decoder = test_hpack_decode_response_with_huffman2
|
258
|
+
encoded = "\x88\xc1\x61\x96\xd0\x7a\xbe\x94\x10\x54\xd4\x44\xa8\x20\x05\x95\x04\x0b\x81\x66\xe0\x84\xa6\x2d\x1b\xff\xc0\x5a\x83\x9b\xd9\xab\x77\xad\x94\xe7\x82\x1d\xd7\xf2\xe6\xc7\xb3\x35\xdf\xdf\xcd\x5b\x39\x60\xd5\xaf\x27\x08\x7f\x36\x72\xc1\xab\x27\x0f\xb5\x29\x1f\x95\x87\x31\x60\x65\xc0\x03\xed\x4e\xe5\xb1\x06\x3d\x50\x07"
|
259
|
+
result = decoder.decode(encoded)
|
260
|
+
expected = [
|
261
|
+
[":status", "200"],
|
262
|
+
["cache-control", "private"],
|
263
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT"],
|
264
|
+
["location", "https://www.example.com"],
|
265
|
+
["content-encoding", "gzip"],
|
266
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"]
|
267
|
+
]
|
268
|
+
assert_equal(expected, result)
|
269
|
+
end
|
270
|
+
|
271
|
+
def test_hpack_decode_index_zero
|
272
|
+
decoder = new_decoder(256)
|
273
|
+
encoded = "\x80"
|
274
|
+
assert_raises(HPACKError) {
|
275
|
+
decoder.decode(encoded)
|
276
|
+
}
|
277
|
+
end
|
278
|
+
|
279
|
+
def test_hpack_decode_changing_limit
|
280
|
+
decoder = new_decoder(256)
|
281
|
+
encoded = "\x34"
|
282
|
+
assert_equal(256, decoder.limit)
|
283
|
+
decoder.decode(encoded)
|
284
|
+
assert_equal(0b00010100, decoder.limit)
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
def new_decoder(settings_header_table_size = 1 << 31)
|
289
|
+
Plum::HPACK::Decoder.new(settings_header_table_size)
|
290
|
+
end
|
291
|
+
end
|