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 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