goliath-rack_proxy 0.1.3 → 1.0.0
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 +1 -8
- data/goliath-rack_proxy.gemspec +2 -2
- data/lib/goliath/rack_proxy.rb +36 -131
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca389756f31e83a5660faf6628a02cdc94c42431
|
4
|
+
data.tar.gz: 2b5c39cba40a5a989870b8e2acc2548b3e7c6bdc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51840456dab796caa37a5390b58e85cf5b5a4be37c40cae3caaa094aae32a2c9514046425f27400654c2933af01fd532784873259432bb11888ec413bd732c67
|
7
|
+
data.tar.gz: 8c2fac7e1676038be1da9b58e38f5f27da879341324a6b5c810949ac8d18729d9c9f293b84cdba033b5ad61f472820fa3947c87bda1993372d91f0b552a170b9
|
data/README.md
CHANGED
@@ -57,14 +57,7 @@ Goliath server (see [list of available options][goliath server options]):
|
|
57
57
|
$ ruby app.rb --port 3000 --stdout
|
58
58
|
```
|
59
59
|
|
60
|
-
|
61
|
-
20 threads. You can increase the number of threads EventMachine uses:
|
62
|
-
|
63
|
-
```rb
|
64
|
-
EventMachine.threadpool_size = 100
|
65
|
-
```
|
66
|
-
|
67
|
-
You can also spawn multiple EventMachine processes using [Einhorn]:
|
60
|
+
You can scale Goliath applications into multiple processes using [Einhorn]:
|
68
61
|
|
69
62
|
```sh
|
70
63
|
$ einhorn -n COUNT -b 127.0.0.1:3000 ruby app.rb --einhorn
|
data/goliath-rack_proxy.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = "goliath-rack_proxy"
|
3
|
-
gem.version = "0.
|
3
|
+
gem.version = "1.0.0"
|
4
4
|
|
5
5
|
gem.required_ruby_version = ">= 2.1"
|
6
6
|
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "*.gemspec"]
|
15
15
|
gem.require_path = "lib"
|
16
16
|
|
17
|
-
gem.add_dependency "goliath", ">= 1.0.
|
17
|
+
gem.add_dependency "goliath", ">= 1.0.6", "< 2"
|
18
18
|
|
19
19
|
gem.add_development_dependency "rake", "~> 11.1"
|
20
20
|
gem.add_development_dependency "minitest", "~> 5.8"
|
data/lib/goliath/rack_proxy.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
require "goliath/rack_proxy/rack_2_compatibility"
|
3
3
|
require "goliath"
|
4
4
|
require "tempfile"
|
5
|
+
require "fiber"
|
5
6
|
|
6
7
|
module Goliath
|
7
8
|
class RackProxy < Goliath::API
|
@@ -22,151 +23,62 @@ module Goliath
|
|
22
23
|
|
23
24
|
# Called when request headers were parsed.
|
24
25
|
def on_headers(env, headers)
|
25
|
-
|
26
|
-
|
27
|
-
rack_app = self.class.rack_proxy_options.fetch(:rack_app)
|
26
|
+
rack_app = self.class.rack_proxy_options.fetch(:rack_app)
|
27
|
+
rewindable_input = self.class.rack_proxy_options.fetch(:rewindable_input, true)
|
28
28
|
|
29
|
-
|
30
|
-
|
29
|
+
env["rack_proxy.call"] = RackCall.new(rack_app, env, rewindable_input: rewindable_input)
|
30
|
+
env["rack_proxy.call"].resume
|
31
31
|
end
|
32
32
|
|
33
33
|
# Called on each request body chunk received from the client.
|
34
34
|
def on_body(env, data)
|
35
35
|
# write data to the input, which will be read by the Rack app
|
36
|
-
env["rack_proxy.
|
36
|
+
env["rack_proxy.call"].resume(data)
|
37
37
|
end
|
38
38
|
|
39
39
|
# Called at the end of the request (after #response) or on client disconnect.
|
40
40
|
def on_close(env)
|
41
41
|
# reading the request body has finished, so we close write end of the input
|
42
|
-
env["rack_proxy.
|
42
|
+
env["rack_proxy.call"].resume
|
43
43
|
end
|
44
44
|
|
45
45
|
# Called after all the data has been received from the client.
|
46
46
|
def response(env)
|
47
|
-
|
48
|
-
env["rack_proxy.input"].close_write
|
49
|
-
|
50
|
-
# prevent Goliath from sending a response, we will send it once the
|
51
|
-
# asynchronous request to the rack app finishes
|
52
|
-
nil
|
47
|
+
env["rack_proxy.call"].resume
|
53
48
|
end
|
54
49
|
|
55
50
|
private
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
52
|
+
class RackCall
|
53
|
+
def initialize(app, env, rewindable_input: true)
|
54
|
+
@fiber = Fiber.new do
|
55
|
+
rack_input = RackInput.new(rewindable: rewindable_input) { Fiber.yield }
|
56
|
+
|
57
|
+
result = app.call env.merge(
|
58
|
+
"rack.input" => rack_input,
|
59
|
+
"async.callback" => nil, # prevent Roda/Sinatra from calling EventMachine while streaming the response
|
60
|
+
)
|
62
61
|
|
63
|
-
|
64
|
-
EM.defer do
|
65
|
-
rack_response = make_request(rack_app, env)
|
62
|
+
rack_input.close
|
66
63
|
|
67
|
-
|
68
|
-
env["goliath.request"].callback do
|
69
|
-
# spawn a thread for the response
|
70
|
-
EM.defer { send_response(rack_response, env) }
|
64
|
+
result
|
71
65
|
end
|
72
66
|
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def make_request(rack_app, env)
|
76
|
-
# call the rack app with some patches
|
77
|
-
rack_app.call env.merge(
|
78
|
-
"rack.url_scheme" => env["options"][:ssl] ? "https" : "http", # https://github.com/postrank-labs/goliath/issues/210
|
79
|
-
"async.callback" => nil, # prevent Roda/Sinatra from calling EventMachine while streaming the response
|
80
|
-
)
|
81
|
-
rescue Exception => exception
|
82
|
-
# log the exception that occurred
|
83
|
-
log_exception(exception, env)
|
84
|
-
|
85
|
-
# return a generic error message on production, or a more detailed one otherwise
|
86
|
-
body = Goliath.env?(:production) ? ["An error occurred"] : [exception.inspect]
|
87
|
-
headers = {"Content-Length" => body[0].bytesize.to_s}
|
88
|
-
|
89
|
-
[500, headers, body]
|
90
|
-
ensure
|
91
|
-
# request has finished, so we close the read end of the rack input
|
92
|
-
env["rack.input"].close_read
|
93
|
-
end
|
94
67
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
connection = request.conn
|
99
|
-
response = request.response
|
100
|
-
|
101
|
-
response.status, response.headers, response.body = rack_response
|
102
|
-
response.each { |data| connection.send_data(data) }
|
103
|
-
|
104
|
-
connection.terminate_request(keep_alive?(env))
|
105
|
-
rescue Exception => exception
|
106
|
-
# log the exception that occurred
|
107
|
-
log_exception(exception, env)
|
108
|
-
|
109
|
-
# communicate that sending response failed and close the connection
|
110
|
-
connection.send_data("HTTP/1.1 500 Internal Server Error\r\n\r\n")
|
111
|
-
connection.terminate_request(false)
|
112
|
-
ensure
|
113
|
-
# log the response information
|
114
|
-
log_response(response, env)
|
115
|
-
end
|
116
|
-
|
117
|
-
# Returns whether the TCP connection should be kept alive.
|
118
|
-
def keep_alive?(env)
|
119
|
-
if env["HTTP_VERSION"] >= "1.1"
|
120
|
-
# HTTP 1.1: all requests are persistent requests, client must
|
121
|
-
# send a "Connection: close" header to indicate otherwise
|
122
|
-
env["HTTP_CONNECTION"].to_s.downcase != "close"
|
123
|
-
elsif env["HTTP_VERSION"] == "1.0"
|
124
|
-
# HTTP 1.0: all requests are non keep-alive, client must
|
125
|
-
# send a "Connection: Keep-Alive" to indicate otherwise
|
126
|
-
env["HTTP_CONNECTION"].to_s.downcase == "keep-alive"
|
68
|
+
def resume(data = nil)
|
69
|
+
@result = @fiber.resume(data) if @fiber.alive?
|
70
|
+
@result
|
127
71
|
end
|
128
72
|
end
|
129
73
|
|
130
|
-
# Logs the response in the Rack::CommonLogger format.
|
131
|
-
def log_response(response, env)
|
132
|
-
length = response.headers["Content-Length"]
|
133
|
-
length = nil if length.to_s == "0"
|
134
|
-
|
135
|
-
# log the response as Goliath would log it
|
136
|
-
env.logger.info '%s - %s [%s] "%s %s%s %s" %d %s %0.4f' % [
|
137
|
-
env["HTTP_X_FORWARDED_FOR"] || env["REMOTE_ADDR"] || "-",
|
138
|
-
env["REMOTE_USER"] || "-",
|
139
|
-
Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
140
|
-
env["REQUEST_METHOD"],
|
141
|
-
env["PATH_INFO"],
|
142
|
-
env["QUERY_STRING"].empty? ? "" : "?#{env["QUERY_STRING"]}",
|
143
|
-
env["HTTP_VERSION"],
|
144
|
-
response.status,
|
145
|
-
length || "-",
|
146
|
-
Time.now.to_f - env[:start_time],
|
147
|
-
]
|
148
|
-
end
|
149
|
-
|
150
|
-
# Logs the exception and adds it to the env hash.
|
151
|
-
def log_exception(exception, env)
|
152
|
-
# mimic how Ruby would display the error
|
153
|
-
stderr = "#{exception.backtrace[0]}: #{exception.message} (#{exception.class})\n".dup
|
154
|
-
exception.backtrace[1..-1].each do |line|
|
155
|
-
stderr << " from #{line}\n"
|
156
|
-
end
|
157
|
-
env.logger.error(stderr)
|
158
|
-
|
159
|
-
# save the exception in the env hash
|
160
|
-
env["rack.exception"] = exception
|
161
|
-
end
|
162
|
-
|
163
74
|
# IO-like object that acts as a bidirectional pipe, which returns the data
|
164
75
|
# that has been written to it.
|
165
76
|
class RackInput
|
166
|
-
def initialize(rewindable: true)
|
167
|
-
@
|
77
|
+
def initialize(rewindable: true, &next_chunk)
|
78
|
+
@next_chunk = next_chunk
|
168
79
|
@cache = Tempfile.new("goliath-rack_input") if rewindable
|
169
80
|
@buffer = nil
|
81
|
+
@eof = false
|
170
82
|
end
|
171
83
|
|
172
84
|
# Pops chunks of data from the queue and implements
|
@@ -180,7 +92,7 @@ module Goliath
|
|
180
92
|
|
181
93
|
break if remaining_length == 0
|
182
94
|
|
183
|
-
@buffer =
|
95
|
+
@buffer = next_chunk or break if @buffer.nil?
|
184
96
|
|
185
97
|
buffered_data = if remaining_length && remaining_length < @buffer.bytesize
|
186
98
|
@buffer.byteslice(0, remaining_length)
|
@@ -206,12 +118,6 @@ module Goliath
|
|
206
118
|
data.to_s unless length && (data.nil? || data.empty?)
|
207
119
|
end
|
208
120
|
|
209
|
-
# Pushes data to the queue, which is then popped in #read.
|
210
|
-
def write(data)
|
211
|
-
@data_queue.push(data) unless @data_queue.closed?
|
212
|
-
rescue ClosedQueueError
|
213
|
-
end
|
214
|
-
|
215
121
|
# Rewinds the cache IO if it's configured, otherwise raises Errno::ESPIPE
|
216
122
|
# exception, which mimics the behaviour of caling #rewind on
|
217
123
|
# non-rewindable IOs such as pipes, sockets, and ttys.
|
@@ -220,21 +126,20 @@ module Goliath
|
|
220
126
|
@cache.rewind
|
221
127
|
end
|
222
128
|
|
223
|
-
#
|
224
|
-
def
|
225
|
-
@data_queue.close
|
129
|
+
# Conforming to the Rack specification.
|
130
|
+
def close
|
226
131
|
@cache.close! if @cache
|
227
132
|
end
|
228
133
|
|
229
|
-
|
230
|
-
# pop remaining chunks from it.
|
231
|
-
def close_write
|
232
|
-
@data_queue.close
|
233
|
-
end
|
134
|
+
private
|
234
135
|
|
235
|
-
#
|
236
|
-
|
237
|
-
|
136
|
+
# Retrieves the next chunk by calling the block, and marks EOF when nil
|
137
|
+
# was returned.
|
138
|
+
def next_chunk
|
139
|
+
return if @eof
|
140
|
+
chunk = @next_chunk.call
|
141
|
+
@eof = true if chunk.nil?
|
142
|
+
chunk
|
238
143
|
end
|
239
144
|
end
|
240
145
|
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: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janko Marohnić
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: goliath
|
@@ -16,7 +16,7 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.0.
|
19
|
+
version: 1.0.6
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: '2'
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 1.0.
|
29
|
+
version: 1.0.6
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '2'
|