goliath 1.0.4 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of goliath might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +5 -4
- data/goliath.gemspec +11 -7
- data/lib/goliath/api.rb +1 -1
- data/lib/goliath/connection.rb +15 -11
- data/lib/goliath/constants.rb +1 -0
- data/lib/goliath/rack/default_response_format.rb +3 -9
- data/lib/goliath/rack/formatters/json.rb +1 -1
- data/lib/goliath/rack/jsonp.rb +7 -2
- data/lib/goliath/rack/params.rb +4 -3
- data/lib/goliath/rack/validator.rb +1 -1
- data/lib/goliath/request.rb +37 -20
- data/lib/goliath/response.rb +2 -0
- data/lib/goliath/runner.rb +4 -3
- data/lib/goliath/server.rb +17 -2
- data/lib/goliath/test_helper_sse.rb +76 -0
- data/lib/goliath/validation/error.rb +4 -1
- data/lib/goliath/version.rb +1 -1
- data/spec/integration/async_request_processing.rb +2 -2
- data/spec/integration/chunked_streaming_spec.rb +1 -1
- data/spec/integration/early_abort_spec.rb +6 -6
- data/spec/integration/echo_spec.rb +7 -7
- data/spec/integration/empty_body_spec.rb +2 -2
- data/spec/integration/event_stream_spec.rb +50 -0
- data/spec/integration/exception_handling_spec.rb +202 -0
- data/spec/integration/http_log_spec.rb +16 -16
- data/spec/integration/jsonp_spec.rb +61 -10
- data/spec/integration/keepalive_spec.rb +2 -2
- data/spec/integration/pipelining_spec.rb +3 -3
- data/spec/integration/reloader_spec.rb +3 -3
- data/spec/integration/template_spec.rb +7 -7
- data/spec/integration/test_helper_spec.rb +3 -3
- data/spec/integration/trace_spec.rb +2 -2
- data/spec/integration/valid_spec.rb +25 -5
- data/spec/integration/websocket_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -3
- data/spec/unit/api_spec.rb +1 -1
- data/spec/unit/connection_spec.rb +8 -8
- data/spec/unit/console_spec.rb +3 -3
- data/spec/unit/env_spec.rb +9 -9
- data/spec/unit/headers_spec.rb +8 -8
- data/spec/unit/rack/default_mime_type_spec.rb +3 -3
- data/spec/unit/rack/formatters/json_spec.rb +35 -13
- data/spec/unit/rack/formatters/plist_spec.rb +8 -8
- data/spec/unit/rack/formatters/xml_spec.rb +18 -18
- data/spec/unit/rack/formatters/yaml_spec.rb +13 -13
- data/spec/unit/rack/heartbeat_spec.rb +15 -15
- data/spec/unit/rack/params_spec.rb +99 -62
- data/spec/unit/rack/render_spec.rb +14 -14
- data/spec/unit/rack/validation/boolean_value_spec.rb +6 -6
- data/spec/unit/rack/validation/default_params_spec.rb +13 -13
- data/spec/unit/rack/validation/numeric_range_spec.rb +17 -17
- data/spec/unit/rack/validation/param_spec.rb +75 -75
- data/spec/unit/rack/validation/request_method_spec.rb +9 -9
- data/spec/unit/rack/validation/required_param_spec.rb +39 -39
- data/spec/unit/rack/validation/required_value_spec.rb +19 -19
- data/spec/unit/request_spec.rb +18 -18
- data/spec/unit/response_spec.rb +6 -6
- data/spec/unit/runner_spec.rb +31 -31
- data/spec/unit/server_spec.rb +21 -21
- data/spec/unit/validation/standard_http_errors_spec.rb +6 -6
- metadata +149 -94
- data/examples/around.rb +0 -38
- data/examples/clone.rb +0 -26
- data/examples/router.rb +0 -15
- data/examples/test.rb +0 -31
- data/examples/upload.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2bfab8c9906a1737abc3e7cdf163a52e13e516c7
|
4
|
+
data.tar.gz: a1ac6184a35dbfe56a04d117872d31b7c3647c71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 833e67c341fde76ca1e5d69430f0f0c2d70186de0474979421fbbeeac78d5755eb79bfde0495e606105fb71f0291a6e8fa6be4c3e55ea6da2d663b8aa6c20937
|
7
|
+
data.tar.gz: 15de365fe175166182ead10d6c397971d33391e190f994552216433ebf121be35732805cd56cc364c3f9f0a5361589cd4205954014288fd52ab995d6807ccc93
|
data/README.md
CHANGED
@@ -64,13 +64,14 @@ Goliath has been used in production environments for 2+ years, across many diffe
|
|
64
64
|
* [Middleware](https://github.com/postrank-labs/goliath/wiki/Middleware)
|
65
65
|
* [Configuration](https://github.com/postrank-labs/goliath/wiki/Configuration)
|
66
66
|
* [Plugins](https://github.com/postrank-labs/goliath/wiki/Plugins)
|
67
|
+
* [Zero Downtime Restart](https://github.com/postrank-labs/goliath/wiki/Zero-downtime-restart)
|
68
|
+
|
67
69
|
|
68
70
|
### Hands-on applications:
|
69
71
|
|
70
|
-
If you are you new to EventMachine, or want a detailed walk-through of building a Goliath powered API? You're in luck,
|
72
|
+
If you are you new to EventMachine, or want a detailed walk-through of building a Goliath powered API? You're in luck, a super-awesome Pluralsight screencast which will teach you all you need to know:
|
71
73
|
|
72
|
-
* [Meet EventMachine
|
73
|
-
* [Meet EventMachine: Part 2](http://peepcode.com/products/eventmachine-ii) - building an API with Goliath
|
74
|
+
* [Meet EventMachine](http://www.pluralsight.com/courses/meet-eventmachine) - introduction to EM, Fibers, building an API with Goliath
|
74
75
|
|
75
76
|
Additionally, you can also watch this presentation from GoGaRuCo 2011, which describes the design and motivation behind Goliath:
|
76
77
|
|
@@ -90,4 +91,4 @@ Other resources:
|
|
90
91
|
|
91
92
|
## License & Acknowledgments
|
92
93
|
|
93
|
-
Goliath is distributed under the MIT license, for full details please see the LICENSE file.
|
94
|
+
Goliath is distributed under the MIT license, for full details please see the LICENSE file.
|
data/goliath.gemspec
CHANGED
@@ -12,13 +12,14 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.summary = 'Async framework for writing API servers'
|
13
13
|
s.description = s.summary
|
14
14
|
|
15
|
-
s.required_ruby_version = '>=1.
|
15
|
+
s.required_ruby_version = '>=2.1.0'
|
16
16
|
|
17
17
|
s.add_dependency 'eventmachine', '>= 1.0.0.beta.4'
|
18
18
|
s.add_dependency 'em-synchrony', '>= 1.0.0'
|
19
|
-
s.add_dependency 'em-websocket',
|
20
|
-
s.add_dependency 'http_parser.rb', '0.6.0'
|
19
|
+
s.add_dependency 'em-websocket', '0.3.8'
|
20
|
+
s.add_dependency 'http_parser.rb', '>= 0.6.0'
|
21
21
|
s.add_dependency 'log4r'
|
22
|
+
s.add_dependency 'einhorn'
|
22
23
|
|
23
24
|
s.add_dependency 'rack', '>=1.2.2'
|
24
25
|
s.add_dependency 'rack-contrib'
|
@@ -27,7 +28,8 @@ Gem::Specification.new do |s|
|
|
27
28
|
s.add_dependency 'multi_json'
|
28
29
|
|
29
30
|
s.add_development_dependency 'rake', '>=0.8.7'
|
30
|
-
s.add_development_dependency 'rspec', '
|
31
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
32
|
+
s.add_development_dependency 'test-unit'
|
31
33
|
s.add_development_dependency 'nokogiri'
|
32
34
|
s.add_development_dependency 'em-http-request', '>=1.0.0'
|
33
35
|
s.add_development_dependency 'em-mongo', '~> 0.4.0'
|
@@ -35,14 +37,16 @@ Gem::Specification.new do |s|
|
|
35
37
|
s.add_development_dependency 'multipart_body'
|
36
38
|
s.add_development_dependency 'amqp', '>=0.7.1'
|
37
39
|
s.add_development_dependency 'em-websocket-client'
|
40
|
+
s.add_development_dependency 'em-eventsource'
|
41
|
+
s.add_development_dependency 'rack', '< 2'
|
38
42
|
|
39
43
|
s.add_development_dependency 'tilt', '>=1.2.2'
|
40
44
|
s.add_development_dependency 'haml', '>=3.0.25'
|
41
45
|
s.add_development_dependency 'yard'
|
42
46
|
|
43
|
-
s.add_development_dependency 'guard', '~>
|
44
|
-
s.add_development_dependency 'guard-rspec', '~>
|
45
|
-
s.add_development_dependency 'listen', '~>
|
47
|
+
s.add_development_dependency 'guard', '~> 2.0'
|
48
|
+
s.add_development_dependency 'guard-rspec', '~> 4.0'
|
49
|
+
s.add_development_dependency 'listen', '~> 2.0'
|
46
50
|
|
47
51
|
if RUBY_PLATFORM != 'java'
|
48
52
|
s.add_development_dependency 'yajl-ruby'
|
data/lib/goliath/api.rb
CHANGED
@@ -172,7 +172,7 @@ module Goliath
|
|
172
172
|
|
173
173
|
rescue Goliath::Validation::Error => e
|
174
174
|
env[RACK_EXCEPTION] = e
|
175
|
-
env[ASYNC_CALLBACK].call(validation_error(e.status_code, e.message))
|
175
|
+
env[ASYNC_CALLBACK].call(validation_error(e.status_code, e.message, e.headers))
|
176
176
|
|
177
177
|
rescue Exception => e
|
178
178
|
logthis = "#{e.backtrace[0]}: #{e.message} (#{e.class})\n"
|
data/lib/goliath/connection.rb
CHANGED
@@ -23,12 +23,13 @@ module Goliath
|
|
23
23
|
@parser = Http::Parser.new
|
24
24
|
@parser.on_headers_complete = proc do |h|
|
25
25
|
env = Goliath::Env.new
|
26
|
-
env[SERVER_PORT]
|
27
|
-
env[RACK_LOGGER]
|
28
|
-
env[OPTIONS]
|
29
|
-
env[STATUS]
|
30
|
-
env[CONFIG]
|
31
|
-
env[REMOTE_ADDR]
|
26
|
+
env[SERVER_PORT] = port.to_s
|
27
|
+
env[RACK_LOGGER] = logger
|
28
|
+
env[OPTIONS] = options
|
29
|
+
env[STATUS] = status
|
30
|
+
env[CONFIG] = config
|
31
|
+
env[REMOTE_ADDR] = remote_address
|
32
|
+
env[RACK_URL_SCHEME] = options[:ssl] ? "https" : "http"
|
32
33
|
|
33
34
|
r = Goliath::Request.new(@app, self, env)
|
34
35
|
r.parse_header(h, @parser) do
|
@@ -41,7 +42,8 @@ module Goliath
|
|
41
42
|
end
|
42
43
|
|
43
44
|
@parser.on_body = proc do |data|
|
44
|
-
@requests.first
|
45
|
+
req = @requests.first
|
46
|
+
req.parse(data) unless req.env[:terminate_connection]
|
45
47
|
end
|
46
48
|
|
47
49
|
@parser.on_message_complete = proc do
|
@@ -83,15 +85,17 @@ module Goliath
|
|
83
85
|
end
|
84
86
|
|
85
87
|
def terminate_request(keep_alive)
|
86
|
-
if
|
87
|
-
@current = req
|
88
|
-
@current.succeed
|
89
|
-
elsif @current
|
88
|
+
if @current
|
90
89
|
@current.close
|
91
90
|
@current = nil
|
92
91
|
end
|
93
92
|
|
94
93
|
close_connection_after_writing rescue nil if !keep_alive
|
94
|
+
|
95
|
+
if req = @pending.shift
|
96
|
+
@current = req
|
97
|
+
@current.succeed
|
98
|
+
end
|
95
99
|
end
|
96
100
|
|
97
101
|
def remote_address
|
data/lib/goliath/constants.rb
CHANGED
@@ -4,17 +4,11 @@ module Goliath
|
|
4
4
|
include Goliath::Rack::AsyncMiddleware
|
5
5
|
|
6
6
|
def post_process(env, status, headers, body)
|
7
|
-
|
8
|
-
|
9
|
-
new_body = []
|
10
|
-
if body.respond_to?(:each)
|
11
|
-
body.each { |chunk| new_body << chunk }
|
7
|
+
if body.is_a?(String)
|
8
|
+
[status, headers, [body]]
|
12
9
|
else
|
13
|
-
|
10
|
+
[status, headers, body]
|
14
11
|
end
|
15
|
-
new_body.collect! { |item| item.to_s }
|
16
|
-
|
17
|
-
[status, headers, new_body.flatten]
|
18
12
|
end
|
19
13
|
end
|
20
14
|
end
|
data/lib/goliath/rack/jsonp.rb
CHANGED
@@ -18,10 +18,15 @@ module Goliath
|
|
18
18
|
response = body
|
19
19
|
end
|
20
20
|
|
21
|
+
response = "#{env.params['callback']}(#{response})"
|
22
|
+
|
21
23
|
headers[Goliath::Constants::CONTENT_TYPE] = 'application/javascript'
|
22
|
-
|
24
|
+
if headers['Content-Length']
|
25
|
+
headers['Content-Length'] = response.bytesize.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
[status, headers, [response]]
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
27
|
-
|
data/lib/goliath/rack/params.rb
CHANGED
@@ -4,7 +4,7 @@ require 'rack/utils'
|
|
4
4
|
module Goliath
|
5
5
|
module Rack
|
6
6
|
URL_ENCODED = %r{^application/x-www-form-urlencoded}
|
7
|
-
JSON_ENCODED = %r{^application/json}
|
7
|
+
JSON_ENCODED = %r{^application/((vnd\.api\+)?json|javascript)}
|
8
8
|
|
9
9
|
# A middle ware to parse params. This will parse both the
|
10
10
|
# query string parameters and the body and place them into
|
@@ -60,10 +60,11 @@ module Goliath
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def call(env)
|
63
|
-
Goliath::Rack::Validator.safely(env) do
|
63
|
+
error_response = Goliath::Rack::Validator.safely(env) do
|
64
64
|
env['params'] = retrieve_params(env)
|
65
|
-
|
65
|
+
nil
|
66
66
|
end
|
67
|
+
error_response || @app.call(env)
|
67
68
|
end
|
68
69
|
end
|
69
70
|
end
|
@@ -39,7 +39,7 @@ module Goliath
|
|
39
39
|
begin
|
40
40
|
yield
|
41
41
|
rescue Goliath::Validation::Error => e
|
42
|
-
validation_error(e.status_code, e.message, headers)
|
42
|
+
validation_error(e.status_code, e.message, e.headers.merge(headers))
|
43
43
|
rescue Exception => e
|
44
44
|
env.logger.error(e.message)
|
45
45
|
env.logger.error(e.backtrace.join("\n"))
|
data/lib/goliath/request.rb
CHANGED
@@ -92,7 +92,7 @@ module Goliath
|
|
92
92
|
|
93
93
|
@env[REQUEST_METHOD] = parser.http_method
|
94
94
|
@env[REQUEST_URI] = parser.request_url
|
95
|
-
@env[QUERY_STRING] = uri.query
|
95
|
+
@env[QUERY_STRING] = uri.query || ''.freeze
|
96
96
|
@env[HTTP_VERSION] = parser.http_version.join('.')
|
97
97
|
@env[SCRIPT_NAME] = ""
|
98
98
|
@env[REQUEST_PATH] = uri.path
|
@@ -197,22 +197,14 @@ module Goliath
|
|
197
197
|
callback do
|
198
198
|
begin
|
199
199
|
@response.status, @response.headers, @response.body = status, headers, body
|
200
|
-
@response.each { |chunk| @conn.send_data(chunk) }
|
201
|
-
|
202
|
-
elapsed_time = (Time.now.to_f - @env[:start_time]) * 1000
|
203
|
-
begin
|
204
|
-
Goliath::Request.log_block.call(@env, @response, elapsed_time)
|
205
|
-
rescue => err
|
206
|
-
# prevent an infinite loop if the block raised an error
|
207
|
-
@env[RACK_LOGGER].error("log block raised #{err}")
|
208
|
-
end
|
209
200
|
|
210
|
-
@
|
201
|
+
stream_data(@response.each) do
|
202
|
+
terminate_request
|
203
|
+
end
|
211
204
|
rescue Exception => e
|
212
205
|
server_exception(e)
|
213
206
|
end
|
214
207
|
end
|
215
|
-
|
216
208
|
rescue Exception => e
|
217
209
|
server_exception(e)
|
218
210
|
end
|
@@ -220,28 +212,53 @@ module Goliath
|
|
220
212
|
|
221
213
|
private
|
222
214
|
|
215
|
+
# Writes each chunk of the response data in a new tick. This achieves
|
216
|
+
# streaming, because EventMachine flushes the sent data to the socket at
|
217
|
+
# the end of each tick.
|
218
|
+
def stream_data(chunks, &block)
|
219
|
+
@conn.send_data(chunks.next)
|
220
|
+
EM.next_tick { stream_data(chunks, &block) }
|
221
|
+
rescue StopIteration
|
222
|
+
block.call
|
223
|
+
rescue Exception => e
|
224
|
+
server_exception(e)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Logs the response time and terminates the request.
|
228
|
+
def terminate_request
|
229
|
+
elapsed_time = (Time.now.to_f - @env[:start_time]) * 1000
|
230
|
+
begin
|
231
|
+
Goliath::Request.log_block.call(@env, @response, elapsed_time)
|
232
|
+
rescue => err
|
233
|
+
# prevent an infinite loop if the block raised an error
|
234
|
+
@env[RACK_LOGGER].error("log block raised #{err}")
|
235
|
+
end
|
236
|
+
|
237
|
+
@conn.terminate_request(keep_alive)
|
238
|
+
end
|
239
|
+
|
223
240
|
# Handles logging server exceptions
|
224
241
|
#
|
225
242
|
# @param e [Exception] The exception to log
|
226
243
|
# @return [Nil]
|
227
244
|
def server_exception(e)
|
228
245
|
if e.is_a?(Goliath::Validation::Error)
|
229
|
-
status, headers, body = [e.status_code,
|
246
|
+
status, headers, body = [e.status_code, e.headers, ('{"error":"%s"}' % e.message)]
|
230
247
|
else
|
231
|
-
|
232
|
-
|
248
|
+
logthis = "#{e.backtrace[0]}: #{e.message} (#{e.class})\n"
|
249
|
+
e.backtrace[1..-1].each do |bt|
|
250
|
+
logthis += " from #{bt}\n"
|
251
|
+
end
|
252
|
+
@env.logger.error(logthis)
|
253
|
+
@env[RACK_EXCEPTION] = e
|
233
254
|
|
255
|
+
message = Goliath.env?(:production) ? 'An error happened' : e.message
|
234
256
|
status, headers, body = [500, {}, message]
|
235
257
|
end
|
236
258
|
|
237
259
|
headers['Content-Length'] = body.bytesize.to_s
|
238
260
|
@env[:terminate_connection] = true
|
239
261
|
post_process([status, headers, body])
|
240
|
-
|
241
|
-
# Mark the request as complete to force a flush on the response.
|
242
|
-
# Note: #on_body and #response hooks may still fire if the data
|
243
|
-
# is already in the parser buffer.
|
244
|
-
succeed
|
245
262
|
end
|
246
263
|
|
247
264
|
# Used to determine if the connection should be kept open
|
data/lib/goliath/response.rb
CHANGED
data/lib/goliath/runner.rb
CHANGED
@@ -150,6 +150,7 @@ module Goliath
|
|
150
150
|
opts.on('-a', '--address HOST', "Bind to HOST address (default: #{@options[:address]})") { |addr| @options[:address] = addr }
|
151
151
|
opts.on('-p', '--port PORT', "Use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
|
152
152
|
opts.on('-S', '--socket FILE', "Bind to unix domain socket") { |v| @options[:address] = v; @options[:port] = nil }
|
153
|
+
opts.on('-E', '--einhorn', "Use Einhorn socket manager") { |v| @options[:einhorn] = true }
|
153
154
|
|
154
155
|
opts.separator ""
|
155
156
|
opts.separator "Daemon options:"
|
@@ -211,7 +212,8 @@ module Goliath
|
|
211
212
|
|
212
213
|
File.umask(0000)
|
213
214
|
|
214
|
-
|
215
|
+
log_extension = File.extname(@log_file)
|
216
|
+
stdout_log_file = "#{File.dirname(@log_file)}/#{File.basename(@log_file, log_extension)}_stdout#{log_extension}"
|
215
217
|
|
216
218
|
STDIN.reopen("/dev/null")
|
217
219
|
STDOUT.reopen(stdout_log_file, "a")
|
@@ -296,8 +298,7 @@ module Goliath
|
|
296
298
|
# @return [Nil]
|
297
299
|
def run_server
|
298
300
|
log = setup_logger
|
299
|
-
|
300
|
-
log.info("Starting server on #{@address}:#{@port} in #{Goliath.env} mode. Watch out for stones.")
|
301
|
+
log.info("Starting server on http#{ @server_options[:ssl] ? 's' : nil }://#{@address}:#{@port} in #{Goliath.env} mode. Watch out for stones.")
|
301
302
|
|
302
303
|
server = setup_server(log)
|
303
304
|
server.api.setup if server.api.respond_to?(:setup)
|
data/lib/goliath/server.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'em-synchrony'
|
2
|
+
require 'einhorn'
|
2
3
|
require 'goliath/connection'
|
3
4
|
require 'goliath/goliath'
|
4
5
|
|
@@ -83,7 +84,7 @@ module Goliath
|
|
83
84
|
|
84
85
|
EM.set_effective_user(options[:user]) if options[:user]
|
85
86
|
|
86
|
-
config[Goliath::Constants::GOLIATH_SIGNATURE] =
|
87
|
+
config[Goliath::Constants::GOLIATH_SIGNATURE] = start_server(options) do |conn|
|
87
88
|
if options[:ssl]
|
88
89
|
conn.start_tls(
|
89
90
|
:private_key_file => options[:ssl_key],
|
@@ -105,9 +106,23 @@ module Goliath
|
|
105
106
|
end
|
106
107
|
end
|
107
108
|
|
109
|
+
def start_server(options, &blk)
|
110
|
+
if options[:einhorn]
|
111
|
+
fd_num = Einhorn::Worker.socket!
|
112
|
+
socket = Socket.for_fd(fd_num)
|
113
|
+
|
114
|
+
EM.attach_server(socket, Goliath::Connection, &blk)
|
115
|
+
else
|
116
|
+
EM.start_server(address, port, Goliath::Connection, &blk)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
108
120
|
# Stops the server running.
|
109
121
|
def stop
|
110
|
-
EM.
|
122
|
+
EM.add_timer(0) do
|
123
|
+
logger.info('Stopping server...')
|
124
|
+
EM.stop
|
125
|
+
end
|
111
126
|
end
|
112
127
|
|
113
128
|
# Loads a configuration file
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'em-eventsource'
|
2
|
+
|
3
|
+
module Goliath
|
4
|
+
module TestHelper
|
5
|
+
class SSEHelper
|
6
|
+
attr_reader :connection
|
7
|
+
def initialize(url)
|
8
|
+
@message_queue = EM::Queue.new
|
9
|
+
@named_queues = {}
|
10
|
+
@connection = EM::EventSource.new(url)
|
11
|
+
end
|
12
|
+
|
13
|
+
def listen
|
14
|
+
@connection.message do |message|
|
15
|
+
@message_queue.push(message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def listen_to(name)
|
20
|
+
queue = (@named_queues[name] ||= [])
|
21
|
+
@connection.on(name) do |message|
|
22
|
+
queue.push(message)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def receive
|
27
|
+
pop_queue(@message_queue)
|
28
|
+
end
|
29
|
+
|
30
|
+
def receive_on(name)
|
31
|
+
queue = @named_queues.fetch(name) do
|
32
|
+
raise ArgumentError, "You have to call listen_to('#{name}') first"
|
33
|
+
end
|
34
|
+
|
35
|
+
pop_queue(queue)
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_async_http
|
39
|
+
klass = EM::HttpConnection
|
40
|
+
if klass.instance_methods.include?(:aget)
|
41
|
+
begin
|
42
|
+
klass.send(:class_eval) do
|
43
|
+
alias :sget :get
|
44
|
+
alias :get :aget
|
45
|
+
end
|
46
|
+
yield if block_given?
|
47
|
+
ensure
|
48
|
+
klass.send(:class_eval) do
|
49
|
+
alias :get :sget
|
50
|
+
remove_method :sget
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
yield if block_given?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def pop_queue(queue)
|
61
|
+
fiber = Fiber.current
|
62
|
+
queue.pop { |m| fiber.resume(m) }
|
63
|
+
Fiber.yield
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def sse_client_connect(path,&blk)
|
68
|
+
url = "http://localhost:#{@test_server_port}#{path}"
|
69
|
+
client = SSEHelper.new(url)
|
70
|
+
client.with_async_http { client.connection.start }
|
71
|
+
client.listen
|
72
|
+
Fiber.new { blk.call(client) }.resume if blk
|
73
|
+
stop
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -4,6 +4,8 @@ module Goliath
|
|
4
4
|
class Error < StandardError
|
5
5
|
# The status code to return from the error handler
|
6
6
|
attr_accessor :status_code
|
7
|
+
# The headers to return from the error handler
|
8
|
+
attr_accessor :headers
|
7
9
|
|
8
10
|
# Create a new Goliath::Validation::Error.
|
9
11
|
#
|
@@ -13,9 +15,10 @@ module Goliath
|
|
13
15
|
# @param status_code [Integer] The status code to return
|
14
16
|
# @param message [String] The error message to return
|
15
17
|
# @return [Goliath::Validation::Error] The Goliath::Validation::Error
|
16
|
-
def initialize(status_code, message)
|
18
|
+
def initialize(status_code, message, headers = {})
|
17
19
|
super(message)
|
18
20
|
@status_code = status_code
|
21
|
+
@headers = headers
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
data/lib/goliath/version.rb
CHANGED
@@ -13,8 +13,8 @@ describe 'Async Request processing' do
|
|
13
13
|
|
14
14
|
post_request(request_data, err) do |c|
|
15
15
|
resp = MultiJson.load(c.response)
|
16
|
-
resp['body'].
|
17
|
-
resp['head'].
|
16
|
+
expect(resp['body']).to match('some=data')
|
17
|
+
expect(resp['head']).to include('X-Upload')
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -27,7 +27,7 @@ describe "ChunkedStreaming" do
|
|
27
27
|
it "should stream content" do
|
28
28
|
with_api(ChunkedStreaming, {:verbose => true, :log_stdout => true}) do |server|
|
29
29
|
streaming_client_connect('/streaming') do |client|
|
30
|
-
client.receive.
|
30
|
+
expect(client.receive).to eq("chunked")
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -11,7 +11,7 @@ describe EarlyAbort do
|
|
11
11
|
it "should return OK" do
|
12
12
|
with_api(EarlyAbort) do
|
13
13
|
get_request({}, err) do |c|
|
14
|
-
c.response.
|
14
|
+
expect(c.response).to eq("OK")
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -24,8 +24,8 @@ describe EarlyAbort do
|
|
24
24
|
}
|
25
25
|
|
26
26
|
post_request(request_data, err) do |c|
|
27
|
-
c.response.
|
28
|
-
File.exist?("/tmp/goliath-test-error.log").
|
27
|
+
expect(c.response).to eq("{\"error\":\"Can't handle requests with X-Crash: true.\"}")
|
28
|
+
expect(File.exist?("/tmp/goliath-test-error.log")).to be false
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -35,9 +35,9 @@ describe EarlyAbort do
|
|
35
35
|
request_data = { :body => "a" * 20 }
|
36
36
|
|
37
37
|
post_request(request_data, err) do |c|
|
38
|
-
c.response.
|
39
|
-
File.exist?("/tmp/goliath-test-error.log").
|
38
|
+
expect(c.response).to match(/Payload size can't exceed 10 bytes/)
|
39
|
+
expect(File.exist?("/tmp/goliath-test-error.log")).to be false
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
|
-
end
|
43
|
+
end
|
@@ -10,7 +10,7 @@ describe Echo do
|
|
10
10
|
with_api(Echo) do
|
11
11
|
get_request({:query => {:echo => 'test'}}, err) do |c|
|
12
12
|
b = MultiJson.load(c.response)
|
13
|
-
b['response'].
|
13
|
+
expect(b['response']).to eq('test')
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -19,8 +19,8 @@ describe Echo do
|
|
19
19
|
with_api(Echo) do
|
20
20
|
get_request({}, err) do |c|
|
21
21
|
b = MultiJson.load(c.response)
|
22
|
-
b['error'].
|
23
|
-
b['error'].
|
22
|
+
expect(b['error']).not_to be_nil
|
23
|
+
expect(b['error']).to eq('echo identifier missing')
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -29,7 +29,7 @@ describe Echo do
|
|
29
29
|
with_api(Echo) do
|
30
30
|
post_request({:body => {'echo' => 'test'}}, err) do |c|
|
31
31
|
b = MultiJson.load(c.response)
|
32
|
-
b['response'].
|
32
|
+
expect(b['response']).to eq('test')
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -42,7 +42,7 @@ describe Echo do
|
|
42
42
|
post_request({:body => body.to_s,
|
43
43
|
:head => head}, err) do |c|
|
44
44
|
b = MultiJson.load(c.response)
|
45
|
-
b['response'].
|
45
|
+
expect(b['response']).to eq('test')
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
@@ -55,7 +55,7 @@ describe Echo do
|
|
55
55
|
post_request({:body => body.to_s,
|
56
56
|
:head => head}, err) do |c|
|
57
57
|
b = MultiJson.load(c.response)
|
58
|
-
b['response'].
|
58
|
+
expect(b['response']).to eq('My Echo')
|
59
59
|
end
|
60
60
|
end
|
61
61
|
end
|
@@ -64,7 +64,7 @@ describe Echo do
|
|
64
64
|
with_api(Echo) do
|
65
65
|
patch_request({:body => {'echo' => 'test'}}, err) do |c|
|
66
66
|
b = MultiJson.load(c.response)
|
67
|
-
b['response'].
|
67
|
+
expect(b['response']).to eq('test')
|
68
68
|
end
|
69
69
|
end
|
70
70
|
end
|
@@ -12,8 +12,8 @@ describe 'Empty body API' do
|
|
12
12
|
it 'serves a 201 with no body' do
|
13
13
|
with_api(Empty) do
|
14
14
|
get_request({}, err) do |c|
|
15
|
-
c.response_header.status.
|
16
|
-
c.response_header['CONTENT_LENGTH'].
|
15
|
+
expect(c.response_header.status).to eq(201)
|
16
|
+
expect(c.response_header['CONTENT_LENGTH']).to eq('0')
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|