goliath 0.9.2 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of goliath might be problematic. Click here for more details.

Files changed (49) hide show
  1. data/Gemfile +1 -1
  2. data/{HISTORY → HISTORY.md} +26 -12
  3. data/README.md +17 -10
  4. data/examples/api_proxy.rb +28 -0
  5. data/examples/async_aroundware_demo.rb +14 -10
  6. data/examples/auth_and_rate_limit.rb +160 -38
  7. data/examples/config/auth_and_rate_limit.rb +8 -5
  8. data/examples/config/content_stream.rb +5 -9
  9. data/examples/early_abort.rb +37 -0
  10. data/examples/env_use_statements.rb +3 -0
  11. data/examples/favicon.rb +40 -0
  12. data/examples/http_log.rb +2 -1
  13. data/examples/public/favicon.ico +0 -0
  14. data/examples/rack_routes.rb +19 -0
  15. data/examples/rasterize/rasterize.rb +2 -1
  16. data/examples/rasterize/rasterize_and_shorten.rb +10 -5
  17. data/goliath.gemspec +7 -9
  18. data/lib/goliath/api.rb +16 -4
  19. data/lib/goliath/connection.rb +8 -7
  20. data/lib/goliath/deprecated/async_aroundware.rb +133 -0
  21. data/lib/goliath/{synchrony → deprecated}/mongo_receiver.rb +28 -8
  22. data/lib/goliath/deprecated/response_receiver.rb +97 -0
  23. data/lib/goliath/env.rb +5 -0
  24. data/lib/goliath/rack.rb +6 -1
  25. data/lib/goliath/rack/async_middleware.rb +34 -12
  26. data/lib/goliath/rack/barrier_aroundware.rb +228 -0
  27. data/lib/goliath/rack/barrier_aroundware_factory.rb +60 -0
  28. data/lib/goliath/rack/builder.rb +22 -6
  29. data/lib/goliath/rack/heartbeat.rb +8 -5
  30. data/lib/goliath/rack/simple_aroundware.rb +114 -0
  31. data/lib/goliath/rack/simple_aroundware_factory.rb +121 -0
  32. data/lib/goliath/rack/validation/required_param.rb +9 -2
  33. data/lib/goliath/request.rb +7 -0
  34. data/lib/goliath/runner.rb +17 -5
  35. data/lib/goliath/server.rb +11 -3
  36. data/lib/goliath/test_helper.rb +14 -14
  37. data/lib/goliath/version.rb +1 -1
  38. data/spec/integration/early_abort_spec.rb +50 -0
  39. data/spec/integration/keepalive_spec.rb +2 -2
  40. data/spec/integration/pipelining_spec.rb +2 -2
  41. data/spec/integration/rack_routes_spec.rb +25 -0
  42. data/spec/integration/template_spec.rb +2 -0
  43. data/spec/unit/rack/heartbeat_spec.rb +11 -1
  44. data/spec/unit/rack/validation/required_param_spec.rb +10 -0
  45. data/spec/unit/runner_spec.rb +13 -0
  46. data/spec/unit/server_spec.rb +4 -0
  47. metadata +218 -265
  48. data/lib/goliath/rack/async_aroundware.rb +0 -56
  49. data/lib/goliath/synchrony/response_receiver.rb +0 -64
@@ -11,20 +11,23 @@ environment(:development) do
11
11
  timebin = ((Time.now.to_i / 3600).floor * 3600)
12
12
 
13
13
  # This user's calls should all go through
