falcon 0.15.2 → 0.16.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: 682a4983771c1015ffd59ff7d241109f598fadfed95db19bfb418e9149d9e4a4
4
- data.tar.gz: '081b506a5324694090bfb87020711761b732aff8f860d1dbe4f606a0f405d157'
3
+ metadata.gz: bf2ce761fe961a8a5a8e6427007be3bbe882ada827c366c89a6c1f6b20803538
4
+ data.tar.gz: 3bac595cb42bc4c6eee5b3731979ea812426b309d511046c413caac91e612e16
5
5
  SHA512:
6
- metadata.gz: 32491eb4950dc9667a63fc075242e778cd2bb5725da0eb9fd52d399585a8072d9ab505ab2de4c02521d1098295816875145a0dca990c04e062eb7ddd09de4dfc
7
- data.tar.gz: df2a3b8800e5fa83ad8d27f5a0f6e282f7a140b046e0c552a7b308b5ab4ae47fc3ff20a5894ee14f45189867ab8a823554c806f71733c9639d942e984b3a4180
6
+ metadata.gz: 55a2923c02b53cee2d0cdbc59213f57a7ebbc254ba668e580f57373d36c664df4c81014c5409ed33eba55fc1893c45d15bc6e7cb174b354d0163938043fdf8c2
7
+ data.tar.gz: 101e5db3f05438da0f1a6e9792dab8ada074bbbd98b7688049ae2bc97c790df8ea7e6a3f7f10d7194cae772c14d2cb2aa8111cf7b5f77b15dd51c18745f40820
data/.travis.yml CHANGED
@@ -1,6 +1,14 @@
1
1
  language: ruby
2
+ sudo: required
3
+ dist: xenial
2
4
  cache: bundler
3
5
 
6
+ addons:
7
+ apt:
8
+ packages:
9
+ - wrk
10
+ - apache2-utils
11
+
4
12
  before_script:
5
13
  - gem update --system
6
14
  - gem install bundler
@@ -10,6 +18,7 @@ matrix:
10
18
  - rvm: 2.3
11
19
  - rvm: 2.4
12
20
  - rvm: 2.5
21
+ - rvm: 2.6
13
22
  - rvm: jruby-head
14
23
  env: JRUBY_OPTS="--debug -X+O"
15
24
  - rvm: ruby-head
data/README.md CHANGED
@@ -28,7 +28,13 @@ Or install it yourself as:
28
28
 
29
29
  ## Usage
30
30
 
31
- You can run `falcon` directly, and it will load the `config.ru` and start serving on port 8080.
31
+ You can run `falcon serve` directly, and it will load the `config.ru` and start serving on port 9292.
32
+
33
+ ### WebSockets
34
+
35
+ Falcon supports `rack.hijack` for HTTP/1.x connections. You can thus use [async-websocket] in any controller layer to serve WebSocket connections.
36
+
37
+ [async-websocket]: https://github.com/socketry/async-websocket
32
38
 
33
39
  ### Integration with Guard
34
40
 
@@ -1,39 +1,42 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- async (1.2.2)
5
- nio4r
4
+ async (1.9.1)
5
+ nio4r (~> 2.3)
6
6
  timers (~> 4.1)
7
- async-container (0.2.2)
7
+ async-container (0.5.0)
8
8
  async (~> 1.0)
9
- async-http (0.6.0)
10
- async (~> 1.1)
11
- async-io (~> 1.0)
12
- async-io (1.3.0)
13
- async (~> 1.0)
14
- falcon (0.6.1)
15
- async-container (~> 0.1)
16
- async-http (~> 0.3)
17
- async-io (~> 1.1)
9
+ async-io (~> 1.4)
10
+ async-http (0.24.3)
11
+ async (~> 1.6)
12
+ async-io (~> 1.12)
13
+ http-2 (~> 0.9.0)
14
+ async-io (1.12.1)
15
+ async (~> 1.3)
16
+ falcon (0.15.2)
17
+ async-container (~> 0.5.0)
18
+ async-http (~> 0.24.0)
19
+ async-io (~> 1.9)
18
20
  rack (>= 1.0)
