rackup 2.0.0 → 2.1.0

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
  SHA256:
3
- metadata.gz: c58b0cded0e6edaba53fc8bd56f6b86b362feb6c96933ad81ee31f618015adcb
4
- data.tar.gz: e5bf647ba5b1108790c63ed32dd9684f948fa7d6347359bf70ad39497ac5e144
3
+ metadata.gz: a0b31e72267a0dc7a7cc3bbe41d06b49ea16ec3612edc7474fe5c99c97e568cf
4
+ data.tar.gz: 6b9b9f197c6929bed47b7bb3c81f13f956427e1d078ab2a4b12afc0c4400f881
5
5
  SHA512:
6
- metadata.gz: b9b36c45895e8e373d4308b4893e4cb93d2db3cf0e9fa24043f773eac0c4e735eecceba460591caa78bf99ff7271b98e14e161aa590658a3801e7007f6cbb807
7
- data.tar.gz: 32b1b2c2768b0f5cf1b45b1f5b8e75742318c6a172c4af7d8e64990739d0754823534091520b677026630fd96a7395ee275f414fdfef52c8b5ab6413725f1fb7
6
+ metadata.gz: 21aba248befe16cd582ac7c867bc28cecd41155e2f8979e8cecba248e2f1623894c3f943484a85a189d3e2bdec45c836ad8f9f2995a2362809332b1b99dcd3d5
7
+ data.tar.gz: 258c30b02fc3d454df9dcd1d6285f0f3b6d592728f856c53e761c37db2a56925bc0a90bf0b3330441c754fddec280501abc13aa2032a120f3ce5917bd38c817f
@@ -11,28 +11,11 @@ require 'rack/constants'
11
11
  require_relative '../handler'
12
12
  require_relative '../version'
13
13
 
14
- # This monkey patch allows for applications to perform their own chunking
15
- # through WEBrick::HTTPResponse if rack is set to true.
16
- class WEBrick::HTTPResponse
17
- attr_accessor :rack
18
-
19
- alias _rack_setup_header setup_header
20
- def setup_header
21
- app_chunking = rack && @header['transfer-encoding'] == 'chunked'
22
-
23
- @chunked = app_chunking if app_chunking
24
-
25
- _rack_setup_header
26
-
27
- @chunked = false if app_chunking
28
- end
29
- end
14
+ require_relative '../stream'
30
15
 
31
16
  module Rackup
32
17
  module Handler
33
18
  class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
34
- include Rack
35
-
36
19
  def self.run(app, **options)
37
20
  environment = ENV['RACK_ENV'] || 'development'
38
21
  default_host = environment == 'development' ? 'localhost' : nil
@@ -73,43 +56,75 @@ module Rackup
73
56
  @app = app
74
57
  end
75
58
 
59
+ # This handles mapping the WEBrick request to a Rack input stream.
60
+ class Input
61
+ include Stream::Reader
62
+
63
+ def initialize(request)
64
+ @request = request
65
+
66
+ @reader = Fiber.new do
67
+ @request.body do |chunk|
68
+ Fiber.yield(chunk)
69
+ end
70
+
71
+ Fiber.yield(nil)
72
+
73
+ # End of stream:
74
+ @reader = nil
75
+ end
76
+ end
77
+
78
+ def close
79
+ @request = nil
80
+ @reader = nil
81
+ end
82
+
83
+ private
84
+
85
+ # Read one chunk from the request body.
86
+ def read_next
87
+ @reader&.resume
88
+ end
89
+ end
90
+
76
91
  def service(req, res)
77
- res.rack = true
78
92
  env = req.meta_vars
79
93
  env.delete_if { |k, v| v.nil? }
80
94
 
81
- rack_input = StringIO.new(req.body.to_s)
82
- rack_input.set_encoding(Encoding::BINARY)
95
+ input = Input.new(req)
83
96
 
84
97
  env.update(
85
- RACK_INPUT => rack_input,
86
- RACK_ERRORS => $stderr,
87
- RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http",
88
- RACK_IS_HIJACK => true,
98
+ ::Rack::RACK_INPUT => input,
99
+ ::Rack::RACK_ERRORS => $stderr,
100
+ ::Rack::RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[::Rack::HTTPS]) ? "https" : "http",
101
+ ::Rack::RACK_IS_HIJACK => true,
89
102
  )
90
103
 
91
- env[QUERY_STRING] ||= ""
92
- unless env[PATH_INFO] == ""
93
- path, n = req.request_uri.path, env[SCRIPT_NAME].length
94
- env[PATH_INFO] = path[n, path.length - n]
104
+ env[::Rack::QUERY_STRING] ||= ""
105
+ unless env[::Rack::PATH_INFO] == ""
106
+ path, n = req.request_uri.path, env[::Rack::SCRIPT_NAME].length
107
+ env[::Rack::PATH_INFO] = path[n, path.length - n]
95
108
  end
