goliath 0.9.1 → 0.9.2
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.
- data/.gitignore +1 -0
- data/HISTORY +50 -0
- data/README.md +2 -0
- data/examples/activerecord/srv.rb +1 -3
- data/examples/async_aroundware_demo.rb +81 -0
- data/examples/async_upload.rb +1 -2
- data/examples/auth_and_rate_limit.rb +143 -0
- data/examples/chunked_streaming.rb +37 -0
- data/examples/conf_test.rb +1 -3
- data/examples/config/auth_and_rate_limit.rb +30 -0
- data/examples/config/template.rb +8 -0
- data/examples/content_stream.rb +1 -3
- data/examples/echo.rb +8 -6
- data/examples/env_use_statements.rb +17 -0
- data/examples/gziped.rb +1 -3
- data/examples/public/stylesheets/style.css +296 -0
- data/examples/rack_routes.rb +65 -3
- data/examples/rasterize/rasterize.js +15 -0
- data/examples/rasterize/rasterize.rb +36 -0
- data/examples/rasterize/rasterize_and_shorten.rb +37 -0
- data/examples/stream.rb +2 -2
- data/examples/template.rb +48 -0
- data/examples/test_rig.rb +125 -0
- data/examples/valid.rb +4 -2
- data/examples/views/debug.haml +4 -0
- data/examples/views/joke.markdown +13 -0
- data/examples/views/layout.erb +12 -0
- data/examples/views/layout.haml +39 -0
- data/examples/views/root.haml +28 -0
- data/goliath.gemspec +10 -3
- data/lib/goliath.rb +0 -36
- data/lib/goliath/api.rb +137 -26
- data/lib/goliath/application.rb +71 -21
- data/lib/goliath/connection.rb +4 -2
- data/lib/goliath/constants.rb +1 -0
- data/lib/goliath/env.rb +40 -1
- data/lib/goliath/goliath.rb +30 -15
- data/lib/goliath/headers.rb +2 -2
- data/lib/goliath/plugins/latency.rb +8 -2
- data/lib/goliath/rack.rb +18 -0
- data/lib/goliath/rack/async_aroundware.rb +56 -0
- data/lib/goliath/rack/async_middleware.rb +93 -0
- data/lib/goliath/rack/builder.rb +42 -0
- data/lib/goliath/rack/default_response_format.rb +3 -15
- data/lib/goliath/rack/formatters.rb +11 -0
- data/lib/goliath/rack/formatters/html.rb +2 -18
- data/lib/goliath/rack/formatters/json.rb +2 -17
- data/lib/goliath/rack/formatters/plist.rb +32 -0
- data/lib/goliath/rack/formatters/xml.rb +23 -31
- data/lib/goliath/rack/formatters/yaml.rb +27 -0
- data/lib/goliath/rack/jsonp.rb +1 -13
- data/lib/goliath/rack/params.rb +55 -27
- data/lib/goliath/rack/render.rb +13 -22
- data/lib/goliath/rack/templates.rb +357 -0
- data/lib/goliath/rack/tracer.rb +11 -12
- data/lib/goliath/rack/validation.rb +12 -0
- data/lib/goliath/rack/validation/default_params.rb +0 -2
- data/lib/goliath/rack/validation/numeric_range.rb +11 -2
- data/lib/goliath/rack/validation/request_method.rb +3 -2
- data/lib/goliath/rack/validation/required_param.rb +13 -11
- data/lib/goliath/rack/validation/required_value.rb +11 -15
- data/lib/goliath/rack/validator.rb +51 -0
- data/lib/goliath/request.rb +34 -20
- data/lib/goliath/response.rb +3 -2
- data/lib/goliath/runner.rb +5 -11
- data/lib/goliath/server.rb +2 -1
- data/lib/goliath/synchrony/mongo_receiver.rb +64 -0
- data/lib/goliath/synchrony/response_receiver.rb +64 -0
- data/lib/goliath/test_helper.rb +39 -21
- data/lib/goliath/validation.rb +2 -0
- data/lib/goliath/{rack/validation_error.rb → validation/error.rb} +0 -16
- data/lib/goliath/validation/standard_http_errors.rb +31 -0
- data/lib/goliath/version.rb +1 -1
- data/spec/integration/http_log_spec.rb +16 -16
- data/spec/integration/rack_routes_spec.rb +144 -0
- data/spec/integration/reloader_spec.rb +4 -4
- data/spec/integration/template_spec.rb +54 -0
- data/spec/integration/trace_spec.rb +23 -0
- data/spec/integration/valid_spec.rb +21 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/unit/api_spec.rb +30 -0
- data/spec/unit/rack/builder_spec.rb +40 -0
- data/spec/unit/rack/formatters/plist_spec.rb +51 -0
- data/spec/unit/rack/formatters/yaml_spec.rb +53 -0
- data/spec/unit/rack/params_spec.rb +22 -0
- data/spec/unit/rack/render_spec.rb +10 -5
- data/spec/unit/rack/validation/numeric_range_spec.rb +8 -1
- data/spec/unit/rack/validation/request_method_spec.rb +8 -8
- data/spec/unit/rack/validation/required_param_spec.rb +19 -15
- data/spec/unit/rack/validation/required_value_spec.rb +10 -13
- data/spec/unit/server_spec.rb +4 -4
- data/spec/unit/validation/standard_http_errors_spec.rb +21 -0
- metadata +177 -35
- data/spec/unit/rack/validation_error_spec.rb +0 -40
data/lib/goliath/application.rb
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
require 'goliath/goliath'
|
2
|
+
require 'goliath/runner'
|
3
|
+
require 'goliath/rack'
|
4
|
+
|
5
|
+
# Pre-load the goliath environment so it's available as we try to parse the class.
|
6
|
+
# This means we can use Goliath.dev? or Goliath.prod? in the use statements.
|
7
|
+
#
|
8
|
+
# Note, as implmented, you have to have -e as it's own flag, you can't do -sve dev
|
9
|
+
# as it won't pickup the e flag.
|
10
|
+
env = ENV['RACK_ENV']
|
11
|
+
env ||= begin
|
12
|
+
if ((i = ARGV.index('-e')) || (i = ARGV.index('--environment')))
|
13
|
+
ARGV[i + 1]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
Goliath.env = env if env
|
17
|
+
|
1
18
|
module Goliath
|
2
19
|
# The main execution class for Goliath. This will execute in the at_exit
|
3
20
|
# handler to run the server.
|
@@ -6,9 +23,10 @@ module Goliath
|
|
6
23
|
class Application
|
7
24
|
# Most of this stuff is straight out of sinatra.
|
8
25
|
|
9
|
-
# Set of caller regex's to be
|
26
|
+
# Set of caller regex's to be skipped when looking for our API file
|
10
27
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
11
|
-
/\/goliath(\/
|
28
|
+
/\/goliath(\/application)?\.rb$/, # all goliath code
|
29
|
+
/\/goliath(\/(rack|validation|plugins)\/)/, # all goliath code
|
12
30
|
/rubygems\/custom_require\.rb$/, # rubygems require hacks
|
13
31
|
/bundler(\/runtime)?\.rb/, # bundler require hacks
|
14
32
|
/<internal:/ # internal in ruby >= 1.9.2
|
@@ -40,34 +58,66 @@ module Goliath
|
|
40
58
|
c
|
41
59
|
end
|
42
60
|
|
61
|
+
# Returns the userland class which inherits the Goliath API
|
62
|
+
#
|
63
|
+
# @return [String] The app class
|
64
|
+
def self.app_class
|
65
|
+
@app_class
|
66
|
+
end
|
67
|
+
|
68
|
+
# Sets the userland class that should use the Goliath API
|
69
|
+
#
|
70
|
+
# @param app_class [String|Symbol|Constant] The new app class
|
71
|
+
# @return [String] app_class The new app class
|
72
|
+
def self.app_class=(app_class)
|
73
|
+
@app_class = app_class.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
# Retrive the base directory for the API before we've changed directories
|
77
|
+
#
|
78
|
+
# @note Note sure of a better way to handle this. Goliath will do a chdir
|
79
|
+
# when the runner is executed. If you need the +root_path+ before
|
80
|
+
# the runner is executing (like, in a use statement) you need this method.
|
81
|
+
#
|
82
|
+
# @param args [Array] Any arguments to append to the path
|
83
|
+
# @return [String] path for the given arguments
|
84
|
+
def self.app_path(*args)
|
85
|
+
@app_path ||= File.expand_path(File.dirname(app_file))
|
86
|
+
File.join(@app_path, *args)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Retrieve the base directory for the API
|
90
|
+
#
|
91
|
+
# @param args [Array] Any arguments to append to the path
|
92
|
+
# @return [String] path for the given arguments
|
93
|
+
def self.root_path(*args)
|
94
|
+
return app_path(args) if Goliath.test?
|
95
|
+
|
96
|
+
@root_path ||= File.expand_path("./")
|
97
|
+
File.join(@root_path, *args)
|
98
|
+
end
|
99
|
+
|
43
100
|
# Execute the application
|
44
101
|
#
|
45
102
|
# @return [Nil]
|
46
103
|
def self.run!
|
47
|
-
|
48
|
-
|
49
|
-
|
104
|
+
unless @app_class
|
105
|
+
file = File.basename(app_file, '.rb')
|
106
|
+
@app_class = camel_case(file)
|
107
|
+
end
|
108
|
+
|
109
|
+
begin
|
110
|
+
klass = Kernel
|
111
|
+
@app_class.split('::').each do |con|
|
112
|
+
klass = klass.const_get(con)
|
113
|
+
end
|
50
114
|
rescue NameError
|
51
|
-
raise NameError, "Class #{
|
115
|
+
raise NameError, "Class #{@app_class} not found."
|
52
116
|
end
|
53
117
|
api = klass.new
|
54
118
|
|
55
119
|
runner = Goliath::Runner.new(ARGV, api)
|
56
|
-
runner.
|
57
|
-
klass.middlewares.each do |mw|
|
58
|
-
use(*(mw[0..1].compact), &mw[2])
|
59
|
-
end
|
60
|
-
|
61
|
-
# If you use map you can't use run as
|
62
|
-
# the rack builder will blowup.
|
63
|
-
if klass.maps.empty?
|
64
|
-
run api
|
65
|
-
else
|
66
|
-
klass.maps.each do |mp|
|
67
|
-
map(mp.first, &mp.last)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
120
|
+
runner.app = Goliath::Rack::Builder.build(klass, api)
|
71
121
|
|
72
122
|
runner.load_plugins(klass.plugins)
|
73
123
|
runner.run
|
data/lib/goliath/connection.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'http/parser'
|
2
2
|
require 'goliath/env'
|
3
|
+
require 'goliath/constants'
|
4
|
+
require 'goliath/request'
|
3
5
|
|
4
6
|
module Goliath
|
5
7
|
# The Goliath::Connection class handles sending and receiving data
|
@@ -12,7 +14,7 @@ module Goliath
|
|
12
14
|
attr_accessor :app, :api, :port, :logger, :status, :config, :options
|
13
15
|
attr_reader :parser
|
14
16
|
|
15
|
-
AsyncResponse = [-1, {}, []]
|
17
|
+
AsyncResponse = [-1, {}, []]
|
16
18
|
|
17
19
|
def post_init
|
18
20
|
@current = nil
|
@@ -76,7 +78,7 @@ module Goliath
|
|
76
78
|
if req = @pending.shift
|
77
79
|
@current = req
|
78
80
|
@current.succeed
|
79
|
-
|
81
|
+
elsif @current
|
80
82
|
@current.close
|
81
83
|
@current = nil
|
82
84
|
end
|
data/lib/goliath/constants.rb
CHANGED
data/lib/goliath/env.rb
CHANGED
@@ -47,7 +47,7 @@ module Goliath
|
|
47
47
|
# @example
|
48
48
|
# [200, {}, {:meta => {:trace => env.trace_stats}}, {}]
|
49
49
|
#
|
50
|
-
# @return [Array] Array of [name, time] pairs with a Total entry added.
|
50
|
+
# @return [Array] Array of [name, time] pairs with a Total entry added.
|
51
51
|
def trace_stats
|
52
52
|
self[:trace] + [['total', self[:trace].collect { |s| s[1].to_f }.inject(:+).to_s]]
|
53
53
|
end
|
@@ -67,6 +67,45 @@ module Goliath
|
|
67
67
|
self[STREAM_CLOSE].call
|
68
68
|
end
|
69
69
|
|
70
|
+
# Sends a chunk in a Chunked transfer encoding stream.
|
71
|
+
#
|
72
|
+
# Each chunk starts with the number of octets of the data it embeds expressed
|
73
|
+
# in hexadecimal followed by optional parameters (chunk extension) and a
|
74
|
+
# terminating CRLF (carriage return and line feed) sequence, followed by the
|
75
|
+
# chunk data. The chunk is terminated by CRLF. If chunk extensions are
|
76
|
+
# provided, the chunk size is terminated by a semicolon followed with the
|
77
|
+
# extension name and an optional equal sign and value
|
78
|
+
#
|
79
|
+
# Note: chunk extensions aren't provided yet
|
80
|
+
#
|
81
|
+
# This will do nothing if the chunk is empty -- sending a zero-length chunk
|
82
|
+
# signals the end of a stream.
|
83
|
+
#
|
84
|
+
def chunked_stream_send(chunk)
|
85
|
+
return if chunk.empty?
|
86
|
+
chunk_len_in_hex = chunk.bytesize.to_s(16)
|
87
|
+
body = [chunk_len_in_hex, "\r\n", chunk, "\r\n"].join
|
88
|
+
stream_send(body)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Sends the terminating chunk in a chunked transfer encoding stream, and
|
92
|
+
# closes the stream.
|
93
|
+
#
|
94
|
+
# The last chunk is a zero-length chunk, with the chunk size coded as 0, but
|
95
|
+
# without any chunk data section. The final chunk may be followed by an
|
96
|
+
# optional trailer of additional entity header fields that are normally
|
97
|
+
# delivered in the HTTP header to allow the delivery of data that can only
|
98
|
+
# be computed after all chunk data has been generated. The sender may
|
99
|
+
# indicate in a Trailer header field which additional fields it will send
|
100
|
+
# in the trailer after the chunks.
|
101
|
+
#
|
102
|
+
# Note: trailer headers aren't provided yet
|
103
|
+
#
|
104
|
+
def chunked_stream_close
|
105
|
+
stream_send([0, "\r\n", "\r\n"].join)
|
106
|
+
stream_close
|
107
|
+
end
|
108
|
+
|
70
109
|
# Convenience method for accessing the rack.logger item in the environment.
|
71
110
|
#
|
72
111
|
# @return [Logger] The logger object
|
data/lib/goliath/goliath.rb
CHANGED
@@ -1,49 +1,64 @@
|
|
1
1
|
require 'eventmachine'
|
2
2
|
require 'http/parser'
|
3
3
|
require 'async_rack'
|
4
|
+
require 'goliath/constants'
|
5
|
+
require 'goliath/version'
|
4
6
|
|
5
7
|
# The Goliath Framework
|
6
8
|
module Goliath
|
7
9
|
module_function
|
8
10
|
|
9
|
-
|
11
|
+
ENVIRONMENTS = [:development, :production, :test, :staging]
|
10
12
|
|
11
13
|
# Retrieves the current goliath environment
|
12
14
|
#
|
13
15
|
# @return [String] the current environment
|
14
16
|
def env
|
15
|
-
@env
|
17
|
+
@env or fail "environment has not been set"
|
16
18
|
end
|
17
19
|
|
18
20
|
# Sets the current goliath environment
|
19
21
|
#
|
20
|
-
# @param [String] env the environment
|
21
|
-
def env=(
|
22
|
-
case(
|
23
|
-
when
|
24
|
-
when
|
25
|
-
|
22
|
+
# @param [String|Symbol] env the environment symbol of [dev | development | prod | production | test]
|
23
|
+
def env=(e)
|
24
|
+
es = case(e.to_sym)
|
25
|
+
when :dev then :development
|
26
|
+
when :prod then :production
|
27
|
+
else e.to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
if ENVIRONMENTS.include?(es)
|
31
|
+
@env = es
|
32
|
+
else
|
33
|
+
fail "did not recognize environment: #{e}, expected one of: #{ENVIRONMENTS.join(', ')}"
|
26
34
|
end
|
27
35
|
end
|
28
36
|
|
29
37
|
# Determines if we are in the production environment
|
30
38
|
#
|
31
|
-
# @return [Boolean] true if current
|
39
|
+
# @return [Boolean] true if current environment is production, false otherwise
|
32
40
|
def prod?
|
33
|
-
|
41
|
+
env == :production
|
34
42
|
end
|
35
43
|
|
36
44
|
# Determines if we are in the development environment
|
37
45
|
#
|
38
|
-
# @return [Boolean] true if current
|
46
|
+
# @return [Boolean] true if current environment is development, false otherwise
|
39
47
|
def dev?
|
40
|
-
|
48
|
+
env == :development
|
41
49
|
end
|
42
50
|
|
43
51
|
# Determines if we are in the test environment
|
44
52
|
#
|
45
|
-
# @return [Boolean] true if current
|
53
|
+
# @return [Boolean] true if current environment is test, false otherwise
|
46
54
|
def test?
|
47
|
-
|
55
|
+
env == :test
|
56
|
+
end
|
57
|
+
|
58
|
+
# Determines if we are in the staging environment
|
59
|
+
#
|
60
|
+
# @return [Boolean] true if current environment is staging.
|
61
|
+
def staging?
|
62
|
+
env == :staging
|
48
63
|
end
|
49
|
-
end
|
64
|
+
end
|
data/lib/goliath/headers.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Goliath
|
2
2
|
# @private
|
3
3
|
class Headers
|
4
|
-
HEADER_FORMAT = "%s: %s\r\n"
|
5
|
-
ALLOWED_DUPLICATES = %w(Set-Cookie Set-Cookie2 Warning WWW-Authenticate)
|
4
|
+
HEADER_FORMAT = "%s: %s\r\n"
|
5
|
+
ALLOWED_DUPLICATES = %w(Set-Cookie Set-Cookie2 Warning WWW-Authenticate)
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
@sent = {}
|
@@ -21,13 +21,19 @@ module Goliath
|
|
21
21
|
@last = Time.now.to_f
|
22
22
|
end
|
23
23
|
|
24
|
+
@@recent_latency = 0
|
25
|
+
def self.recent_latency
|
26
|
+
@@recent_latency
|
27
|
+
end
|
28
|
+
|
24
29
|
# Called automatically to start the plugin
|
25
30
|
def run
|
26
31
|
EM.add_periodic_timer(1) do
|
27
|
-
|
32
|
+
@@recent_latency = (Time.now.to_f - @last)
|
33
|
+
@logger.info "LATENCY: #{@@recent_latency}"
|
28
34
|
@last = Time.now.to_f
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
32
38
|
end
|
33
|
-
end
|
39
|
+
end
|
data/lib/goliath/rack.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
autoload :AsyncAroundware, 'goliath/rack/async_aroundware'
|
4
|
+
autoload :AsyncMiddleware, 'goliath/rack/async_middleware'
|
5
|
+
autoload :Builder, 'goliath/rack/builder'
|
6
|
+
autoload :DefaultMimeType, 'goliath/rack/default_mime_type'
|
7
|
+
autoload :DefaultResponseFormat, 'goliath/rack/default_response_format'
|
8
|
+
autoload :Formatters, 'goliath/rack/formatters'
|
9
|
+
autoload :Heartbeat, 'goliath/rack/heartbeat'
|
10
|
+
autoload :JSONP, 'goliath/rack/jsonp'
|
11
|
+
autoload :Params, 'goliath/rack/params'
|
12
|
+
autoload :Render, 'goliath/rack/render'
|
13
|
+
autoload :Templates, 'goliath/rack/templates'
|
14
|
+
autoload :Tracer, 'goliath/rack/tracer'
|
15
|
+
autoload :Validator, 'goliath/rack/validator'
|
16
|
+
autoload :Validation, 'goliath/rack/validation'
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
class AsyncAroundware
|
4
|
+
# Create a new AsyncAroundware
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class MyResponseReceiver < Goliath::Rack::MultiReceiver
|
8
|
+
# # ... define pre_process and post_process ...
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# class AsyncAroundwareDemoMulti < Goliath::API
|
12
|
+
# use Goliath::Rack::AsyncAroundware, MyResponseReceiver
|
13
|
+
# # ... stuff ...
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @param app [#call] the downstream app
|
17
|
+
# @param response_receiver_klass a class that quacks like a
|
18
|
+
# Goliath::Rack::ResponseReceiver and an EM::Deferrable
|
19
|
+
# @param *args [Array] extra args to pass to the response_receiver
|
20
|
+
def initialize app, response_receiver_klass, *args
|
21
|
+
@app = app
|
22
|
+
@response_receiver_klass = response_receiver_klass
|
23
|
+
@response_receiver_args = args
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
def call(env)
|
28
|
+
response_receiver = new_response_receiver(env)
|
29
|
+
|
30
|
+
# put response_receiver in the middle of the async_callback chain:
|
31
|
+
# * save the old callback chain;
|
32
|
+
# * put the response_receiver in as the new async_callback;
|
33
|
+
# * when the response_receiver completes, invoke the old callback chain
|
34
|
+
async_callback = env['async.callback']
|
35
|
+
env['async.callback'] = response_receiver
|
36
|
+
response_receiver.callback{ do_postprocess(env, async_callback, response_receiver) }
|
37
|
+
response_receiver.errback{ do_postprocess(env, async_callback, response_receiver) }
|
38
|
+
|
39
|
+
response_receiver.pre_process
|
40
|
+
|
41
|
+
response_receiver.call(@app.call(env))
|
42
|
+
end
|
43
|
+
|
44
|
+
def new_response_receiver(env)
|
45
|
+
@response_receiver_klass.new(env, *@response_receiver_args)
|
46
|
+
end
|
47
|
+
|
48
|
+
include Goliath::Rack::Validator
|
49
|
+
def do_postprocess(env, async_callback, response_receiver)
|
50
|
+
Goliath::Rack::Validator.safely(env) do
|
51
|
+
async_callback.call(response_receiver.post_process)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
#
|
4
|
+
# Include this to enable middleware that can perform post-processing.
|
5
|
+
#
|
6
|
+
# For internal reasons, you can't do the following as you would in Rack:
|
7
|
+
#
|
8
|
+
# def call(env)
|
9
|
+
# # ... do pre-processing
|
10
|
+
# status, headers, body = @app.call(env)
|
11
|
+
# new_body = make_totally_awesome(body) ## !! BROKEN !!
|
12
|
+
# [status, headers, new_body]
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# By including this middleware, you can do that kind of "around" middleware:
|
16
|
+
# it lets goliath proceed asynchronously, but still "unwind" the request by
|
17
|
+
# walking up the callback chain.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# class AwesomeMiddleware
|
21
|
+
# include Goliath::Rack::AsyncMiddleware
|
22
|
+
#
|
23
|
+
# def call(env)
|
24
|
+
# awesomness_quotient = 3
|
25
|
+
# # the extra args sent to super are passed along to post_process
|
26
|
+
# super(env, awesomness_quotient)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# def post_process(env, status, headers, body, awesomness_quotient)
|
30
|
+
# new_body = make_totally_awesome(body, awesomness_quotient)
|
31
|
+
# [status, headers, new_body]
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @note Some caveats on writing middleware. Unlike other Rack powered app
|
36
|
+
# servers, Goliath creates a single instance of the middleware chain at
|
37
|
+
# startup, and reuses it for all incoming requests. Since everything is
|
38
|
+
# asynchronous, you can have multiple requests using the middleware chain
|
39
|
+
# at the same time. If your middleware tries to store any instance or
|
40
|
+
# class level variables they'll end up getting stomped all over by the
|
41
|
+
# next request. Everything that you need to store needs to be stored in
|
42
|
+
# local variables.
|
43
|
+
module AsyncMiddleware
|
44
|
+
# Called by the framework to create the middleware.
|
45
|
+
#
|
46
|
+
# @param app [Proc] The application
|
47
|
+
# @return [Goliath::Rack::AsyncMiddleware]
|
48
|
+
def initialize(app)
|
49
|
+
@app = app
|
50
|
+
end
|
51
|
+
|
52
|
+
# Store the previous async.callback into async_cb and redefines it to be
|
53
|
+
# our own. When the asynchronous response is done, Goliath can "unwind"
|
54
|
+
# the request by walking up the callback chain.
|
55
|
+
#
|
56
|
+
# However, you will notice that we execute the post_process method in the
|
57
|
+
# default return case. If the validations fail later in the middleware
|
58
|
+
# chain before your classes response(env) method is executed, the response
|
59
|
+
# will come back up through the chain normally and be returned.
|
60
|
+
#
|
61
|
+
# To do preprocessing, override this method in your subclass and invoke
|
62
|
+
# super(env) as the last line. Any extra arguments will be made available
|
63
|
+
# to the post_process method.
|
64
|
+
#
|
65
|
+
# @param env [Goliath::Env] The goliath environment
|
66
|
+
# @return [Array] The [status_code, headers, body] tuple
|
67
|
+
def call(env, *args)
|
68
|
+
async_cb = env['async.callback']
|
69
|
+
|
70
|
+
env['async.callback'] = Proc.new do |status, headers, body|
|
71
|
+
async_cb.call(post_process(env, status, headers, body, *args))
|
72
|
+
end
|
73
|
+
|
74
|
+
status, headers, body = @app.call(env)
|
75
|
+
|
76
|
+
if status == Goliath::Connection::AsyncResponse.first
|
77
|
+
[status, headers, body]
|
78
|
+
else
|
79
|
+
post_process(env, status, headers, body, *args)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Override this method in your middleware to perform any
|
84
|
+
# postprocessing. Note that this can be called in the asynchronous case
|
85
|
+
# (walking back up the middleware async.callback chain), or synchronously
|
86
|
+
# (in the case of a validation error, or if a downstream middleware
|
87
|
+
# supplied a direct response).
|
88
|
+
def post_process(env, status, headers, body)
|
89
|
+
[status, headers, body]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|