goliath 0.9.1 → 0.9.2

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.

Potentially problematic release.


This version of goliath might be problematic. Click here for more details.

Files changed (94) hide show
  1. data/.gitignore +1 -0
  2. data/HISTORY +50 -0
  3. data/README.md +2 -0
  4. data/examples/activerecord/srv.rb +1 -3
  5. data/examples/async_aroundware_demo.rb +81 -0
  6. data/examples/async_upload.rb +1 -2
  7. data/examples/auth_and_rate_limit.rb +143 -0
  8. data/examples/chunked_streaming.rb +37 -0
  9. data/examples/conf_test.rb +1 -3
  10. data/examples/config/auth_and_rate_limit.rb +30 -0
  11. data/examples/config/template.rb +8 -0
  12. data/examples/content_stream.rb +1 -3
  13. data/examples/echo.rb +8 -6
  14. data/examples/env_use_statements.rb +17 -0
  15. data/examples/gziped.rb +1 -3
  16. data/examples/public/stylesheets/style.css +296 -0
  17. data/examples/rack_routes.rb +65 -3
  18. data/examples/rasterize/rasterize.js +15 -0
  19. data/examples/rasterize/rasterize.rb +36 -0
  20. data/examples/rasterize/rasterize_and_shorten.rb +37 -0
  21. data/examples/stream.rb +2 -2
  22. data/examples/template.rb +48 -0
  23. data/examples/test_rig.rb +125 -0
  24. data/examples/valid.rb +4 -2
  25. data/examples/views/debug.haml +4 -0
  26. data/examples/views/joke.markdown +13 -0
  27. data/examples/views/layout.erb +12 -0
  28. data/examples/views/layout.haml +39 -0
  29. data/examples/views/root.haml +28 -0
  30. data/goliath.gemspec +10 -3
  31. data/lib/goliath.rb +0 -36
  32. data/lib/goliath/api.rb +137 -26
  33. data/lib/goliath/application.rb +71 -21
  34. data/lib/goliath/connection.rb +4 -2
  35. data/lib/goliath/constants.rb +1 -0
  36. data/lib/goliath/env.rb +40 -1
  37. data/lib/goliath/goliath.rb +30 -15
  38. data/lib/goliath/headers.rb +2 -2
  39. data/lib/goliath/plugins/latency.rb +8 -2
  40. data/lib/goliath/rack.rb +18 -0
  41. data/lib/goliath/rack/async_aroundware.rb +56 -0
  42. data/lib/goliath/rack/async_middleware.rb +93 -0
  43. data/lib/goliath/rack/builder.rb +42 -0
  44. data/lib/goliath/rack/default_response_format.rb +3 -15
  45. data/lib/goliath/rack/formatters.rb +11 -0
  46. data/lib/goliath/rack/formatters/html.rb +2 -18
  47. data/lib/goliath/rack/formatters/json.rb +2 -17
  48. data/lib/goliath/rack/formatters/plist.rb +32 -0
  49. data/lib/goliath/rack/formatters/xml.rb +23 -31
  50. data/lib/goliath/rack/formatters/yaml.rb +27 -0
  51. data/lib/goliath/rack/jsonp.rb +1 -13
  52. data/lib/goliath/rack/params.rb +55 -27
  53. data/lib/goliath/rack/render.rb +13 -22
  54. data/lib/goliath/rack/templates.rb +357 -0
  55. data/lib/goliath/rack/tracer.rb +11 -12
  56. data/lib/goliath/rack/validation.rb +12 -0
  57. data/lib/goliath/rack/validation/default_params.rb +0 -2
  58. data/lib/goliath/rack/validation/numeric_range.rb +11 -2
  59. data/lib/goliath/rack/validation/request_method.rb +3 -2
  60. data/lib/goliath/rack/validation/required_param.rb +13 -11
  61. data/lib/goliath/rack/validation/required_value.rb +11 -15
  62. data/lib/goliath/rack/validator.rb +51 -0
  63. data/lib/goliath/request.rb +34 -20
  64. data/lib/goliath/response.rb +3 -2
  65. data/lib/goliath/runner.rb +5 -11
  66. data/lib/goliath/server.rb +2 -1
  67. data/lib/goliath/synchrony/mongo_receiver.rb +64 -0
  68. data/lib/goliath/synchrony/response_receiver.rb +64 -0
  69. data/lib/goliath/test_helper.rb +39 -21
  70. data/lib/goliath/validation.rb +2 -0
  71. data/lib/goliath/{rack/validation_error.rb → validation/error.rb} +0 -16
  72. data/lib/goliath/validation/standard_http_errors.rb +31 -0
  73. data/lib/goliath/version.rb +1 -1
  74. data/spec/integration/http_log_spec.rb +16 -16
  75. data/spec/integration/rack_routes_spec.rb +144 -0
  76. data/spec/integration/reloader_spec.rb +4 -4
  77. data/spec/integration/template_spec.rb +54 -0
  78. data/spec/integration/trace_spec.rb +23 -0
  79. data/spec/integration/valid_spec.rb +21 -0
  80. data/spec/spec_helper.rb +3 -1
  81. data/spec/unit/api_spec.rb +30 -0
  82. data/spec/unit/rack/builder_spec.rb +40 -0
  83. data/spec/unit/rack/formatters/plist_spec.rb +51 -0
  84. data/spec/unit/rack/formatters/yaml_spec.rb +53 -0
  85. data/spec/unit/rack/params_spec.rb +22 -0
  86. data/spec/unit/rack/render_spec.rb +10 -5
  87. data/spec/unit/rack/validation/numeric_range_spec.rb +8 -1
  88. data/spec/unit/rack/validation/request_method_spec.rb +8 -8
  89. data/spec/unit/rack/validation/required_param_spec.rb +19 -15
  90. data/spec/unit/rack/validation/required_value_spec.rb +10 -13
  91. data/spec/unit/server_spec.rb +4 -4
  92. data/spec/unit/validation/standard_http_errors_spec.rb +21 -0
  93. metadata +177 -35
  94. data/spec/unit/rack/validation_error_spec.rb +0 -40