96
- env[REQUEST_PATH] ||= [env[SCRIPT_NAME], env[PATH_INFO]].join
109
+ env[::Rack::REQUEST_PATH] ||= [env[::Rack::SCRIPT_NAME], env[::Rack::PATH_INFO]].join
97
110
 
98
111
  status, headers, body = @app.call(env)
99
112
  begin
100
113
  res.status = status
101
114
 
102
- if value = headers[RACK_HIJACK]
115
+ if value = headers[::Rack::RACK_HIJACK]
103
116
  io_lambda = value
117
+ body = nil
104
118
  elsif !body.respond_to?(:to_path) && !body.respond_to?(:each)
105
119
  io_lambda = body
120
+ body = nil
106
121
  end
107
122
 
108
123
  if value = headers.delete('set-cookie')
109
124
  res.cookies.concat(Array(value))
110
125
  end
111
126
 
112
- headers.each { |key, value|
127
+ headers.each do |key, value|
113
128
  # Skip keys starting with rack., per Rack SPEC
114
129
  next if key.start_with?('rack.')
115
130
 
@@ -117,22 +132,27 @@ module Rackup
117
132
  # merge the values per RFC 1945 section 4.2.
118
133
  value = value.join(", ") if Array === value
119
134
  res[key] = value
120
- }
135
+ end
121
136
 
122
137
  if io_lambda
123
- rd, wr = IO.pipe
124
- res.body = rd
125
- res.chunked = true
126
- io_lambda.call wr
138
+ protocol = headers['rack.protocol'] || headers['upgrade']
139
+
140
+ if protocol
141
+ # Set all the headers correctly for an upgrade response:
142
+ res.upgrade!(protocol)
143
+ end
144
+ res.body = io_lambda
127
145
  elsif body.respond_to?(:to_path)
128
146
  res.body = ::File.open(body.to_path, 'rb')
129
147
  else
130
- body.each { |part|
131
- res.body << part
132
- }
148
+ buffer = String.new
149
+ body.each do |part|
150
+ buffer << part
151
+ end
152
+ res.body = buffer
133
153
  end
134
154
  ensure
135
- body.close if body.respond_to? :close
155
+ body.close if body.respond_to?(:close)
136
156
  end
137
157
  end
138
158
  end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2022, by Samuel Williams.
