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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fbb3d755285f78919374f46331be663f93b0c2a2
4
- data.tar.gz: 7fec872c23645cd58bfaac4498687d7ed30163c5
3
+ metadata.gz: df4cac7083913b918a193ac157d4fd88bd1c0022
4
+ data.tar.gz: ef49c0a591dbcb1fa671b9f206705531f006e025
5
5
  SHA512:
6
- metadata.gz: b89109d466c9f33465f45080c664b16e4f897a505837a91d98bc4c2785968ee1899be7cf92f3a94512632eae35c475b43c3a09d5148195d52d1864e3948de838
7
- data.tar.gz: 333261402b76c021fcd3385dbd8e6ac8e2a1d77d65155978ebf23fbd08cac2dea8d6401047024850ebaeb311eedecf27d3723b483edc3eb9f71042f8c7e43ba4
6
+ metadata.gz: 635b191791c1f6465d0a8e8f3d84c763506f9449c5e685c3ce4c80ed1b5bd22f1fc906d0f1f4c74a47e52e1d44dbea6245282575f88eff61af784fae8be1dbb2
7
+ data.tar.gz: 30cdfdd10ff68e47521f6d5928f920300706f9722bf32002844c18b2ced3d7215e2a24165a37abdcd9a53009173d5efc578ef5ddee61a766c2ca90af60cc9707
data/Guardfile CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  guard :minitest, env: { "SKIP_COVERAGE" => true } do
2
3
  # with Minitest::Unit
3
4
  watch(%r{^test/(.*)\/?test_(.*)\.rb$})
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
- ```ruby
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
- Then run it with:
36
+ You can run it:
37
37
 
38
38
  ```sh
39
39
  % plum -e production -p 8080 --https config.ru
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  require "bundler/gem_tasks"
2
3
  require "rake/testtask"
3
4
  require "yard"
@@ -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 = "Hello World! <a href=/abc.html>ABC</a> <a href=/fgsd>Not found</a>"
73
- body << <<-EOF
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.size
84
+ "content-length": body.bytesize
84
85
  }, body)
85
86
  when ["POST", "/post.page"]
86
87
  body = "Posted value is: #{CGI.unescape(data).gsub("<", "&lt;").gsub(">", "&gt;")}<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.size
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.size
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
- data = "Use modern web browser with HTTP/2 support."
110
-
111
- resp = ""
112
- resp << "HTTP/1.1 505 HTTP Version Not Supported\r\n"
113
- resp << "Content-Type: text/plain\r\n"
114
- resp << "Content-Length: #{data.bytesize}\r\n"
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 bytes*"*400]
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
@@ -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 = "Hello World! <a href=/abc.html>ABC</a> <a href=/fgsd>Not found</a>"
91
- body << <<-EOF
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",
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Plum
2
3
  module BinaryString
3
4
  refine String do
@@ -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".freeze
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 = "".force_encoding(Encoding::BINARY)
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
- if @streams.key?(frame.stream_id)
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
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  using Plum::BinaryString
2
3
 
3
4
  module Plum
data/lib/plum/errors.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Plum
2
3
  class Error < StandardError; end
3
4
  class HPACKError < Error; end
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Plum
2
3
  module EventEmitter
3
4
  # Registers an event handler to specified event. An event can have multiple handlers.
@@ -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 = "".push_uint32(wsi)
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
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  using Plum::BinaryString
2
3
 
3
4
  module Plum