@@ -0,0 +1,42 @@
1
+ require 'http_router'
2
+
3
+ module Goliath
4
+ module Rack
5
+ class Builder < ::Rack::Builder
6
+ attr_accessor :params
7
+ include Params::Parser
8
+
9
+ # Builds the rack middleware chain for the given API
10
+ #
11
+ # @param klass [Class] The API class to build the middlewares for
12
+ # @param api [Object] The instantiated API
13
+ # @return [Object] The Rack middleware chain
14
+ def self.build(klass, api)
15
+ Builder.app do
16
+ klass.middlewares.each do |mw_klass, args, blk|
17
+ use(mw_klass, *args, &blk)
18
+ end
19
+ if klass.maps?
20
+ klass.maps.each do |path, route_klass, opts, blk|
21
+ blk ||= Proc.new {
22
+ run Builder.build(route_klass, route_klass.new)
23
+ }
24
+ klass.router.add(path, opts.dup).to {|env|
25
+ builder = Builder.new
26
+ env['params'] ||= {}
27
+ env['params'].merge!(env['router.params']) if env['router.params']
28
+ builder.params = builder.retrieve_params(env)
29
+ builder.instance_eval(&blk)
30
+ builder.to_app.call(env)
31
+ }
32
+ end
33
+ run klass.router
34
+ else
35
+ run api
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -1,21 +1,9 @@
1
1
  module Goliath
2
2
  module Rack
3
3
  class DefaultResponseFormat
4
- def initialize(app)
5
- @app = app
6
- end
7
-
8
- def call(env)
9
- async_cb = env['async.callback']
10
- env['async.callback'] = Proc.new do |status, headers, body|
11
- async_cb.call(post_process(status, headers, body))
12
- end
13
-
14
- status, headers, body = @app.call(env)
15
- post_process(status, headers, body)
16
- end
4
+ include Goliath::Rack::AsyncMiddleware
17
5
 
18
- def post_process(status, headers, body)
6
+ def post_process(env, status, headers, body)
19
7
  return [status, headers, body] if body.respond_to?(:to_ary)
20
8
 
21
9
  new_body = []
@@ -30,4 +18,4 @@ module Goliath
30
18
  end
31
19
  end
32
20
  end
33
- end
21
+ end
@@ -0,0 +1,11 @@
1
+ module Goliath
2
+ module Rack
3
+ module Formatters
4
+ autoload :HTML, 'goliath/rack/formatters/html'
5
+ autoload :JSON, 'goliath/rack/formatters/json'
6
+ autoload :Plist, 'goliath/rack/formatters/plist'
7
+ autoload :XML, 'goliath/rack/formatters/xml'
8
+ autoload :YAML, 'goliath/rack/formatters/yaml'
9
+ end
10
+ end
11
+ end
@@ -10,25 +10,9 @@ module Goliath
10
10
  # @example
11
11
  # use Goliath::Rack::Formatters::HTML
12
12
  class HTML
