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