goliath-rack_proxy 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 286f8e3a28f2ed207430e4107674e0d63c6fc402
4
- data.tar.gz: 25876f371228c74715d45ff096825fa3b84c5294
3
+ metadata.gz: ca389756f31e83a5660faf6628a02cdc94c42431
4
+ data.tar.gz: 2b5c39cba40a5a989870b8e2acc2548b3e7c6bdc
5
5
  SHA512:
6
- metadata.gz: 33c54b664d86d967ad1fd1bcf2fc8c5996ceae9c4586c791572731466137d3c401fd6b9fad7737232e74b8d0dc4b53e0a7336979ca69abd22d97b3a6b93ae6a2
7
- data.tar.gz: '0957145d2940f2ea7a384082b40ffabec5c60cb6854d01f9e330036ccef634ac90487194b65a65ee481faa9d05b35994df4f4e6a442eca5d579d4376700090d7'
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
- This will run a single EventMachine process, which by default uses a pool of
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "goliath-rack_proxy"
3
- gem.version = "0.1.3"
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.5", "< 2"
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"
@@ -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
- # assign a streaming input that acts as a bidirectional pipe
26
- env["rack_proxy.input"] = RackInput.new(rewindable: self.class.rack_proxy_options.fetch(:rewindable_input, true))
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
- # start the rack request asynchronously with the created rack input
30
- async_rack_call rack_app, env.merge("rack.input" => env["rack_proxy.input"])
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.input"].write(data)
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.input"].close_write
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
- # reading the request body has finished, so we close write end of the input
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
- # Spawns a thread and initiates the call to the Rack application, which
58
- # will be reading from Rack input that is being written to in #on_body. Once
59
- # the request has finished, we stream the response back to the client.
60
- def async_rack_call(rack_app, env)
61
- env["goliath.request"] = env["stream.start"].binding.receiver # https://github.com/postrank-labs/goliath/pull/341
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
- # spawn a thread for the request
64
- EM.defer do
65
- rack_response = make_request(rack_app, env)
62
+ rack_input.close
66
63
 
67
- # wait for client to stop sending data before sending the response
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
- # Streams the response to the client.
96
- def send_response(rack_response, env)
97
- request = env["goliath.request"]
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
- @data_queue = Queue.new
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 = @data_queue.pop or break if @buffer.nil?
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
- # Closes the queue and deletes the cache IO.
224
- def close_read
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
- # Closes the queue, which prevents fruther pushing, but #read can still
230
- # pop remaining chunks from it.
231
- def close_write
232
- @data_queue.close
233
- end
134
+ private
234
135
 
235
- # Conforming to the Rack specification.
236
- def close
237
- # no-op
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.1.3
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-07-26 00:00:00.000000000 Z
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.5
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.5
29
+ version: 1.0.6
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2'