@@ -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 = "".push_uint32(HTTPError::ERROR_CODES[error_type])
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 = "".push_uint32((last_id || 0) | (0 << 31))
20
- .push_uint32(HTTPError::ERROR_CODES[error_type])
21
- .push(message)
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("") {|payload, (key, value)|
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 DATA frame.
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 = "".push_uint32(0 << 31 | new_id)
75
- .push(encoded)
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
 
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  using Plum::BinaryString
2
3
 
3
4
  module Plum
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Plum
2
3
  module HPACK
3
4
  # RFC7541 Appendix A
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Plum
2
3
  module HPACK
3
4
  module Context
@@ -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 = first_byte % mask
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
- index, ilen = read_integer(str, pos, 6)
108
- if index == 0
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
- val, vlen = read_string(str, pos + ilen + nlen)
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
- index, ilen = read_integer(str, pos, 4)
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
@@ -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.force_encoding(Encoding::BINARY)
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.force_encoding(Encoding::BINARY) << encode_string(name) << encode_string(value)
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
- lenstr = encode_integer(huffman_str.bytesize, 7, 0b10000000)
110
- lenstr << huffman_str
109
+ encode_integer(huffman_str.bytesize, 7, 0b10000000) << huffman_str
111
110
  end
112
111
  end
113
112
  end
@@ -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
 
@@ -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
- resp << "HTTP/1.1 101 Switching Protocols\r\n"
70
- resp << "Connection: Upgrade\r\n"
71
- resp << "Upgrade: h2c\r\n"
72
- resp << "Server: plum/#{Plum::VERSION}\r\n"
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
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Plum
2
3
  class HTTPSConnection < Connection
3
4
  attr_reader :sock
data/lib/plum/rack.rb CHANGED
@@ -7,4 +7,4 @@ require "plum/rack/config"
7
7
  require "plum/rack/dsl"
8
8
  require "plum/rack/listener"
9
9
  require "plum/rack/server"
10
- require "plum/rack/connection"
10
+ require "plum/rack/session"
data/lib/plum/rack/cli.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  require "optparse"
2
3
  require "rack/builder"
3
4
 
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Plum
2
3
  module Rack
3
4
  class Config
data/lib/plum/rack/dsl.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Plum
2
3
  module Rack
3
4
  module DSL
@@ -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")
@@ -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 = Connection.new(@app, plum, @logger)
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
- class Connection
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, plum, logger)
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: "".force_encoding(Encoding::BINARY) }
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?(Array)
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
- body.each_with_index { |part, i|
61
+ i = 0
62
+ body.each { |part|
55
63
  stream.send_data(part, end_stream: last == i)
64
+ i += 1
56
65
  }
57
- elsif body.is_a?(IO)
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(r_extheaders)
69
- if pushs = r_extheaders["plum.serverpush"]
70
- pushs.split(";").map { |push| push.split(" ", 2) }
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
- r_pushstreams = r_topushs.map { |method, path|
84
- preq = { ":authority" => headers.find { |k, v| k == ":authority" }[1],
85
- ":method" => method.to_s.upcase,
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
- r_pushstreams.each { |st, preq|
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"] = (cport || 443).to_i
143
+ ebase["SERVER_PORT"] = cport || "443"
127
144
  when ":scheme"
128
145
  ebase["rack.url_scheme"] = v
129
146
  else
130
- if k.start_with?(":")
131
- # unknown HTTP/2 pseudo-headers
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_" << k.tr("-", "_").upcase!] = v
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
- else
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(0)
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
- body = frame.payload.byteslice(1, frame.length - padding_length - 1)
119
+ callback(:data, frame.payload.byteslice(1, frame.length - padding_length - 1))
119
120
  else
120
- body = frame.payload
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
- first_length -= 1 + padding_length
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 > first_length
143
+ if padding_length > payload.bytesize
148
144
  raise ConnectionError.new(:protocol_error, "padding is too long")
149
145
  end
150
146
 
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  using Plum::BinaryString
2
3
 
3
4
  module Plum
data/lib/plum/version.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Plum
2
- VERSION = "0.1.2"
3
+ VERSION = "0.1.3"
3
4
  end
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  module Rack
2
3
  module Handler
3
4
  class Plum
data/plum.gemspec CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- frozen-string-literal: true -*-
1
2
  lib = File.expand_path("../lib", __FILE__)
2
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
4
  require "plum/version"
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.2
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-10-25 00:00:00.000000000 Z
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