13
- # Called by the framework to create the formatter.
14
- #
15
- # @param app The application
16
- # @return [Goliath::Rack::Formatters::HTML] The HTML formatter
17
- def initialize(app)
18
- @app = app
19
- end
20
-
21
- def call(env)
22
- async_cb = env['async.callback']
23
- env['async.callback'] = Proc.new do |status, headers, body|
24
- async_cb.call(post_process(status, headers, body))
25
- end
26
-
27
- status, headers, body = @app.call(env)
28
- post_process(status, headers, body)
29
- end
13
+ include Goliath::Rack::AsyncMiddleware
30
14
 
31
- def post_process(status, headers, body)
15
+ def post_process(env, status, headers, body)
32
16
  body = [to_html(body, false)] if html_response?(headers)
33
17
  [status, headers, body]
34
18
  end
@@ -9,24 +9,9 @@ module Goliath
9
9
  # @example
10
10
  # use Goliath::Rack::Formatters::JSON
11
11
  class JSON
12
- # Called by the framework to create the formatter.
13
- #
14
- # @return [Goliath::Rack::Formatters::JSON] The JSON formatter.
15
- def initialize(app)
16
- @app = app
17
- end
18
-
19
- def call(env)
20
- async_cb = env['async.callback']
21
- env['async.callback'] = Proc.new do |status, headers, body|
22
- async_cb.call(post_process(status, headers, body))
23
- end
24
-
25
- status, headers, body = @app.call(env)
26
- post_process(status, headers, body)
27
- end
12
+ include AsyncMiddleware
28
13
 
29
- def post_process(status, headers, body)
14
+ def post_process(env, status, headers, body)
30
15
  if json_response?(headers)
31
16
  body = [MultiJson.encode(body)]
32
17
  end
@@ -0,0 +1,32 @@
1
+ module Goliath
2
+ module Rack
3
+ module Formatters
4
+ # A plist formatter. Pass in to_plist options as an option to the middleware
5
+ #
6
+ # @example
7
+ # use Goliath::Rack::Formatters::PLIST, :convert_unknown_to_string => true
8
+ class PLIST
9
+ include AsyncMiddleware
10
+
11
+ def initialize(app, opts = {})
12
+ unless Hash.new.respond_to? :to_plist
13
+ fail "Please require a plist library that adds a to_plist method"
14
+ end
15
+ @app = app
16
+ @opts = opts
17
+ end
18
+
19
+ def post_process(env, status, headers, body)
20
+ if plist_response?(headers)
21
+ body = [body.to_plist(@opts)]
22
+ end
23
+ [status, headers, body]
24
+ end
25
+
26
+ def plist_response?(headers)
27
+ headers['Content-Type'] =~ %r{^application/x-plist}
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -9,25 +9,19 @@ module Goliath
9
9
  # @example
10
10
  # use Goliath::Rack::Formatters::XML
11
11
  class XML
12
- # Called by the framework to create the formatter.
13
- #
14
- # @return [Goliath::Rack::Formatters::XML] The XML formatter.
15
- def initialize(app)
12
+ include Goliath::Rack::AsyncMiddleware
13
+
14
+ def initialize(app, opts = {})
16
15
  @app = app
16
+ @opts = opts
17
+ @opts[:root] ||= 'results'
18
+ @opts[:item] ||= 'item'
17
19
  end
18
20
 
19
- def call(env)
20
- async_cb = env['async.callback']
21
- env['async.callback'] = Proc.new do |status, headers, body|
22
- async_cb.call(post_process(status, headers, body))
21
+ def post_process(env, status, headers, body)
22
+ if xml_response?(headers)
23
+ body = [to_xml(body)]
23
24
  end
24
-
25
- status, headers, body = @app.call(env)
26
- post_process(status, headers, body)
27
- end
28
-
29
- def post_process(status, headers, body)
30
- body = [to_xml(body, false)] if xml_response?(headers)
31
25
  [status, headers, body]
32
26
  end
33
27
 
@@ -35,18 +29,18 @@ module Goliath
35
29
  headers['Content-Type'] =~ %r{^application/xml}
36
30
  end
37
31
 
38
- def to_xml(content, fragment=true, root='results', item='item')
32
+ def to_xml(content, fragment=false)
39
33
  xml_string = ''
40
- xml_string += xml_header(root) unless fragment
34
+ xml_string << xml_header(@opts[:root]) unless fragment
41
35
 
