forked from postrank-labs/goliath
/
async_middleware.rb
93 lines (88 loc) · 3.64 KB
/
async_middleware.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
module Goliath
module Rack
#
# Include this to enable middleware that can perform post-processing.
#
# For internal reasons, you can't do the following as you would in Rack:
#
# def call(env)
# # ... do pre-processing
# status, headers, body = @app.call(env)
# new_body = make_totally_awesome(body) ## !! BROKEN !!
# [status, headers, new_body]
# end
#
# By including this middleware, you can do that kind of "around" middleware:
# it lets goliath proceed asynchronously, but still "unwind" the request by
# walking up the callback chain.
#
# @example
# class AwesomeMiddleware
# include Goliath::Rack::AsyncMiddleware
#
# def call(env)
# awesomeness_quotient = 3
# # the extra args sent to super are passed along to post_process
# super(env, awesomeness_quotient)
# end
#
# def post_process(env, status, headers, body, awesomeness_quotient)
# new_body = make_totally_awesome(body, awesomeness_quotient)
# [status, headers, new_body]
# end
# end
#
# @note Some caveats on writing middleware. Unlike other Rack powered app
# servers, Goliath creates a single instance of the middleware chain at
# startup, and reuses it for all incoming requests. Since everything is
# asynchronous, you can have multiple requests using the middleware chain
# at the same time. If your middleware tries to store any instance or
# class level variables they'll end up getting stomped all over by the
# next request. Everything that you need to store needs to be stored in
# local variables.
module AsyncMiddleware
# Called by the framework to create the middleware.
#
# @param app [Proc] The application
# @return [Goliath::Rack::AsyncMiddleware]
def initialize(app)
@app = app
end
# Store the previous async.callback into async_cb and redefines it to be
# our own. When the asynchronous response is done, Goliath can "unwind"
# the request by walking up the callback chain.
#
# However, you will notice that we execute the post_process method in the
# default return case. If the validations fail later in the middleware
# chain before your classes response(env) method is executed, the response
# will come back up through the chain normally and be returned.
#
# To do preprocessing, override this method in your subclass and invoke
# super(env) as the last line. Any extra arguments will be made available
# to the post_process method.
#
# @param env [Goliath::Env] The goliath environment
# @return [Array] The [status_code, headers, body] tuple
def call(env, *args)
async_cb = env['async.callback']
env['async.callback'] = Proc.new do |status, headers, body|
async_cb.call(post_process(env, status, headers, body, *args))
end
status, headers, body = @app.call(env)
if status == Goliath::Connection::AsyncResponse.first
[status, headers, body]
else
post_process(env, status, headers, body, *args)
end
end
# Override this method in your middleware to perform any
# postprocessing. Note that this can be called in the asynchronous case
# (walking back up the middleware async.callback chain), or synchronously
# (in the case of a validation error, or if a downstream middleware
# supplied a direct response).
def post_process(env, status, headers, body)
[status, headers, body]
end
end
end
end