falcon 0.27.0 → 0.28.0

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
  SHA256:
3
- metadata.gz: 5d8f6d04def38b5f391b3cdcc17935b06612a9c95137bfaf563c58ac21cc0edc
4
- data.tar.gz: '085234503af9930b2d0a9bf7430673bb896456c2093cc627eee83fb7a6d9e1a3'
3
+ metadata.gz: 93cb18cb7157a7f267220d2d81a93d3c28bd96efddc04f799f64bdb658f9fa6b
4
+ data.tar.gz: efba9da82250f426f87705588d1601e0e6a349d1879a07b1fd18d7130f4a1091
5
5
  SHA512:
6
- metadata.gz: 41223e695feb4428ef06b260dd4b4a178c35f8889708198f5f94f4f598b6322b2c0d431e0268372debe54fd670fdb146efa2126295d8022a956a4878dd9ce095
7
- data.tar.gz: 33728e72a25355cb7025b0ae1c4f4883fc200251d82fe8d7ee1011c240ec9f49769c601a95da80801736a6cfeb3c1f78f3d499ac9c013425cc82c27c90987016
6
+ metadata.gz: f076c28cf4e3012624f56e775f27403bcd36aca3bf6c2f30120ce3c3fe60476a2cc1f68d2e64f235b66b51e9d08219d5242ec993b53a32e65e6e187dce991187
7
+ data.tar.gz: ae523db0420b3b8408ed84d9d6f99ae4ab6cabd0672699ed81c0d89a866aeed157d6b14984ce72ce8bd2bf930a074d5e702b4f4dfc74c73113eb9d2e2fb408b7
data/README.md CHANGED
@@ -42,7 +42,7 @@ Alternatively, install in terminal:
42
42
 
43
43
  ## Usage
44
44
 
45
- You can run `falcon serve` directly. It will load the `config.ru` and start serving on https://localhost:9292.
45
+ You can run `falcon serve` directly. It will load the `config.ru` and start serving on https://localhost:9292. Please [try the interactive online tutorial](https://katacoda.com/ioquatix/scenarios/falcon-introduction).
46
46
 
47
47
  The `falcon serve` command has the following options for you to use:
48
48
 
@@ -16,10 +16,10 @@ Gem::Specification.new do |spec|
16
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
17
  spec.require_paths = ["lib"]
18
18
 
19
- spec.add_dependency "http-protocol", "~> 0.15"
19
+ spec.add_dependency "http-protocol", "~> 0.17"
20
20
 
21
21
  spec.add_dependency "async", "~> 1.13"
22
- spec.add_dependency "async-io", "~> 1.20"
22
+ spec.add_dependency "async-io", "~> 1.22"
23
23
  spec.add_dependency "async-http", "~> 0.38.0"
24
24
  spec.add_dependency "async-container", "~> 0.10.0"
25
25
 
@@ -0,0 +1,90 @@
1
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/http/middleware'
22
+ require 'async/io/buffer'
23
+
24
+ module Falcon
25
+ module Adapters
26
+ # This is used for implementing partial hijack.
27
+ class Hijack
28
+ def self.for(env, block, socket = nil, task: Async::Task.current)
29
+ input = env[Rack::RACK_INPUT]
30
+ output = Async::HTTP::Body::Writable.new
31
+
32
+ stream = Hijack.new(input, output, socket)
33
+
34
+ task.async do
35
+ begin
36
+ block.call(stream)
37
+ ensure
38
+ stream.close
39
+ end
40
+ end
41
+
42
+ return output
43
+ end
44
+
45
+ def initialize(input, output, socket)
46
+ @input = input
47
+ @output = output
48
+ @socket = socket
49
+ @closed = false
50
+ end
51
+
52
+ def read(length = nil, buffer = nil)
53
+ @input.read(length, buffer)
54
+ end
55
+
56
+ def read_nonblock(length, buffer = nil)
57
+ @input.read(length, buffer)
58
+ end
59
+
60
+ def write(buffer)
61
+ @output.write(buffer)
62
+ end
63
+
64
+ alias write_nonblock write
65
+
66
+ def flush
67
+ end
68
+
69
+ def close
70
+ return if @closed
71
+
72
+ @input.close
73
+ @output.close
74
+ @closed = true
75
+ end
76
+
77
+ def close_read
78
+ @input.close
79
+ end
80
+
81
+ def close_write
82
+ @output.close
83
+ end
84
+
85
+ def closed?
86
+ @closed
87
+ end
88
+ end
89
+ end
90
+ end
@@ -23,6 +23,7 @@ require 'rack'
23
23
  require_relative 'input'
24
24
  require_relative 'response'
25
25
  require_relative 'early_hints'
26
+ require_relative 'hijack'
26
27
 
27
28
  require 'async/logger'
28
29
 
@@ -111,12 +112,6 @@ module Falcon
111
112
  end
112
113
  end
113
114
 
114
- def make_response(request, status, headers, body)
115
- # @logger.debug(request) {"Rack response: #{status} #{headers.inspect} #{body.class}"}
116
-
117
- return Response.wrap(status, headers, body)
118
- end
119
-
120
115
  def call(request)
121
116
  request_path, query_string = request.path.split('?', 2)
122
117
  server_name, server_port = (request.authority || '').split(':', 2)
@@ -157,6 +152,9 @@ module Falcon
157
152
  # I'm not sure what sane defaults should be here:
158
153
  SERVER_NAME => server_name || '',
159
154
  SERVER_PORT => server_port || '',
155
+
156
+ # We support both request and response hijack.
157
+ RACK_IS_HIJACK => true,
160
158
  }
161
159
 