14
- config['api_auth_db'].collection('AccountInfo').save({
14
+ config['api_auth_db'].collection(:account_info).save({
15
15
  :_id => 'i_am_awesome', 'valid' => true, 'max_call_rate' => 1_000_000 })
16
16
 
17
17
  # this user's account is disabled
18
- config['api_auth_db'].collection('AccountInfo').save({
18
+ config['api_auth_db'].collection(:account_info).save({
19
19
  :_id => 'i_am_lame', 'valid' => false, 'max_call_rate' => 1_000 })
20
20
 
21
21
  # this user has not been seen, but will very quickly hit their limit
22
- config['api_auth_db'].collection('AccountInfo').save({
22
+ config['api_auth_db'].collection(:account_info).save({
23
23
  :_id => 'i_am_limited', 'valid' => true, 'max_call_rate' => 10 })
24
+ config['api_auth_db'].collection(:usage_info).save({
25
+ :_id => "i_am_limited-#{timebin}", 'calls' => 0 })
24
26
 
25
27
  # fakes a user with a bunch of calls already made this hour -- two more = no yuo
26
- config['api_auth_db'].collection('AccountInfo').save({
28
+ config['api_auth_db'].collection(:account_info).save({
27
29
  :_id => 'i_am_busy', 'valid' => true, 'max_call_rate' => 1_000 })
28
- config['api_auth_db'].collection('UsageInfo').save({
30
+ config['api_auth_db'].collection(:usage_info).save({
29
31
  :_id => "i_am_busy-#{timebin}", 'calls' => 999 })
32
+
30
33
  end
@@ -10,21 +10,17 @@ amqp_config = {
10
10
  }
11
11
 
12
12
  conn = AMQP.connect(amqp_config)
13
-
14
13
  xchange = AMQP::Channel.new(conn).fanout('stream')
14
+
15
15
  q = AMQP::Channel.new(conn).queue('stream/StreamAPI')
16
16
  q.bind(xchange)
17
17
 
18
- # pull data off the exchange and push to streams
19
- q.pop do |data|
20
- if data.nil?
21
- EM.add_timer(1) { q.pop }
22
- else
23
- config['channel'].push(data)
24
- q.pop
25
- end
18
+ def handle_message(metadata, payload)
19
+ config['channel'].push(payload)
26
20
  end
27
21
 
22
+ q.subscribe(&method(:handle_message))
23
+
28
24
  # push data into the stream. (Just so we have stuff going in)
29
25
  count = 0
30
26
  EM.add_periodic_timer(2) do
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ $:<< '../lib' << 'lib'
3
+ require 'goliath'
4
+
5
+ class EarlyAbort < Goliath::API
6
+ include Goliath::Validation
7
+ MAX_SIZE = 10
8
+ TEST_FILE = "/tmp/goliath-test-error.log"
9
+
10
+ def on_headers(env, headers)
11
+ env.logger.info 'received headers: ' + headers.inspect
12
+ env['async-headers'] = headers
13
+
14
+ if env['HTTP_X_CRASH'] && env['HTTP_X_CRASH'] == 'true'
15
+ raise Goliath::Validation::NotImplementedError.new("Can't handle requests with X-Crash: true.")
16
+ end
17
+ end
18
+
19
+ def on_body(env, data)
20
+ env.logger.info 'received data: ' + data
21
+ (env['async-body'] ||= '') << data
22
+ size = env['async-body'].size
23
+
24
+ if size >= MAX_SIZE
25
+ raise Goliath::Validation::BadRequestError.new("Payload size can't exceed #{MAX_SIZE} bytes. Received #{size.inspect} bytes.")
26
+ end
27
+ end
28
+
29
+ def on_close(env)
30
+ env.logger.info 'closing connection'
31
+ end
32
+
33
+ def response(env)
34
+ File.open(TEST_FILE, "w+") { |f| f << "response that should not be here"}
35
+ [200, {}, "OK"]
36
+ end
37
+ end
@@ -4,6 +4,9 @@ $:<< '../lib' << 'lib'
4
4
  require 'goliath'
5
5
  require 'yajl'
6
6
 
7
+ # API must be started with -e [production, development, ...]
8
+ # or set your ENV['RACK_ENV'] to specify the environemtn
9
+
7
10
  class EnvUseStatements < Goliath::API
8
11
  if Goliath.dev?
9
12
  use Goliath::Rack::Render, 'json'
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+ require 'time'
3
+
4
+ #
5
+ # Reads a favicon.ico statically at load time, renders it on any request for
6
+ # '/favicon.ico', and sends every other request on downstream.
7
+ #
8
+ # If you will be serving even one more file than this one, you should instead
9
+ # use Rack::Static:
10
+ #
11
+ # use(Rack::Static, # render static files from ./public
12
+ # :root => Goliath::Application.app_path("public"),
13
+ # :urls => ["/favicon.ico", '/stylesheets', '/javascripts', '/images'])
14
+ #
15
+ class Favicon
16
+ def initialize(app, filename)
17
+ @@favicon = File.read(filename)
18
+ @@last_mod = File.mtime(filename).utc.rfc822
19
+ @@expires = Time.at(Time.now + 604800).utc.rfc822 # 1 week from now
20
+ @app = app
21
+ end
22
+
23
+ def call(env, *args)
24
+ if env['REQUEST_PATH'] == '/favicon.ico'
25
+ return [200, {"Last-Modified"=> @@last_mod.to_s, "Expires" => @@expires, "Content-Type"=>"image/vnd.microsoft.icon"}, @@favicon]
26
+ else
27
+ return @app.call(env)
28
+ end
29
+ end
30
+ end
31
+
32
+ if File.expand_path($0) == File.expand_path(__FILE__)
33
+ $:<< '../lib' << 'lib'
34
+ require 'goliath'
35
+ puts "starting hello world!"
36
+ class HelloWorld < Goliath::API
37
+ HelloWorld.use(Favicon, File.expand_path(File.dirname(__FILE__)+"/public/favicon.ico"))
38
+ end
39
+ require(File.dirname(__FILE__)+'/hello_world.rb')
40
+ end
data/examples/http_log.rb CHANGED
@@ -56,6 +56,7 @@ class HttpLog < Goliath::API
56
56
  # Write the request information into mongo
57
57
  def record(process_time, resp, client_headers, response_headers)
58
58
  e = env
59
+ e.trace('http_log_record')
59
60
  EM.next_tick do
60
61
  doc = {
61
62
  request: {
@@ -81,4 +82,4 @@ class HttpLog < Goliath::API
81
82
  e.mongo.insert(doc)
82
83
  end
83
84
  end
84
- end
85
+ end
Binary file
@@ -26,6 +26,17 @@ class PostHelloWorld < Goliath::API
26
26
  end
27
27
  end
28
28
 
29
+ class HeaderCollector < Goliath::API
30
+ def on_headers(env, header)
31
+ @headers ||= {}
32
+ @headers.merge!(header)
33
+ end
34
+
35
+ def response(env)
36
+ [200, {}, "headers: #{@headers.inspect}"]
37
+ end
38
+ end
39
+
29
40
  class HelloNumber < Goliath::API
30
41
  use Goliath::Rack::Params
31
42
  def response(env)
@@ -76,6 +87,14 @@ class RackRoutes < Goliath::API
76
87
  run HelloWorld.new
77
88
  end
78
89
 
90
+ head "/hello_world" do
91
+ run HelloWorld.new
92
+ end
93
+
94
+ map "/headers", HeaderCollector do
95
+ use Goliath::Rack::Validation::RequestMethod, %w(GET)
96
+ end
97
+
79
98
  map "/bonjour" do
80
99
  run Bonjour.new
81
100
  end
@@ -2,6 +2,7 @@
2
2
  $: << File.dirname(__FILE__)+'/../../lib'
3
3
  require 'goliath'
4
4
  require 'postrank-uri'
5
+ require File.dirname(__FILE__)+'/../favicon'
5
6
 
6
7
  # Install phantomjs: http://code.google.com/p/phantomjs/wiki/QuickStart
7
8
  # $> ruby rasterize.rb -sv
@@ -9,7 +10,7 @@ require 'postrank-uri'
9
10
 
10
11
  class Rasterize < Goliath::API
11
12
  use Goliath::Rack::Params
12
-
13
+ use Favicon, File.expand_path(File.dirname(__FILE__)+"/../public/favicon.ico")
13
14
  use Goliath::Rack::Validation::RequestMethod, %w(GET)
14
15
  use Goliath::Rack::Validation::RequiredParam, {:key => 'url'}
15
16
 
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  $: << File.dirname(__FILE__)+'/../../lib'
3
3
  require File.dirname(__FILE__)+'/rasterize'
4
+ require File.dirname(__FILE__)+'/../favicon'
4
5
 
5
6
  require 'goliath'
6
7
  require 'em-synchrony/em-http'
@@ -11,18 +12,21 @@ require 'postrank-uri'
11
12
  # generate a shortened link, stuffing it in the header. Both requests happen
12
13
  # simultaneously.
13
14
  #
14
- class ShortenURL < Goliath::Synchrony::MultiReceiver
15
+ class ShortenURL
16
+ include Goliath::Rack::BarrierAroundware
15
17
  SHORTENER_URL_BASE = 'http://is.gd/create.php'
18
+ attr_accessor :shortened_url
16
19
 
17
20
  def pre_process
18
21
  target_url = PostRank::URI.clean(env.params['url'])
19
22
  shortener_request = EM::HttpRequest.new(SHORTENER_URL_BASE).aget(:query => { :format => 'simple', :url => target_url })
20
- enqueue :shortener, shortener_request
23
+ enqueue :shortened_url, shortener_request
24
+ return Goliath::Connection::AsyncResponse
21
25
  end
22
26
 
23
27
  def post_process
24
- if successes[:shortener]
25
- headers['X-Shortened-URI'] = successes[:shortener].response
28
+ if shortened_url
29
+ headers['X-Shortened-URI'] = shortened_url.response
26
30
  end
27
31
  [status, headers, body]
28
32
  end
@@ -30,8 +34,9 @@ end
30
34
 
31
35
  class RasterizeAndShorten < Rasterize
32
36
  use Goliath::Rack::Params
37
+ use Favicon, File.expand_path(File.dirname(__FILE__)+"/../public/favicon.ico")
33
38
  use Goliath::Rack::Validation::RequestMethod, %w(GET)
34
39
  use Goliath::Rack::Validation::RequiredParam, {:key => 'url'}
35
40
  #
36
- use Goliath::Rack::AsyncAroundware, ShortenURL
41
+ use Goliath::Rack::BarrierAroundwareFactory, ShortenURL
37
42
  end
data/goliath.gemspec CHANGED
@@ -8,14 +8,12 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ['dan sinclair', 'Ilya Grigorik']
10
10
  s.email = ['dj2@everburning.com', 'ilya@igvita.com']
11
- s.homepage = 'http://labs.postrank.com/'
12
- s.summary = 'Framework for writing API servers'
11
+ s.homepage = 'http://goliath.io/'
12
+ s.summary = 'Async framework for writing API servers'
13
13
  s.description = s.summary
14
14
 
15
- s.required_ruby_version = '>=1.9.2'
16
-
17
15
  s.add_dependency 'eventmachine', '>= 1.0.0.beta.3'
18
- s.add_dependency 'em-synchrony', '>= 0.3.0.beta.1'
16
+ s.add_dependency 'em-synchrony', '>= 1.0.0'
19
17
  s.add_dependency 'http_parser.rb'
20
18
  s.add_dependency 'log4r'
21
19
 
@@ -24,13 +22,13 @@ Gem::Specification.new do |s|
24
22
  s.add_dependency 'rack-respond_to'
25
23
  s.add_dependency 'async-rack'
26
24
  s.add_dependency 'multi_json'
27
- s.add_dependency 'http_router', '~> 0.8.9'
25
+ s.add_dependency 'http_router', '~> 0.9.0'
28
26
 
29
- s.add_development_dependency 'rake', '0.8.7'
27
+ s.add_development_dependency 'rake', '>=0.8.7'
30
28
  s.add_development_dependency 'rspec', '>2.0'
31
29
  s.add_development_dependency 'nokogiri'
32
- s.add_development_dependency 'em-http-request', '>= 1.0.0.beta.1'
33
- s.add_development_dependency 'em-mongo'
30
+ s.add_development_dependency 'em-http-request', '>=1.0.0'
31
+ s.add_development_dependency 'em-mongo', '~> 0.4.0'
34
32
  s.add_development_dependency 'yajl-ruby'
35
33
  s.add_development_dependency 'rack-rewrite'
36
34
  s.add_development_dependency 'multipart_body'
data/lib/goliath/api.rb CHANGED
@@ -125,9 +125,6 @@ module Goliath
125
125
  def map(name, *args, &block)
126
126
  opts = args.last.is_a?(Hash) ? args.pop : {}
127
127
  klass = args.first
128
- if klass && block_given?
129
- raise "Can't provide class and block to map"
130
- end
131
128
  maps.push([name, klass, opts, block])
132
129
  end
133
130
 
@@ -137,7 +134,7 @@ module Goliath
137
134
  opts = args.last.is_a?(Hash) ? args.pop : {}
138
135
  klass = args.first
139
136
  opts[:conditions] ||= {}
140
- opts[:conditions][:request_method] = [#{http_method == :get ? "'HEAD', 'GET'" : http_method.to_s.upcase.inspect}]
137
+ opts[:conditions][:request_method] = [#{http_method.to_s.upcase.inspect}]
141
138
  map(name, klass, opts, &block)
142
139
  end
143
140
  EOT
@@ -281,5 +278,20 @@ module Goliath
281
278
  def chunked_streaming_response(status_code = 200, headers = {})
282
279
  streaming_response(status_code, headers.merge(Goliath::Response::CHUNKED_STREAM_HEADERS))
283
280
  end
281
+
282
+ # Helper method to initialize the approriate API handler
283
+ #
284
+ # Called by the parser once headers are available to detect
285
+ # which API class should be handling the incoming request
286
+ def set_event_handler!(env)
287
+ if self.class.maps?
288
+ response = self.class.router.recognize(env)
289
+ if response = self.class.router.recognize(env) and response.respond_to?(:path) and response.path.route.api_class
290
+ env.event_handler = response.path.route.api_class.new
291
+ end
292
+ end
293
+ env.event_handler ||= self
294
+ end
295
+
284
296
  end
285
297
  end
@@ -15,7 +15,6 @@ module Goliath
15
15
  attr_reader :parser
16
16
 
17
17
  AsyncResponse = [-1, {}, []]
18
-
19
18
  def post_init
20
19
  @current = nil
21
20
  @requests = []
@@ -24,7 +23,7 @@ module Goliath
24
23
  @parser = Http::Parser.new
25
24
  @parser.on_headers_complete = proc do |h|
26
25
 
27
- env = Goliath::Env.new
26
+ env = Thread.current[GOLIATH_ENV] = Goliath::Env.new
28
27
  env[SERVER_PORT] = port.to_s
29
28
  env[RACK_LOGGER] = logger
30
29
  env[OPTIONS] = options
@@ -32,12 +31,14 @@ module Goliath
32
31
  env[CONFIG] = config
33
32
  env[REMOTE_ADDR] = remote_address
34
33
 
35
- env[ASYNC_HEADERS] = @api.method(:on_headers) if @api.respond_to? :on_headers
36
- env[ASYNC_BODY] = @api.method(:on_body) if @api.respond_to? :on_body
37
- env[ASYNC_CLOSE] = @api.method(:on_close) if @api.respond_to? :on_close
38
-
39
34
  r = Goliath::Request.new(@app, self, env)
40
- r.parse_header(h, @parser)
35
+ r.parse_header(h, @parser) do
36
+ @api.set_event_handler!(env) if @api
37
+
38
+ env[ASYNC_HEADERS] = env.event_handler.method(:on_headers) if env.event_handler.respond_to? :on_headers
39
+ env[ASYNC_BODY] = env.event_handler.method(:on_body) if env.event_handler.respond_to? :on_body
40
+ env[ASYNC_CLOSE] = env.event_handler.method(:on_close) if env.event_handler.respond_to? :on_close
41
+ end
41
42
 
42
43
  @requests.push(r)
43
44
  end
@@ -0,0 +1,133 @@
1
+ module Goliath
2
+ module Rack
3
+ #
4
+ # Note: This class is deprecated. Instead, use BarrierAroundwareFactory
5
+ # (orchestrates multiple concurrent requests) or SimpleAroundwareFactory
6
+ # (like AsyncMiddleware, but with a simpler interface).
7
+ #
8
+ # The differences:
9
+ # * ResponseReceiver/MultiReceiver was a stupid name. The thing that has
10
+ # pre_ and post_process is the Aroundware, the thing that manufactures
11
+ # it is an AroundwareFactory.
12
+ # * An aroundware's pre_process may return a direct response, which is
13
+ # immediately sent back upstream (no further downstream processing
14
+ # happens). In the typical case, you will want to add
15
+ # return Goliath::Connection::AsyncResponse
16
+ # to your pre_process method.
17
+ # * ResponseReceiver used to masquerade as callback and middleware. Yuck.
18
+ # The downstream response is now set via #accept_response, not #call.
19
+ #
20
+ # * change
21
+ # use Goliath::Rack::AsyncAroundware, MyObsoleteReceiver
22
+ # to
23
+ # use Goliath::Rack::BarrierAroundwareFactory, MyHappyBarrier
24
+ # * `BarrierAroundware` provides the combined functionality of
25
+ # `MultiReceiver` and `ResponseReceiver`, which will go away. It's now a
26
+ # mixin (module) so you're not forced to inherit from it.
27
+ # * There is no more `responses` method: either use instance accessors or
28
+ # look in the `successes`/`failures` hashes for yourresults.
29
+ # * Both enqueued responses and the downstream response are sent to
30
+ # `accept_response`; there is no more `call` method.
31
+ # * `MongoReceiver` will go away, because there's no need for it. See
32
+ # `examples/auth_and_rate_limit.rb` for examples
33
+ #
34
+ class AsyncAroundware
35
+ include Goliath::Rack::Validator
36
+
37
+ #
38
+ # Called by the framework to create the middleware.
39
+ #
40
+ # Any extra args passed to the use statement are sent to each
41
+ # aroundware_klass as it is created.
42
+ #
43
+ # @example
44
+ # class Awesomizer2011 < Goliath::Rack::MultiReceiver
45
+ # def initialize(env, aq)
46
+ # @awesomeness_quotient = aq
47
+ # super(env)
48
+ # end
49
+ # # ... define pre_process and post_process ...
50
+ # end
51
+ #
52
+ # class AwesomeApiWithShortening < Goliath::API
53
+ # use Goliath::Rack::AsyncAroundware, Awesomizer2011, 3
54
+ # # ... stuff ...
55
+ # end
56
+ #
57
+ # @param app [#call] the downstream app
58
+ # @param aroundware_klass a class that quacks like a
59
+ # Goliath::Rack::ResponseReceiver and an EM::Deferrable
60
+ # @param *args [Array] extra args to pass to the aroundware
61
+ # @return [Goliath::Rack::AsyncAroundware]
62
+ def initialize app, aroundware_klass, *args
63
+ @app = app
64
+ @aroundware_klass = aroundware_klass
65
+ @aroundware_args = args
66
+ end
67
+
68
+ # Coordinates aroundware to process a request.
69
+ #
70
+ # We hook the aroundware in the middle of the async_callback chain:
71
+ # * send the downstream response to the aroundware, whether received directly
72
+ # from @app.call or via async callback
73
+ # * have the upstream callback chain be invoked when the aroundware completes
74
+ #
75
+ # @param env [Goliath::Env] The goliath environment
76
+ # @return [Array] The [status_code, headers, body] tuple
77
+ def call(env)
78
+ aroundware = new_aroundware(env)
79
+
80
+ aroundware_resp = aroundware.pre_process
81
+
82
+ hook_into_callback_chain(env, aroundware)
83
+
84
+ downstream_resp = @app.call(env)
85
+
86
+ # if downstream resp is final, pass it to the aroundware; it will invoke
87
+ # the callback chain at its leisure. Our response is *always* async.
88
+ if final_response?(downstream_resp)
89
+ aroundware.call(downstream_resp)
90
+ end
91
+ return Goliath::Connection::AsyncResponse
92
+ end
93
+
94
+ # Put aroundware in the middle of the async_callback chain:
95
+ # * save the old callback chain;
96
+ # * have the downstream callback send results to the aroundware (possibly
97
+ # completing it)
98
+ # * set the old callback chain to fire when the aroundware completes
99
+ def hook_into_callback_chain(env, aroundware)
100
+ async_callback = env['async.callback']
101
+
102
+ # The response from the downstream app is accepted by the aroundware...
103
+ downstream_callback = Proc.new do |resp|
104
+ safely(env){ aroundware.call(resp) }
105
+ end
106
+
107
+ # .. but the upstream chain is only invoked when the aroundware completes
108
+ invoke_upstream_chain = Proc.new do
109
+ new_resp = safely(env){ aroundware.post_process }
110
+ async_callback.call(new_resp)
111
+ end
112
+
113
+ env['async.callback'] = downstream_callback
114
+ aroundware.callback(&invoke_upstream_chain)
115
+ aroundware.errback(&invoke_upstream_chain)
116
+ end
117
+
118
+ def final_response?(resp)
119
+ resp != Goliath::Connection::AsyncResponse
120
+ end
121
+
122
+ # Generate a aroundware to process the request, using request env & any args
123
+ # passed to this AsyncAroundware at creation
124
+ #
125
+ # @param env [Goliath::Env] The goliath environment
126
+ # @return [Goliath::Rack::ResponseReceiver] The response_receiver to process this request
127
+ def new_aroundware(env)
128
+ @aroundware_klass.new(env, *@aroundware_args)
129
+ end
130
+
131
+ end
132
+ end
133
+ end