goliath 0.9.4 → 1.0.0.beta.1
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.
- data/.gitignore +3 -0
- data/Guardfile +8 -0
- data/HISTORY.md +10 -0
- data/LICENSE +1 -1
- data/README.md +28 -29
- data/Rakefile +10 -2
- data/examples/activerecord/config/srv.rb +2 -1
- data/examples/activerecord/srv.rb +14 -5
- data/examples/api_proxy.rb +3 -7
- data/examples/around.rb +38 -0
- data/examples/async_aroundware_demo.rb +2 -2
- data/examples/async_upload.rb +1 -1
- data/examples/auth_and_rate_limit.rb +1 -1
- data/examples/clone.rb +26 -0
- data/examples/config/websocket.rb +1 -0
- data/examples/custom_logs.rb +21 -0
- data/examples/custom_server.rb +6 -44
- data/examples/early_abort.rb +6 -3
- data/examples/echo.rb +1 -1
- data/examples/fiber_pool.rb +35 -0
- data/examples/grape/config/apiserver.rb +8 -0
- data/examples/grape/server.rb +67 -0
- data/examples/gziped.rb +1 -1
- data/examples/params.rb +36 -0
- data/examples/rasterize/rasterize.rb +1 -2
- data/examples/router.rb +15 -0
- data/examples/template.rb +14 -7
- data/examples/test.rb +31 -0
- data/examples/upload.rb +17 -0
- data/examples/views/joke.markdown +4 -4
- data/examples/websocket.rb +39 -0
- data/examples/ws/favicon.ico +0 -0
- data/examples/ws/index.erb +50 -0
- data/goliath.gemspec +24 -6
- data/lib/goliath/api.rb +15 -82
- data/lib/goliath/application.rb +3 -17
- data/lib/goliath/connection.rb +16 -9
- data/lib/goliath/constants.rb +2 -0
- data/lib/goliath/env.rb +2 -3
- data/lib/goliath/goliath.rb +24 -34
- data/lib/goliath/plugins/latency.rb +7 -3
- data/lib/goliath/rack/builder.rb +1 -37
- data/lib/goliath/rack/favicon.rb +31 -0
- data/lib/goliath/rack/formatters/json.rb +1 -1
- data/lib/goliath/rack/heartbeat.rb +1 -0
- data/lib/goliath/rack/jsonp.rb +1 -0
- data/lib/goliath/rack/params.rb +2 -17
- data/lib/goliath/rack/render.rb +0 -1
- data/lib/goliath/rack/templates.rb +18 -7
- data/lib/goliath/rack/types/base.rb +24 -0
- data/lib/goliath/rack/types/boolean.rb +18 -0
- data/lib/goliath/rack/types/core.rb +19 -0
- data/lib/goliath/rack/types/symbol.rb +16 -0
- data/lib/goliath/rack/types.rb +10 -0
- data/lib/goliath/rack/validation/coerce.rb +48 -0
- data/lib/goliath/rack/validation/param.rb +113 -0
- data/lib/goliath/rack/validation/request_method.rb +1 -1
- data/lib/goliath/rack/validation/required.rb +47 -0
- data/lib/goliath/rack/validation/required_param.rb +42 -17
- data/lib/goliath/rack/validation.rb +3 -0
- data/lib/goliath/rack.rb +2 -2
- data/lib/goliath/request.rb +41 -9
- data/lib/goliath/response.rb +0 -2
- data/lib/goliath/runner.rb +55 -4
- data/lib/goliath/server.rb +7 -3
- data/lib/goliath/test_helper.rb +70 -16
- data/lib/goliath/test_helper_ws.rb +42 -0
- data/lib/goliath/version.rb +1 -1
- data/lib/goliath/websocket.rb +80 -0
- data/pkg/goliath-0.9.4.gem +0 -0
- data/pkg/goliath-1.0.0.beta.1.gem +0 -0
- data/spec/integration/async_request_processing.rb +1 -1
- data/spec/integration/early_abort_spec.rb +3 -10
- data/spec/integration/echo_spec.rb +8 -8
- data/spec/integration/jsonp_spec.rb +31 -0
- data/spec/integration/keepalive_spec.rb +2 -2
- data/spec/integration/template_spec.rb +10 -5
- data/spec/integration/test_helper_spec.rb +33 -0
- data/spec/integration/valid_spec.rb +35 -5
- data/spec/integration/websocket_spec.rb +44 -0
- data/spec/unit/api_spec.rb +2 -19
- data/spec/unit/connection_spec.rb +3 -0
- data/spec/unit/rack/formatters/json_spec.rb +3 -3
- data/spec/unit/rack/heartbeat_spec.rb +13 -0
- data/spec/unit/rack/params_spec.rb +2 -8
- data/spec/unit/rack/validation/param_spec.rb +443 -0
- data/spec/unit/rack/validation/request_method_spec.rb +5 -0
- data/spec/unit/rack/validation/required_param_spec.rb +71 -1
- data/spec/unit/runner_spec.rb +21 -7
- data/test/echo_test.rb +25 -0
- data/test/test_helper.rb +5 -0
- metadata +316 -78
- data/examples/env_use_statements.rb +0 -20
- data/examples/favicon.rb +0 -40
- data/examples/rack_routes.rb +0 -125
- data/examples/rasterize/thumb/f7ad4cb03e5bfd0e2c43db8e598fb3cd.png +0 -0
- data/examples/valid.rb +0 -17
- data/lib/goliath/deprecated/async_aroundware.rb +0 -133
- data/lib/goliath/deprecated/mongo_receiver.rb +0 -84
- data/lib/goliath/deprecated/response_receiver.rb +0 -97
- data/spec/integration/rack_routes_spec.rb +0 -169
- data/spec/unit/rack/builder_spec.rb +0 -40
data/lib/goliath/request.rb
CHANGED
@@ -16,6 +16,30 @@ module Goliath
|
|
16
16
|
|
17
17
|
attr_accessor :app, :conn, :env, :response, :body
|
18
18
|
|
19
|
+
class << self
|
20
|
+
##
|
21
|
+
# Allow user to redefine how fibers are handled, the
|
22
|
+
# default is to spawn a new fiber each time but another
|
23
|
+
# option is to use a pool of fibers.
|
24
|
+
#
|
25
|
+
attr_accessor :execute_block
|
26
|
+
|
27
|
+
##
|
28
|
+
# Allow users to redefine what exactly is logged
|
29
|
+
#
|
30
|
+
attr_accessor :log_block
|
31
|
+
end
|
32
|
+
|
33
|
+
self.log_block = proc do |env, response, elapsed_time|
|
34
|
+
env[RACK_LOGGER].info("Status: #{response.status}, " +
|
35
|
+
"Content-Length: #{response.headers['Content-Length']}, " +
|
36
|
+
"Response Time: #{"%.2f" % elapsed_time}ms")
|
37
|
+
end
|
38
|
+
|
39
|
+
self.execute_block = proc do |&block|
|
40
|
+
Fiber.new(&block).resume
|
41
|
+
end
|
42
|
+
|
19
43
|
def initialize(app, conn, env)
|
20
44
|
@app = app
|
21
45
|
@conn = conn
|
@@ -120,7 +144,7 @@ module Goliath
|
|
120
144
|
begin
|
121
145
|
@env[ASYNC_CLOSE].call(@env) if @env[ASYNC_CLOSE]
|
122
146
|
rescue Exception => e
|
123
|
-
|
147
|
+
@env[RACK_LOGGER].error("on_close Exception: #{e.class}, message: #{e.message}")
|
124
148
|
end
|
125
149
|
end
|
126
150
|
|
@@ -132,7 +156,7 @@ module Goliath
|
|
132
156
|
#
|
133
157
|
# @return [Nil]
|
134
158
|
def process
|
135
|
-
|
159
|
+
Goliath::Request.execute_block.call do
|
136
160
|
begin
|
137
161
|
@state = :finished
|
138
162
|
@env['rack.input'].rewind if @env['rack.input']
|
@@ -140,7 +164,7 @@ module Goliath
|
|
140
164
|
rescue Exception => e
|
141
165
|
server_exception(e)
|
142
166
|
end
|
143
|
-
|
167
|
+
end
|
144
168
|
end
|
145
169
|
|
146
170
|
# Invoked by the app / middleware once the request
|
@@ -171,11 +195,16 @@ module Goliath
|
|
171
195
|
begin
|
172
196
|
@response.status, @response.headers, @response.body = status, headers, body
|
173
197
|
@response.each { |chunk| @conn.send_data(chunk) }
|
174
|
-
@env[RACK_LOGGER].info("Status: #{@response.status}, " +
|
175
|
-
"Content-Length: #{@response.headers['Content-Length']}, " +
|
176
|
-
"Response Time: #{"%.2f" % ((Time.now.to_f - @env[:start_time]) * 1000)}ms")
|
177
198
|
|
178
|
-
|
199
|
+
elapsed_time = (Time.now.to_f - @env[:start_time]) * 1000
|
200
|
+
begin
|
201
|
+
Goliath::Request.log_block.call(@env, @response, elapsed_time)
|
202
|
+
rescue => err
|
203
|
+
# prevent an infinite loop if the block raised an error
|
204
|
+
@env[RACK_LOGGER].error("log block raised #{err}")
|
205
|
+
end
|
206
|
+
|
207
|
+
@conn.terminate_request(keep_alive)
|
179
208
|
rescue Exception => e
|
180
209
|
server_exception(e)
|
181
210
|
end
|
@@ -194,11 +223,14 @@ module Goliath
|
|
194
223
|
# @return [Nil]
|
195
224
|
def server_exception(e)
|
196
225
|
if e.is_a?(Goliath::Validation::Error)
|
197
|
-
status, headers, body = [e.status_code, {}, ('{"error":"%s"}'%e.message)]
|
226
|
+
status, headers, body = [e.status_code, {}, ('{"error":"%s"}' % e.message)]
|
198
227
|
else
|
199
228
|
@env[RACK_LOGGER].error("#{e.message}\n#{e.backtrace.join("\n")}")
|
200
|
-
|
229
|
+
message = Goliath.env?(:production) ? 'An error happened' : e.message
|
230
|
+
|
231
|
+
status, headers, body = [500, {}, message]
|
201
232
|
end
|
233
|
+
|
202
234
|
headers['Content-Length'] = body.bytesize.to_s
|
203
235
|
@env[:terminate_connection] = true
|
204
236
|
post_process([status, headers, body])
|
data/lib/goliath/response.rb
CHANGED
data/lib/goliath/runner.rb
CHANGED
@@ -4,6 +4,44 @@ require 'optparse'
|
|
4
4
|
require 'log4r'
|
5
5
|
|
6
6
|
module Goliath
|
7
|
+
|
8
|
+
# The default run environment, should one not be set.
|
9
|
+
DEFAULT_ENV = :development
|
10
|
+
|
11
|
+
# The environment for a Goliath app can come from a variety of different
|
12
|
+
# sources. Due to the loading order of middleware, we must parse this out
|
13
|
+
# at load-time rather than run time.
|
14
|
+
#
|
15
|
+
# Note that, as implemented, you cannot pass -e as part of a compound flag
|
16
|
+
# (e.g. `-sve production`) as it won't be picked up. The example given would
|
17
|
+
# have to be provided as `-sv -e production`.
|
18
|
+
#
|
19
|
+
# For more detail, see: https://github.com/postrank-labs/goliath/issues/18
|
20
|
+
class EnvironmentParser
|
21
|
+
|
22
|
+
# Work out the current runtime environemnt.
|
23
|
+
#
|
24
|
+
# The sources of environment, in increasing precedence, are:
|
25
|
+
#
|
26
|
+
# 1. Default (see Goliath::DEFAULT_ENV)
|
27
|
+
# 2. RACK_ENV
|
28
|
+
# 3. -e/--environment command line options
|
29
|
+
# 4. Hard-coded call to Goliath#env=
|
30
|
+
#
|
31
|
+
# @param argv [Array] The command line arguments
|
32
|
+
# @return [Symbol] The current environemnt
|
33
|
+
def self.parse(argv = [])
|
34
|
+
env = ENV["RACK_ENV"] || Goliath::DEFAULT_ENV
|
35
|
+
if (i = argv.index('-e')) || (i = argv.index('--environment'))
|
36
|
+
env = argv[i + 1]
|
37
|
+
end
|
38
|
+
env.to_sym
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Set the environment immediately before we do anything else.
|
43
|
+
Goliath.env = Goliath::EnvironmentParser.parse(ARGV)
|
44
|
+
|
7
45
|
# The Goliath::Runner is responsible for parsing any provided options, setting up the
|
8
46
|
# rack application, creating a logger, and then executing the Goliath::Server with the loaded information.
|
9
47
|
class Runner
|
@@ -63,7 +101,9 @@ module Goliath
|
|
63
101
|
def initialize(argv, api)
|
64
102
|
api.options_parser(options_parser, options) if api
|
65
103
|
options_parser.parse!(argv)
|
66
|
-
|
104
|
+
|
105
|
+
# We've already dealt with the environemnt, so just discard it.
|
106
|
+
options.delete(:env)
|
67
107
|
|
68
108
|
@api = api
|
69
109
|
@address = options.delete(:address)
|
@@ -90,7 +130,7 @@ module Goliath
|
|
90
130
|
:daemonize => false,
|
91
131
|
:verbose => false,
|
92
132
|
:log_stdout => false,
|
93
|
-
:env =>
|
133
|
+
:env => Goliath::DEFAULT_ENV
|
94
134
|
}
|
95
135
|
|
96
136
|
@options_parser ||= OptionParser.new do |opts|
|
@@ -99,7 +139,10 @@ module Goliath
|
|
99
139
|
opts.separator ""
|
100
140
|
opts.separator "Server options:"
|
101
141
|
|
102
|
-
|
142
|
+
# The environment isn't set as part of this option parsing routine, but
|
143
|
+
# we'll leave the flag here so a call to --help shows it correctly.
|
144
|
+
opts.on('-e', '--environment NAME', "Set the execution environment (default: #{@options[:env]})") { |val| @options[:env] = val }
|
145
|
+
|
103
146
|
opts.on('-a', '--address HOST', "Bind to HOST address (default: #{@options[:address]})") { |addr| @options[:address] = addr }
|
104
147
|
opts.on('-p', '--port PORT', "Use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
|
105
148
|
opts.on('-S', '--socket FILE', "Bind to unix domain socket") { |v| @options[:address] = v; @options[:port] = nil }
|
@@ -142,7 +185,7 @@ module Goliath
|
|
142
185
|
#
|
143
186
|
# @return [Nil]
|
144
187
|
def run
|
145
|
-
unless Goliath.test
|
188
|
+
unless Goliath.env?(:test)
|
146
189
|
$LOADED_FEATURES.unshift(File.basename($0))
|
147
190
|
Dir.chdir(File.expand_path(File.dirname($0)))
|
148
191
|
end
|
@@ -165,6 +208,7 @@ module Goliath
|
|
165
208
|
STDERR.reopen(STDOUT)
|
166
209
|
|
167
210
|
run_server
|
211
|
+
remove_pid
|
168
212
|
end
|
169
213
|
else
|
170
214
|
run_server
|
@@ -244,5 +288,12 @@ module Goliath
|
|
244
288
|
FileUtils.mkdir_p(File.dirname(@pid_file))
|
245
289
|
File.open(@pid_file, 'w') { |f| f.write(pid) }
|
246
290
|
end
|
291
|
+
|
292
|
+
# Remove the pid file specified by @pid_file
|
293
|
+
#
|
294
|
+
# @return [Nil]
|
295
|
+
def remove_pid
|
296
|
+
File.delete(@pid_file)
|
297
|
+
end
|
247
298
|
end
|
248
299
|
end
|
data/lib/goliath/server.rb
CHANGED
@@ -73,14 +73,17 @@ module Goliath
|
|
73
73
|
EM.synchrony do
|
74
74
|
trap("INT") { EM.stop }
|
75
75
|
trap("TERM") { EM.stop }
|
76
|
-
|
76
|
+
|
77
|
+
if RUBY_PLATFORM !~ /mswin|mingw/
|
78
|
+
trap("HUP") { load_config(options[:config]) }
|
79
|
+
end
|
77
80
|
|
78
81
|
load_config(options[:config])
|
79
82
|
load_plugins
|
80
83
|
|
81
84
|
EM.set_effective_user(options[:user]) if options[:user]
|
82
85
|
|
83
|
-
EM.start_server(address, port, Goliath::Connection) do |conn|
|
86
|
+
config[Goliath::Constants::GOLIATH_SIGNATURE] = EM.start_server(address, port, Goliath::Connection) do |conn|
|
84
87
|
if options[:ssl]
|
85
88
|
conn.start_tls(
|
86
89
|
:private_key_file => options[:ssl_key],
|
@@ -111,7 +114,8 @@ module Goliath
|
|
111
114
|
file ||= "#{config_dir}/#{api_name}.rb"
|
112
115
|
return unless File.exists?(file)
|
113
116
|
|
114
|
-
|
117
|
+
proc = Proc.new {} # create proc to grab binding
|
118
|
+
eval(IO.read(file), proc.binding, file)
|
115
119
|
end
|
116
120
|
|
117
121
|
# Retrieves the configuration directory for the server
|
data/lib/goliath/test_helper.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'em-synchrony'
|
2
2
|
require 'em-synchrony/em-http'
|
3
3
|
|
4
|
+
require 'goliath/api'
|
4
5
|
require 'goliath/server'
|
5
|
-
require 'goliath/rack
|
6
|
+
require 'goliath/rack'
|
6
7
|
require 'rack'
|
7
8
|
|
8
9
|
module Goliath
|
@@ -16,7 +17,7 @@ module Goliath
|
|
16
17
|
# it 'returns the echo param' do
|
17
18
|
# with_api(Echo) do
|
18
19
|
# get_request({:query => {:echo => 'test'}}, err) do |c|
|
19
|
-
# b =
|
20
|
+
# b = MultiJson.load(c.response)
|
20
21
|
# b['response'].should == 'test'
|
21
22
|
# end
|
22
23
|
# end
|
@@ -39,21 +40,43 @@ module Goliath
|
|
39
40
|
op = OptionParser.new
|
40
41
|
|
41
42
|
s = Goliath::Server.new
|
42
|
-
s.logger =
|
43
|
+
s.logger = setup_logger(options)
|
43
44
|
s.api = api.new
|
44
45
|
s.app = Goliath::Rack::Builder.build(api, s.api)
|
45
46
|
s.api.options_parser(op, options)
|
46
47
|
s.options = options
|
47
|
-
s.port =
|
48
|
+
s.port = port
|
49
|
+
s.plugins = api.plugins
|
50
|
+
@test_server_port = s.port if blk
|
48
51
|
s.start(&blk)
|
49
52
|
s
|
50
53
|
end
|
51
54
|
|
55
|
+
def setup_logger(opts)
|
56
|
+
return fake_logger if opts[:log_file].nil? && opts[:log_stdout].nil?
|
57
|
+
|
58
|
+
log = Log4r::Logger.new('goliath')
|
59
|
+
log_format = Log4r::PatternFormatter.new(:pattern => "[#{Process.pid}:%l] %d :: %m")
|
60
|
+
log.level = opts[:verbose].nil? ? Log4r::INFO : Log4r::DEBUG
|
61
|
+
|
62
|
+
if opts[:log_stdout]
|
63
|
+
log.add(Log4r::StdoutOutputter.new('console', :formatter => log_format))
|
64
|
+
elsif opts[:log_file]
|
65
|
+
file = opts[:log_file]
|
66
|
+
FileUtils.mkdir_p(File.dirname(file))
|
67
|
+
|
68
|
+
log.add(Log4r::FileOutputter.new('fileOutput', {:filename => file,
|
69
|
+
:trunc => false,
|
70
|
+
:formatter => log_format}))
|
71
|
+
end
|
72
|
+
log
|
73
|
+
end
|
74
|
+
|
52
75
|
# Stops the launched API
|
53
76
|
#
|
54
77
|
# @return [Nil]
|
55
78
|
def stop
|
56
|
-
EM.
|
79
|
+
EM.stop_event_loop
|
57
80
|
end
|
58
81
|
|
59
82
|
# Wrapper for launching API and executing given code block. This
|
@@ -84,33 +107,33 @@ module Goliath
|
|
84
107
|
req.errback { stop }
|
85
108
|
end
|
86
109
|
|
87
|
-
# Make a HEAD request the currently launched API.
|
110
|
+
# Make a HEAD request against the currently launched API.
|
88
111
|
#
|
89
112
|
# @param request_data [Hash] Any data to pass to the HEAD request.
|
90
113
|
# @param errback [Proc] An error handler to attach
|
91
114
|
# @param blk [Proc] The callback block to execute
|
92
115
|
def head_request(request_data = {}, errback = nil, &blk)
|
93
|
-
req =
|
116
|
+
req = create_test_request(request_data).head(request_data)
|
94
117
|
hookup_request_callbacks(req, errback, &blk)
|
95
118
|
end
|
96
119
|
|
97
|
-
# Make a GET request the currently launched API.
|
120
|
+
# Make a GET request against the currently launched API.
|
98
121
|
#
|
99
122
|
# @param request_data [Hash] Any data to pass to the GET request.
|
100
123
|
# @param errback [Proc] An error handler to attach
|
101
124
|
# @param blk [Proc] The callback block to execute
|
102
125
|
def get_request(request_data = {}, errback = nil, &blk)
|
103
|
-
req =
|
126
|
+
req = create_test_request(request_data).get(request_data)
|
104
127
|
hookup_request_callbacks(req, errback, &blk)
|
105
128
|
end
|
106
129
|
|
107
|
-
# Make a POST request the currently launched API.
|
130
|
+
# Make a POST request against the currently launched API.
|
108
131
|
#
|
109
132
|
# @param request_data [Hash] Any data to pass to the POST request.
|
110
133
|
# @param errback [Proc] An error handler to attach
|
111
134
|
# @param blk [Proc] The callback block to execute
|
112
135
|
def post_request(request_data = {}, errback = nil, &blk)
|
113
|
-
req =
|
136
|
+
req = create_test_request(request_data).post(request_data)
|
114
137
|
hookup_request_callbacks(req, errback, &blk)
|
115
138
|
end
|
116
139
|
|
@@ -120,23 +143,54 @@ module Goliath
|
|
120
143
|
# @param errback [Proc] An error handler to attach
|
121
144
|
# @param blk [Proc] The callback block to execute
|
122
145
|
def put_request(request_data = {}, errback = nil, &blk)
|
123
|
-
req =
|
146
|
+
req = create_test_request(request_data).put(request_data)
|
124
147
|
hookup_request_callbacks(req, errback, &blk)
|
125
148
|
end
|
126
149
|
|
127
|
-
# Make a
|
150
|
+
# Make a PATCH request against the currently launched API.
|
151
|
+
#
|
152
|
+
# @param request_data [Hash] Any data to pass to the PUT request.
|
153
|
+
# @param errback [Proc] An error handler to attach
|
154
|
+
# @param blk [Proc] The callback block to execute
|
155
|
+
def patch_request(request_data = {}, errback = nil, &blk)
|
156
|
+
req = create_test_request(request_data).patch(request_data)
|
157
|
+
hookup_request_callbacks(req, errback, &blk)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Make a DELETE request against the currently launched API.
|
128
161
|
#
|
129
162
|
# @param request_data [Hash] Any data to pass to the DELETE request.
|
130
163
|
# @param errback [Proc] An error handler to attach
|
131
164
|
# @param blk [Proc] The callback block to execute
|
132
165
|
def delete_request(request_data = {}, errback = nil, &blk)
|
133
|
-
req =
|
166
|
+
req = create_test_request(request_data).delete(request_data)
|
167
|
+
hookup_request_callbacks(req, errback, &blk)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Make an OPTIONS request against the currently launched API.
|
171
|
+
#
|
172
|
+
# @param request_data [Hash] Any data to pass to the OPTIONS request.
|
173
|
+
# @param errback [Proc] An error handler to attach
|
174
|
+
# @param blk [Proc] The callback block to execute
|
175
|
+
def options_request(request_data = {}, errback = nil, &blk)
|
176
|
+
req = create_test_request(request_data).options(request_data)
|
134
177
|
hookup_request_callbacks(req, errback, &blk)
|
135
178
|
end
|
136
179
|
|
137
|
-
def
|
180
|
+
def create_test_request(request_data)
|
138
181
|
path = request_data.delete(:path) || ''
|
139
|
-
|
182
|
+
opts = request_data.delete(:connection_options) || {}
|
183
|
+
EM::HttpRequest.new("http://localhost:#{@test_server_port}#{path}", opts)
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def fake_logger
|
189
|
+
Class.new do
|
190
|
+
def method_missing(name, *args, &blk)
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
end.new
|
140
194
|
end
|
141
195
|
end
|
142
196
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'em-websocket-client'
|
2
|
+
|
3
|
+
module Goliath
|
4
|
+
module TestHelper
|
5
|
+
class WSHelper
|
6
|
+
attr_reader :connection
|
7
|
+
def initialize(url)
|
8
|
+
@queue = EM::Queue.new
|
9
|
+
|
10
|
+
fiber = Fiber.current
|
11
|
+
@connection = EventMachine::WebSocketClient.connect(url)
|
12
|
+
@connection.errback do |e|
|
13
|
+
puts "Error encountered during connection: #{e}"
|
14
|
+
EM::stop_event_loop
|
15
|
+
end
|
16
|
+
|
17
|
+
@connection.callback { fiber.resume }
|
18
|
+
@connection.disconnect { EM::stop_event_loop }
|
19
|
+
@connection.stream { |m| @queue.push(m) }
|
20
|
+
|
21
|
+
Fiber.yield
|
22
|
+
end
|
23
|
+
|
24
|
+
def send(m)
|
25
|
+
@connection.send_msg(m)
|
26
|
+
end
|
27
|
+
|
28
|
+
def receive
|
29
|
+
fiber = Fiber.current
|
30
|
+
@queue.pop {|m| fiber.resume(m) }
|
31
|
+
Fiber.yield
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def ws_client_connect(path,&blk)
|
36
|
+
url = "ws://localhost:#{@test_server_port}#{path}"
|
37
|
+
client = WSHelper.new(url)
|
38
|
+
blk.call(client) if blk
|
39
|
+
stop
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/goliath/version.rb
CHANGED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'em-websocket'
|
2
|
+
|
3
|
+
module Goliath
|
4
|
+
class WebSocket < Goliath::API
|
5
|
+
include Goliath::Constants
|
6
|
+
|
7
|
+
def on_open(env) ; end
|
8
|
+
def on_message(env, msg) ; end
|
9
|
+
def on_close(env) ; end
|
10
|
+
def on_error(env, error) ; end
|
11
|
+
|
12
|
+
def on_headers(env, headers)
|
13
|
+
env['goliath.request-headers'] = headers
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_body(env, data)
|
17
|
+
env.handler.receive_data(data)
|
18
|
+
end
|
19
|
+
|
20
|
+
def response(env)
|
21
|
+
request = {}
|
22
|
+
|
23
|
+
# em-websockets expects the keys to all be lowercase so we need to convert
|
24
|
+
env['goliath.request-headers'].each_pair do |key, value|
|
25
|
+
request[key.downcase] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
request['path'] = env[REQUEST_PATH]
|
29
|
+
request['method'] = env[REQUEST_METHOD]
|
30
|
+
request['query'] = env[QUERY_STRING]
|
31
|
+
|
32
|
+
old_stream_send = env[STREAM_SEND]
|
33
|
+
old_stream_close = env[STREAM_CLOSE]
|
34
|
+
env[STREAM_SEND] = proc { |data| env.handler.send_text_frame(data) }
|
35
|
+
env[STREAM_CLOSE] = proc { |code, body| env.handler.close_websocket(code, body) }
|
36
|
+
env[STREAM_START] = proc { }
|
37
|
+
|
38
|
+
conn = Class.new do
|
39
|
+
def initialize(env, parent, stream_send, connection_close)
|
40
|
+
@env = env
|
41
|
+
@parent = parent
|
42
|
+
@stream_send = stream_send
|
43
|
+
@connection_close = connection_close
|
44
|
+
end
|
45
|
+
|
46
|
+
def trigger_on_open
|
47
|
+
@parent.on_open(@env)
|
48
|
+
end
|
49
|
+
|
50
|
+
def trigger_on_close
|
51
|
+
@parent.on_close(@env)
|
52
|
+
end
|
53
|
+
|
54
|
+
def trigger_on_message(msg)
|
55
|
+
@parent.on_message(@env, msg)
|
56
|
+
end
|
57
|
+
|
58
|
+
def close_connection_after_writing
|
59
|
+
@connection_close.call
|
60
|
+
end
|
61
|
+
|
62
|
+
def send_data(data)
|
63
|
+
@stream_send.call(data)
|
64
|
+
end
|
65
|
+
end.new(env, self, old_stream_send, old_stream_close)
|
66
|
+
|
67
|
+
upgrade_data = env[UPGRADE_DATA]
|
68
|
+
begin
|
69
|
+
env['handler'] = EM::WebSocket::HandlerFactory.build_with_request(conn, request,
|
70
|
+
upgrade_data, false, false)
|
71
|
+
rescue Exception => e
|
72
|
+
env.logger.error("#{e.message}\n#{e.backtrace.join("\n")}")
|
73
|
+
return [500, {}, {:error => "Upgrade failed"}]
|
74
|
+
end
|
75
|
+
env['handler'].run
|
76
|
+
|
77
|
+
Goliath::Connection::AsyncResponse
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
Binary file
|
Binary file
|
@@ -12,7 +12,7 @@ describe 'Async Request processing' do
|
|
12
12
|
err = Proc.new { fail "API request failed" }
|
13
13
|
|
14
14
|
post_request(request_data, err) do |c|
|
15
|
-
resp =
|
15
|
+
resp = MultiJson.load(c.response)
|
16
16
|
resp['body'].should match('some=data')
|
17
17
|
resp['head'].should include('X-Upload')
|
18
18
|
end
|
@@ -16,14 +16,10 @@ describe EarlyAbort do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
# The following two tests are very brittle, since they depend on the speed
|
20
|
-
# of the machine executing the test and the size of the incoming data
|
21
|
-
# packets. I hope someone more knowledgeable will be able to refactor these
|
22
|
-
# ;-)
|
23
19
|
it 'fails without going in the response method if exception is raised in on_header hook' do
|
24
20
|
with_api(EarlyAbort) do
|
25
21
|
request_data = {
|
26
|
-
:body =>
|
22
|
+
:body => "a" * 20,
|
27
23
|
:head => {'X-Crash' => 'true'}
|
28
24
|
}
|
29
25
|
|
@@ -36,9 +32,7 @@ describe EarlyAbort do
|
|
36
32
|
|
37
33
|
it 'fails without going in the response method if exception is raised in on_body hook' do
|
38
34
|
with_api(EarlyAbort) do
|
39
|
-
request_data = {
|
40
|
-
:body => (["abcd"] * 200_000).join
|
41
|
-
}
|
35
|
+
request_data = { :body => "a" * 20 }
|
42
36
|
|
43
37
|
post_request(request_data, err) do |c|
|
44
38
|
c.response.should =~ /Payload size can't exceed 10 bytes/
|
@@ -46,5 +40,4 @@ describe EarlyAbort do
|
|
46
40
|
end
|
47
41
|
end
|
48
42
|
end
|
49
|
-
|
50
|
-
end
|
43
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require File.join(File.dirname(__FILE__), '../../', 'examples/echo')
|
3
3
|
require 'multipart_body'
|
4
|
-
require 'yajl'
|
4
|
+
require 'yajl' if RUBY_PLATFORM != 'java'
|
5
5
|
|
6
6
|
describe Echo do
|
7
7
|
let(:err) { Proc.new { fail "API request failed" } }
|
@@ -9,7 +9,7 @@ describe Echo do
|
|
9
9
|
it 'returns the echo param' do
|
10
10
|
with_api(Echo) do
|
11
11
|
get_request({:query => {:echo => 'test'}}, err) do |c|
|
12
|
-
b =
|
12
|
+
b = MultiJson.load(c.response)
|
13
13
|
b['response'].should == 'test'
|
14
14
|
end
|
15
15
|
end
|
@@ -18,9 +18,9 @@ describe Echo do
|
|
18
18
|
it 'returns error without echo' do
|
19
19
|
with_api(Echo) do
|
20
20
|
get_request({}, err) do |c|
|
21
|
-
b =
|
21
|
+
b = MultiJson.load(c.response)
|
22
22
|
b['error'].should_not be_nil
|
23
|
-
b['error'].should == '
|
23
|
+
b['error'].should == 'echo identifier missing'
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -28,7 +28,7 @@ describe Echo do
|
|
28
28
|
it 'echos POST data' do
|
29
29
|
with_api(Echo) do
|
30
30
|
post_request({:body => {'echo' => 'test'}}, err) do |c|
|
31
|
-
b =
|
31
|
+
b = MultiJson.load(c.response)
|
32
32
|
b['response'].should == 'test'
|
33
33
|
end
|
34
34
|
end
|
@@ -41,7 +41,7 @@ describe Echo do
|
|
41
41
|
|
42
42
|
post_request({:body => body.to_s,
|
43
43
|
:head => head}, err) do |c|
|
44
|
-
b =
|
44
|
+
b = MultiJson.load(c.response)
|
45
45
|
b['response'].should == 'test'
|
46
46
|
end
|
47
47
|
end
|
@@ -49,12 +49,12 @@ describe Echo do
|
|
49
49
|
|
50
50
|
it 'echos application/json POST body data' do
|
51
51
|
with_api(Echo) do
|
52
|
-
body =
|
52
|
+
body = MultiJson.dump({'echo' => 'My Echo'})
|
53
53
|
head = {'content-type' => 'application/json'}
|
54
54
|
|
55
55
|
post_request({:body => body.to_s,
|
56
56
|
:head => head}, err) do |c|
|
57
|
-
b =
|
57
|
+
b = MultiJson.load(c.response)
|
58
58
|
b['response'].should == 'My Echo'
|
59
59
|
end
|
60
60
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class JSON_API < Goliath::API
|
4
|
+
use Goliath::Rack::Params
|
5
|
+
use Goliath::Rack::JSONP
|
6
|
+
use Goliath::Rack::Render, 'json'
|
7
|
+
|
8
|
+
def response(env)
|
9
|
+
[200, {'CONTENT_TYPE' => 'application/json'}, "OK"]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'JSONP' do
|
14
|
+
let(:err) { Proc.new { fail "API request failed" } }
|
15
|
+
|
16
|
+
it 'sets the content type' do
|
17
|
+
with_api(JSON_API) do
|
18
|
+
get_request({:query => {:callback => 'test'}}, err) do |c|
|
19
|
+
c.response_header['CONTENT_TYPE'].should =~ %r{^application/javascript}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'wraps response with callback' do
|
25
|
+
with_api(JSON_API) do
|
26
|
+
get_request({:query => {:callback => 'test'}}, err) do |c|
|
27
|
+
c.response.should =~ /^test\(.*\)$/
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|