42
- xml_string += case(content.class.to_s)
43
- when "Hash" then hash_to_xml(content, root, item)
44
- when "Array" then array_to_xml(content, root, item)
36
+ xml_string << case(content.class.to_s)
37
+ when "Hash" then hash_to_xml(content)
38
+ when "Array" then array_to_xml(content, @opts[:item])
45
39
  when "String" then string_to_xml(content)
46
40
  else string_to_xml(content)
47
41
  end
48
42
 
49
- xml_string += xml_footer(root) unless fragment
43
+ xml_string << xml_footer(@opts[:root]) unless fragment
50
44
  xml_string
51
45
  end
52
46
 
@@ -54,35 +48,33 @@ module Goliath
54
48
  ::Rack::Utils.escape_html(content.to_s)
55
49
  end
56
50
 
57
- def hash_to_xml(content, root='results', item='item')
51
+ def hash_to_xml(content)
58
52
  xml_string = ''
59
53
  if content.key?('meta')
60
- xml_string += xml_item('meta', content['meta'], root)
54
+ xml_string += xml_item('meta', content['meta'])
61
55
  content.delete('meta')
62
56
  end
63
57
 
64
- content.each_pair { |key, value| xml_string += xml_item(key, value, root) }
58
+ content.each_pair { |key, value| xml_string << xml_item(key, value) }
65
59
  xml_string
66
60
  end
67
61
 
68
- def array_to_xml(content, root='results', item='item')
62
+ def array_to_xml(content, item='item')
69
63
  xml_string = ''
70
- content.each { |value| xml_string += xml_item(item, value, root) }
64
+ content.each { |value| xml_string << xml_item(item, value) }
71
65
  xml_string
72
66
  end
73
67
 
74
68
  def xml_header(root)
75
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
76
- "<#{root} xmlns:opensearch='http://a9.com/-/spec/opensearch/1.1/'\n" +
77
- " xmlns:postrank='http://www.postrank.com/xsd/2007-11-30/postrank'>\n"
69
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<#{root}>"
78
70
  end
79
71
 
80
72
  def xml_footer(root)
81
73
  "</#{root}>"
82
74
  end
83
75
 
84
- def xml_item(key, value, root)
85
- "<#{key}>#{to_xml(value, true, root)}</#{key}>\n"
76
+ def xml_item(key, value)
77
+ "<#{key}>#{to_xml(value, true)}</#{key}>\n"
86
78
  end
87
79
  end
88
80
  end
@@ -0,0 +1,27 @@
1
+ require 'yaml'
2
+
3
+ module Goliath
4
+ module Rack
5
+ module Formatters
6
+ # A YAML formatter.
7
+ #
8
+ # @example
9
+ # use Goliath::Rack::Formatters::YAML
10
+ class YAML
11
+ include Goliath::Rack::AsyncMiddleware
12
+
13
+ def post_process(env, status, headers, body)
14
+ if yaml_response?(headers)
15
+ body = [body.to_yaml]
16
+ end
17
+ [status, headers, body]
18
+ end
19
+
20
+ def yaml_response?(headers)
21
+ headers['Content-Type'] =~ %r{^text/yaml}
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -6,19 +6,7 @@ module Goliath
6
6
  # use Goliath::Rack::JSONP
7
7
  #
8
8
  class JSONP
9
- def initialize(app)
10
- @app = app
11
- end
12
-
13
- def call(env)
14
- async_cb = env['async.callback']
15
-
16
- env['async.callback'] = Proc.new do |status, headers, body|
17
- async_cb.call(post_process(env, status, headers, body))
18
- end
19
- status, headers, body = @app.call(env)
20
- post_process(env, status, headers, body)
21
- end
9
+ include Goliath::Rack::AsyncMiddleware
22
10
 
23
11
  def post_process(env, status, headers, body)
24
12
  return [status, headers, body] unless env.params['callback']
@@ -14,40 +14,68 @@ module Goliath
14
14
  # use Goliath::Rack::Params
15
15
  #
16
16
  class Params
17
- def initialize(app)
18
- @app = app
19
- end
17
+ module Parser
18
+ def retrieve_params(env)
19
+ params = env['params'] || {}
20
+ params.merge!(::Rack::Utils.parse_nested_query(env['QUERY_STRING']))
20
21
 
21
- def call(env)
22
- env['params'] = retrieve_params(env)
23
- @app.call(env)
24
- end
22
+ if env['rack.input']
23
+ post_params = ::Rack::Utils::Multipart.parse_multipart(env)
24
+ unless post_params
25
+ body = env['rack.input'].read
26
+ env['rack.input'].rewind
25
27
 
