goliath 0.9.0

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 (84) hide show
  1. data/.gitignore +15 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +2 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +66 -0
  6. data/README.md +86 -0
  7. data/Rakefile +18 -0
  8. data/examples/activerecord/config/srv.rb +7 -0
  9. data/examples/activerecord/srv.rb +37 -0
  10. data/examples/async_upload.rb +34 -0
  11. data/examples/conf_test.rb +27 -0
  12. data/examples/config/conf_test.rb +12 -0
  13. data/examples/config/echo.rb +1 -0
  14. data/examples/config/http_log.rb +7 -0
  15. data/examples/config/shared.rb +5 -0
  16. data/examples/custom_server.rb +57 -0
  17. data/examples/echo.rb +37 -0
  18. data/examples/gziped.rb +40 -0
  19. data/examples/hello_world.rb +10 -0
  20. data/examples/http_log.rb +85 -0
  21. data/examples/rack_routes.rb +44 -0
  22. data/examples/stream.rb +37 -0
  23. data/examples/valid.rb +19 -0
  24. data/goliath.gemspec +41 -0
  25. data/lib/goliath.rb +38 -0
  26. data/lib/goliath/api.rb +165 -0
  27. data/lib/goliath/application.rb +90 -0
  28. data/lib/goliath/connection.rb +94 -0
  29. data/lib/goliath/constants.rb +51 -0
  30. data/lib/goliath/env.rb +92 -0
  31. data/lib/goliath/goliath.rb +49 -0
  32. data/lib/goliath/headers.rb +37 -0
  33. data/lib/goliath/http_status_codes.rb +44 -0
  34. data/lib/goliath/plugins/latency.rb +33 -0
  35. data/lib/goliath/rack/default_mime_type.rb +30 -0
  36. data/lib/goliath/rack/default_response_format.rb +33 -0
  37. data/lib/goliath/rack/formatters/html.rb +90 -0
  38. data/lib/goliath/rack/formatters/json.rb +42 -0
  39. data/lib/goliath/rack/formatters/xml.rb +90 -0
  40. data/lib/goliath/rack/heartbeat.rb +23 -0
  41. data/lib/goliath/rack/jsonp.rb +38 -0
  42. data/lib/goliath/rack/params.rb +30 -0
  43. data/lib/goliath/rack/render.rb +66 -0
  44. data/lib/goliath/rack/tracer.rb +31 -0
  45. data/lib/goliath/rack/validation/boolean_value.rb +59 -0
  46. data/lib/goliath/rack/validation/default_params.rb +46 -0
  47. data/lib/goliath/rack/validation/numeric_range.rb +59 -0
  48. data/lib/goliath/rack/validation/request_method.rb +33 -0
  49. data/lib/goliath/rack/validation/required_param.rb +54 -0
  50. data/lib/goliath/rack/validation/required_value.rb +58 -0
  51. data/lib/goliath/rack/validation_error.rb +38 -0
  52. data/lib/goliath/request.rb +199 -0
  53. data/lib/goliath/response.rb +93 -0
  54. data/lib/goliath/runner.rb +236 -0
  55. data/lib/goliath/server.rb +149 -0
  56. data/lib/goliath/test_helper.rb +118 -0
  57. data/lib/goliath/version.rb +4 -0
  58. data/spec/integration/async_request_processing.rb +23 -0
  59. data/spec/integration/echo_spec.rb +27 -0
  60. data/spec/integration/keepalive_spec.rb +28 -0
  61. data/spec/integration/pipelining_spec.rb +43 -0
  62. data/spec/integration/valid_spec.rb +24 -0
  63. data/spec/spec_helper.rb +6 -0
  64. data/spec/unit/connection_spec.rb +59 -0
  65. data/spec/unit/env_spec.rb +55 -0
  66. data/spec/unit/headers_spec.rb +53 -0
  67. data/spec/unit/rack/default_mime_type_spec.rb +34 -0
  68. data/spec/unit/rack/formatters/json_spec.rb +54 -0
  69. data/spec/unit/rack/formatters/xml_spec.rb +66 -0
  70. data/spec/unit/rack/heartbeat_spec.rb +47 -0
  71. data/spec/unit/rack/params_spec.rb +94 -0
  72. data/spec/unit/rack/render_spec.rb +87 -0
  73. data/spec/unit/rack/validation/boolean_value_spec.rb +54 -0
  74. data/spec/unit/rack/validation/default_params_spec.rb +71 -0
  75. data/spec/unit/rack/validation/numeric_range_spec.rb +96 -0
  76. data/spec/unit/rack/validation/request_method_spec.rb +47 -0
  77. data/spec/unit/rack/validation/required_param_spec.rb +92 -0
  78. data/spec/unit/rack/validation/required_value_spec.rb +99 -0
  79. data/spec/unit/rack/validation_error_spec.rb +40 -0
  80. data/spec/unit/request_spec.rb +59 -0
  81. data/spec/unit/response_spec.rb +35 -0
  82. data/spec/unit/runner_spec.rb +129 -0
  83. data/spec/unit/server_spec.rb +137 -0
  84. metadata +409 -0
