goliath-rack_proxy 1.0.0 → 1.0.1
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 +4 -4
- data/README.md +18 -26
- data/goliath-rack_proxy.gemspec +1 -1
- data/lib/goliath/rack_proxy.rb +40 -25
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab4ba13cd9def1d20578a29ffefabf79080598ff
|
4
|
+
data.tar.gz: 6ace1d22f5aba4ecd862dfa72ec959dd378b2cd4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0bacf146eea719d6e0e124cd99e1e1194a86d7d79a1d7931035c78bebcfca8e2a85be2a9693e51b0b304440c34d4eb90ecb5d4d26954569ece2719c5af2d2cec
|
7
|
+
data.tar.gz: 8e22fc46cf8edab53d430b017e8b6f3421ec7530a88d9963a83c5485f8e168cb9ee24102e3543707e655ce51128df65e1823dca39b74abfc0c8d2ce50bfa90a7
|
data/README.md
CHANGED
@@ -9,21 +9,27 @@ uploads and large downloads, I wanted to find an appropriate web server to
|
|
9
9
|
recommend. I needed a web server that supports **streaming uploads**, allowing
|
10
10
|
the Rack application to start processing the request while the request body is
|
11
11
|
still being received, and that way giving it the ability to save whatever data
|
12
|
-
it received before possible request
|
13
|
-
**streaming downloads**, sending response body in chunks back to
|
12
|
+
it received before possible potential request interruption. I also needed
|
13
|
+
support for **streaming downloads**, sending response body in chunks back to
|
14
|
+
the client.
|
14
15
|
|
15
16
|
The only web server I found that supported all of this was [Unicorn]. However,
|
16
17
|
Unicorn needs to spawn a whole process for serving each concurrent request,
|
17
|
-
which isn't the most efficent use of server resources.
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
which isn't the most efficent use of server resources. It's also difficult to
|
19
|
+
estimate how many workers you need, because once you disable request buffering
|
20
|
+
in the application server, you become vulnerable to slow clients.
|
21
|
+
|
22
|
+
Then I came across [Goliath], which gave me the control I needed for handling
|
23
|
+
requests. It's built on top of [EventMachine], which uses the reactor pattern
|
24
|
+
to schedule work efficiently. The most important feature is that long-running
|
25
|
+
requests won't impact request throughput, as there are no web workers that are
|
26
|
+
waiting for incoming data.
|
27
|
+
|
28
|
+
However, Goliath itself is designed to be used standalone, not in tandem with
|
29
|
+
another Rack app. So I created `Goliath::RackProxy`, which is a `Goliath::API`
|
30
|
+
subclass that proxies incoming/outgoing requests/responses to/from the
|
31
|
+
specified Rack app in a streaming fashion, essentially making it act like a web
|
32
|
+
server for the Rack app.
|
27
33
|
|
28
34
|
## Installation
|
29
35
|
|
@@ -75,20 +81,6 @@ class MyGoliathApp < Goliath::RackProxy
|
|
75
81
|
end
|
76
82
|
```
|
77
83
|
|
78
|
-
If you want to report any exceptions that might occur with the Rack app, you can
|
79
|
-
override `Goliath::RackProxy#log_exception`:
|
80
|
-
|
81
|
-
```rb
|
82
|
-
class MyGoliathApp < Goliath::RackProxy
|
83
|
-
rack_app MyRackApp
|
84
|
-
|
85
|
-
def log_exception(exception, env)
|
86
|
-
super
|
87
|
-
Airbrake.notify(exception)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
```
|
91
|
-
|
92
84
|
## License
|
93
85
|
|
94
86
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/goliath-rack_proxy.gemspec
CHANGED
data/lib/goliath/rack_proxy.rb
CHANGED
@@ -11,7 +11,7 @@ module Goliath
|
|
11
11
|
rack_proxy_options[:rack_app] = app
|
12
12
|
end
|
13
13
|
|
14
|
-
# Whether the
|
14
|
+
# Whether the request body should be rewindable.
|
15
15
|
def self.rewindable_input(value)
|
16
16
|
rack_proxy_options[:rewindable_input] = value
|
17
17
|
end
|
@@ -21,7 +21,7 @@ module Goliath
|
|
21
21
|
@rack_proxy_options ||= {}
|
22
22
|
end
|
23
23
|
|
24
|
-
#
|
24
|
+
# Starts the request to the given Rack application.
|
25
25
|
def on_headers(env, headers)
|
26
26
|
rack_app = self.class.rack_proxy_options.fetch(:rack_app)
|
27
27
|
rewindable_input = self.class.rack_proxy_options.fetch(:rewindable_input, true)
|
@@ -30,31 +30,48 @@ module Goliath
|
|
30
30
|
env["rack_proxy.call"].resume
|
31
31
|
end
|
32
32
|
|
33
|
-
#
|
33
|
+
# Resumes the Rack request with the received request body data.
|
34
34
|
def on_body(env, data)
|
35
|
-
# write data to the input, which will be read by the Rack app
|
36
35
|
env["rack_proxy.call"].resume(data)
|
37
36
|
end
|
38
37
|
|
39
|
-
#
|
38
|
+
# Resumes the Rack request with no more data.
|
40
39
|
def on_close(env)
|
41
|
-
# reading the request body has finished, so we close write end of the input
|
42
40
|
env["rack_proxy.call"].resume
|
43
41
|
end
|
44
42
|
|
45
|
-
#
|
43
|
+
# Resumes the Rack request with no more data.
|
46
44
|
def response(env)
|
47
45
|
env["rack_proxy.call"].resume
|
48
46
|
end
|
49
47
|
|
50
48
|
private
|
51
49
|
|
50
|
+
# Allows "curry-calling" the Rack application, resuming the call as we're
|
51
|
+
# receiving more request body data.
|
52
52
|
class RackCall
|
53
53
|
def initialize(app, env, rewindable_input: true)
|
54
|
-
@
|
55
|
-
|
54
|
+
@app = app
|
55
|
+
@env = env
|
56
|
+
@rewindable_input = rewindable_input
|
57
|
+
end
|
58
|
+
|
59
|
+
def resume(data = nil)
|
60
|
+
@result = fiber.resume(data) if fiber.alive?
|
61
|
+
@result
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Calls the Rack application inside a Fiber, using the RackInput object as
|
67
|
+
# the request body. When the Rack application wants to read request body
|
68
|
+
# data that hasn't been received yet, the execution is automatically
|
69
|
+
# paused so that the event loop can go on.
|
70
|
+
def fiber
|
71
|
+
@fiber ||= Fiber.new do
|
72
|
+
rack_input = RackInput.new(rewindable: @rewindable_input) { Fiber.yield }
|
56
73
|
|
57
|
-
result = app.call env.merge(
|
74
|
+
result = @app.call @env.merge(
|
58
75
|
"rack.input" => rack_input,
|
59
76
|
"async.callback" => nil, # prevent Roda/Sinatra from calling EventMachine while streaming the response
|
60
77
|
)
|
@@ -64,25 +81,23 @@ module Goliath
|
|
64
81
|
result
|
65
82
|
end
|
66
83
|
end
|
67
|
-
|
68
|
-
def resume(data = nil)
|
69
|
-
@result = @fiber.resume(data) if @fiber.alive?
|
70
|
-
@result
|
71
|
-
end
|
72
84
|
end
|
73
85
|
|
74
|
-
# IO-like object that
|
75
|
-
#
|
86
|
+
# IO-like object that conforms to the Rack specification for the request
|
87
|
+
# body ("rack input"). It takes a block which produces chunks of data, and
|
88
|
+
# makes this data retrievable through the IO#read interface. When rewindable
|
89
|
+
# caches the retrieved content onto disk.
|
76
90
|
class RackInput
|
77
91
|
def initialize(rewindable: true, &next_chunk)
|
78
92
|
@next_chunk = next_chunk
|
79
|
-
@cache = Tempfile.new("goliath-rack_input") if rewindable
|
93
|
+
@cache = Tempfile.new("goliath-rack_input", binmode: true) if rewindable
|
80
94
|
@buffer = nil
|
81
95
|
@eof = false
|
82
96
|
end
|
83
97
|
|
84
|
-
#
|
85
|
-
#
|
98
|
+
# Retrieves data using the IO#read semantics. If rack input is declared
|
99
|
+
# rewindable, writes retrieved content into a Tempfile object so that
|
100
|
+
# it can later be re-read.
|
86
101
|
def read(length = nil, outbuf = nil)
|
87
102
|
data = outbuf.clear if outbuf
|
88
103
|
data = @cache.read(length, outbuf) if @cache && !@cache.eof?
|
@@ -118,15 +133,15 @@ module Goliath
|
|
118
133
|
data.to_s unless length && (data.nil? || data.empty?)
|
119
134
|
end
|
120
135
|
|
121
|
-
# Rewinds the
|
122
|
-
# exception, which
|
123
|
-
# non-rewindable IOs such as pipes, sockets, and ttys.
|
136
|
+
# Rewinds the tempfile if rewindable. Otherwise raises Errno::ESPIPE
|
137
|
+
# exception, which is what other non-rewindable Ruby IO objects raise.
|
124
138
|
def rewind
|
125
|
-
raise Errno::ESPIPE if @cache.nil?
|
139
|
+
raise Errno::ESPIPE if @cache.nil?
|
126
140
|
@cache.rewind
|
127
141
|
end
|
128
142
|
|
129
|
-
#
|
143
|
+
# Deletes the tempfile. The #close method is also part of the Rack
|
144
|
+
# specification.
|
130
145
|
def close
|
131
146
|
@cache.close! if @cache
|
132
147
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: goliath-rack_proxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janko Marohnić
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: goliath
|