26
- def retrieve_params(env)
27
- params = {}
28
- params.merge!(::Rack::Utils.parse_nested_query(env['QUERY_STRING']))
29
-
30
- if env['rack.input']
31
- post_params = ::Rack::Utils::Multipart.parse_multipart(env)
32
- unless post_params
33
- body = env['rack.input'].read
34
- env['rack.input'].rewind
35
-
36
- post_params = case(env['CONTENT_TYPE'])
37
- when URL_ENCODED then
38
- ::Rack::Utils.parse_nested_query(body)
39
- when JSON_ENCODED then
40
- MultiJson.decode(body)
41
- else
42
- {}
28
+ unless body.empty?
29
+ begin
30
+ post_params = case(env['CONTENT_TYPE'])
31
+ when URL_ENCODED then
32
+ ::Rack::Utils.parse_nested_query(body)
33
+ when JSON_ENCODED then
34
+ MultiJson.decode(body)
35
+ else
36
+ {}
37
+ end
38
+ rescue StandardError => e
39
+ raise Goliath::Validation::BadRequestError, "Invalid parameters: #{e.class.to_s}"
40
+ end
41
+ else
42
+ post_params = {}
43
+ end
43
44
  end
45
+
46
+ params.merge!(post_params)
44
47
  end
45
48
 
46
- params.merge!(post_params)
49
+ indifferent_params(params)
47
50
  end
48
51
 
49
- params
52
+ def indifferent_params(params)
53
+ params = indifferent_hash.merge(params)
54
+ params.each do |key, value|
55
+ next unless value.is_a?(Hash)
56
+ params[key] = indifferent_params(value)
57
+ end
58
+ end
59
+
60
+ # Creates a Hash with indifferent access.
61
+ def indifferent_hash
62
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
63
+ end
64
+ end
65
+
66
+ include Goliath::Rack::Validator
67
+ include Parser
68
+
69
+ def initialize(app)
70
+ @app = app
71
+ end
72
+
73
+ def call(env)
74
+ Goliath::Rack::Validator.safely(env) do
75
+ env['params'] = retrieve_params(env)
76
+ @app.call(env)
77
+ end
50
78
  end
51
79
  end
52
80
  end
53
- end
81
+ end
@@ -11,23 +11,13 @@ module Goliath
11
11
  #
12
12
  class Render
13
13
  include ::Rack::RespondTo
14
+ include Goliath::Rack::AsyncMiddleware
14
15
 
15
16
  def initialize(app, types = nil)
16
17
  @app = app
17
18
  ::Rack::RespondTo.media_types = [types].flatten if types
18
19
  end
19
20
 
20
- def call(env)
21
- async_cb = env['async.callback']
22
-
23
- env['async.callback'] = Proc.new do |status, headers, body|
24
- async_cb.call(post_process(env, status, headers, body))
25
- end
26
-
27
- status, headers, body = @app.call(env)
28
- post_process(env, status, headers, body)
29
- end
30
-
31
21
  def post_process(env, status, headers, body)
32
22
  ::Rack::RespondTo.env = env
33
23
 
@@ -35,11 +25,9 @@ module Goliath
35
25
  # setting of selected_media_type, so it's required
36
26
 
37
27
  respond_to do |format|
38
- format.json { body }
39
- format.html { body }
40
- format.xml { body }
41
- format.rss { body }
42
- format.js { body }
28
+ ::Rack::RespondTo.media_types.each do |type|
29
+ format.send(type, Proc.new { body })
30
+ end
43
31
  end
44
32
 
45
33
  extra = { 'Content-Type' => get_content_type(env),
@@ -50,14 +38,17 @@ module Goliath
50
38
  end
51
39
 
52
40
  def get_content_type(env)
53
- fmt = env.params['format']
54
- fmt = fmt.last if fmt.is_a?(Array)
41
+ type = if env.respond_to? :params
42
+ fmt = env.params['format']
43
+ fmt = fmt.last if fmt.is_a?(Array)
55
44
 
56
- type = if fmt.nil? || fmt =~ /^\s*$/
57
- ::Rack::RespondTo.selected_media_type
58
- else
59
- ::Rack::RespondTo::MediaType(fmt)
45
+ if !fmt.nil? && fmt !~ /^\s*$/
46
+ ::Rack::RespondTo::MediaType(fmt)
47
+ end
60
48
  end
49
+
50
+ type = ::Rack::RespondTo.env['HTTP_ACCEPT'] if type.nil?
51
+ type = ::Rack::RespondTo.selected_media_type if type == '*/*'
61
52
 
62
53
  "#{type}; charset=utf-8"
63
54
  end