@@ -0,0 +1,33 @@
1
+ module Goliath
2
+ module Plugin
3
+ # Report latency information about the EventMachine reactor to the log file.
4
+ #
5
+ # @example
6
+ # plugin Goliath::Plugin::Latency
7
+ #
8
+ class Latency
9
+ # Called by the framework to initialize the plugin
10
+ #
11
+ # @param port [Integer] Unused
12
+ # @param config [Hash] The server configuration data
13
+ # @param status [Hash] A status hash
14
+ # @param logger [Log4R::Logger] The logger
15
+ # @return [Goliath::Plugin::Latency] An instance of the Goliath::Plugin::Latency plugin
16
+ def initialize(port, config, status, logger)
17
+ @status = status
18
+ @config = config
19
+ @logger = logger
20
+
21
+ @last = Time.now.to_f
22
+ end
23
+
24
+ # Called automatically to start the plugin
25
+ def run
26
+ EM.add_periodic_timer(1) do
27
+ @logger.info "LATENCY: #{Time.now.to_f - @last}"
28
+ @last = Time.now.to_f
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ require 'rack/mime'
2
+ require 'rack/respond_to'
3
+
4
+ module Goliath
5
+ module Rack
6
+ # Does some basic cleanup / handling of the HTTP_ACCEPT header.
7
+ # This will remove gzip, deflate, compressed and identity. If
8
+ # there are no values left the header will be set to \*/\*.
9
+ #
10
+ # @example
11
+ # use Goliath::Rack::DefaultMimeType
12
+ #
13
+ class DefaultMimeType
14
+ def initialize(app)
15
+ @app = app
16
+ end
17
+
18
+ def call(env)
19
+ accept = env['HTTP_ACCEPT'] || ''
20
+ accept = accept.split(/\s*,\s*/)
21
+ accept.delete_if { |a| a =~ /gzip|deflate|compressed|identity/ }
22
+ accept = accept.join(", ")
23
+
24
+ env['HTTP_ACCEPT'] = accept
25
+ env['HTTP_ACCEPT'] = '*/*' if env['HTTP_ACCEPT'] == ''
26
+ @app.call(env)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module Goliath
2
+ module Rack
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
17
+
18
+ def post_process(status, headers, body)
19
+ return [status, headers, body] if body.respond_to?(:to_ary)
20
+
21
+ new_body = []
22
+ if body.respond_to?(:each)
23
+ body.each { |chunk| new_body << chunk }
24
+ else
25
+ new_body << body
26
+ end
27
+ new_body.collect! { |item| item.to_s }
28
+
29
+ [status, headers, new_body.flatten]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,90 @@
1
+ require 'rack'
2
+
3
+ module Goliath
4
+ module Rack
5
+ module Formatters
6
+ # A simple HTML formatter. This doesn't try to be fancy and just turns
7
+ # Hashes into tables, Arrays into ordered lists and strings are output
8
+ # as HTML escaped strings.
9
+ #
10
+ # @example
11
+ # use Goliath::Rack::Formatters::HTML
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
30
+
31
+ def post_process(status, headers, body)
32
+ body = [to_html(body, false)] if html_response?(headers)
33
+ [status, headers, body]
34
+ end
35
+
36
+ def html_response?(headers)
37
+ headers['Content-Type'] =~ %r{^text/html}
38
+ end
39
+
40
+ def to_html(content, fragment=true)
41
+ html_string = ''
42
+ html_string += html_header unless fragment
43
+
44
+ html_string += case(content.class.to_s)
45
+ when "Hash" then hash_to_html(content)
46
+ when "Array" then array_to_html(content)
47
+ when "String" then string_to_html(content)
48
+ else string_to_html(content)
49
+ end
50
+
51
+ html_string += html_footer unless fragment
52
+ html_string
53
+ end
54
+
55
+ def string_to_html(content)
56
+ ::Rack::Utils.escape_html(content.to_s)
57
+ end
58
+
59
+ def hash_to_html(content)
60
+ html_string = "<table>\n"
61
+ if content.key?('meta')
62
+ html_string += "<tr><td>meta</td><td>\n"
63
+ html_string += to_html(content['meta'])
64
+ html_string += "</td></tr>\n"
65
+ content.delete('meta')
66
+ end
67
+
68
+ content.each_pair { |key, value| html_string += "<tr><td>#{to_html(key)}</td><td>#{to_html(value)}</td></tr>\n" }
69
+ html_string += "</table>\n"
70
+ html_string
71
+ end
72
+
73
+ def array_to_html(content)
74
+ html_string = '<ol>\n'
75
+ content.each { |value| html_string += "<li>#{to_html(value)}</li>\n" }
76
+ html_string +="</ol>\n"
77
+ html_string
78
+ end
79
+
80
+ def html_header
81
+ "<html><body>"
82
+ end
83
+
84
+ def html_footer
85
+ "</body></html>"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,42 @@
1
+ require 'multi_json'
2
+
3
+ module Goliath
4
+ module Rack
5
+ module Formatters
6
+ # A JSON formatter. Uses MultiJson so you can use the JSON
7
+ # encoder that is right for your project.
8
+ #
9
+ # @example
10
+ # use Goliath::Rack::Formatters::JSON
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
28
+
29
+ def post_process(status, headers, body)
30
+ if json_response?(headers)
31
+ body = [MultiJson.encode(body)]
32
+ end
33
+ [status, headers, body]
34
+ end
35
+
36
+ def json_response?(headers)
37
+ headers['Content-Type'] =~ %r{^application/(json|javascript)}
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,90 @@
1
+ require 'rack'
2
+
3
+ module Goliath
4
+ module Rack
5
+ module Formatters
6
+ # A XML formatter. Attempts to convert your data into
7
+ # an XML document.
8
+ #
9
+ # @example
10
+ # use Goliath::Rack::Formatters::XML
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)
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
28
+
29
+ def post_process(status, headers, body)
30
+ body = [to_xml(body, false)] if xml_response?(headers)
31
+ [status, headers, body]
32
+ end
33
+
34
+ def xml_response?(headers)
35
+ headers['Content-Type'] =~ %r{^application/xml}
36
+ end
37
+
38
+ def to_xml(content, fragment=true, root='results', item='item')
39
+ xml_string = ''
40
+ xml_string += xml_header(root) unless fragment
41
+
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)
45
+ when "String" then string_to_xml(content)
46
+ else string_to_xml(content)
47
+ end
48
+
49
+ xml_string += xml_footer(root) unless fragment
50
+ xml_string
51
+ end
52
+
53
+ def string_to_xml(content)
54
+ ::Rack::Utils.escape_html(content.to_s)
55
+ end
56
+
57
+ def hash_to_xml(content, root='results', item='item')
58
+ xml_string = ''
59
+ if content.key?('meta')
60
+ xml_string += xml_item('meta', content['meta'], root)
61
+ content.delete('meta')
62
+ end
63
+
64
+ content.each_pair { |key, value| xml_string += xml_item(key, value, root) }
65
+ xml_string
66
+ end
67
+
68
+ def array_to_xml(content, root='results', item='item')
69
+ xml_string = ''
70
+ content.each { |value| xml_string += xml_item(item, value, root) }
71
+ xml_string
72
+ end
73
+
74
+ 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"
78
+ end
79
+
80
+ def xml_footer(root)
81
+ "</#{root}>"
82
+ end
83
+
84
+ def xml_item(key, value, root)
85
+ "<#{key}>#{to_xml(value, true, root)}</#{key}>\n"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,23 @@
1
+ module Goliath
2
+ module Rack
3
+ # A heartbeat mechanism for the server. This will add a _/status_ endpoint
4
+ # that returns status 200 and content OK when executed.
5
+ #
6
+ # @example
7
+ # use Goliath::Rack::Heartbeat
8
+ #
9
+ class Heartbeat
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ if env['PATH_INFO'] == '/status'
16
+ [200, {}, 'OK']
17
+ else
18
+ @app.call(env)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ module Goliath
2
+ module Rack
3
+ # A middleware to wrap the response into a JSONP callback.
4
+ #
5
+ # @example
6
+ # use Goliath::Rack::JSONP
7
+ #
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
22
+
23
+ def post_process(env, status, headers, body)
24
+ return [status, headers, body] unless env.params['callback']
25
+
26
+ response = ""
27
+ if body.respond_to?(:each)
28
+ body.each { |s| response << s }
29
+ else
30
+ response = body
31
+ end
32
+
33
+ [status, headers, ["#{env.params['callback']}(#{response})"]]
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,30 @@
1
+ require 'rack/utils'
2
+
3
+ module Goliath
4
+ module Rack
5
+ # A middle ware to parse params. This will parse both the
6
+ # query string parameters and the body and place them into
7
+ # the _params_ hash of the Goliath::Env for the request.
8
+ #
9
+ # @example
10
+ # use Goliath::Rack::Params
11
+ #
12
+ class Params
13
+ def initialize(app)
14
+ @app = app
15
+ end
16
+
17
+ def call(env)
18
+ env['params'] = retrieve_params(env)
19
+ @app.call(env)
20
+ end
21
+
22
+ def retrieve_params(env)
23
+ params = {}
24
+ params.merge!(::Rack::Utils.parse_query(env['QUERY_STRING']))
25
+ params.merge!(::Rack::Utils.parse_query(env['rack.input'].read)) unless env['rack.input'].nil?
26
+ params
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ require 'rack/mime'
2
+ require 'rack/respond_to'
3
+
4
+ module Goliath
5
+ module Rack
6
+ # The render middleware will set the Content-Type of the response
7
+ # based on the provided HTTP_ACCEPT headers.
8
+ #
9
+ # @example
10
+ # use Goliath::Rack::Render
11
+ #
12
+ class Render
13
+ include ::Rack::RespondTo
14
+
15
+ def initialize(app, types = nil)
16
+ @app = app
17
+ ::Rack::RespondTo.media_types = [types].flatten if types
18
+ end
19
+
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
+ def post_process(env, status, headers, body)
32
+ ::Rack::RespondTo.env = env
33
+
34
+ # the respond_to block is what actually triggers the
35
+ # setting of selected_media_type, so it's required
36
+
37
+ respond_to do |format|
38
+ format.json { body }
39
+ format.html { body }
40
+ format.xml { body }
41
+ format.rss { body }
42
+ format.js { body }
43
+ end
44
+
45
+ extra = { 'Content-Type' => get_content_type(env),
46
+ 'Server' => 'PostRank Goliath API Server',
47
+ 'Vary' => [headers.delete('Vary'), 'Accept'].compact.join(',') }
48
+
49
+ [status, extra.merge(headers), body]
50
+ end
51
+
52
+ def get_content_type(env)
53
+ fmt = env.params['format']
54
+ fmt = fmt.last if fmt.is_a?(Array)
55
+
56
+ type = if fmt.nil? || fmt =~ /^\s*$/
57
+ ::Rack::RespondTo.selected_media_type
58
+ else
59
+ ::Rack::RespondTo::MediaType(fmt)
60
+ end
61
+
62
+ "#{type}; charset=utf-8"
63
+ end
64
+ end
65
+ end
66
+ end