5
+
6
+ module Rackup
7
+ # The input stream is an IO-like object which contains the raw HTTP POST data. When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to gets, each, read and rewind.
8
+ class Stream
9
+ def initialize(input = nil, output = Buffered.new)
10
+ @input = input
11
+ @output = output
12
+
13
+ raise ArgumentError, "Non-writable output!" unless output.respond_to?(:write)
14
+
15
+ # Will hold remaining data in `#read`.
16
+ @buffer = nil
17
+ @closed = false
18
+ end
19
+
20
+ attr :input
21
+ attr :output
22
+
23
+ # This provides a read-only interface for data, which is surprisingly tricky to implement correctly.
24
+ module Reader
25
+ # rack.hijack_io must respond to:
26
+ # read, write, read_nonblock, write_nonblock, flush, close, close_read, close_write, closed?
27
+
28
+ # read behaves like IO#read. Its signature is read([length, [buffer]]). If given, length must be a non-negative Integer (>= 0) or nil, and buffer must be a String and may not be nil. If length is given and not nil, then this method reads at most length bytes from the input stream. If length is not given or nil, then this method reads all data until EOF. When EOF is reached, this method returns nil if length is given and not nil, or “” if length is not given or is nil. If buffer is given, then the read data will be placed into buffer instead of a newly created String object.
29
+ # @param length [Integer] the amount of data to read
30
+ # @param buffer [String] the buffer which will receive the data
31
+ # @return a buffer containing the data
32
+ def read(length = nil, buffer = nil)
33
+ return '' if length == 0
34
+
35
+ buffer ||= String.new.force_encoding(Encoding::BINARY)
36
+
37
+ # Take any previously buffered data and replace it into the given buffer.
38
+ if @buffer
39
+ buffer.replace(@buffer)
40
+ @buffer = nil
41
+ else
42
+ buffer.clear
43
+ end
44
+
45
+ if length
46
+ while buffer.bytesize < length and chunk = read_next
47
+ buffer << chunk
48
+ end
49
+
50
+ # This ensures the subsequent `slice!` works correctly.
51
+ buffer.force_encoding(Encoding::BINARY)
52
+
53
+ # This will be at least one copy:
54
+ @buffer = buffer.byteslice(length, buffer.bytesize)
55
+
56
+ # This should be zero-copy:
57
+ buffer.slice!(length, buffer.bytesize)
58
+
59
+ if buffer.empty?
60
+ return nil
61
+ else
62
+ return buffer
63
+ end
64
+ else
65
+ while chunk = read_next
66
+ buffer << chunk
67
+ end
68
+
69
+ return buffer
70
+ end
71
+ end
72
+
73
+ # Read at most `length` bytes from the stream. Will avoid reading from the underlying stream if possible.
74
+ def read_partial(length = nil)
75
+ if @buffer
76
+ buffer = @buffer
77
+ @buffer = nil
78
+ else
79
+ buffer = read_next
80
+ end
81
+
82
+ if buffer and length
83
+ if buffer.bytesize > length
84
+ # This ensures the subsequent `slice!` works correctly.
85
+ buffer.force_encoding(Encoding::BINARY)
86
+
87
+ @buffer = buffer.byteslice(length, buffer.bytesize)
88
+ buffer.slice!(length, buffer.bytesize)
89
+ end
90
+ end
91
+
92
+ return buffer
93
+ end
94
+
95
+ def gets
96
+ read_partial
97
+ end
98
+
99
+ def each
100
+ while chunk = read_partial
101
+ yield chunk
102
+ end
103
+ end
104
+
105
+ def read_nonblock(length, buffer = nil)
106
+ @buffer ||= read_next
107
+ chunk = nil
108
+
109
+ unless @buffer
110
+ buffer&.clear
111
+ return
112
+ end
113
+
114
+ if @buffer.bytesize > length
115
+ chunk = @buffer.byteslice(0, length)
116
+ @buffer = @buffer.byteslice(length, @buffer.bytesize)
117
+ else
118
+ chunk = @buffer
119
+ @buffer = nil
120
+ end
121
+
122
+ if buffer
123
+ buffer.replace(chunk)
124
+ else
125
+ buffer = chunk
126
+ end
127
+
128
+ return buffer
129
+ end
130
+ end
131
+
132
+ include Reader
133
+
134
+ def write(buffer)
135
+ if @output
136
+ @output.write(buffer)
137
+ return buffer.bytesize
138
+ else
139
+ raise IOError, "Stream is not writable, output has been closed!"
140
+ end
141
+ end
142
+
143
+ def write_nonblock(buffer)
144
+ write(buffer)
145
+ end
146
+
147
+ def <<(buffer)
148
+ write(buffer)
149
+ end
150
+
151
+ def flush
152
+ end
153
+
154
+ def close_read
155
+ @input&.close
156
+ @input = nil
157
+ end
158
+
159
+ # close must never be called on the input stream. huh?
160
+ def close_write
161
+ if @output.respond_to?(:close)
162
+ @output&.close
163
+ end
164
+
165
+ @output = nil
166
+ end
167
+
168
+ # Close the input and output bodies.
169
+ def close(error = nil)
170
+ self.close_read
171
+ self.close_write
172
+
173
+ return nil
174
+ ensure
175
+ @closed = true
176
+ end
177
+
178
+ # Whether the stream has been closed.
179
+ def closed?
180
+ @closed
181
+ end
182
+
183
+ # Whether there are any output chunks remaining?
184
+ def empty?
185
+ @output.empty?
186
+ end
187
+
188
+ private
189
+
190
+ def read_next
191
+ if @input
192
+ return @input.read
193
+ else
194
+ @input = nil
195
+ raise IOError, "Stream is not readable, input has been closed!"
196
+ end
197
+ end
198
+ end
199
+ end
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2022-2023, by Samuel Williams.
5
5
 
6
6
  module Rackup
7
- VERSION = "2.0.0"
7
+ VERSION = "2.1.0"
8
8
  end
data/license.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Copyright, 2007-2009, by Leah Neukirchen.
4
4
  Copyright, 2008, by Marc-André Cournoyer.
5
5
  Copyright, 2009, by Aaron Pfeifer.
6
- Copyright, 2009, by Graham Batty.
6
+ Copyright, 2009-2010, by Megan Batty.
7
7
  Copyright, 2009-2010, by Michael Fellinger.
8
8
  Copyright, 2009, by Genki Takiuchi.
9
9
  Copyright, 2009, by Joshua Peek.
@@ -16,7 +16,6 @@ Copyright, 2010, by Loren Segal.
16
16
  Copyright, 2010, by Andrew Bortz.
17
17
  Copyright, 2010, by John Barnette.
18
18
  Copyright, 2010, by John Sumsion.
19
- Copyright, 2010, by Graham.
20
19
  Copyright, 2011-2018, by Aaron Patterson.
21
20
  Copyright, 2011, by Konstantin Haase.
22
21
  Copyright, 2011, by Blake Mizerany.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rackup
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-01-18 00:00:00.000000000 Z
12
+ date: 2023-01-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -29,16 +29,16 @@ dependencies:
29
29
  name: webrick
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ">="
32
+ - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '0'
34
+ version: '1.8'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ">="
39
+ - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '0'
41
+ version: '1.8'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: bundler
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -125,6 +125,7 @@ files:
125
125
  - lib/rackup/handler/webrick.rb
126
126
  - lib/rackup/lobster.rb
127
127
  - lib/rackup/server.rb
128
+ - lib/rackup/stream.rb
128
129
  - lib/rackup/version.rb
129
130
  - license.md
130
131
  - readme.md