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.

Files changed (102) hide show
  1. data/.gitignore +3 -0
  2. data/Guardfile +8 -0
  3. data/HISTORY.md +10 -0
  4. data/LICENSE +1 -1
  5. data/README.md +28 -29
  6. data/Rakefile +10 -2
  7. data/examples/activerecord/config/srv.rb +2 -1
  8. data/examples/activerecord/srv.rb +14 -5
  9. data/examples/api_proxy.rb +3 -7
  10. data/examples/around.rb +38 -0
  11. data/examples/async_aroundware_demo.rb +2 -2
  12. data/examples/async_upload.rb +1 -1
  13. data/examples/auth_and_rate_limit.rb +1 -1
  14. data/examples/clone.rb +26 -0
  15. data/examples/config/websocket.rb +1 -0
  16. data/examples/custom_logs.rb +21 -0
  17. data/examples/custom_server.rb +6 -44
  18. data/examples/early_abort.rb +6 -3
  19. data/examples/echo.rb +1 -1
  20. data/examples/fiber_pool.rb +35 -0
  21. data/examples/grape/config/apiserver.rb +8 -0
  22. data/examples/grape/server.rb +67 -0
  23. data/examples/gziped.rb +1 -1
  24. data/examples/params.rb +36 -0
  25. data/examples/rasterize/rasterize.rb +1 -2
  26. data/examples/router.rb +15 -0
  27. data/examples/template.rb +14 -7
  28. data/examples/test.rb +31 -0
  29. data/examples/upload.rb +17 -0
  30. data/examples/views/joke.markdown +4 -4
  31. data/examples/websocket.rb +39 -0
  32. data/examples/ws/favicon.ico +0 -0
  33. data/examples/ws/index.erb +50 -0
  34. data/goliath.gemspec +24 -6
  35. data/lib/goliath/api.rb +15 -82
  36. data/lib/goliath/application.rb +3 -17
  37. data/lib/goliath/connection.rb +16 -9
  38. data/lib/goliath/constants.rb +2 -0
  39. data/lib/goliath/env.rb +2 -3
  40. data/lib/goliath/goliath.rb +24 -34
  41. data/lib/goliath/plugins/latency.rb +7 -3
  42. data/lib/goliath/rack/builder.rb +1 -37
  43. data/lib/goliath/rack/favicon.rb +31 -0
  44. data/lib/goliath/rack/formatters/json.rb +1 -1
  45. data/lib/goliath/rack/heartbeat.rb +1 -0
  46. data/lib/goliath/rack/jsonp.rb +1 -0
  47. data/lib/goliath/rack/params.rb +2 -17
  48. data/lib/goliath/rack/render.rb +0 -1
  49. data/lib/goliath/rack/templates.rb +18 -7
  50. data/lib/goliath/rack/types/base.rb +24 -0
  51. data/lib/goliath/rack/types/boolean.rb +18 -0
  52. data/lib/goliath/rack/types/core.rb +19 -0
  53. data/lib/goliath/rack/types/symbol.rb +16 -0
  54. data/lib/goliath/rack/types.rb +10 -0
  55. data/lib/goliath/rack/validation/coerce.rb +48 -0
  56. data/lib/goliath/rack/validation/param.rb +113 -0
  57. data/lib/goliath/rack/validation/request_method.rb +1 -1
  58. data/lib/goliath/rack/validation/required.rb +47 -0
  59. data/lib/goliath/rack/validation/required_param.rb +42 -17
  60. data/lib/goliath/rack/validation.rb +3 -0
  61. data/lib/goliath/rack.rb +2 -2
  62. data/lib/goliath/request.rb +41 -9
  63. data/lib/goliath/response.rb +0 -2
  64. data/lib/goliath/runner.rb +55 -4
  65. data/lib/goliath/server.rb +7 -3
  66. data/lib/goliath/test_helper.rb +70 -16
  67. data/lib/goliath/test_helper_ws.rb +42 -0
  68. data/lib/goliath/version.rb +1 -1
  69. data/lib/goliath/websocket.rb +80 -0
  70. data/pkg/goliath-0.9.4.gem +0 -0
  71. data/pkg/goliath-1.0.0.beta.1.gem +0 -0
  72. data/spec/integration/async_request_processing.rb +1 -1
  73. data/spec/integration/early_abort_spec.rb +3 -10
  74. data/spec/integration/echo_spec.rb +8 -8
  75. data/spec/integration/jsonp_spec.rb +31 -0
  76. data/spec/integration/keepalive_spec.rb +2 -2
  77. data/spec/integration/template_spec.rb +10 -5
  78. data/spec/integration/test_helper_spec.rb +33 -0
  79. data/spec/integration/valid_spec.rb +35 -5
  80. data/spec/integration/websocket_spec.rb +44 -0
  81. data/spec/unit/api_spec.rb +2 -19
  82. data/spec/unit/connection_spec.rb +3 -0
  83. data/spec/unit/rack/formatters/json_spec.rb +3 -3
  84. data/spec/unit/rack/heartbeat_spec.rb +13 -0
  85. data/spec/unit/rack/params_spec.rb +2 -8
  86. data/spec/unit/rack/validation/param_spec.rb +443 -0
  87. data/spec/unit/rack/validation/request_method_spec.rb +5 -0
  88. data/spec/unit/rack/validation/required_param_spec.rb +71 -1
  89. data/spec/unit/runner_spec.rb +21 -7
  90. data/test/echo_test.rb +25 -0
  91. data/test/test_helper.rb +5 -0
  92. metadata +316 -78
  93. data/examples/env_use_statements.rb +0 -20
  94. data/examples/favicon.rb +0 -40
  95. data/examples/rack_routes.rb +0 -125
  96. data/examples/rasterize/thumb/f7ad4cb03e5bfd0e2c43db8e598fb3cd.png +0 -0
  97. data/examples/valid.rb +0 -17
  98. data/lib/goliath/deprecated/async_aroundware.rb +0 -133
  99. data/lib/goliath/deprecated/mongo_receiver.rb +0 -84
  100. data/lib/goliath/deprecated/response_receiver.rb +0 -97
  101. data/spec/integration/rack_routes_spec.rb +0 -169
  102. data/spec/unit/rack/builder_spec.rb +0 -40