19
21
  samovar (~> 1.3)
20
22
  hitimes (1.2.6)
23
+ http-2 (0.9.1)
21
24
  mapping (1.1.1)
22
25
  mustermann (1.0.2)
23
- nio4r (2.2.0)
24
- rack (2.0.4)
25
- rack-protection (2.0.1)
26
+ nio4r (2.3.1)
27
+ rack (2.0.5)
28
+ rack-protection (2.0.3)
26
29
  rack
27
30
  rainbow (2.2.2)
28
31
  rake
29
- rake (12.3.0)
32
+ rake (12.3.1)
30
33
  samovar (1.8.0)
31
34
  mapping (~> 1.0)
32
35
  rainbow (~> 2.0)
33
- sinatra (2.0.1)
36
+ sinatra (2.0.3)
34
37
  mustermann (~> 1.0)
35
38
  rack (~> 2.0)
36
- rack-protection (= 2.0.1)
39
+ rack-protection (= 2.0.3)
37
40
  tilt (~> 2.0)
38
41
  tilt (2.0.8)
39
42
  timers (4.1.2)
data/falcon.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.require_paths = ["lib"]
18
18
 
19
19
  spec.add_dependency("async-io", "~> 1.9")
20
- spec.add_dependency("async-http", "~> 0.24.0")
20
+ spec.add_dependency("async-http", "~> 0.27.0")
21
21
  spec.add_dependency("async-container", "~> 0.5.0")
22
22
 
23
23
  spec.add_dependency("rack", ">= 1.0")
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency('samovar', "~> 1.3")
26
26
 
27
27
  spec.add_development_dependency "async-rspec", "~> 1.2"
28
+ spec.add_development_dependency "async-websocket"
28
29
 
29
30
  spec.add_development_dependency "bundler", "~> 1.3"
30
31
  spec.add_development_dependency "rspec", "~> 3.6"
@@ -19,36 +19,50 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require 'async/http/body'
22
+ require 'async/http/body/rewindable'
22
23
 
23
24
  module Falcon
24
25
  module Adapters
26
+ # 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.
25
27
  class Input
26
28
  def initialize(body)
27
29
  @body = body
28
- @chunks = []
29
30
 
30
- @index = 0
31
+ # The current buffer, which is extended by calling `#fill_buffer`.
31
32
  @buffer = Async::IO::BinaryString.new
32
33
  @finished = @body.nil?
33
34
  end
34
35
 
36
+ attr :body
37
+
38
+ # each must be called without arguments and only yield Strings.
35
39
  def each(&block)
36
40
  return to_enum unless block_given?
37
41
 
38
- while chunk = read_next
42
+ while chunk = gets
39
43
  yield chunk
40
44
  end
41
-
42
- @closed = true
43
45
  end
44
46
 
47
+ # rewind must be called without arguments. It rewinds the input stream back to the beginning. It must not raise Errno::ESPIPE: that is, it may not be a pipe or a socket. Therefore, handler developers must buffer the input data into some rewindable object if the underlying input stream is not rewindable.
45
48
  def rewind
46
- @index = 0
47
- @finished = false
48
- @buffer.clear
49
+ if @body
50
+ # If the body is not rewindable, this will fail.
51
+ @body.rewind
52
+ @buffer.clear
53
+ @finished = false
54
+ end
55
+ end
56
+
57
+ def respond_to?(name, *)
58
+ if name == :rewind
59
+ @body.respond_to?(:rewind)
60
+ else
61
+ super
62
+ end
49
63
  end
50
64
 
51
- # Read some data from the underlying body. Similar to `IO#read`.
65
+ # 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.
52
66
  # @param length [Integer] the amount of data to read
53
67
  # @param buffer [String] the buffer which will receive the data
54
68
  # @return a buffer containing the data
