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