goliath 0.9.1 → 0.9.2

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 (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,37 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.dirname(__FILE__)+'/../../lib'
3
+ require File.dirname(__FILE__)+'/rasterize'
4
+
5
+ require 'goliath'
6
+ require 'em-synchrony/em-http'
7
+ require 'postrank-uri'
8
+
9
+ #
10
+ # Aroundware: while the Rasterize API is processing, this uses http://is.gd to
11
+ # generate a shortened link, stuffing it in the header. Both requests happen
12
+ # simultaneously.
13
+ #
14
+ class ShortenURL < Goliath::Synchrony::MultiReceiver
15
+ SHORTENER_URL_BASE = 'http://is.gd/create.php'
16
+
17
+ def pre_process
18
+ target_url = PostRank::URI.clean(env.params['url'])
19
+ shortener_request = EM::HttpRequest.new(SHORTENER_URL_BASE).aget(:query => { :format => 'simple', :url => target_url })
20
+ enqueue :shortener, shortener_request
21
+ end
22
+
23
+ def post_process
24
+ if successes[:shortener]
25
+ headers['X-Shortened-URI'] = successes[:shortener].response
26
+ end
27
+ [status, headers, body]
28
+ end
29
+ end
30
+
31
+ class RasterizeAndShorten < Rasterize
32
+ use Goliath::Rack::Params
33
+ use Goliath::Rack::Validation::RequestMethod, %w(GET)
34
+ use Goliath::Rack::Validation::RequiredParam, {:key => 'url'}
35
+ #
36
+ use Goliath::Rack::AsyncAroundware, ShortenURL
37
+ end
@@ -21,7 +21,7 @@ class Stream < Goliath::API
21
21
  def response(env)
22
22
  i = 0
23
23
  pt = EM.add_periodic_timer(1) do
24
- env.stream_send("#{i} ")
24
+ env.stream_send("#{i}\n")
25
25
  i += 1
26
26
  end
27
27
 
@@ -32,6 +32,6 @@ class Stream < Goliath::API
32
32
  env.stream_close
33
33
  end
34
34
 
35
- [200, {}, Goliath::Response::STREAMING]
35
+ streaming_response(202, {'X-Stream' => 'Goliath'})
36
36
  end
37
37
  end
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ $:<< '../lib' << 'lib'
3
+
4
+ # A simple dashboard for goliath
5
+ # See
6
+ # examples/views -- templates
7
+ # examples/public -- static files
8
+ # examples/config/template.rb -- configuration
9
+ #
10
+ # The templating is based on, but not as fancy-pants as, Sinatra's. Notably,
11
+ # your template's extension must match the engine (foo.markdown, not foo.md)
12
+
13
+ require 'tilt'
14
+ # use bluecloth as default markdown renderer
15
+ require 'bluecloth'
16
+ Tilt.register 'markdown', Tilt::BlueClothTemplate
17
+ require 'yajl/json_gem'
18
+
19
+ require 'goliath'
20
+ require 'goliath/rack/templates'
21
+ require 'goliath/plugins/latency'
22
+
23
+ class Template < Goliath::API
24
+ include Goliath::Rack::Templates # render templated files from ./views
25
+
26
+ use(Rack::Static, # render static files from ./public
27
+ :root => Goliath::Application.app_path("public"),
28
+ :urls => ["/favicon.ico", '/stylesheets', '/javascripts', '/images'])
29
+
30
+ plugin Goliath::Plugin::Latency # ask eventmachine reactor to track its latency
31
+
32
+ def recent_latency
33
+ Goliath::Plugin::Latency.recent_latency
34
+ end
35
+
36
+ def response(env)
37
+ case env['PATH_INFO']
38
+ when '/' then [200, {}, haml(:root)]
39
+ when '/debug' then [200, {}, haml(:debug)]
40
+ when '/oops' then [200, {}, haml(:no_such_template)] # will 500
41
+ when '/joke' then
42
+ [200, {}, markdown(:joke, :locals => {:title => "HERE IS A JOKE"})]
43
+ when '/erb_me' then
44
+ [200, {}, markdown(:joke, :layout_engine => :erb, :locals => {:title => "HERE IS A JOKE"})]
45
+ else raise Goliath::Validation::NotFoundError
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env ruby
2
+ $:<< '../lib' << 'lib'
3
+
4
+ require 'goliath'
5
+
6
+ #
7
+ # A test endpoint that will:
8
+ # * with 'delay' parameter, take the given time to respond
9
+ # * with 'drop' parameter, drop connection before responding
10
+ # * with 'fail' parameter, raise an error of the given type (eg 400 raises a BadRequestError)
11
+ # * with 'echo_status', 'echo_headers', or 'echo_body' parameter, replace the given component directly.
12
+ #
13
+
14
+ # If the delay param is given, sleep for that many seconds
15
+ #
16
+ # Note that though this is non-blocking, the call chain does *not* proceed in parallel
17
+ class Delay
18
+ include Goliath::Rack::AsyncMiddleware
19
+
20
+ def post_process(env, status, headers, body)
21
+ if delay = env.params['delay']
22
+ delay = [0, [delay.to_f, 5].min].max
23
+ EM::Synchrony.sleep(delay)
24
+ body.merge!(:delay => delay, :actual => (Time.now.to_f - env[:start_time]))
25
+ end
26
+ [status, headers, body]
27
+ end
28
+ end
29
+
30
+ # if the middleware_failure parameter is given, raise an error causing that
31
+ # status code
32
+ class MiddlewareFailure
33
+ include Goliath::Rack::AsyncMiddleware
34
+
35
+ def call(env)
36
+ if code = env.params['fail']
37
+ raise Goliath::Validation::Error.new(code.to_i, "Middleware error #{code}")
38
+ end
39
+ super
40
+ end
41
+ end
42
+
43
+ # if the drop_pre parameter is given, close the connection before headers are sent
44
+ # This works, but probably does awful awful things to Goliath's innards
45
+ class DropConnection
46
+ include Goliath::Rack::AsyncMiddleware
47
+
48
+ def call(env)
49
+ if env.params['drop'].to_s == 'true'
50
+ env.logger.info "Dropping connection"
51
+ env.stream_close
52
+ [0, {}, '']
53
+ else
54
+ super
55
+ end
56
+ end
57
+ end
58
+
59
+ # if echo_status, echo_headers or echo_body are given, blindly substitute their
60
+ # value, clobbering whatever was there.
61
+ #
62
+ # If you are going to use echo_headers you probably need to use a JSON post body:
63
+ # curl -v -H "Content-Type: application/json" --data-ascii '{"echo_headers":{"X-Question":"What is brown and sticky"},"echo_body":{"answer":"a stick"}}' 'http://127.0.0.1:9001/'
64
+ #
65
+ class Echo
66
+ include Goliath::Rack::AsyncMiddleware
67
+
68
+ def post_process env, status, headers, body
69
+ if env.params['echo_status']
70
+ status = env.params['echo_status'].to_i
71
+ end
72
+ if env.params['echo_headers']
73
+ headers = env.params['echo_headers']
74
+ end
75
+ if env.params['echo_body']
76
+ body = env.params['echo_body']
77
+ end
78
+ [status, headers, body]
79
+ end
80
+ end
81
+
82
+ # Rescue validation errors and send them up the chain as normal non-200 responses
83
+ class ExceptionHandler
84
+ include Goliath::Rack::AsyncMiddleware
85
+ include Goliath::Rack::Validator
86
+
87
+ def call(env)
88
+ begin
89
+ super
90
+ rescue Goliath::Validation::Error => e
91
+ validation_error(e.status_code, e.message)
92
+ end
93
+ end
94
+ end
95
+
96
+ class TestRig < Goliath::API
97
+ use Goliath::Rack::Tracer # log trace statistics
98
+ use Goliath::Rack::Params # parse & merge query and body parameters
99
+ #
100
+ use Goliath::Rack::DefaultMimeType # cleanup accepted media types
101
+ use Goliath::Rack::Render, 'json' # auto-negotiate response format
102
+ #
103
+ use ExceptionHandler # turn raised errors into HTTP responses
104
+ use MiddlewareFailure # make response fail if 'fail' param
105
+ use DropConnection # drop connection if 'drop' param
106
+ use Echo # replace status, headers or body if 'echo_status' etc given
107
+ use Delay # make response take X seconds if 'delay' param
108
+
109
+ def on_headers(env, headers)
110
+ env['client-headers'] = headers
111
+ end
112
+
113
+ def response(env)
114
+ query_params = env.params.collect { |param| param.join(": ") }
115
+ query_headers = env['client-headers'].collect { |param| param.join(": ") }
116
+
117
+ headers = {
118
+ "Special" => "Header",
119
+ "Params" => query_params.join("|"),
120
+ "Path" => env[Goliath::Request::REQUEST_PATH],
121
+ "Headers" => query_headers.join("|"),
122
+ "Method" => env[Goliath::Request::REQUEST_METHOD]}
123
+ [200, headers, headers.dup]
124
+ end
125
+ end
@@ -5,10 +5,12 @@ require 'goliath'
5
5
 
6
6
  class Valid < Goliath::API
7
7
  use Goliath::Rack::Params
8
- use Goliath::Rack::ValidationError
9
-
10
8
  use Goliath::Rack::Validation::RequiredParam, {:key => 'test'}
11
9
 
10
+ # If you are using Golaith version <=0.9.1 you need to use Goliath::Rack::ValidationError
11
+ # to prevent the request from remaining open after an error occurs
12
+ #use Goliath::Rack::ValidationError
13
+
12
14
  def response(env)
13
15
  [200, {}, 'OK']
14
16
  end
@@ -0,0 +1,4 @@
1
+ %h1 Debug
2
+
3
+ %pre
4
+ = JSON.pretty_generate(env)
@@ -0,0 +1,13 @@
1
+ # Pirate Joke
2
+
3
+ Pirate walks into a bar with a steering wheel half-in, half-out of his pants.
4
+
5
+
6
+ Bartender says
7
+
8
+ "Hey Pirate, What's With The Steering Wheel?"
9
+
10
+
11
+ Pirate says
12
+
13
+ "Arr, I dunno matey -- but it's drivin' me nuts!"
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= title || 'Goliath' %></title>
5
+ </head>
6
+ <body lang='en'>
7
+ <h1 id='title'><%= title || 'Goliath' %> - I AM ERB</h1>
8
+
9
+ <%= yield %>
10
+
11
+ </body>
12
+ </html>
@@ -0,0 +1,39 @@
1
+ - title ||= 'Goliath'
2
+ !!! 5
3
+ %html
4
+ %head
5
+ %meta{ :charset => "utf-8" }/
6
+ %meta{ :content => "IE=edge,chrome=1", "http-equiv" => "X-UA-Compatible" }/
7
+
8
+ %title= title
9
+ <link href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAF0lEQVRIx2NgGAWjYBSMglEwCkbBSAcACBAAAeaR9cIAAAAASUVORK5CYII=" rel="icon" type="image/x-icon" />
10
+ %link{ :href => "/stylesheets/style.css", :rel => "stylesheet", :type => "text/css", :media => "all" }
11
+
12
+ %body{ :lang => 'en' }
13
+ #header-container
14
+ %header.wrapper
15
+ %h1#title
16
+ == <a href="/">#{title}</a>
17
+
18
+ %nav
19
+ %ul.menu
20
+ %li.item
21
+ <a href="/">Home</a>
22
+ %li.item
23
+ <a href="/debug">Debug</a>
24
+ %li &nbsp;
25
+
26
+ #main.wrapper{ :role => 'main' }
27
+ != yield
28
+
29
+ #footer-container
30
+ %footer.wrapper
31
+ .copyright
32
+ %p Copyright &copy; #{Time.now.strftime("%Y")}
33
+
34
+ %script{ :src => "http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js", :type => "text/javascript" }/
35
+ :javascript
36
+ !window.jQuery && document.write(unescape('%3Cscript src="/javascripts/jquery.min.js"%3E%3C/script%3E'))
37
+
38
+ /
39
+ haml layout
@@ -0,0 +1,28 @@
1
+ %h2 Routes
2
+
3
+ %ul
4
+ %li <a href="/joke">Tell me a joke</a>
5
+ %li <a href="/erb_me">Tell me a joke, but with a boring .erb layout</a>
6
+ %li <a href="/debug">debug info</a>
7
+ %li <a href="/oops">Template missing</a>
8
+
9
+ %h2 Dashboard
10
+
11
+ %table.vertical
12
+ %tr
13
+ %th Script
14
+ %td= $0
15
+ %tr
16
+ %th Server Port
17
+ %td= env['SERVER_PORT']
18
+ %tr
19
+ %th Latency
20
+ %td= recent_latency
21
+ %tr
22
+ %th Request took
23
+ %td= (Time.now.to_f - env[:start_time]).round(3)
24
+ %tr
25
+ %th Config
26
+ %td
27
+ %pre
28
+ = "\n"+JSON.pretty_generate(env.config)
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.required_ruby_version = '>=1.9.2'
16
16
 
17
- s.add_dependency 'eventmachine', '>= 1.0.0.beta.1'
17
+ s.add_dependency 'eventmachine', '>= 1.0.0.beta.3'
18
18
  s.add_dependency 'em-synchrony', '>= 0.3.0.beta.1'
19
19
  s.add_dependency 'http_parser.rb'
20
20
  s.add_dependency 'log4r'
@@ -24,7 +24,9 @@ Gem::Specification.new do |s|
24
24
  s.add_dependency 'rack-respond_to'
25
25
  s.add_dependency 'async-rack'
26
26
  s.add_dependency 'multi_json'
27
+ s.add_dependency 'http_router', '~> 0.8.9'
27
28
 
29
+ s.add_development_dependency 'rake', '0.8.7'
28
30
  s.add_development_dependency 'rspec', '>2.0'
29
31
  s.add_development_dependency 'nokogiri'
30
32
  s.add_development_dependency 'em-http-request', '>= 1.0.0.beta.1'
@@ -34,10 +36,15 @@ Gem::Specification.new do |s|
34
36
  s.add_development_dependency 'multipart_body'
35
37
  s.add_development_dependency 'amqp', '>=0.7.1'
36
38
 
39
+ s.add_development_dependency 'tilt', '>=1.2.2'
40
+ s.add_development_dependency 'haml', '>=3.0.25'
37
41
  s.add_development_dependency 'yard'
38
42
  s.add_development_dependency 'bluecloth'
39
43
 
40
- s.files = `git ls-files`.split("\n")
41
- s.test_files = `git ls-files -- spec/*`.split("\n")
44
+ ignores = File.readlines(".gitignore").grep(/\S+/).map {|s| s.chomp }
45
+ dotfiles = [".gemtest", ".gitignore", ".rspec", ".yardopts"]
46
+
47
+ s.files = Dir["**/*"].reject {|f| File.directory?(f) || ignores.any? {|i| File.fnmatch(i, f) } } + dotfiles
48
+ s.test_files = s.files.grep(/^spec\//)
42
49
  s.require_paths = ['lib']
43
50
  end
@@ -1,38 +1,2 @@
1
- require 'eventmachine'
2
- require 'http/parser'
3
- require 'async_rack'
4
- require 'stringio'
5
-
6
- require 'goliath/version'
7
- require 'goliath/goliath'
8
- require 'goliath/runner'
9
- require 'goliath/server'
10
- require 'goliath/constants'
11
- require 'goliath/connection'
12
- require 'goliath/request'
13
- require 'goliath/response'
14
- require 'goliath/headers'
15
- require 'goliath/http_status_codes'
16
-
17
- require 'goliath/rack/default_response_format'
18
- require 'goliath/rack/heartbeat'
19
- require 'goliath/rack/params'
20
- require 'goliath/rack/render'
21
- require 'goliath/rack/default_mime_type'
22
- require 'goliath/rack/tracer'
23
- require 'goliath/rack/validation_error'
24
- require 'goliath/rack/formatters/json'
25
- require 'goliath/rack/formatters/html'
26
- require 'goliath/rack/formatters/xml'
27
- require 'goliath/rack/jsonp'
28
-
29
- require 'goliath/rack/validation/request_method'
30
- require 'goliath/rack/validation/required_param'
31
- require 'goliath/rack/validation/required_value'
32
- require 'goliath/rack/validation/numeric_range'
33
- require 'goliath/rack/validation/default_params'
34
- require 'goliath/rack/validation/boolean_value'
35
-
36
1
  require 'goliath/api'
37
-
38
2
  require 'goliath/application'
@@ -1,5 +1,9 @@
1
+ require 'http_router'
2
+ require 'goliath/goliath'
1
3
  require 'goliath/response'
2
4
  require 'goliath/request'
5
+ require 'goliath/rack'
6
+ require 'goliath/validation'
3
7
 
4
8
  module Goliath
5
9
  # All Goliath APIs subclass Goliath::API. All subclasses _must_ override the
@@ -15,19 +19,33 @@ module Goliath
15
19
  # end
16
20
  #
17
21
  class API
22
+ include Goliath::Constants
23
+ include Goliath::Rack::Validator
24
+
18
25
  class << self
26
+ # Catches the userland class which inherits the Goliath API
27
+ #
28
+ # In case of further subclassing, the very last class encountered is used.
29
+ def inherited(subclass)
30
+ Goliath::Application.app_class = subclass.name if defined?(Goliath::Application)
31
+ end
32
+
19
33
  # Retrieves the middlewares defined by this API server
20
34
  #
21
35
  # @return [Array] array contains [middleware class, args, block]
22
36
  def middlewares
23
37
  @middlewares ||= []
24
- @middlewares.unshift([::Goliath::Rack::DefaultResponseFormat, nil, nil])
25
- @middlewares.unshift([::Rack::ContentLength, nil, nil])
26
38
 
27
- if Goliath.dev?
28
- reloader = @middlewares.detect {|mw| mw.first == ::Rack::Reloader}
29
- @middlewares.unshift([::Rack::Reloader, 0, nil]) if !reloader
30
- end
39
+ unless @loaded_default_middlewares
40
+ @middlewares.unshift([::Goliath::Rack::DefaultResponseFormat, nil, nil])
41
+ @middlewares.unshift([::Rack::ContentLength, nil, nil])
42
+
43
+ if Goliath.dev? && !@middlewares.detect {|mw| mw.first == ::Rack::Reloader}
44
+ @middlewares.unshift([::Rack::Reloader, 0, nil])
45
+ end
46
+
47
+ @loaded_default_middlewares = true
48
+ end
31
49
 
32
50
  @middlewares
33
51
  end
@@ -44,8 +62,16 @@ module Goliath
44
62
  # @param name [Class] The middleware class to use
45
63
  # @param args Any arguments to pass to the middeware
46
64
  # @param block A block to pass to the middleware
47
- def use(name, args = nil, &block)
65
+ def use(name, *args, &block)
48
66
  @middlewares ||= []
67
+
68
+ if name == Goliath::Rack::Render
69
+ [args].flatten.each do |type|
70
+ type = Goliath::Rack::Formatters.const_get type.upcase
71
+ @middlewares << [type, nil, nil]
72
+ end
73
+ end
74
+
49
75
  @middlewares << [name, args, block]
50
76
  end
51
77
 
@@ -69,11 +95,15 @@ module Goliath
69
95
 
70
96
  # Returns the router maps configured for the API
71
97
  #
72
- # @return [Array] array contains [path, block]
98
+ # @return [Array] array contains [path, klass, block]
73
99
  def maps
74
100
  @maps ||= []
75
101
  end
76
102
 
103
+ def maps?
104
+ !maps.empty?
105
+ end
106
+
77
107
  # Specify a router map to be used by the API
78
108
  #
79
109
  # @example
@@ -81,10 +111,53 @@ module Goliath
81
111
  # run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Version 0.1"]] }
82
112
  # end
83
113
  #
84
- # @param name [String] The URL path to map
114
+ # @example
115
+ # map '/user/:id', :id => /\d+/ do
116
+ # # params[:id] will be a number
117
+ # run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Loading user #{params[:id]}"]] }
118
+ # end
119
+ #
120
+ # @param name [String] The URL path to map.
121
+ # Optional parts are supported via <tt>(.:format)</tt>, variables as <tt>:var</tt> and globs via <tt>*remaining_path</tt>.
122
+ # Variables can be validated by supplying a Regexp.
123
+ # @param klass [Class] The class to retrieve the middlewares from
85
124
  # @param block The code to execute
86
- def map(name, &block)
87
- maps.push([name, block])
125
+ def map(name, *args, &block)
126
+ opts = args.last.is_a?(Hash) ? args.pop : {}
127
+ klass = args.first
128
+ if klass && block_given?
129
+ raise "Can't provide class and block to map"
130
+ end
131
+ maps.push([name, klass, opts, block])
132
+ end
133
+
134
+ [:get, :post, :head, :put, :delete].each do |http_method|
135
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
136
+ def #{http_method}(name, *args, &block)
137
+ opts = args.last.is_a?(Hash) ? args.pop : {}
138
+ klass = args.first
139
+ opts[:conditions] ||= {}
140
+ opts[:conditions][:request_method] = [#{http_method == :get ? "'HEAD', 'GET'" : http_method.to_s.upcase.inspect}]
141
+ map(name, klass, opts, &block)
142
+ end
143
+ EOT
144
+ end
145
+
146
+ def router
147
+ unless @router
148
+ @router = HttpRouter.new
149
+ @router.default(proc{ |env|
150
+ @router.routes.last.dest.call(env)
151
+ })
152
+ end
153
+ @router
154
+ end
155
+
156
+ # Use to define the 404 routing logic. As well, define any number of other paths to also run the not_found block.
157
+ def not_found(*other_paths, &block)
158
+ app = ::Rack::Builder.new(&block).to_app
159
+ router.default(app)
160
+ other_paths.each {|path| router.add(path).to(app) }
88
161
  end
89
162
  end
90
163
 
@@ -107,7 +180,7 @@ module Goliath
107
180
  #
108
181
  # @return [Goliath::Env] The current environment data for the request
109
182
  def env
110
- Thread.current[Goliath::Constants::GOLIATH_ENV]
183
+ Thread.current[GOLIATH_ENV]
111
184
  end
112
185
 
113
186
  # The API will proxy missing calls to the env object if possible.
@@ -128,32 +201,42 @@ module Goliath
128
201
  end
129
202
  end
130
203
 
204
+ # @param name [Symbol] The method to check if we respond to it.
205
+ # @return [Boolean] True if the API's method_missing responds to the method
206
+ def respond_to_missing?(name, *)
207
+ env.respond_to? name
208
+ end
209
+
131
210
  # {#call} is executed automatically by the middleware chain and will setup
132
211
  # the environment for the {#response} method to execute. This includes setting
133
- # up a new Fiber, handing any execptions thrown from the API and executing
212
+ # up a new Fiber, handing any exceptions thrown from the API and executing
134
213
  # the appropriate callback method for the API.
135
214
  #
136
215
  # @param env [Goliath::Env] The request environment
137
216
  # @return [Goliath::Connection::AsyncResponse] An async response.
138
217
  def call(env)
139
- Fiber.new {
140
- begin
141
- Thread.current[Goliath::Constants::GOLIATH_ENV] = env
142
- status, headers, body = response(env)
218
+ begin
219
+ Thread.current[GOLIATH_ENV] = env
220
+ status, headers, body = response(env)
143
221
 
222
+ if status
144
223
  if body == Goliath::Response::STREAMING
145
- env[Goliath::Constants::STREAM_START].call(status, headers)
224
+ env[STREAM_START].call(status, headers)
146
225
  else
147
- env[Goliath::Constants::ASYNC_CALLBACK].call([status, headers, body])
226
+ env[ASYNC_CALLBACK].call([status, headers, body])
148
227
  end
228
+ end
149
229
 
150
- rescue Exception => e
151
- env.logger.error(e.message)
152
- env.logger.error(e.backtrace.join("\n"))
230
+ rescue Goliath::Validation::Error => e
231
+ env[RACK_EXCEPTION] = e
232
+ env[ASYNC_CALLBACK].call(validation_error(e.status_code, e.message))
153
233
 
154
- env[Goliath::Constants::ASYNC_CALLBACK].call([400, {}, {:error => e.message}])
155
- end
156
- }.resume
234
+ rescue Exception => e
235
+ env.logger.error(e.message)
236
+ env.logger.error(e.backtrace.join("\n"))
237
+ env[RACK_EXCEPTION] = e
238
+ env[ASYNC_CALLBACK].call(validation_error(500, e.message))
239
+ end
157
240
 
158
241
  Goliath::Connection::AsyncResponse
159
242
  end
@@ -168,7 +251,35 @@ module Goliath
168
251
  # @return [Array] Array contains [Status code, Headers Hash, Body]
169
252
  def response(env)
170
253
  env.logger.error('You need to implement response')
171
- [400, {}, {:error => 'No response implemented'}]
254
+ raise Goliath::Validation::InternalServerError.new('No response implemented')
255
+ end
256
+
257
+ # Helper method for streaming response apis.
258
+ #
259
+ # @param status_code [Integer] The status code to return (200 by default).
260
+ # @param headers [Hash] Headers to return.
261
+ def streaming_response(status_code = 200, headers = {})
262
+ [status_code, headers, Goliath::Response::STREAMING]
263
+ end
264
+
265
+ # Helper method for chunked transfer streaming response apis
266
+ #
267
+ # Chunked transfer streaming is transparent to all clients (it's just as
268
+ # good as a normal response), but allows an aware client to begin consuming
269
+ # the stream even as it's produced.
270
+ #
271
+ # * http://en.wikipedia.org/wiki/Chunked_transfer_encoding
272
+ # * http://developers.sun.com/mobility/midp/questions/chunking/
273
+ # * http://blog.port80software.com/2006/11/08/
274
+ #
275
+ # @param status_code [Integer] The status code to return.
276
+ # @param headers [Hash] Headers to return. The Transfer-Encoding=chunked
277
+ # header is set for you.
278
+ #
279
+ # If you are using chunked streaming, you must use
280
+ # env.chunked_stream_send and env.chunked_stream_close
281
+ def chunked_streaming_response(status_code = 200, headers = {})
282
+ streaming_response(status_code, headers.merge(Goliath::Response::CHUNKED_STREAM_HEADERS))
172
283
  end
173
284
  end
174
285
  end