plum 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +84 -12
- data/circle.yml +27 -0
- data/examples/client/large.rb +20 -0
- data/examples/client/twitter.rb +51 -0
- data/examples/non_tls_server.rb +15 -9
- data/examples/static_server.rb +30 -23
- data/lib/plum.rb +9 -2
- data/lib/plum/client.rb +198 -0
- data/lib/plum/client/client_session.rb +91 -0
- data/lib/plum/client/connection.rb +19 -0
- data/lib/plum/client/legacy_client_session.rb +118 -0
- data/lib/plum/client/response.rb +100 -0
- data/lib/plum/client/upgrade_client_session.rb +46 -0
- data/lib/plum/connection.rb +58 -65
- data/lib/plum/connection_utils.rb +1 -1
- data/lib/plum/errors.rb +7 -3
- data/lib/plum/flow_control.rb +3 -3
- data/lib/plum/rack/listener.rb +3 -3
- data/lib/plum/rack/server.rb +1 -0
- data/lib/plum/rack/session.rb +5 -2
- data/lib/plum/server/connection.rb +42 -0
- data/lib/plum/{http_connection.rb → server/http_connection.rb} +7 -14
- data/lib/plum/{https_connection.rb → server/https_connection.rb} +2 -9
- data/lib/plum/stream.rb +54 -24
- data/lib/plum/stream_utils.rb +0 -12
- data/lib/plum/version.rb +1 -1
- data/plum.gemspec +2 -2
- data/test/plum/client/test_client.rb +152 -0
- data/test/plum/client/test_connection.rb +11 -0
- data/test/plum/client/test_legacy_client_session.rb +90 -0
- data/test/plum/client/test_response.rb +74 -0
- data/test/plum/client/test_upgrade_client_session.rb +45 -0
- data/test/plum/connection/test_handle_frame.rb +4 -1
- data/test/plum/{test_http_connection.rb → server/test_http_connection.rb} +4 -4
- data/test/plum/{test_https_connection.rb → server/test_https_connection.rb} +14 -8
- data/test/plum/test_connection.rb +9 -2
- data/test/plum/test_connection_utils.rb +9 -0
- data/test/plum/test_error.rb +1 -2
- data/test/plum/test_frame_factory.rb +37 -0
- data/test/plum/test_stream.rb +24 -4
- data/test/plum/test_stream_utils.rb +0 -1
- data/test/test_helper.rb +5 -2
- data/test/utils/assertions.rb +9 -9
- data/test/utils/client.rb +19 -0
- data/test/utils/server.rb +6 -6
- data/test/utils/string_socket.rb +15 -0
- metadata +36 -12
@@ -0,0 +1,91 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
2
|
+
module Plum
|
3
|
+
# HTTP/2 client session.
|
4
|
+
class ClientSession
|
5
|
+
HTTP2_DEFAULT_SETTINGS = {
|
6
|
+
enable_push: 0, # TODO: api?
|
7
|
+
initial_window_size: 2 ** 30, # TODO
|
8
|
+
}
|
9
|
+
|
10
|
+
attr_reader :plum
|
11
|
+
|
12
|
+
def initialize(socket, config)
|
13
|
+
@socket = socket
|
14
|
+
@config = config
|
15
|
+
@http2_settings = HTTP2_DEFAULT_SETTINGS.merge(@config[:http2_settings])
|
16
|
+
|
17
|
+
@plum = setup_plum
|
18
|
+
@responses = Set.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def succ
|
22
|
+
@plum << @socket.readpartial(16384)
|
23
|
+
rescue => e
|
24
|
+
fail(e)
|
25
|
+
end
|
26
|
+
|
27
|
+
def empty?
|
28
|
+
@responses.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@closed = true
|
33
|
+
@responses.each(&:_fail)
|
34
|
+
@responses.clear
|
35
|
+
@plum.close
|
36
|
+
end
|
37
|
+
|
38
|
+
def request(headers, body, options, &headers_cb)
|
39
|
+
headers = { ":method" => nil,
|
40
|
+
":path" => nil,
|
41
|
+
":authority" => @config[:hostname],
|
42
|
+
":scheme" => @config[:scheme]
|
43
|
+
}.merge(headers)
|
44
|
+
|
45
|
+
response = Response.new
|
46
|
+
@responses << response
|
47
|
+
stream = @plum.open_stream
|
48
|
+
stream.send_headers(headers, end_stream: !body)
|
49
|
+
stream.send_data(body, end_stream: true) if body
|
50
|
+
|
51
|
+
stream.on(:headers) { |resp_headers_raw|
|
52
|
+
response._headers(resp_headers_raw)
|
53
|
+
headers_cb.call(response) if headers_cb
|
54
|
+
}
|
55
|
+
stream.on(:data) { |chunk|
|
56
|
+
response._chunk(chunk)
|
57
|
+
check_window(stream)
|
58
|
+
}
|
59
|
+
stream.on(:end_stream) {
|
60
|
+
response._finish
|
61
|
+
@responses.delete(response)
|
62
|
+
}
|
63
|
+
stream.on(:stream_error) { |ex|
|
64
|
+
response._fail
|
65
|
+
raise ex
|
66
|
+
}
|
67
|
+
response
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def fail(exception)
|
72
|
+
close
|
73
|
+
raise exception
|
74
|
+
end
|
75
|
+
|
76
|
+
def setup_plum
|
77
|
+
plum = ClientConnection.new(@socket.method(:write), @http2_settings)
|
78
|
+
plum.on(:connection_error) { |ex|
|
79
|
+
fail(ex)
|
80
|
+
}
|
81
|
+
plum.window_update(@http2_settings[:initial_window_size])
|
82
|
+
plum
|
83
|
+
end
|
84
|
+
|
85
|
+
def check_window(stream)
|
86
|
+
ws = @http2_settings[:initial_window_size]
|
87
|
+
stream.window_update(ws) if stream.recv_remaining_window < (ws / 2)
|
88
|
+
@plum.window_update(ws) if @plum.recv_remaining_window < (ws / 2)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
2
|
+
using Plum::BinaryString
|
3
|
+
module Plum
|
4
|
+
class ClientConnection < Connection
|
5
|
+
def initialize(writer, local_settings = {})
|
6
|
+
super(writer, local_settings)
|
7
|
+
|
8
|
+
writer.call(CLIENT_CONNECTION_PREFACE)
|
9
|
+
settings(local_settings)
|
10
|
+
@state = :waiting_settings
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create a new stream for HTTP request.
|
14
|
+
def open_stream
|
15
|
+
next_id = @max_stream_id + (@max_stream_id.even? ? 1 : 2)
|
16
|
+
stream(next_id)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
2
|
+
module Plum
|
3
|
+
# HTTP/1.x client session.
|
4
|
+
class LegacyClientSession
|
5
|
+
# Creates a new HTTP/1.1 client session
|
6
|
+
def initialize(socket, config)
|
7
|
+
require "http/parser"
|
8
|
+
@socket = socket
|
9
|
+
@config = config
|
10
|
+
|
11
|
+
@parser = setup_parser
|
12
|
+
@requests = []
|
13
|
+
@response = nil
|
14
|
+
@headers_callback = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def succ
|
18
|
+
@parser << @socket.readpartial(16384)
|
19
|
+
rescue => e # including HTTP::Parser::Error
|
20
|
+
fail(e)
|
21
|
+
end
|
22
|
+
|
23
|
+
def empty?
|
24
|
+
!@response
|
25
|
+
end
|
26
|
+
|
27
|
+
def close
|
28
|
+
@closed = true
|
29
|
+
@response._fail if @response
|
30
|
+
end
|
31
|
+
|
32
|
+
def request(headers, body, options, &headers_cb)
|
33
|
+
headers["host"] = headers[":authority"] || headers["host"] || @config[:hostname]
|
34
|
+
if body
|
35
|
+
if headers["content-length"] || headers["transfer-encoding"]
|
36
|
+
chunked = false
|
37
|
+
else
|
38
|
+
chunked = true
|
39
|
+
headers["transfer-encoding"] = "chunked"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
response = Response.new
|
44
|
+
@requests << [response, headers, body, chunked, headers_cb]
|
45
|
+
consume_queue
|
46
|
+
response
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def fail(exception)
|
51
|
+
close
|
52
|
+
raise exception
|
53
|
+
end
|
54
|
+
|
55
|
+
def consume_queue
|
56
|
+
return if @response || @requests.empty?
|
57
|
+
|
58
|
+
response, headers, body, chunked, cb = @requests.shift
|
59
|
+
@response = response
|
60
|
+
@headers_callback = cb
|
61
|
+
|
62
|
+
@socket << construct_request(headers)
|
63
|
+
|
64
|
+
if body
|
65
|
+
if chunked
|
66
|
+
read_object(body) { |chunk|
|
67
|
+
@socket << chunk.bytesize.to_s(16) << "\r\n" << chunk << "\r\n"
|
68
|
+
}
|
69
|
+
else
|
70
|
+
read_object(body) { |chunk| @socket << chunk }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def construct_request(headers)
|
76
|
+
out = String.new
|
77
|
+
out << "%s %s HTTP/1.1\r\n" % [headers[":method"], headers[":path"]]
|
78
|
+
headers.each { |key, value|
|
79
|
+
next if key.start_with?(":") # HTTP/2 psuedo headers
|
80
|
+
out << "%s: %s\r\n" % [key, value]
|
81
|
+
}
|
82
|
+
out << "\r\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
def read_object(body)
|
86
|
+
if body.is_a?(String)
|
87
|
+
yield body
|
88
|
+
else # IO
|
89
|
+
until body.eof?
|
90
|
+
yield body.readpartial(1024)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def setup_parser
|
96
|
+
parser = HTTP::Parser.new
|
97
|
+
parser.on_headers_complete = proc {
|
98
|
+
resp_headers = parser.headers.map { |key, value| [key.downcase, value] }.to_h
|
99
|
+
@response._headers({ ":status" => parser.status_code.to_s }.merge(resp_headers))
|
100
|
+
@headers_callback.call(@response) if @headers_callback
|
101
|
+
}
|
102
|
+
|
103
|
+
parser.on_body = proc { |chunk|
|
104
|
+
@response._chunk(chunk)
|
105
|
+
}
|
106
|
+
|
107
|
+
parser.on_message_complete = proc { |env|
|
108
|
+
@response._finish
|
109
|
+
@response = nil
|
110
|
+
@headers_callback = nil
|
111
|
+
close unless parser.keep_alive?
|
112
|
+
consume_queue
|
113
|
+
}
|
114
|
+
|
115
|
+
parser
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
2
|
+
module Plum
|
3
|
+
class Response
|
4
|
+
# The response headers
|
5
|
+
# @return [Hash<String, String>]
|
6
|
+
attr_reader :headers
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
def initialize
|
10
|
+
@body = Queue.new
|
11
|
+
@finished = false
|
12
|
+
@failed = false
|
13
|
+
@body = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the HTTP status code.
|
17
|
+
# @return [String] the HTTP status code
|
18
|
+
def status
|
19
|
+
@headers && @headers[":status"]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the header value that correspond to the header name.
|
23
|
+
# @param key [String] the header name
|
24
|
+
# @return [String] the header value
|
25
|
+
def [](key)
|
26
|
+
@headers[key.to_s.downcase]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns whether the response is complete or not.
|
30
|
+
# @return [Boolean]
|
31
|
+
def finished?
|
32
|
+
@finished
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns whether the request has failed or not.
|
36
|
+
# @return [Boolean]
|
37
|
+
def failed?
|
38
|
+
@failed
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set callback tha called when received a chunk of response body.
|
42
|
+
# @yield [chunk] A chunk of the response body.
|
43
|
+
def on_chunk(&block)
|
44
|
+
raise "Body already read" if @on_chunk
|
45
|
+
raise ArgumentError, "block must be given" unless block_given?
|
46
|
+
@on_chunk = block
|
47
|
+
unless @body.empty?
|
48
|
+
@body.each(&block)
|
49
|
+
@body.clear
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set callback that will be called when the response finished.
|
54
|
+
def on_finish(&block)
|
55
|
+
raise ArgumentError, "block must be given" unless block_given?
|
56
|
+
if finished?
|
57
|
+
block.call
|
58
|
+
else
|
59
|
+
@on_finish = block
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the complete response body. Use #each_body instead if the body can be very large.
|
64
|
+
# @return [String] the whole response body
|
65
|
+
def body
|
66
|
+
raise "Body already read" if @on_chunk
|
67
|
+
if finished?
|
68
|
+
@body.join
|
69
|
+
else
|
70
|
+
raise "Response body is not complete"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# @api private
|
75
|
+
def _headers(raw_headers)
|
76
|
+
# response headers should not have duplicates
|
77
|
+
@headers = raw_headers.to_h.freeze
|
78
|
+
end
|
79
|
+
|
80
|
+
# @api private
|
81
|
+
def _chunk(chunk)
|
82
|
+
if @on_chunk
|
83
|
+
@on_chunk.call(chunk)
|
84
|
+
else
|
85
|
+
@body << chunk
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @api private
|
90
|
+
def _finish
|
91
|
+
@finished = true
|
92
|
+
@on_finish.call if @on_finish
|
93
|
+
end
|
94
|
+
|
95
|
+
# @api private
|
96
|
+
def _fail
|
97
|
+
@failed = true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
2
|
+
module Plum
|
3
|
+
# Try upgrade to HTTP/2
|
4
|
+
class UpgradeClientSession
|
5
|
+
def initialize(socket, config)
|
6
|
+
prepare_session(socket, config)
|
7
|
+
end
|
8
|
+
|
9
|
+
def succ
|
10
|
+
@session.succ
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
@session.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
@session.close
|
19
|
+
end
|
20
|
+
|
21
|
+
def request(headers, body, options, &headers_cb)
|
22
|
+
@session.request(headers, body, options, &headers_cb)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def prepare_session(socket, config)
|
27
|
+
lcs = LegacyClientSession.new(socket, config)
|
28
|
+
opt_res = lcs.request({ ":method" => "OPTIONS",
|
29
|
+
":path" => "*",
|
30
|
+
"User-Agent" => config[:user_agent],
|
31
|
+
"Connection" => "Upgrade, HTTP2-Settings",
|
32
|
+
"Upgrade" => "h2c",
|
33
|
+
"HTTP2-Settings" => "" }, nil, {})
|
34
|
+
lcs.succ until opt_res.finished?
|
35
|
+
|
36
|
+
if opt_res.status == "101"
|
37
|
+
lcs.close
|
38
|
+
@session = ClientSession.new(socket, config)
|
39
|
+
@session.plum.stream(1).set_state(:half_closed_local)
|
40
|
+
else
|
41
|
+
@session = lcs
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
data/lib/plum/connection.rb
CHANGED
@@ -23,23 +23,23 @@ module Plum
|
|
23
23
|
attr_reader :state, :streams
|
24
24
|
|
25
25
|
def initialize(writer, local_settings = {})
|
26
|
+
@state = :open
|
26
27
|
@writer = writer
|
27
28
|
@local_settings = Hash.new {|hash, key| DEFAULT_SETTINGS[key] }.merge!(local_settings)
|
28
29
|
@remote_settings = Hash.new {|hash, key| DEFAULT_SETTINGS[key] }
|
29
30
|
@buffer = String.new
|
30
31
|
@streams = {}
|
31
|
-
@state = :negotiation
|
32
32
|
@hpack_decoder = HPACK::Decoder.new(@local_settings[:header_table_size])
|
33
33
|
@hpack_encoder = HPACK::Encoder.new(@remote_settings[:header_table_size])
|
34
34
|
initialize_flow_control(send: @remote_settings[:initial_window_size],
|
35
35
|
recv: @local_settings[:initial_window_size])
|
36
|
-
@
|
37
|
-
@max_even_stream_id = 0
|
36
|
+
@max_stream_id = 0
|
38
37
|
end
|
39
|
-
private :initialize
|
40
38
|
|
41
39
|
# Emits :close event. Doesn't actually close socket.
|
42
40
|
def close
|
41
|
+
return if @state == :closed
|
42
|
+
@state = :closed
|
43
43
|
# TODO: server MAY wait streams
|
44
44
|
callback(:close)
|
45
45
|
end
|
@@ -47,80 +47,69 @@ module Plum
|
|
47
47
|
# Receives the specified data and process.
|
48
48
|
# @param new_data [String] The data received from the peer.
|
49
49
|
def receive(new_data)
|
50
|
+
return if @state == :closed
|
50
51
|
return if new_data.empty?
|
51
52
|
@buffer << new_data
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
if @state != :negotiation
|
56
|
-
while frame = Frame.parse!(@buffer)
|
57
|
-
callback(:frame, frame)
|
58
|
-
receive_frame(frame)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
rescue ConnectionError => e
|
53
|
+
consume_buffer
|
54
|
+
rescue RemoteConnectionError => e
|
62
55
|
callback(:connection_error, e)
|
63
56
|
goaway(e.http2_error_type)
|
64
57
|
close
|
65
58
|
end
|
66
59
|
alias << receive
|
67
60
|
|
68
|
-
#
|
69
|
-
# @param
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
stream
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
def send_immediately(frame)
|
78
|
-
callback(:send_frame, frame)
|
79
|
-
@writer.call(frame.assemble)
|
80
|
-
end
|
61
|
+
# Returns a Stream object with the specified ID.
|
62
|
+
# @param stream_id [Integer] the stream id
|
63
|
+
# @return [Stream] the stream
|
64
|
+
def stream(stream_id)
|
65
|
+
raise ArgumentError, "stream_id can't be 0" if stream_id == 0
|
81
66
|
|
82
|
-
|
83
|
-
|
84
|
-
|
67
|
+
stream = @streams[stream_id]
|
68
|
+
if stream
|
69
|
+
if stream.state == :idle && stream.id < @max_stream_id
|
70
|
+
stream.set_state(:closed_implicitly)
|
71
|
+
end
|
72
|
+
elsif stream_id > @max_stream_id
|
73
|
+
@max_stream_id = stream_id
|
74
|
+
stream = Stream.new(self, stream_id, state: :idle)
|
75
|
+
callback(:stream, stream)
|
76
|
+
@streams[stream_id] = stream
|
77
|
+
else
|
78
|
+
stream = Stream.new(self, stream_id, state: :closed_implicitly)
|
79
|
+
callback(:stream, stream)
|
85
80
|
end
|
86
81
|
|
87
|
-
|
88
|
-
@buffer.byteshift(24)
|
89
|
-
@state = :waiting_settings
|
90
|
-
settings(@local_settings)
|
91
|
-
end
|
82
|
+
stream
|
92
83
|
end
|
93
84
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
85
|
+
private
|
86
|
+
def consume_buffer
|
87
|
+
while frame = Frame.parse!(@buffer)
|
88
|
+
callback(:frame, frame)
|
89
|
+
receive_frame(frame)
|
99
90
|
end
|
91
|
+
end
|
100
92
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
stream
|
93
|
+
def send_immediately(frame)
|
94
|
+
callback(:send_frame, frame)
|
95
|
+
@writer.call(frame.assemble)
|
105
96
|
end
|
106
97
|
|
107
98
|
def validate_received_frame(frame)
|
108
99
|
if @state == :waiting_settings && frame.type != :settings
|
109
|
-
raise
|
100
|
+
raise RemoteConnectionError.new(:protocol_error)
|
110
101
|
end
|
111
102
|
|
112
103
|
if @state == :waiting_continuation
|
113
104
|
if frame.type != :continuation || frame.stream_id != @continuation_id
|
114
|
-
raise
|
105
|
+
raise RemoteConnectionError.new(:protocol_error)
|
115
106
|
end
|
116
|
-
|
117
107
|
if frame.end_headers?
|
118
108
|
@state = :open
|
119
|
-
@continuation_id = nil
|
120
109
|
end
|
121
110
|
end
|
122
111
|
|
123
|
-
if frame.type == :headers
|
112
|
+
if frame.type == :headers || frame.type == :push_promise
|
124
113
|
if !frame.end_headers?
|
125
114
|
@state = :waiting_continuation
|
126
115
|
@continuation_id = frame.stream_id
|
@@ -135,20 +124,13 @@ module Plum
|
|
135
124
|
if frame.stream_id == 0
|
136
125
|
receive_control_frame(frame)
|
137
126
|
else
|
138
|
-
|
139
|
-
if frame.stream_id.even? || @max_odd_stream_id >= frame.stream_id
|
140
|
-
raise Plum::ConnectionError.new(:protocol_error)
|
141
|
-
end
|
142
|
-
|
143
|
-
stream = new_stream(frame.stream_id)
|
144
|
-
end
|
145
|
-
stream.receive_frame(frame)
|
127
|
+
stream(frame.stream_id).receive_frame(frame)
|
146
128
|
end
|
147
129
|
end
|
148
130
|
|
149
131
|
def receive_control_frame(frame)
|
150
132
|
if frame.length > @local_settings[:max_frame_size]
|
151
|
-
raise
|
133
|
+
raise RemoteConnectionError.new(:frame_size_error)
|
152
134
|
end
|
153
135
|
|
154
136
|
case frame.type
|
@@ -159,11 +141,9 @@ module Plum
|
|
159
141
|
when :ping
|
160
142
|
receive_ping(frame)
|
161
143
|
when :goaway
|
162
|
-
|
163
|
-
goaway
|
164
|
-
close
|
144
|
+
receive_goaway(frame)
|
165
145
|
when :data, :headers, :priority, :rst_stream, :push_promise, :continuation
|
166
|
-
raise Plum::
|
146
|
+
raise Plum::RemoteConnectionError.new(:protocol_error)
|
167
147
|
else
|
168
148
|
# MUST ignore unknown frame type.
|
169
149
|
end
|
@@ -171,11 +151,11 @@ module Plum
|
|
171
151
|
|
172
152
|
def receive_settings(frame, send_ack: true)
|
173
153
|
if frame.ack?
|
174
|
-
raise
|
154
|
+
raise RemoteConnectionError.new(:frame_size_error) if frame.length != 0
|
175
155
|
callback(:settings_ack)
|
176
156
|
return
|
177
157
|
else
|
178
|
-
raise
|
158
|
+
raise RemoteConnectionError.new(:frame_size_error) if frame.length % 6 != 0
|
179
159
|
end
|
180
160
|
|
181
161
|
old_remote_settings = @remote_settings.dup
|
@@ -198,7 +178,7 @@ module Plum
|
|
198
178
|
end
|
199
179
|
|
200
180
|
def receive_ping(frame)
|
201
|
-
raise Plum::
|
181
|
+
raise Plum::RemoteConnectionError.new(:frame_size_error) if frame.length != 8
|
202
182
|
|
203
183
|
if frame.ack?
|
204
184
|
callback(:ping_ack)
|
@@ -208,5 +188,18 @@ module Plum
|
|
208
188
|
send_immediately Frame.ping(:ack, opaque_data)
|
209
189
|
end
|
210
190
|
end
|
191
|
+
|
192
|
+
def receive_goaway(frame)
|
193
|
+
callback(:goaway, frame)
|
194
|
+
goaway
|
195
|
+
close
|
196
|
+
|
197
|
+
last_id = frame.payload.uint32(0)
|
198
|
+
error_code = frame.payload.uint32(4)
|
199
|
+
message = frame.payload.byteslice(8, frame.length - 8)
|
200
|
+
if error_code > 0
|
201
|
+
raise LocalConnectionError.new(HTTPError::ERROR_CODES.key(error_code), message)
|
202
|
+
end
|
203
|
+
end
|
211
204
|
end
|
212
205
|
end
|