@@ -88,10 +102,19 @@ module Falcon
88
102
  @finished and @buffer.empty?
89
103
  end
90
104
 
105
+ # gets must be called without arguments and return a string, or nil on EOF.
106
+ # @return [String, nil] The next chunk from the body.
91
107
  def gets
92
- read
108
+ if @buffer.empty?
109
+ return read_next
110
+ else
111
+ buffer = @buffer.dup
112
+ @buffer.clear
113
+ return buffer
114
+ end
93
115
  end
94
116
 
117
+ # close must never be called on the input stream. huh?
95
118
  def close
96
119
  @body.finish
97
120
  end
@@ -101,21 +124,12 @@ module Falcon
101
124
  def read_next
102
125
  return nil if @finished
103
126
 
104
- chunk = nil
105
-
106
- if @index < @chunks.count
107
- chunk = @chunks[@index]
108
- @index += 1
127
+ if chunk = @body.read
128
+ return chunk
109
129
  else
110
- if chunk = @body.read
111
- @chunks << chunk
112
- @index += 1
113
- end
130
+ @finished = true
131
+ return nil
114
132
  end
115
-
116
- @finished = true if chunk.nil?
117
-
118
- return chunk
119
133
  end
120
134
 
121
135
  def fill_buffer(length)
@@ -0,0 +1,66 @@
1
+ # Copyright, 2018, 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/body/readable'
22
+ require 'async/http/body/file'
23
+
24
+ module Falcon
25
+ module Adapters
26
+ class Output < Async::HTTP::Body::Readable
27
+ CONTENT_LENGTH = 'content-length'.freeze
28
+
29
+ # Wraps an array into a buffered body.
30
+ def self.wrap(headers, body)
31
+ if body.is_a?(Async::HTTP::Body::Readable)
32
+ return body
33
+ # This needs more testing:
34
+ elsif body.respond_to?(:to_path)
35
+ return Async::HTTP::Body::File.open(body.to_path)
36
+ else
37
+ return self.new(headers, body)
38
+ end
39
+ end
40
+
41
+ def initialize(headers, body)
42
+ # We don't trust the user to provide the right length to the transport.
43
+ @length = headers.delete(CONTENT_LENGTH)
44
+
45
+ @body = body
46
+ @chunks = body.to_enum(:each)
47
+ end
48
+
49
+ attr :length
50
+
51
+ def empty?
52
+ @length == 0
53
+ end
54
+
55
+ def read
56
+ @chunks.next
57
+ rescue StopIteration
58
+ nil
59
+ end
60
+
61
+ def inspect
62
+ "\#<#{self.class} length=#{@length.inspect} body=#{@body.class}>"
63
+ end
64
+ end
65
+ end
66
+ end
@@ -19,8 +19,8 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'input'
22
+ require_relative 'response'
22
23
 
23
- require 'async/http/body/buffered'
24
24
  require 'async/logger'
25
25
 
26
26
  module Falcon
@@ -31,7 +31,7 @@ module Falcon
31
31
  @logger = logger
32
32
  end
33
33
 
34
- def call(request, peer: nil, address: nil)
34
+ def call(request)
35
35
  request_path, query_string = request.path.split('?', 2)
36
36
  server_name, server_port = (request.authority || '').split(':', 2)
37
37
 
@@ -78,27 +78,36 @@ module Falcon
78
78
  env["HTTP_#{key.upcase.tr('-', '_')}"] = value
79
79
  end
80
80
 
81
- if peer
81
+ if remote_address = request.remote_address
82
+ env['REMOTE_ADDR'] = remote_address.ip_address if remote_address.ip?
83
+ end
84
+
85
+ if request.hijack?
82
86
  env['rack.hijack?'] = true
83
- env['rack.hijack'] = lambda do
84
- env['rack.hijack_io'] = peer
85
- end
86
87
 
87
- if remote_address = peer.remote_address
88
- env['REMOTE_ADDR'] = remote_address.ip_address if remote_address.ip?
88
+ env['rack.hijack'] = lambda do
89
+ env['rack.hijack_io'] = request.hijack
89
90
  end
