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.
- data/.gitignore +1 -0
- data/HISTORY +50 -0
- data/README.md +2 -0
- data/examples/activerecord/srv.rb +1 -3
- data/examples/async_aroundware_demo.rb +81 -0
- data/examples/async_upload.rb +1 -2
- data/examples/auth_and_rate_limit.rb +143 -0
- data/examples/chunked_streaming.rb +37 -0
- data/examples/conf_test.rb +1 -3
- data/examples/config/auth_and_rate_limit.rb +30 -0
- data/examples/config/template.rb +8 -0
- data/examples/content_stream.rb +1 -3
- data/examples/echo.rb +8 -6
- data/examples/env_use_statements.rb +17 -0
- data/examples/gziped.rb +1 -3
- data/examples/public/stylesheets/style.css +296 -0
- data/examples/rack_routes.rb +65 -3
- data/examples/rasterize/rasterize.js +15 -0
- data/examples/rasterize/rasterize.rb +36 -0
- data/examples/rasterize/rasterize_and_shorten.rb +37 -0
- data/examples/stream.rb +2 -2
- data/examples/template.rb +48 -0
- data/examples/test_rig.rb +125 -0
- data/examples/valid.rb +4 -2
- data/examples/views/debug.haml +4 -0
- data/examples/views/joke.markdown +13 -0
- data/examples/views/layout.erb +12 -0
- data/examples/views/layout.haml +39 -0
- data/examples/views/root.haml +28 -0
- data/goliath.gemspec +10 -3
- data/lib/goliath.rb +0 -36
- data/lib/goliath/api.rb +137 -26
- data/lib/goliath/application.rb +71 -21
- data/lib/goliath/connection.rb +4 -2
- data/lib/goliath/constants.rb +1 -0
- data/lib/goliath/env.rb +40 -1
- data/lib/goliath/goliath.rb +30 -15
- data/lib/goliath/headers.rb +2 -2
- data/lib/goliath/plugins/latency.rb +8 -2
- data/lib/goliath/rack.rb +18 -0
- data/lib/goliath/rack/async_aroundware.rb +56 -0
- data/lib/goliath/rack/async_middleware.rb +93 -0
- data/lib/goliath/rack/builder.rb +42 -0
- data/lib/goliath/rack/default_response_format.rb +3 -15
- data/lib/goliath/rack/formatters.rb +11 -0
- data/lib/goliath/rack/formatters/html.rb +2 -18
- data/lib/goliath/rack/formatters/json.rb +2 -17
- data/lib/goliath/rack/formatters/plist.rb +32 -0
- data/lib/goliath/rack/formatters/xml.rb +23 -31
- data/lib/goliath/rack/formatters/yaml.rb +27 -0
- data/lib/goliath/rack/jsonp.rb +1 -13
- data/lib/goliath/rack/params.rb +55 -27
- data/lib/goliath/rack/render.rb +13 -22
- data/lib/goliath/rack/templates.rb +357 -0
- data/lib/goliath/rack/tracer.rb +11 -12
- data/lib/goliath/rack/validation.rb +12 -0
- data/lib/goliath/rack/validation/default_params.rb +0 -2
- data/lib/goliath/rack/validation/numeric_range.rb +11 -2
- data/lib/goliath/rack/validation/request_method.rb +3 -2
- data/lib/goliath/rack/validation/required_param.rb +13 -11
- data/lib/goliath/rack/validation/required_value.rb +11 -15
- data/lib/goliath/rack/validator.rb +51 -0
- data/lib/goliath/request.rb +34 -20
- data/lib/goliath/response.rb +3 -2
- data/lib/goliath/runner.rb +5 -11
- data/lib/goliath/server.rb +2 -1
- data/lib/goliath/synchrony/mongo_receiver.rb +64 -0
- data/lib/goliath/synchrony/response_receiver.rb +64 -0
- data/lib/goliath/test_helper.rb +39 -21
- data/lib/goliath/validation.rb +2 -0
- data/lib/goliath/{rack/validation_error.rb → validation/error.rb} +0 -16
- data/lib/goliath/validation/standard_http_errors.rb +31 -0
- data/lib/goliath/version.rb +1 -1
- data/spec/integration/http_log_spec.rb +16 -16
- data/spec/integration/rack_routes_spec.rb +144 -0
- data/spec/integration/reloader_spec.rb +4 -4
- data/spec/integration/template_spec.rb +54 -0
- data/spec/integration/trace_spec.rb +23 -0
- data/spec/integration/valid_spec.rb +21 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/unit/api_spec.rb +30 -0
- data/spec/unit/rack/builder_spec.rb +40 -0
- data/spec/unit/rack/formatters/plist_spec.rb +51 -0
- data/spec/unit/rack/formatters/yaml_spec.rb +53 -0
- data/spec/unit/rack/params_spec.rb +22 -0
- data/spec/unit/rack/render_spec.rb +10 -5
- data/spec/unit/rack/validation/numeric_range_spec.rb +8 -1
- data/spec/unit/rack/validation/request_method_spec.rb +8 -8
- data/spec/unit/rack/validation/required_param_spec.rb +19 -15
- data/spec/unit/rack/validation/required_value_spec.rb +10 -13
- data/spec/unit/server_spec.rb +4 -4
- data/spec/unit/validation/standard_http_errors_spec.rb +21 -0
- metadata +177 -35
- 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
|
data/examples/stream.rb
CHANGED
@@ -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
|
-
|
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
|
data/examples/valid.rb
CHANGED
@@ -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,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
|
25
|
+
|
26
|
+
#main.wrapper{ :role => 'main' }
|
27
|
+
!= yield
|
28
|
+
|
29
|
+
#footer-container
|
30
|
+
%footer.wrapper
|
31
|
+
.copyright
|
32
|
+
%p Copyright © #{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)
|
data/goliath.gemspec
CHANGED
@@ -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.
|
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
|
-
|
41
|
-
|
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
|
data/lib/goliath.rb
CHANGED
@@ -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'
|
data/lib/goliath/api.rb
CHANGED
@@ -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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
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
|
-
# @
|
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
|
-
|
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[
|
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
|
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
|
-
|
140
|
-
|
141
|
-
|
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[
|
224
|
+
env[STREAM_START].call(status, headers)
|
146
225
|
else
|
147
|
-
env[
|
226
|
+
env[ASYNC_CALLBACK].call([status, headers, body])
|
148
227
|
end
|
228
|
+
end
|
149
229
|
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
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
|