plum 0.1.2 → 0.1.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/Guardfile +1 -0
- data/README.md +4 -4
- data/Rakefile +1 -0
- data/examples/non_tls_server.rb +13 -15
- data/examples/rack.ru +2 -2
- data/examples/static_server.rb +7 -6
- data/lib/plum/binary_string.rb +1 -0
- data/lib/plum/connection.rb +4 -5
- data/lib/plum/connection_utils.rb +1 -0
- data/lib/plum/errors.rb +1 -0
- data/lib/plum/event_emitter.rb +1 -0
- data/lib/plum/flow_control.rb +2 -1
- data/lib/plum/frame.rb +1 -0
- data/lib/plum/frame_factory.rb +12 -9
- data/lib/plum/frame_utils.rb +1 -0
- data/lib/plum/hpack/constants.rb +1 -0
- data/lib/plum/hpack/context.rb +1 -0
- data/lib/plum/hpack/decoder.rb +12 -14
- data/lib/plum/hpack/encoder.rb +5 -6
- data/lib/plum/hpack/huffman.rb +4 -3
- data/lib/plum/http_connection.rb +7 -7
- data/lib/plum/https_connection.rb +1 -0
- data/lib/plum/rack.rb +1 -1
- data/lib/plum/rack/cli.rb +1 -0
- data/lib/plum/rack/config.rb +1 -0
- data/lib/plum/rack/dsl.rb +1 -0
- data/lib/plum/rack/listener.rb +2 -1
- data/lib/plum/rack/server.rb +9 -1
- data/lib/plum/rack/{connection.rb → session.rb} +43 -28
- data/lib/plum/stream.rb +7 -11
- data/lib/plum/stream_utils.rb +1 -0
- data/lib/plum/version.rb +2 -1
- data/lib/rack/handler/plum.rb +1 -0
- data/plum.gemspec +1 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df4cac7083913b918a193ac157d4fd88bd1c0022
|
4
|
+
data.tar.gz: ef49c0a591dbcb1fa671b9f206705531f006e025
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 635b191791c1f6465d0a8e8f3d84c763506f9449c5e685c3ce4c80ed1b5bd22f1fc906d0f1f4c74a47e52e1d44dbea6245282575f88eff61af784fae8be1dbb2
|
7
|
+
data.tar.gz: 30cdfdd10ff68e47521f6d5928f920300706f9722bf32002844c18b2ced3d7215e2a24165a37abdcd9a53009173d5efc578ef5ddee61a766c2ca90af60cc9707
|
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -17,11 +17,11 @@ A minimal pure Ruby implementation of HTTP/2 library / server.
|
|
17
17
|
* [rhenium/plum-server](https://github.com/rhenium/plum-server) - A static-file server for https://rhe.jp and http://rhe.jp.
|
18
18
|
|
19
19
|
### As a Rack-compatible server
|
20
|
-
Insert `require "plum/rack"` to your `config.ru`
|
21
20
|
|
22
|
-
|
23
|
-
require "plum/rack"
|
21
|
+
Most existing Rack-based applications (plum doesn't support Rack hijack API) should work without modification.
|
24
22
|
|
23
|
+
```ruby
|
24
|
+
# config.ru
|
25
25
|
App = -> env {
|
26
26
|
[
|
27
27
|
200,
|
@@ -33,7 +33,7 @@ App = -> env {
|
|
33
33
|
run App
|
34
34
|
```
|
35
35
|
|
36
|
-
|
36
|
+
You can run it:
|
37
37
|
|
38
38
|
```sh
|
39
39
|
% plum -e production -p 8080 --https config.ru
|
data/Rakefile
CHANGED
data/examples/non_tls_server.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
|
2
3
|
require "plum"
|
3
4
|
require "socket"
|
@@ -53,7 +54,7 @@ loop do
|
|
53
54
|
|
54
55
|
stream.on(:open) do
|
55
56
|
headers = nil
|
56
|
-
data =
|
57
|
+
data = String.new
|
57
58
|
end
|
58
59
|
|
59
60
|
stream.on(:headers) do |headers_|
|
@@ -69,8 +70,8 @@ loop do
|
|
69
70
|
stream.on(:end_stream) do
|
70
71
|
case [headers[":method"], headers[":path"]]
|
71
72
|
when ["GET", "/"]
|
72
|
-
body =
|
73
|
-
|
73
|
+
body = <<-EOF
|
74
|
+
Hello World! <a href=/abc.html>ABC</a> <a href=/fgsd>Not found</a>
|
74
75
|
<form action=post.page method=post>
|
75
76
|
<input type=text name=key value=default_value>
|
76
77
|
<input type=submit>
|
@@ -80,7 +81,7 @@ loop do
|
|
80
81
|
":status": "200",
|
81
82
|
"server": "plum",
|
82
83
|
"content-type": "text/html",
|
83
|
-
"content-length": body.
|
84
|
+
"content-length": body.bytesize
|
84
85
|
}, body)
|
85
86
|
when ["POST", "/post.page"]
|
86
87
|
body = "Posted value is: #{CGI.unescape(data).gsub("<", "<").gsub(">", ">")}<br> <a href=/>Back to top page</a>"
|
@@ -88,7 +89,7 @@ loop do
|
|
88
89
|
":status": "200",
|
89
90
|
"server": "plum",
|
90
91
|
"content-type": "text/html",
|
91
|
-
"content-length": body.
|
92
|
+
"content-length": body.bytesize
|
92
93
|
}, body)
|
93
94
|
else
|
94
95
|
body = "Page not found! <a href=/>Back to top page</a>"
|
@@ -96,7 +97,7 @@ loop do
|
|
96
97
|
":status": "404",
|
97
98
|
"server": "plum",
|
98
99
|
"content-type": "text/html",
|
99
|
-
"content-length": body.
|
100
|
+
"content-length": body.bytesize
|
100
101
|
}, body)
|
101
102
|
end
|
102
103
|
end
|
@@ -106,15 +107,12 @@ loop do
|
|
106
107
|
begin
|
107
108
|
plum.run
|
108
109
|
rescue Plum::LegacyHTTPError
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
resp << "Server: plum/#{Plum::VERSION}\r\n"
|
116
|
-
resp << "\r\n"
|
117
|
-
resp << data
|
110
|
+
resp = "HTTP/1.1 505 HTTP Version Not Supported\r\n"
|
111
|
+
"Content-Type: text/plain\r\n"
|
112
|
+
"Content-Length: #{data.bytesize}\r\n"
|
113
|
+
"Server: plum/#{Plum::VERSION}\r\n"
|
114
|
+
"\r\n"
|
115
|
+
"Use modern web browser with HTTP/2 support."
|
118
116
|
|
119
117
|
sock.write(resp)
|
120
118
|
rescue
|
data/examples/rack.ru
CHANGED
@@ -7,13 +7,13 @@ class App2
|
|
7
7
|
[
|
8
8
|
200,
|
9
9
|
{ "Content-Type" => "text/html" },
|
10
|
-
["
|
10
|
+
["8 bytes-" * 512]
|
11
11
|
]
|
12
12
|
else
|
13
13
|
[
|
14
14
|
404,
|
15
15
|
{ "Content-Type" => "text/html" },
|
16
|
-
[""]
|
16
|
+
["#{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"]
|
17
17
|
]
|
18
18
|
end
|
19
19
|
end
|
data/examples/static_server.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
|
2
3
|
require "plum"
|
3
4
|
require "openssl"
|
@@ -71,7 +72,7 @@ loop do
|
|
71
72
|
|
72
73
|
stream.on(:open) do
|
73
74
|
headers = nil
|
74
|
-
data =
|
75
|
+
data = String.new
|
75
76
|
end
|
76
77
|
|
77
78
|
stream.on(:headers) do |headers_|
|
@@ -87,8 +88,8 @@ loop do
|
|
87
88
|
stream.on(:end_stream) do
|
88
89
|
case [headers[":method"], headers[":path"]]
|
89
90
|
when ["GET", "/"]
|
90
|
-
body =
|
91
|
-
|
91
|
+
body = <<-EOF
|
92
|
+
Hello World! <a href=/abc.html>ABC</a> <a href=/fgsd>Not found</a>
|
92
93
|
<form action=post.page method=post>
|
93
94
|
<input type=text name=key value=default_value>
|
94
95
|
<input type=submit>
|
@@ -114,9 +115,9 @@ loop do
|
|
114
115
|
"content-type": "text/html",
|
115
116
|
"content-length": body.size
|
116
117
|
}, body)
|
117
|
-
image = ("iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAgMAAADXB5lNAAAACVBMVEX///93o0jG/4mTMy20AAAA"
|
118
|
-
"bklEQVQ4y2NgoAoIRQJkCoSimIdTgJGBBU1ABE1A1AVdBQuaACu6gCALhhZ0axlZCDgMWYAB6ilU"
|
119
|
-
"35IoADEMxWyyBDD45AhQCFahM0kXWIVu3sAJrILzyBcgytoFeATABBcXWohhCEC14BCgGAAAX1ZQ"
|
118
|
+
image = ("iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAgMAAADXB5lNAAAACVBMVEX///93o0jG/4mTMy20AAAA"
|
119
|
+
"bklEQVQ4y2NgoAoIRQJkCoSimIdTgJGBBU1ABE1A1AVdBQuaACu6gCALhhZ0axlZCDgMWYAB6ilU"
|
120
|
+
"35IoADEMxWyyBDD45AhQCFahM0kXWIVu3sAJrILzyBcgytoFeATABBcXWohhCEC14BCgGAAAX1ZQ"
|
120
121
|
"ZtJp0zAAAAAASUVORK5CYII=").unpack("m")[0]
|
121
122
|
i_stream.respond({
|
122
123
|
":status": "200",
|
data/lib/plum/binary_string.rb
CHANGED
data/lib/plum/connection.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
using Plum::BinaryString
|
2
3
|
|
3
4
|
module Plum
|
@@ -6,7 +7,7 @@ module Plum
|
|
6
7
|
include FlowControl
|
7
8
|
include ConnectionUtils
|
8
9
|
|
9
|
-
CLIENT_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
10
|
+
CLIENT_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
10
11
|
|
11
12
|
DEFAULT_SETTINGS = {
|
12
13
|
header_table_size: 4096, # octets
|
@@ -25,7 +26,7 @@ module Plum
|
|
25
26
|
@writer = writer
|
26
27
|
@local_settings = Hash.new {|hash, key| DEFAULT_SETTINGS[key] }.merge!(local_settings)
|
27
28
|
@remote_settings = Hash.new {|hash, key| DEFAULT_SETTINGS[key] }
|
28
|
-
@buffer =
|
29
|
+
@buffer = String.new
|
29
30
|
@streams = {}
|
30
31
|
@state = :negotiation
|
31
32
|
@hpack_decoder = HPACK::Decoder.new(@local_settings[:header_table_size])
|
@@ -134,9 +135,7 @@ module Plum
|
|
134
135
|
if frame.stream_id == 0
|
135
136
|
receive_control_frame(frame)
|
136
137
|
else
|
137
|
-
|
138
|
-
stream = @streams[frame.stream_id]
|
139
|
-
else
|
138
|
+
unless stream = @streams[frame.stream_id]
|
140
139
|
if frame.stream_id.even? || @max_odd_stream_id >= frame.stream_id
|
141
140
|
raise Plum::ConnectionError.new(:protocol_error)
|
142
141
|
end
|
data/lib/plum/errors.rb
CHANGED
data/lib/plum/event_emitter.rb
CHANGED
data/lib/plum/flow_control.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
using Plum::BinaryString
|
2
3
|
|
3
4
|
module Plum
|
@@ -27,7 +28,7 @@ module Plum
|
|
27
28
|
# @param wsi [Integer] The amount to increase receiving window size. The legal range is 1 to 2^32-1.
|
28
29
|
def window_update(wsi)
|
29
30
|
@recv_remaining_window += wsi
|
30
|
-
payload =
|
31
|
+
payload = String.new.push_uint32(wsi)
|
31
32
|
sid = (Stream === self) ? self.id : 0
|
32
33
|
send_immediately Frame.new(type: :window_update, stream_id: sid, payload: payload)
|
33
34
|
end
|
data/lib/plum/frame.rb
CHANGED
data/lib/plum/frame_factory.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
using Plum::BinaryString
|
2
3
|
|
3
4
|
module Plum
|
@@ -6,7 +7,7 @@ module Plum
|
|
6
7
|
# @param stream_id [Integer] The stream ID.
|
7
8
|
# @param error_type [Symbol] The error type defined in RFC 7540 Section 7.
|
8
9
|
def rst_stream(stream_id, error_type)
|
9
|
-
payload =
|
10
|
+
payload = String.new.push_uint32(HTTPError::ERROR_CODES[error_type])
|
10
11
|
Frame.new(type: :rst_stream, stream_id: stream_id, payload: payload)
|
11
12
|
end
|
12
13
|
|
@@ -16,9 +17,9 @@ module Plum
|
|
16
17
|
# @param message [String] Additional debug data.
|
17
18
|
# @see RFC 7540 Section 6.8
|
18
19
|
def goaway(last_id, error_type, message = "")
|
19
|
-
payload =
|
20
|
-
|
21
|
-
|
20
|
+
payload = String.new.push_uint32((last_id || 0) | (0 << 31))
|
21
|
+
.push_uint32(HTTPError::ERROR_CODES[error_type])
|
22
|
+
.push(message)
|
22
23
|
Frame.new(type: :goaway, stream_id: 0, payload: payload)
|
23
24
|
end
|
24
25
|
|
@@ -26,7 +27,7 @@ module Plum
|
|
26
27
|
# @param ack [Symbol] Pass :ack to create an ACK frame.
|
27
28
|
# @param args [Hash<Symbol, Integer>] The settings values to send.
|
28
29
|
def settings(ack = nil, **args)
|
29
|
-
payload = args.inject(
|
30
|
+
payload = args.inject(String.new) {|payload, (key, value)|
|
30
31
|
id = Frame::SETTINGS_TYPE[key] or raise ArgumentError.new("invalid settings type")
|
31
32
|
payload.push_uint16(id)
|
32
33
|
payload.push_uint32(value)
|
@@ -40,9 +41,10 @@ module Plum
|
|
40
41
|
# @param payload [String] 8 bytes length data to send.
|
41
42
|
# @overload ping(payload = "plum\x00\x00\x00\x00")
|
42
43
|
# @param payload [String] 8 bytes length data to send.
|
43
|
-
def ping(arg1 = "plum\x00\x00\x00\x00", arg2 = nil)
|
44
|
+
def ping(arg1 = "plum\x00\x00\x00\x00".b, arg2 = nil)
|
44
45
|
if !arg2
|
45
46
|
raise ArgumentError.new("data must be 8 octets") if arg1.bytesize != 8
|
47
|
+
arg1 = arg1.b if arg1.encoding != Encoding::BINARY
|
46
48
|
Frame.new(type: :ping, stream_id: 0, payload: arg1)
|
47
49
|
else
|
48
50
|
Frame.new(type: :ping, stream_id: 0, flags: [:ack], payload: arg2)
|
@@ -54,10 +56,11 @@ module Plum
|
|
54
56
|
# @param payload [String] Payload.
|
55
57
|
# @param flags [Array<Symbol>] Flags.
|
56
58
|
def data(stream_id, payload, *flags)
|
59
|
+
payload = payload.b if payload && payload.encoding != Encoding::BINARY
|
57
60
|
Frame.new(type: :data, stream_id: stream_id, flags: flags, payload: payload)
|
58
61
|
end
|
59
62
|
|
60
|
-
# Creates a
|
63
|
+
# Creates a HEADERS frame.
|
61
64
|
# @param stream_id [Integer] The stream ID.
|
62
65
|
# @param encoded [String] Headers.
|
63
66
|
# @param flags [Array<Symbol>] Flags.
|
@@ -71,8 +74,8 @@ module Plum
|
|
71
74
|
# @param encoded [String] Request headers.
|
72
75
|
# @param flags [Array<Symbol>] Flags.
|
73
76
|
def push_promise(stream_id, new_id, encoded, *flags)
|
74
|
-
payload =
|
75
|
-
|
77
|
+
payload = String.new.push_uint32(new_id)
|
78
|
+
.push(encoded)
|
76
79
|
Frame.new(type: :push_promise, stream_id: stream_id, flags: flags, payload: payload)
|
77
80
|
end
|
78
81
|
|
data/lib/plum/frame_utils.rb
CHANGED
data/lib/plum/hpack/constants.rb
CHANGED
data/lib/plum/hpack/context.rb
CHANGED
data/lib/plum/hpack/decoder.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
using Plum::BinaryString
|
2
3
|
|
3
4
|
module Plum
|
@@ -38,10 +39,9 @@ module Plum
|
|
38
39
|
|
39
40
|
def read_integer(str, pos, prefix_length)
|
40
41
|
raise HPACKError.new("integer: end of buffer") if str.empty?
|
41
|
-
first_byte = str.getbyte(pos)
|
42
42
|
|
43
43
|
mask = 1 << prefix_length
|
44
|
-
ret =
|
44
|
+
ret = str.getbyte(pos) % mask
|
45
45
|
return [ret, 1] if ret != mask - 1
|
46
46
|
|
47
47
|
octets = 0
|
@@ -104,18 +104,10 @@ module Plum
|
|
104
104
|
# +---+---------------------------+
|
105
105
|
# | Value String (Length octets) |
|
106
106
|
# +-------------------------------+
|
107
|
-
|
108
|
-
|
109
|
-
name, nlen = read_string(str, pos + ilen)
|
110
|
-
else
|
111
|
-
name, = fetch(index)
|
112
|
-
nlen = 0
|
113
|
-
end
|
107
|
+
ret, len = parse_literal(str, pos, 6)
|
108
|
+
store(*ret)
|
114
109
|
|
115
|
-
|
116
|
-
store(name, val)
|
117
|
-
|
118
|
-
[[name, val], ilen + nlen + vlen]
|
110
|
+
[ret, len]
|
119
111
|
end
|
120
112
|
|
121
113
|
def parse_no_indexing(str, pos)
|
@@ -138,7 +130,13 @@ module Plum
|
|
138
130
|
# +---+---------------------------+
|
139
131
|
# | Value String (Length octets) |
|
140
132
|
# +-------------------------------+
|
141
|
-
|
133
|
+
ret, len = parse_literal(str, pos, 4)
|
134
|
+
|
135
|
+
[ret, len]
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse_literal(str, pos, pref)
|
139
|
+
index, ilen = read_integer(str, pos, pref)
|
142
140
|
if index == 0
|
143
141
|
name, nlen = read_string(str, pos + ilen)
|
144
142
|
else
|
data/lib/plum/hpack/encoder.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
using Plum::BinaryString
|
2
3
|
|
3
4
|
module Plum
|
@@ -10,9 +11,8 @@ module Plum
|
|
10
11
|
@indexing = indexing
|
11
12
|
@huffman = huffman
|
12
13
|
end
|
13
|
-
|
14
14
|
def encode(headers)
|
15
|
-
out =
|
15
|
+
out = String.new.force_encoding(Encoding::BINARY)
|
16
16
|
headers.each do |name, value|
|
17
17
|
name = name.to_s
|
18
18
|
value = value.to_s
|
@@ -24,7 +24,7 @@ module Plum
|
|
24
24
|
out << encode_literal(name, value)
|
25
25
|
end
|
26
26
|
end
|
27
|
-
out
|
27
|
+
out
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
@@ -46,7 +46,7 @@ module Plum
|
|
46
46
|
else
|
47
47
|
fb = "\x00"
|
48
48
|
end
|
49
|
-
fb
|
49
|
+
(fb + encode_string(name)) << encode_string(value)
|
50
50
|
end
|
51
51
|
|
52
52
|
# +---+---+---+---+---+---+---+---+
|
@@ -106,8 +106,7 @@ module Plum
|
|
106
106
|
|
107
107
|
def encode_string_huffman(str)
|
108
108
|
huffman_str = Huffman.encode(str)
|
109
|
-
|
110
|
-
lenstr << huffman_str
|
109
|
+
encode_integer(huffman_str.bytesize, 7, 0b10000000) << huffman_str
|
111
110
|
end
|
112
111
|
end
|
113
112
|
end
|
data/lib/plum/hpack/huffman.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
using Plum::BinaryString
|
2
3
|
|
3
4
|
module Plum
|
@@ -7,7 +8,7 @@ module Plum
|
|
7
8
|
|
8
9
|
# Static-Huffman-encodes the specified String.
|
9
10
|
def encode(bytestr)
|
10
|
-
out =
|
11
|
+
out = String.new
|
11
12
|
bytestr.each_byte do |b|
|
12
13
|
out << HUFFMAN_TABLE[b]
|
13
14
|
end
|
@@ -19,13 +20,13 @@ module Plum
|
|
19
20
|
def decode(encoded)
|
20
21
|
bits = encoded.unpack("B*")[0]
|
21
22
|
out = []
|
22
|
-
buf =
|
23
|
+
buf = String.new
|
23
24
|
bits.each_char do |cb|
|
24
25
|
buf << cb
|
25
26
|
if c = HUFFMAN_TABLE_INVERSED[buf]
|
26
27
|
raise HPACKError.new("huffman: EOS detected") if c == 256
|
27
28
|
out << c
|
28
|
-
buf
|
29
|
+
buf.clear
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
data/lib/plum/http_connection.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
using Plum::BinaryString
|
2
3
|
|
3
4
|
module Plum
|
@@ -7,7 +8,7 @@ module Plum
|
|
7
8
|
def initialize(sock, local_settings = {})
|
8
9
|
require "http/parser"
|
9
10
|
@_headers = nil
|
10
|
-
@_body =
|
11
|
+
@_body = String.new
|
11
12
|
@_http_parser = setup_parser
|
12
13
|
@sock = sock
|
13
14
|
super(@sock.method(:write), local_settings)
|
@@ -65,12 +66,11 @@ module Plum
|
|
65
66
|
process_first_request
|
66
67
|
}
|
67
68
|
|
68
|
-
resp = ""
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
resp << "\r\n"
|
69
|
+
resp = "HTTP/1.1 101 Switching Protocols\r\n"
|
70
|
+
"Connection: Upgrade\r\n"
|
71
|
+
"Upgrade: h2c\r\n"
|
72
|
+
"Server: plum/#{Plum::VERSION}\r\n"
|
73
|
+
"\r\n"
|
74
74
|
|
75
75
|
@sock.write(resp)
|
76
76
|
end
|
data/lib/plum/rack.rb
CHANGED
data/lib/plum/rack/cli.rb
CHANGED
data/lib/plum/rack/config.rb
CHANGED
data/lib/plum/rack/dsl.rb
CHANGED
data/lib/plum/rack/listener.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
module Plum
|
2
3
|
module Rack
|
3
4
|
class BaseListener
|
@@ -76,7 +77,7 @@ module Plum
|
|
76
77
|
ef.subject_certificate = cert
|
77
78
|
ef.issuer_certificate = cert
|
78
79
|
cert.extensions = [
|
79
|
-
ef.create_extension("basicConstraints","CA:TRUE", true),
|
80
|
+
ef.create_extension("basicConstraints", "CA:TRUE", true),
|
80
81
|
ef.create_extension("subjectKeyIdentifier", "hash"),
|
81
82
|
]
|
82
83
|
cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
|
data/lib/plum/rack/server.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
module Plum
|
2
3
|
module Rack
|
3
4
|
class Server
|
5
|
+
attr_reader :config
|
6
|
+
|
4
7
|
def initialize(app, config)
|
8
|
+
@config = config
|
5
9
|
@state = :null
|
6
10
|
@app = config[:debug] ? ::Rack::CommonLogger.new(app) : app
|
7
11
|
@logger = Logger.new(config[:log] || $stdout).tap { |l|
|
@@ -46,7 +50,11 @@ module Plum
|
|
46
50
|
sock = sock.accept if sock.respond_to?(:accept)
|
47
51
|
plum = svr.plum(sock)
|
48
52
|
|
49
|
-
con =
|
53
|
+
con = Session.new(app: @app,
|
54
|
+
plum: plum,
|
55
|
+
logger: @logger,
|
56
|
+
server_push: @config[:server_push],
|
57
|
+
remote_addr: sock.peeraddr.last)
|
50
58
|
con.run
|
51
59
|
rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINVAL => e # closed
|
52
60
|
sock.close if sock
|
@@ -1,14 +1,19 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
using Plum::BinaryString
|
2
3
|
|
3
4
|
module Plum
|
4
5
|
module Rack
|
5
|
-
|
6
|
+
INVALID_HEADERS = Set["connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade"].freeze
|
7
|
+
|
8
|
+
class Session
|
6
9
|
attr_reader :app, :plum
|
7
10
|
|
8
|
-
def initialize(app
|
11
|
+
def initialize(app:, plum:, logger:, server_push: true, remote_addr: "127.0.0.1")
|
9
12
|
@app = app
|
10
13
|
@plum = plum
|
11
14
|
@logger = logger
|
15
|
+
@server_push = server_push
|
16
|
+
@remote_addr = remote_addr
|
12
17
|
|
13
18
|
setup_plum
|
14
19
|
end
|
@@ -35,7 +40,7 @@ module Plum
|
|
35
40
|
|
36
41
|
reqs = {}
|
37
42
|
@plum.on(:headers) { |stream, h|
|
38
|
-
reqs[stream] = { headers: h, data:
|
43
|
+
reqs[stream] = { headers: h, data: String.new.force_encoding(Encoding::BINARY) }
|
39
44
|
}
|
40
45
|
|
41
46
|
@plum.on(:data) { |stream, d|
|
@@ -49,13 +54,16 @@ module Plum
|
|
49
54
|
|
50
55
|
def send_body(stream, body)
|
51
56
|
begin
|
52
|
-
if body.is_a?(
|
57
|
+
if body.is_a?(IO)
|
58
|
+
stream.send_data(body, end_stream: true)
|
59
|
+
elsif body.respond_to?(:size)
|
53
60
|
last = body.size - 1
|
54
|
-
|
61
|
+
i = 0
|
62
|
+
body.each { |part|
|
55
63
|
stream.send_data(part, end_stream: last == i)
|
64
|
+
i += 1
|
56
65
|
}
|
57
|
-
|
58
|
-
stream.send_data(body, end_stream: true)
|
66
|
+
stream.send_data(nil, end_stream: true) if i == 0
|
59
67
|
else
|
60
68
|
body.each { |part| stream.send_data(part, end_stream: false) }
|
61
69
|
stream.send_data(nil, end_stream: true)
|
@@ -65,9 +73,22 @@ module Plum
|
|
65
73
|
end
|
66
74
|
end
|
67
75
|
|
68
|
-
def extract_push(
|
69
|
-
if
|
70
|
-
|
76
|
+
def extract_push(reqheaders, extheaders)
|
77
|
+
if @server_push &&
|
78
|
+
@plum.push_enabled? &&
|
79
|
+
pushs = extheaders["plum.serverpush"]
|
80
|
+
authority = reqheaders.find { |k, v| k == ":authority" }[1]
|
81
|
+
scheme = reqheaders.find { |k, v| k == ":scheme" }[1]
|
82
|
+
|
83
|
+
pushs.split(";").map { |push|
|
84
|
+
method, path = push.split(" ", 2)
|
85
|
+
{
|
86
|
+
":authority" => authority,
|
87
|
+
":method" => method.to_s.upcase,
|
88
|
+
":scheme" => scheme,
|
89
|
+
":path" => path
|
90
|
+
}
|
91
|
+
}
|
71
92
|
else
|
72
93
|
[]
|
73
94
|
end
|
@@ -77,21 +98,16 @@ module Plum
|
|
77
98
|
env = new_env(headers, data)
|
78
99
|
r_status, r_rawheaders, r_body = @app.call(env)
|
79
100
|
r_headers, r_extheaders = extract_headers(r_status, r_rawheaders)
|
80
|
-
r_topushs = extract_push(r_extheaders)
|
81
101
|
|
82
102
|
stream.send_headers(r_headers, end_stream: false)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
":scheme" => headers.find { |k, v| k == ":scheme" }[1],
|
87
|
-
":path" => path }
|
88
|
-
st = stream.promise(preq)
|
89
|
-
[st, preq]
|
103
|
+
|
104
|
+
push_sts = extract_push(headers, r_extheaders).map { |preq|
|
105
|
+
[stream.promise(preq), preq]
|
90
106
|
}
|
91
107
|
|
92
108
|
send_body(stream, r_body)
|
93
109
|
|
94
|
-
|
110
|
+
push_sts.each { |st, preq|
|
95
111
|
penv = new_env(preq, "")
|
96
112
|
p_status, p_h, p_body = @app.call(penv)
|
97
113
|
p_headers = extract_headers(p_status, p_h)
|
@@ -102,7 +118,6 @@ module Plum
|
|
102
118
|
|
103
119
|
def new_env(h, data)
|
104
120
|
ebase = {
|
105
|
-
"SCRIPT_NAME" => "",
|
106
121
|
"rack.version" => ::Rack::VERSION,
|
107
122
|
"rack.input" => StringIO.new(data),
|
108
123
|
"rack.errors" => $stderr,
|
@@ -110,6 +125,8 @@ module Plum
|
|
110
125
|
"rack.multiprocess" => false,
|
111
126
|
"rack.run_once" => false,
|
112
127
|
"rack.hijack?" => false,
|
128
|
+
"SCRIPT_NAME" => "",
|
129
|
+
"REMOTE_ADDR" => @remote_addr,
|
113
130
|
}
|
114
131
|
|
115
132
|
h.each { |k, v|
|
@@ -123,21 +140,19 @@ module Plum
|
|
123
140
|
when ":authority"
|
124
141
|
chost, cport = v.split(":", 2)
|
125
142
|
ebase["SERVER_NAME"] = chost
|
126
|
-
ebase["SERVER_PORT"] =
|
143
|
+
ebase["SERVER_PORT"] = cport || "443"
|
127
144
|
when ":scheme"
|
128
145
|
ebase["rack.url_scheme"] = v
|
129
146
|
else
|
130
|
-
|
131
|
-
|
132
|
-
else
|
133
|
-
if "cookie" == k && ebase["HTTP_COOKIE"]
|
147
|
+
unless k.start_with?(":") # ignore unknown pseudo-headers
|
148
|
+
if k == "cookie" && ebase["HTTP_COOKIE"]
|
134
149
|
if ebase["HTTP_COOKIE"].frozen?
|
135
|
-
ebase["HTTP_COOKIE"] += "; " << v
|
150
|
+
(ebase["HTTP_COOKIE"] += "; ") << v
|
136
151
|
else
|
137
152
|
ebase["HTTP_COOKIE"] << "; " << v
|
138
153
|
end
|
139
154
|
else
|
140
|
-
ebase["HTTP_"
|
155
|
+
ebase["HTTP_" + k.tr("-", "_").upcase!] = v
|
141
156
|
end
|
142
157
|
end
|
143
158
|
end
|
@@ -161,7 +176,7 @@ module Plum
|
|
161
176
|
|
162
177
|
if "set-cookie" == key
|
163
178
|
rbase[key] = v_.gsub("\n", "; ") # RFC 7540 8.1.2.5
|
164
|
-
|
179
|
+
elsif !INVALID_HEADERS.member?(key)
|
165
180
|
key.byteshift(2) if key.start_with?("x-")
|
166
181
|
rbase[key] = v_.tr("\n", ",") # RFC 7230 7
|
167
182
|
end
|
data/lib/plum/stream.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
1
2
|
using Plum::BinaryString
|
2
3
|
|
3
4
|
module Plum
|
@@ -111,40 +112,35 @@ module Plum
|
|
111
112
|
end
|
112
113
|
|
113
114
|
if frame.padded?
|
114
|
-
padding_length = frame.payload.uint8
|
115
|
+
padding_length = frame.payload.uint8
|
115
116
|
if padding_length >= frame.length
|
116
117
|
raise ConnectionError.new(:protocol_error, "padding is too long")
|
117
118
|
end
|
118
|
-
|
119
|
+
callback(:data, frame.payload.byteslice(1, frame.length - padding_length - 1))
|
119
120
|
else
|
120
|
-
|
121
|
+
callback(:data, frame.payload)
|
121
122
|
end
|
122
|
-
callback(:data, body)
|
123
123
|
|
124
124
|
receive_end_stream if frame.end_stream?
|
125
125
|
end
|
126
126
|
|
127
127
|
def receive_complete_headers(frames)
|
128
128
|
first = frames.shift
|
129
|
-
|
130
129
|
payload = first.payload
|
131
|
-
first_length = first.length
|
132
|
-
padding_length = 0
|
133
130
|
|
134
131
|
if first.padded?
|
135
132
|
padding_length = payload.uint8
|
136
|
-
|
137
|
-
payload = payload.byteslice(1, first_length)
|
133
|
+
payload = payload.byteslice(1, payload.bytesize - padding_length - 1)
|
138
134
|
else
|
135
|
+
padding_length = 0
|
139
136
|
payload = payload.dup
|
140
137
|
end
|
141
138
|
|
142
139
|
if first.priority?
|
143
140
|
receive_priority_payload(payload.byteshift(5))
|
144
|
-
first_length -= 5
|
145
141
|
end
|
146
142
|
|
147
|
-
if padding_length >
|
143
|
+
if padding_length > payload.bytesize
|
148
144
|
raise ConnectionError.new(:protocol_error, "padding is too long")
|
149
145
|
end
|
150
146
|
|
data/lib/plum/stream_utils.rb
CHANGED
data/lib/plum/version.rb
CHANGED
data/lib/rack/handler/plum.rb
CHANGED
data/plum.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- rhenium
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -190,10 +190,10 @@ files:
|
|
190
190
|
- lib/plum/rack.rb
|
191
191
|
- lib/plum/rack/cli.rb
|
192
192
|
- lib/plum/rack/config.rb
|
193
|
-
- lib/plum/rack/connection.rb
|
194
193
|
- lib/plum/rack/dsl.rb
|
195
194
|
- lib/plum/rack/listener.rb
|
196
195
|
- lib/plum/rack/server.rb
|
196
|
+
- lib/plum/rack/session.rb
|
197
197
|
- lib/plum/stream.rb
|
198
198
|
- lib/plum/stream_utils.rb
|
199
199
|
- lib/plum/version.rb
|