91
+ else
92
+ env['rack.hijack?'] = false
90
93
  end
91
94
 
92
95
  status, headers, body = @app.call(env)
93
96
 
97
+ # if hijack = headers.delete('rack.hijack')
98
+ # body = Async::HTTP::Body::Writable.new
99
+ #
100
+ # Task.current.async do
101
+ # hijack.call(body)
102
+ # end
103
+ # return nil
104
+ # end
105
+
94
106
  if env['rack.hijack_io']
95
- throw :hijack
96
- else
97
- # We normalize headers to be lower case.
98
- headers = headers.map{|key, value| [key.downcase, value]}.to_h
99
-
100
- return Async::HTTP::Response[status, headers, Async::HTTP::Body::Buffered.wrap(body)]
107
+ return nil
101
108
  end
109
+
110
+ return Response.new(status, headers, body)
102
111
  rescue => exception
103
112
  @logger.error "#{exception.class}: #{exception.message}\n\t#{$!.backtrace.join("\n\t")}"
104
113
 
@@ -0,0 +1,34 @@
1
+ # Copyright, 2018, 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_relative 'output'
22
+
23
+ module Falcon
24
+ module Adapters
25
+ class Response < Async::HTTP::Response
26
+ def initialize(status, headers, body)
27
+ # We normalize headers to be lower case:
28
+ headers = headers.map{|key, value| [key.downcase, value]}.to_h
29
+
30
+ super(nil, status, nil, headers, Output.wrap(headers, body))
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,63 @@
1
+ # Copyright, 2018, 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/body/rewindable'
22
+
23
+ module Falcon
24
+ module Adapters
25
+ # Content type driven input buffering.
26
+ class Rewindable < Async::HTTP::Middleware
27
+ BUFFERED_MEDIA_TYPES = %r{
28
+ application/x-www-form-urlencoded|
29
+ multipart/form-data|
30
+ multipart/related|
31
+ multipart/mixed
32
+ }x
33
+
34
+ POST = 'POST'.freeze
35
+
36
+ def initialize(app)
37
+ super(app)
38
+ end
39
+
40
+ def needs_rewind?(request)
41
+ content_type = request.headers['content-type']
42
+
43
+ if request.method == POST and content_type.nil?
44
+ return true
45
+ end
46
+
47
+ if BUFFERED_MEDIA_TYPES =~ content_type
48
+ return true
49
+ end
50
+
51
+ return false
52
+ end
53
+
54
+ def call(request)
55
+ if body = request.body and needs_rewind?(request)
56
+ request.body = Async::HTTP::Body::Rewindable.new(body)
57
+ end
58
+
59
+ response = super
60
+ end
61
+ end
62
+ end
63
+ end
@@ -43,7 +43,7 @@ module Falcon
43
43
 
44
44
  option '-b/--bind <address>', "Bind to the given hostname/address", default: "tcp://localhost:9292"
45
45
 
46
- option '--forked | --threaded', "Select a specific concurrency model", key: :container, default: :threaded
46
+ option '--forked | --threaded', "Select a specific concurrency model", key: :container, default: :forked
47
47
  end
48
48
 
49
49
  def container_class
@@ -60,10 +60,14 @@ module Falcon
60
60
  def load_app(verbose)
61
61
  app, options = Rack::Builder.parse_file(@options[:config])
62
62
 
63
- # We adapt the rack app to Async::HTTP::Middleware
63
+ # We adapt the rack app to `Async::HTTP::Middleware`:
64
64
  app = Adapters::Rack.new(app)
65
65
 
66
- app = Async::HTTP::ContentEncoding.new(app)
66
+ # We buffer the input body but only for specific kinds of requests:
67
+ app = Adapters::Rewindable.new(app)
68
+
69
+ # We compress response bodies according to what the client expects:
70
+ app = Async::HTTP::ContentEncoding.new(app)
67
71
 