@@ -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
- server_exception(e)
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
- Fiber.new {
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
- }.resume
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
- @conn.terminate_request(keep_alive)
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
- status, headers, body = [500, {}, 'An error happened']
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])
@@ -59,10 +59,8 @@ module Goliath
59
59
 
60
60
  if vs.is_a?(String)
61
61
  vs.each_line { |v| @headers[k] = v.chomp }
62
-
63
62
  elsif vs.is_a?(Time)
64
63
  @headers[k] = vs
65
-
66
64
  else
67
65
  vs.each { |v| @headers[k] = v.chomp }
68
66
  end
@@ -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
- Goliath.env = options.delete(:env)
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 => :development,
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
- opts.on('-e', '--environment NAME', "Set the execution environment (prod, dev or test) (default: #{@options[:env]})") { |val| @options[:env] = val }
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
@@ -73,14 +73,17 @@ module Goliath
73
73
  EM.synchrony do
74
74
  trap("INT") { EM.stop }
75
75
  trap("TERM") { EM.stop }
76
- trap("HUP") { load_config(options[:config]) }
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
- eval(IO.read(file))
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
@@ -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/builder'
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 = Yajl::Parser.parse(c.response)
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 = mock('log').as_null_object
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 = @test_server_port = 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.stop
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 = test_request(request_data).head(request_data)
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 = test_request(request_data).get(request_data)
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 = test_request(request_data).post(request_data)
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 = test_request(request_data).put(request_data)
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 DELETE request the currently launched API.
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 = test_request(request_data).delete(request_data)
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 test_request(request_data)
180
+ def create_test_request(request_data)
138
181
  path = request_data.delete(:path) || ''
139
- EM::HttpRequest.new("http://localhost:#{@test_server_port}#{path}")
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
@@ -1,4 +1,4 @@
1
1
  module Goliath
2
2
  # The current version of Goliath
3
- VERSION = '0.9.4'
3
+ VERSION = '1.0.0.beta.1'
4
4
  end
@@ -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 = Yajl::Parser.parse(c.response)
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 => (["abcd"] * 200_000).join,
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 = Yajl::Parser.parse(c.response)
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 = Yajl::Parser.parse(c.response)
21
+ b = MultiJson.load(c.response)
22
22
  b['error'].should_not be_nil
23
- b['error'].should == 'Echo identifier missing'
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 = Yajl::Parser.parse(c.response)
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 = Yajl::Parser.parse(c.response)
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 = Yajl::Encoder.encode({'echo' => 'My Echo'})
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 = Yajl::Parser.parse(c.response)
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