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 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'