162
160
  self.unwrap_request(request, env)
@@ -165,11 +163,12 @@ module Falcon
165
163
  env[RACK_EARLY_HINTS] = EarlyHints.new(request)
166
164
  end
167
165
 
166
+ full_hijack = false
167
+
168
168
  if request.hijack?
169
- env[RACK_IS_HIJACK] = true
170
-
171
169
  env[RACK_HIJACK] = lambda do
172
- wrapper = request.hijack
170
+ wrapper = request.hijack!
171
+ full_hijack = true
173
172
 
174
173
  # We dup this as it might be taken out of the normal control flow, and the io will be closed shortly after returning from this method.
175
174
  io = wrapper.io.dup
@@ -178,27 +177,16 @@ module Falcon
178
177
  # This is implicitly returned:
179
178
  env[RACK_HIJACK_IO] = io
180
179
  end
181
- else
182
- env[RACK_IS_HIJACK] = false
183
180
  end
184
181
 
185
182
  status, headers, body = @app.call(env)
186
183
 
187
- # Partial hijack is not supported/tested.
188
- # if hijack = headers.delete('rack.hijack')
189
- # body = Async::HTTP::Body::Writable.new
190
- #
191
- # Task.current.async do
192
- # hijack.call(body)
193
- # end
194
- # return nil
195
- # end
196
-
197
- # if env['rack.hijack_io']
198
- # return nil
199
- # end
200
-
201
- return make_response(request, status, headers, body)
184
+ # If there was a full hijack:
185
+ if full_hijack
186
+ return nil
187
+ else
188
+ return Response.wrap(status, headers, body, request, env)
189
+ end
202
190
  rescue => exception
203
191
  @logger.error(self) {exception}
204
192
 
@@ -19,6 +19,7 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'output'
22
+ require_relative 'hijack'
22
23
  require_relative '../version'
23
24
  require_relative '../proxy'
24
25
 
@@ -27,20 +28,18 @@ require 'time'
27
28
  module Falcon
28
29
  module Adapters
29
30
  class Response < Async::HTTP::Response
30
- IGNORE_HEADERS = Set.new(Proxy::HOP_HEADERS)
31
+ IGNORE_HEADERS = Proxy::HOP_HEADERS
31
32
 
32
33
  # Append a list of newline encoded headers.
33
34
  def self.wrap_headers(fields)
34
35
  headers = ::HTTP::Protocol::Headers.new
36
+ meta = {}
35
37
 
36
38
  fields.each do |key, value|
37
- next if key.start_with? 'rack.'
38
-
39
- # This is a quick fix, but perhaps it should be part of the protocol, because it IS valid for HTTP/1.
40
39
  key = key.downcase
41
40
 
42
- if IGNORE_HEADERS.include? key
43
- Async.logger.warn("Ignoring protocol-level header: #{key}: #{value}!")
41
+ if key.start_with?('rack.')
42
+ meta[key] = value
44
43
  else
45
44
  value.to_s.split("\n").each do |part|
46
45
  headers.add(key, part)
@@ -48,11 +47,23 @@ module Falcon
48
47
  end
49
48
  end
50
49
 
51
- return headers
50
+ return headers, meta
52
51
  end
53
52
 
54
- def self.wrap(status, headers, body)
55
- headers = wrap_headers(headers)
53
+ def self.wrap(status, headers, body, request = nil, env = nil)
54
+ headers, meta = wrap_headers(headers)
55
+
56
+ if block = meta['rack.hijack'] and request and env
57
+ body = Hijack.for(env, block, request.hijack? ? request.hijack! : nil)
58
+ else
59
+ sliced = headers.slice!(IGNORE_HEADERS)
60
+
61
+ unless sliced.empty?
62
+ Async.logger.warn("Ignoring protocol-level headers: #{sliced.inspect}")
63
+ end
64
+
65
+ body = Output.wrap(status, headers, body)
66
+ end
56
67
 
57
68
  # https://tools.ietf.org/html/rfc7231#section-7.4.2
58
69
  # headers.add('server', "falcon/#{Falcon::VERSION}")
@@ -60,8 +71,6 @@ module Falcon
60
71
  # https://tools.ietf.org/html/rfc7231#section-7.1.1.2
61
72
  # headers.add('date', Time.now.httpdate)
62
73
 
63
- body = Output.wrap(status, headers, body)
64
-
65
74
  return self.new(status, headers, body)
66
75
  end
67
76
 
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Falcon
22
- VERSION = "0.27.0"
22
+ VERSION = "0.28.0"
23
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: falcon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.27.0
4
+ version: 0.28.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-26 00:00:00.000000000 Z
11
+ date: 2019-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-protocol
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.15'
19
+ version: '0.17'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.15'
26
+ version: '0.17'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: async
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.20'
47
+ version: '1.22'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.20'
54
+ version: '1.22'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: async-http
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -282,6 +282,7 @@ files:
282
282
  - gems/rack3.gemfile
283
283
  - lib/falcon.rb
284
284
  - lib/falcon/adapters/early_hints.rb
285
+ - lib/falcon/adapters/hijack.rb
285
286
  - lib/falcon/adapters/input.rb
286
287
  - lib/falcon/adapters/output.rb
287
288
  - lib/falcon/adapters/rack.rb
@@ -321,7 +322,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
321
322
  - !ruby/object:Gem::Version
322
323
  version: '0'
323
324
  requirements: []
324
- rubygems_version: 3.0.3
325
+ rubygems_version: 3.0.2
325
326
  signing_key:
326
327
  specification_version: 4
327
328
  summary: A fast, asynchronous, rack-compatible web server.