68
72
  if verbose
69
73
  app = Verbose.new(app)
data/lib/falcon/proxy.rb CHANGED
@@ -22,7 +22,7 @@ require 'async/http/client'
22
22
 
23
23
  module Falcon
24
24
  module BadRequest
25
- def self.call(request, *)
25
+ def self.call(request)
26
26
  return Async::HTTP::Response[400, {}, []]
27
27
  end
28
28
 
@@ -82,11 +82,11 @@ module Falcon
82
82
  headers.slice!(HOP_HEADERS)
83
83
  end
84
84
 
85
- def call(request, peer: nil, **options)
85
+ def call(request)
86
86
  if endpoint = lookup(request)
87
87
  @count += 1
88
88
 
89
- if peer and address = peer.remote_address and address.ip?
89
+ if address = request.remote_address
90
90
  request.headers.add(X_FORWARDED_FOR, address.ip_address)
91
91
  end
92
92
 
data/lib/falcon/server.rb CHANGED
@@ -21,16 +21,8 @@
21
21
  require 'async/http/server'
22
22
  require 'async/http/content_encoding'
23
23
 
24
+ require_relative 'adapters/rewindable'
25
+
24
26
  module Falcon
25
- class Server < Async::HTTP::Server
26
- def initialize(app, *args)
27
- super(*args)
28
-
29
- @app = app
30
- end
31
-
32
- def handle_request(request, peer, address)
33
- @app.call(request, peer: peer, address: address)
34
- end
35
- end
27
+ Server = Async::HTTP::Server
36
28
  end
@@ -29,17 +29,18 @@ module Falcon
29
29
  @logger = logger
30
30
  end
31
31
 
32
- def annotate(request, peer: nil, address: nil)
32
+ def annotate(request)
33
33
  task = Async::Task.current
34
+ address = request.remote_address
34
35
 
35
36
  @logger.debug(request.authority) {"#{request.method} #{request.path} #{request.version} from #{address.inspect}"}
36
- @logger.debug(request.authority) {request.headers.inspect}
37
+ # @logger.debug(request.authority) {request.headers.inspect}
37
38
 
38
39
  task.annotate("#{request.method} #{request.path} from #{address.inspect}")
39
40
  end
40
41
 
41
- def call(request, **options)
42
- annotate(request, **options)
42
+ def call(request)
43
+ annotate(request)
43
44
 
44
45
  statistics = Async::HTTP::Statistics.start
45
46
 
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Falcon
22
- VERSION = "0.15.2"
22
+ VERSION = "0.16.0"
23
23
  end
@@ -18,7 +18,10 @@ module Rack
18
18
  def self.run(app, **options)
19
19
  endpoint = endpoint_for(**options)
20
20
 
21
- server = ::Falcon::Server.new(::Falcon::Adapters::Rack.new(app), endpoint)
21
+ app = ::Falcon::Adapters::Rack.new(app)
22
+ app = ::Falcon::Adapters::Rewindable.new(app)
23
+
24
+ server = ::Falcon::Server.new(app, endpoint)
22
25
 
23
26
  Async::Reactor.run do
24
27
  server.run
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.15.2
4
+ version: 0.16.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: 2018-06-08 00:00:00.000000000 Z
11
+ date: 2018-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-io
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.24.0
33
+ version: 0.27.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.24.0
40
+ version: 0.27.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: async-container
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: async-websocket
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: bundler
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -159,7 +173,10 @@ files:
159
173
  - falcon.gemspec
160
174
  - lib/falcon.rb
161
175
  - lib/falcon/adapters/input.rb
176
+ - lib/falcon/adapters/output.rb
162
177
  - lib/falcon/adapters/rack.rb
178
+ - lib/falcon/adapters/response.rb
179
+ - lib/falcon/adapters/rewindable.rb
163
180
  - lib/falcon/command.rb
164
181
  - lib/falcon/command/serve.rb
165
182
  - lib/falcon/hosts.rb