Skip to content
This repository
Browse code

merge master

  • Loading branch information...
commit 360e28b9aaaae0c345934665449f7f733381814b 2 parents 8ea448c + be04426
dan sinclair dj2 authored

Showing 122 changed files with 4,717 additions and 593 deletions. Show diff stats Hide diff stats

  1. 0  .gemtest
  2. +13 9 .gitignore
  3. +0 3  Gemfile
  4. +64 0 HISTORY.md
  5. +21 13 README.md
  6. +1 0  Rakefile
  7. +1 5 examples/activerecord/srv.rb
  8. +28 0 examples/api_proxy.rb
  9. +85 0 examples/async_aroundware_demo.rb
  10. +1 6 examples/async_upload.rb
  11. +265 0 examples/auth_and_rate_limit.rb
  12. +37 0 examples/chunked_streaming.rb
  13. +1 4 examples/conf_test.rb
  14. +33 0 examples/config/auth_and_rate_limit.rb
  15. +29 0 examples/config/content_stream.rb
  16. +7 5 examples/config/http_log.rb
  17. +8 0 examples/config/template.rb
  18. +39 0 examples/content_stream.rb
  19. +37 0 examples/early_abort.rb
  20. +9 11 examples/echo.rb
  21. +20 0 examples/env_use_statements.rb
  22. +40 0 examples/favicon.rb
  23. +1 3 examples/gziped.rb
  24. +2 2 examples/http_log.rb
  25. BIN  examples/public/favicon.ico
  26. +296 0 examples/public/stylesheets/style.css
  27. +84 3 examples/rack_routes.rb
  28. +15 0 examples/rasterize/rasterize.js
  29. +37 0 examples/rasterize/rasterize.rb
  30. +42 0 examples/rasterize/rasterize_and_shorten.rb
  31. BIN  examples/rasterize/thumb/f7ad4cb03e5bfd0e2c43db8e598fb3cd.png
  32. +2 2 examples/stream.rb
  33. +48 0 examples/template.rb
  34. +125 0 examples/test_rig.rb
  35. +4 6 examples/valid.rb
  36. +4 0 examples/views/debug.haml
  37. +13 0 examples/views/joke.markdown
  38. +12 0 examples/views/layout.erb
  39. +39 0 examples/views/layout.haml
  40. +28 0 examples/views/root.haml
  41. +18 9 goliath.gemspec
  42. +0 36 lib/goliath.rb
  43. +155 23 lib/goliath/api.rb
  44. +71 21 lib/goliath/application.rb
  45. +13 11 lib/goliath/connection.rb
  46. +3 1 lib/goliath/constants.rb
  47. +133 0 lib/goliath/deprecated/async_aroundware.rb
  48. +84 0 lib/goliath/deprecated/mongo_receiver.rb
  49. +97 0 lib/goliath/deprecated/response_receiver.rb
  50. +52 1 lib/goliath/env.rb
  51. +30 15 lib/goliath/goliath.rb
  52. +2 2 lib/goliath/headers.rb
  53. +8 2 lib/goliath/plugins/latency.rb
  54. +23 0 lib/goliath/rack.rb
  55. +115 0 lib/goliath/rack/async_middleware.rb
  56. +228 0 lib/goliath/rack/barrier_aroundware.rb
  57. +60 0 lib/goliath/rack/barrier_aroundware_factory.rb
  58. +58 0 lib/goliath/rack/builder.rb
  59. +3 15 lib/goliath/rack/default_response_format.rb
  60. +11 0 lib/goliath/rack/formatters.rb
  61. +2 18 lib/goliath/rack/formatters/html.rb
  62. +2 17 lib/goliath/rack/formatters/json.rb
  63. +32 0 lib/goliath/rack/formatters/plist.rb
  64. +23 31 lib/goliath/rack/formatters/xml.rb
  65. +27 0 lib/goliath/rack/formatters/yaml.rb
  66. +8 5 lib/goliath/rack/heartbeat.rb
  67. +1 13 lib/goliath/rack/jsonp.rb
  68. +61 10 lib/goliath/rack/params.rb
  69. +13 22 lib/goliath/rack/render.rb
  70. +114 0 lib/goliath/rack/simple_aroundware.rb
  71. +121 0 lib/goliath/rack/simple_aroundware_factory.rb
  72. +357 0 lib/goliath/rack/templates.rb
  73. +11 12 lib/goliath/rack/tracer.rb
  74. +12 0 lib/goliath/rack/validation.rb
  75. +0 2  lib/goliath/rack/validation/default_params.rb
  76. +11 2 lib/goliath/rack/validation/numeric_range.rb
  77. +3 2 lib/goliath/rack/validation/request_method.rb
  78. +21 12 lib/goliath/rack/validation/required_param.rb
  79. +11 15 lib/goliath/rack/validation/required_value.rb
  80. +51 0 lib/goliath/rack/validator.rb
  81. +52 21 lib/goliath/request.rb
  82. +3 2 lib/goliath/response.rb
  83. +28 16 lib/goliath/runner.rb
  84. +19 11 lib/goliath/server.rb
  85. +53 29 lib/goliath/test_helper.rb
  86. +2 0  lib/goliath/validation.rb
  87. +0 16 lib/goliath/{rack/validation_error.rb → validation/error.rb}
  88. +31 0 lib/goliath/validation/standard_http_errors.rb
  89. +1 1  lib/goliath/version.rb
  90. +0 2  spec/integration/async_request_processing.rb
  91. +50 0 spec/integration/early_abort_spec.rb
  92. +37 2 spec/integration/echo_spec.rb
  93. +20 0 spec/integration/empty_body_spec.rb
  94. +138 0 spec/integration/http_log_spec.rb
  95. +2 4 spec/integration/keepalive_spec.rb
  96. +2 4 spec/integration/pipelining_spec.rb
  97. +169 0 spec/integration/rack_routes_spec.rb
  98. +43 0 spec/integration/reloader_spec.rb
  99. +56 0 spec/integration/template_spec.rb
  100. +23 0 spec/integration/trace_spec.rb
  101. +21 2 spec/integration/valid_spec.rb
  102. +8 0 spec/spec_helper.rb
  103. +30 0 spec/unit/api_spec.rb
  104. +2 2 spec/unit/env_spec.rb
  105. +40 0 spec/unit/rack/builder_spec.rb
  106. +2 2 spec/unit/rack/formatters/json_spec.rb
  107. +51 0 spec/unit/rack/formatters/plist_spec.rb
  108. +3 3 spec/unit/rack/formatters/xml_spec.rb
  109. +53 0 spec/unit/rack/formatters/yaml_spec.rb
  110. +12 2 spec/unit/rack/heartbeat_spec.rb
  111. +88 3 spec/unit/rack/params_spec.rb
  112. +11 6 spec/unit/rack/render_spec.rb
  113. +3 3 spec/unit/rack/validation/default_params_spec.rb
  114. +11 4 spec/unit/rack/validation/numeric_range_spec.rb
  115. +9 9 spec/unit/rack/validation/request_method_spec.rb
  116. +31 17 spec/unit/rack/validation/required_param_spec.rb
  117. +12 15 spec/unit/rack/validation/required_value_spec.rb
  118. +0 40 spec/unit/rack/validation_error_spec.rb
  119. +23 4 spec/unit/request_spec.rb
  120. +13 0 spec/unit/runner_spec.rb
  121. +8 4 spec/unit/server_spec.rb
  122. +21 0 spec/unit/validation/standard_http_errors_spec.rb
0  .gemtest
No changes.
22 .gitignore
... ... @@ -1,15 +1,19 @@
1   -Makefile
2   -mkmf.log
3   -*.o
4   -*.bundle
  1 +Gemfile.lock
  2 +
  3 +.bundle/
5 4 doc/
6 5 pkg/
7   -examples/log
8   -*.swp
9   -Gemfile.lock
  6 +_site
  7 +
10 8 .yardoc
11 9 .livereload
  10 +.rvmrc
  11 +
  12 +*.swp
12 13 *.watchr
13 14 *.rbc
14   -.rvmrc
15   -_site
  15 +
  16 +examples/log
  17 +examples/goliath.log*
  18 +examples/goliath.pid
  19 +examples/rasterize/thumb
3  Gemfile
... ... @@ -1,6 +1,3 @@
1 1 source "http://rubygems.org"
2 2
3   -gem 'em-websocket', :git => 'http://github.com/dj2/em-websocket', :branch => 'factory_change'
4   -gem 'http_parser.rb', :git => 'http://github.com/dj2/http_parser.rb', :branch => 'upgrade_data'
5   -
6 3 gemspec
64 HISTORY.md
Source Rendered
... ... @@ -0,0 +1,64 @@
  1 +# HISTORY
  2 +
  3 +## v0.9.3 (Oct 16, 2011)
  4 +
  5 + - new router DSL - much improved, see examples
  6 + - refactored async_aroundware
  7 + - make jruby friendlier (removed 1.9 req in gemspec)
  8 + - enable epoll
  9 + - SSL support
  10 + - unix socket support
  11 + - reload config on HUP
  12 + - and a number of small bugfixes + other improvements..
  13 + - See full list @ https://github.com/postrank-labs/goliath/compare/v0.9.2...v0.9.3
  14 +
  15 +## v0.9.2 (July 21, 2011)
  16 +
  17 + - See full list @ https://github.com/postrank-labs/goliath/compare/v0.9.1...v0.9.2
  18 +
  19 +## v0.9.1 (Apr 12, 2011)
  20 +
  21 + - Added extra messaging around the class not matching the file name (Carlos Brando)
  22 +
  23 + - Fix issue with POST parameters not being parsed by Goliath::Rack::Params
  24 + - Added support for multipart encoded POST bodies
  25 + - Added support for parsing nested query string parameters (Nolan Evans)
  26 + - Added support for parsing application/json POST bodies
  27 + - Content-Types outside of multipart, urlencoded and application/json will not be parsed automatically.
  28 +
  29 + - added 'run as user' option
  30 + - SERVER_NAME and SERVER_PORT are set to values in HOST header
  31 +
  32 + - Cleaned up spec examples (Justin Ko)
  33 +
  34 + - moved logger into 'rack.logger' key to be more Rack compliant (Env#logger added to
  35 + keep original API consistent)
  36 + - add command line option for specifying config file
  37 + - HTTP_CONTENT_LENGTH and HTTP_CONTENT_TYPE were changed to CONTENT_TYPE and CONTENT_LENGTH
  38 + to be more Rack compliant
  39 + - fix issue with loading config file in development mode
  40 +
  41 + - Rack::Reloader will be loaded automatically by the framework in development mode.
  42 +
  43 +
  44 +## v0.9.0 (Mar 9, 2011)
  45 +
  46 +(Initial Public Release)
  47 +
  48 +Goliath is an open source version of the non-blocking (asynchronous) Ruby web server framework
  49 +powering PostRank. It is a lightweight framework designed to meet the following goals: bare
  50 +metal performance, Rack API and middleware support, simple configuration, fully asynchronous
  51 +processing, and readable and maintainable code (read: no callbacks).
  52 +
  53 +The framework is powered by an EventMachine reactor, a high-performance HTTP parser and Ruby 1.9
  54 +runtime. One major advantage Goliath has over other asynchronous frameworks is the fact that by
  55 +leveraging Ruby fibers, it can untangle the complicated callback-based code into a format we are
  56 +all familiar and comfortable with: linear execution, which leads to more maintainable and readable code.
  57 +
  58 +While MRI is the recommend platform, Goliath has been tested to run on JRuby and Rubinius.
  59 +
  60 +Goliath has been in production at PostRank for over a year, serving a sustained 500 requests/s for
  61 +internal and external applications. Many of the Goliath processes have been running for months at
  62 +a time (read: no memory leaks) and have served hundreds of gigabytes of data without restarts. To
  63 +scale up and provide failover and redundancy, our individual Goliath servers at PostRank are usually
  64 +deployed behind a reverse proxy (such as HAProxy).
34 README.md
Source Rendered
@@ -20,21 +20,23 @@ Each HTTP request within Goliath is executed in its own Ruby fiber and all async
20 20
21 21 ## Getting Started: Hello World
22 22
23   - require 'goliath'
  23 +```ruby
  24 +require 'goliath'
24 25
25   - class Hello < Goliath::API
26   - # reload code on every request in dev environment
27   - use ::Rack::Reloader, 0 if Goliath.dev?
  26 +class Hello < Goliath::API
  27 + def response(env)
  28 + [200, {}, "Hello World"]
  29 + end
  30 +end
28 31
29   - def response(env)
30   - [200, {}, "Hello World"]
31   - end
32   - end
  32 +> ruby hello.rb -sv
  33 +> [97570:INFO] 2011-02-15 00:33:51 :: Starting server on 0.0.0.0:9000 in development mode. Watch out for stones.
  34 +```
33 35
34   - > ruby hello.rb -sv
35   - > [97570:INFO] 2011-02-15 00:33:51 :: Starting server on 0.0.0.0:9000 in development mode. Watch out for stones.
  36 +See examples directory for more, hands-on examples of building Goliath powered web-services. Are you new to EventMachine, or want a detailed walk-through of building a Goliath powered API? You're in luck, we have two super-awesome peepcode screencasts which will teach you all you need to know:
36 37
37   -See examples directory for more, hands-on examples of building Goliath powered web-services.
  38 +* [Meet EventMachine: Part 1](http://peepcode.com/products/eventmachine) - introduction to EM, Fibers, etc.
  39 +* [Meet EventMachine: Part 2](http://peepcode.com/products/eventmachine-ii) - building an API with Goliath
38 40
39 41 ## Performance: MRI, JRuby, Rubinius
40 42
@@ -55,7 +57,7 @@ Goliath has been in production at PostRank for over a year, serving a sustained
55 57 * Mongrel is a threaded web-server, and both Passenger and Unicorn fork an entire VM to isolate each request from each other. By contrast, Goliath builds a single instance of the Rack app and runs all requests in parallel through a single VM, which leads to a much smaller memory footprint and less overhead.
56 58
57 59 * How do I deploy Goliath in production?
58   - * We recommend deploying Goliath behind a reverse proxy such as HAProxy, Nginx or equivalent. Using one of the above, you can easily run multiple instances of the same application and load balance between them within the reverse proxy.
  60 + * We recommend deploying Goliath behind a reverse proxy such as HAProxy ([sample config](https://github.com/postrank-labs/goliath/wiki/HAProxy)), Nginx or equivalent. Using one of the above, you can easily run multiple instances of the same application and load balance between them within the reverse proxy.
59 61
60 62 ## Guides
61 63
@@ -66,6 +68,7 @@ Goliath has been in production at PostRank for over a year, serving a sustained
66 68
67 69 ### Hands-on applications:
68 70
  71 +* [Peepcode](http://peepcode.com/products/eventmachine) [screencasts](http://peepcode.com/products/eventmachine-ii)
69 72 * [Asynchronous HTTP, MySQL, etc](https://github.com/postrank-labs/goliath/wiki/Asynchronous-Processing)
70 73 * [Response streaming with Goliath](https://github.com/postrank-labs/goliath/wiki/Streaming)
71 74 * [Examples](https://github.com/postrank-labs/goliath/tree/master/examples)
@@ -74,6 +77,10 @@ Goliath has been in production at PostRank for over a year, serving a sustained
74 77
75 78 * [Goliath: Non-blocking, Ruby 1.9 Web Server](http://www.igvita.com/2011/03/08/goliath-non-blocking-ruby-19-web-server)
76 79 * [Stage left: Enter Goliath - HTTP Proxy + MongoDB](http://everburning.com/news/stage-left-enter-goliath/)
  80 +* [InfoQ: Meet the Goliath of Ruby Application Servers](http://www.infoq.com/articles/meet-goliath)
  81 +* [Node.jsはコールバック・スパゲティを招くか](http://el.jibun.atmarkit.co.jp/rails/2011/03/nodejs-d123.html)
  82 +* [Goliath on LinuxFr.org (french)](http://linuxfr.org/news/en-vrac-spécial-ruby-jruby-sinatra-et-goliath)
  83 +* [Goliath et ses amis (slides in french)](http://nono.github.com/Presentations/20110416_Goliath/)
77 84
78 85 ## Discussion and Support
79 86
@@ -83,4 +90,5 @@ Goliath has been in production at PostRank for over a year, serving a sustained
83 90
84 91 ## License & Acknowledgments
85 92
86   -Goliath is distributed under the MIT license, for full details please see the LICENSE file.
  93 +Goliath is distributed under the MIT license, for full details please see the LICENSE file.
  94 +Rock favicon CC-BY from [Douglas Feer](http://www.favicon.cc/?action=icon&file_id=375421)
1  Rakefile
@@ -5,6 +5,7 @@ require 'yard'
5 5 require 'rspec/core/rake_task'
6 6
7 7 task :default => [:spec]
  8 +task :test => [:spec]
8 9
9 10 desc "run spec tests"
10 11 RSpec::Core::RakeTask.new('spec') do |t|
6 examples/activerecord/srv.rb
@@ -19,13 +19,9 @@ class User < ActiveRecord::Base
19 19 end
20 20
21 21 class Srv < Goliath::API
22   - use ::Rack::Reloader, 0 if Goliath.dev?
23   -
24 22 use Goliath::Rack::Params
25 23 use Goliath::Rack::DefaultMimeType
26   - use Goliath::Rack::Formatters::JSON
27   - use Goliath::Rack::Render
28   - use Goliath::Rack::ValidationError
  24 + use Goliath::Rack::Render, 'json'
29 25
30 26 use Goliath::Rack::Validation::RequiredParam, {:key => 'id', :type => 'ID'}
31 27 use Goliath::Rack::Validation::NumericRange, {:key => 'id', :min => 1}
28 examples/api_proxy.rb
... ... @@ -0,0 +1,28 @@
  1 +#!/usr/bin/env ruby
  2 +
  3 +# Rewrites and proxies requests to a third-party API, with HTTP basic authentication.
  4 +
  5 +require 'goliath'
  6 +require 'em-synchrony/em-http'
  7 +
  8 +class TwilioResponse < Goliath::API
  9 + use Goliath::Rack::Params
  10 + use Goliath::Rack::JSONP
  11 +
  12 + HEADERS = { authorization: ENV.values_at("TWILIO_SID","TWILIO_AUTH_TOKEN") }
  13 + BASE_URL = "https://api.twilio.com/2010-04-01/Accounts/#{ENV['TWILIO_SID']}/AvailablePhoneNumbers/US"
  14 +
  15 + def response(env)
  16 + url = "#{BASE_URL}#{env['REQUEST_PATH']}?#{env['QUERY_STRING']}"
  17 + logger.debug "Proxying #{url}"
  18 +
  19 + http = EM::HttpRequest.new(url).get head: HEADERS
  20 + logger.debug "Received #{http.response_header.status} from Twilio"
  21 +
  22 + [200, {'X-Goliath' => 'Proxy','Content-Type' => 'application/javascript'}, http.response]
  23 + end
  24 +end
  25 +
  26 +class Twilio < Goliath::API
  27 + get %r{^/(Local|TollFree)}, TwilioResponse
  28 +end
85 examples/async_aroundware_demo.rb
... ... @@ -0,0 +1,85 @@
  1 +#!/usr/bin/env ruby
  2 +$: << File.dirname(__FILE__)+'/../lib'
  3 +
  4 +require 'goliath'
  5 +require 'em-synchrony/em-http'
  6 +require 'yajl/json_gem'
  7 +
  8 +#
  9 +# Here's a way to make an asynchronous request in the middleware, and only
  10 +# proceed with the response when both the endpoint and our middleware's
  11 +# responses have completed.
  12 +#
  13 +# To run this, start the 'test_rig.rb' server on port 9002:
  14 +#
  15 +# bundle exec ./examples/test_rig.rb -sv -p 9002
  16 +#
  17 +# And then start this server on port 9000:
  18 +#
  19 +# bundle exec ./examples/barrier_aroundware_demo.rb -sv -p 9000
  20 +#
  21 +# Now curl the async_aroundware_demo_multi:
  22 +#
  23 +# $ time curl 'http://127.0.0.1:9000/?delay_1=1.0&delay_2=1.5'
  24 +# { "results": {
  25 +# "sleep_2": { "delay": 1.5, "actual": 1.5085558891296387 },
  26 +# "sleep_1": { "delay": 1.0, "actual": 1.0098700523376465 }
  27 +# } }
  28 +#
  29 +# The requests are run concurrently:
  30 +#
  31 +# $ ./examples/async_aroundware_demo.rb -sv -p 9000 -e prod &
  32 +# [68463:INFO] 2011-05-03 23:13:03 :: Starting server on 0.0.0.0:9000 in production mode. Watch out for stones.
  33 +# $ ab -n10 -c10 'http://127.0.0.1:9000/?delay_1=1.5&delay_2=2.0'
  34 +# Connection Times (ms)
  35 +# min mean[+/-sd] median max
  36 +# Connect: 0 0 0.1 0 0
  37 +# Processing: 2027 2111 61.6 2112 2204
  38 +# Waiting: 2027 2111 61.5 2112 2204
  39 +# Total: 2027 2112 61.5 2113 2204
  40 +#
  41 +#
  42 +
  43 +BASE_URL = 'http://localhost:9002/'
  44 +
  45 +class RemoteRequestBarrier
  46 + include Goliath::Rack::BarrierAroundware
  47 + attr_accessor :sleep_1
  48 +
  49 + def pre_process
  50 + # Request with delay_1 and drop_1 -- note: 'aget', because we want execution to continue
  51 + req = EM::HttpRequest.new(BASE_URL).aget(:query => { :delay => env.params['delay_1'], :drop => env.params['drop_1'] })
  52 + enqueue :sleep_1, req
  53 + return Goliath::Connection::AsyncResponse
  54 + end
  55 +
  56 + def post_process
  57 + # unify the results with the results of the API call
  58 + if successes.include?(:sleep_1) then body[:results][:sleep_1] = JSON.parse(sleep_1.response)
  59 + else body[:errors][:sleep_1] = sleep_1.error ; end
  60 + [status, headers, JSON.pretty_generate(body)]
  61 + end
  62 +end
  63 +
  64 +class BarrierAroundwareDemo < Goliath::API
  65 + use Goliath::Rack::Params
  66 + use Goliath::Rack::Validation::NumericRange, {:key => 'delay_1', :default => 1.0, :max => 5.0, :min => 0.0, :as => Float}
  67 + use Goliath::Rack::Validation::NumericRange, {:key => 'delay_2', :default => 0.5, :max => 5.0, :min => 0.0, :as => Float}
  68 + #
  69 + use Goliath::Rack::BarrierAroundwareFactory, RemoteRequestBarrier
  70 +
  71 + def response(env)
  72 + # Request with delay_2 and drop_2 -- note: 'get', because we want execution to proceed linearly
  73 + resp = EM::HttpRequest.new(BASE_URL).get(:query => { :delay => env.params['delay_2'], :drop => env.params['drop_2'] })
  74 +
  75 + body = { :results => {}, :errors => {} }
  76 +
  77 + if resp.response_header.status.to_i != 0
  78 + body[:results][:sleep_2] = JSON.parse(resp.response) rescue 'parsing failed'
  79 + else
  80 + body[:errors ][:sleep_2] = resp.error
  81 + end
  82 +
  83 + [200, { }, body]
  84 + end
  85 +end
7 examples/async_upload.rb
@@ -5,14 +5,9 @@
5 5 require 'yajl'
6 6
7 7 class AsyncUpload < Goliath::API
8   -
9   - # reload code on every request in dev environment
10   - use ::Rack::Reloader, 0 if Goliath.dev?
11   -
12 8 use Goliath::Rack::Params # parse & merge query and body parameters
13 9 use Goliath::Rack::DefaultMimeType # cleanup accepted media types
14   - use Goliath::Rack::Formatters::JSON # JSON output formatter
15   - use Goliath::Rack::Render # auto-negotiate response format
  10 + use Goliath::Rack::Render, 'json' # auto-negotiate response format
16 11
17 12 def on_headers(env, headers)
18 13 env.logger.info 'received headers: ' + headers.inspect
265 examples/auth_and_rate_limit.rb
... ... @@ -0,0 +1,265 @@
  1 +#!/usr/bin/env ruby
  2 +$: << File.join(File.dirname(__FILE__), '../lib')
  3 +require 'goliath'
  4 +require 'em-mongo'
  5 +require 'em-http'
  6 +require 'em-synchrony/em-http'
  7 +require 'em-synchrony/em-mongo'
  8 +require 'yajl/json_gem'
  9 +
  10 +require File.join(File.dirname(__FILE__), 'http_log') # Use the HttpLog as our actual endpoint, but include this in the middleware
  11 +
  12 +#
  13 +# Usage:
  14 +#
  15 +# First launch the test rig:
  16 +# bundle exec ./examples/test_rig.rb -sv -p 8080 -e prod &
  17 +#
  18 +# Then launch this script
  19 +# bundle exec ./examples/auth_and_rate_limit.rb -sv -p 9000 --config $PWD/examples/config/auth_and_rate_limit.rb
  20 +#
  21 +# The auth info is returned in the headers:
  22 +#
  23 +# curl -vv 'http://127.0.0.1:9000/?_apikey=i_am_busy&drop=false' ; echo
  24 +# ...snip...
  25 +# < X-RateLimit-MaxRequests: 1000
  26 +# < X-RateLimit-Requests: 999
  27 +# < X-RateLimit-Reset: 1312059600
  28 +#
  29 +# This user will hit the rate limit after 10 requests:
  30 +#
  31 +# for foo in 1 2 3 4 5 6 7 8 9 10 11 12 ; do echo -ne $foo "\t" ; curl 'http://127.0.0.1:9000/?_apikey=i_am_limited' ; echo ; done
  32 +# 1 {"Special":"Header","Params":"_apikey: i_am_awesome|drop: false","Path":"/","Headers":"User-Agent: ...
  33 +# ...
  34 +# 11 [:error, "Your request rate (11) is over your limit (10)"]
  35 +#
  36 +# You can test the barrier (both delays are in fractional seconds):
  37 +# * drop=true will drop the request at the remote host
  38 +# * auth_db_delay will fake a slow response from the mongo
  39 +# * delay will cause a slow response from the remote host
  40 +#
  41 +# time curl -vv 'http://127.0.0.1:9000/?_apikey=i_am_awesome&drop=false&delay=0.4&auth_db_delay=0.3'
  42 +# ...
  43 +# X-Tracer: ... received_usage_info: 0.06, received_sleepy: 299.52, received_downstream_resp: 101.67, ..., total: 406.09
  44 +# ...
  45 +# real 0m0.416s user 0m0.002s sys 0m0.003s pct 1.24
  46 +#
  47 +# This shows the mongodb response returning quickly, the fake DB delay returning
  48 +# after 300ms, and the downstream response returning after an additional 101 ms.
  49 +# The total request took 416ms of wall-clock time
  50 +#
  51 +# This will hold up even in the face of many concurrent connections. Relaunch in
  52 +# production (you may have to edit the config/auth_and_rate_limit scripts):
  53 +#
  54 +# bundle exec ./examples/auth_and_rate_limit.rb -sv -p 9000 -e prod --config $PWD/examples/config/auth_and_rate_limit.rb
  55 +#
  56 +# On my laptop, with 20 concurrent requests (each firing two db gets, a 400 ms
  57 +# http get, and two db writes), the median/90%ile times were 431ms / 457ms:
  58 +#
  59 +# time ab -c20 -n20 'http://127.0.0.1:9000/?_apikey=i_am_awesome&drop=false&delay=0.4&auth_db_delay=0.3'
  60 +# ...
  61 +# Percentage of the requests served within a certain time (ms)
  62 +# 50% 431
  63 +# 90% 457
  64 +# real 0m0.460s user 0m0.001s sys 0m0.003s pct 0.85
  65 +#
  66 +# With 100 concurrent requests, the request latency starts to drop but the
  67 +# throughput and variance stand up:
  68 +#
  69 +# time ab -c100 -n100 'http://127.0.0.1:9000/?_apikey=i_am_awesome&drop=false&delay=0.4&auth_db_delay=0.3'
  70 +# ...
  71 +# Percentage of the requests served within a certain time (ms)
  72 +# 50% 640
  73 +# 90% 673
  74 +# real 0m0.679s user 0m0.002s sys 0m0.007s pct 1.33
  75 +#
  76 +
  77 +# Tracks and enforces account and rate limit policies.
  78 +#
  79 +# This is like a bouncer who lets townies order a drink while he checks their
  80 +# ID, but who's a jerk to college kids.
  81 +#
  82 +# On GET or HEAD requests, it proxies the request and gets account/usage info
  83 +# concurrently; authorizing the account doesn't delay the response.
  84 +#
  85 +# On a POST or other non-idempotent request, it checks the account/usage info
  86 +# *before* allowing the request to fire. This takes longer, but is necessary and
  87 +# tolerable.
  88 +#
  89 +# The magic of BarrierAroundware:
  90 +#
  91 +# 1) In pre_process (before the request):
  92 +# * validate an apikey was given; if not, raise (returning directly)
  93 +# * launch requests for the account and rate limit usage
  94 +#
  95 +# 2) On a POST or other non-GET non-HEAD, we issue `perform`, which barriers
  96 +# (allowing other requests to proceed) until the two pending requests
  97 +# complete. It then checks the account exists and is valid, and that the rate
  98 +# limit is OK
  99 +#
  100 +# 3) If the auth check fails, we raise an error (later caught by a safely{}
  101 +# block and turned into the right 4xx HTTP response.
  102 +#
  103 +# 4) If the auth check succeeds, or the request is a GET or HEAD, we return
  104 +# Goliath::Connection::AsyncResponse, and BarrierAroundwareFactory passes the
  105 +# request down the middleware chain
  106 +#
  107 +# 5) post_process resumes only when both proxied request & auth info are complete
  108 +# (it already has of course in the non-lazy scenario)
  109 +#
  110 +# 6) If we were lazy, the post_process method now checks authorization
  111 +#
  112 +class AuthBarrier
  113 + include Goliath::Rack::BarrierAroundware
  114 + include Goliath::Validation
  115 + attr_reader :db
  116 + attr_accessor :account_info, :usage_info
  117 +
  118 + # time period to aggregate stats over, in seconds
  119 + TIMEBIN_SIZE = 60 * 60
  120 +
  121 + class MissingApikeyError < BadRequestError ; end
  122 + class RateLimitExceededError < ForbiddenError ; end
  123 + class InvalidApikeyError < UnauthorizedError ; end
  124 +
  125 + def initialize(env, db_name)
  126 + @db = env.config[db_name]
  127 + super(env)
  128 + end
  129 +
  130 + def pre_process
  131 + env.trace('pre_process_beg')
  132 + validate_apikey!
  133 +
  134 + # the results of the afirst deferrable will be set right into account_info (and the request into successes)
  135 + enqueue_mongo_request(:account_info, { :_id => apikey })
  136 + enqueue_mongo_request(:usage_info, { :_id => usage_id })
  137 + maybe_fake_delay!
  138 +
  139 + # On non-GET non-HEAD requests, we have to check auth now.
  140 + unless lazy_authorization?
  141 + perform # yield execution until user_info has arrived
  142 + charge_usage
  143 + check_authorization!
  144 + end
  145 +
  146 + env.trace('pre_process_end')
  147 + return Goliath::Connection::AsyncResponse
  148 + end
  149 +
  150 + def post_process
  151 + env.trace('post_process_beg')
  152 + # [:account_info, :usage_info, :status, :headers, :body].each{|attr| env.logger.info(("%23s\t%s" % [attr, self.send(attr).inspect[0..200]])) }
  153 +
  154 + inject_headers
  155 +
  156 + # We have to check auth now, we skipped it before
  157 + if lazy_authorization?
  158 + charge_usage
  159 + check_authorization!
  160 + end
  161 +
  162 + env.trace('post_process_end')
  163 + [status, headers, body]
  164 + end
  165 +
  166 + def lazy_authorization?
  167 + (env['REQUEST_METHOD'] == 'GET') || (env['REQUEST_METHOD'] == 'HEAD')
  168 + end
  169 +
  170 + if defined?(EM::Mongo::Cursor)
  171 + # em-mongo > 0.3.6 gives us a deferrable back. nice and clean.
  172 + def enqueue_mongo_request(handle, query)
  173 + enqueue handle, db.collection(handle).afirst(query)
  174 + end
  175 + else
  176 + # em-mongo <= 0.3.6 makes us fake a deferrable response.
  177 + def enqueue_mongo_request(handle, query)
  178 + enqueue_acceptor(handle) do |acc|
  179 + db.collection(handle).afind(query){|resp| acc.succeed(resp.first) }
  180 + end
  181 + end
  182 + end
  183 +
  184 + # Fake out a delay in the database response if auth_db_delay is given
  185 + def maybe_fake_delay!
  186 + if (auth_db_delay = env.params['auth_db_delay'].to_f) > 0
  187 + enqueue_acceptor(:sleepy){|acc| EM.add_timer(auth_db_delay){ acc.succeed } }
  188 + end
  189 + end
  190 +
  191 + def accept_response(handle, *args)
  192 + env.trace("received_#{handle}")
  193 + super(handle, *args)
  194 + end
  195 +
  196 + # ===========================================================================
  197 +
  198 + def check_authorization!
  199 + check_apikey!
  200 + check_rate_limit!
  201 + end
  202 +
  203 + def validate_apikey!
  204 + if apikey.to_s.empty?
  205 + raise MissingApikeyError
  206 + end
  207 + end
  208 +
  209 + def check_apikey!
  210 + unless account_info && (account_info['valid'] == true)
  211 + raise InvalidApikeyError
  212 + end
  213 + end
  214 +
  215 + def check_rate_limit!
  216 + self.usage_info ||= {}
  217 + rate = usage_info['calls'].to_i + 1
  218 + limit = account_info['max_call_rate'].to_i
  219 + return true if rate <= limit
  220 + raise RateLimitExceededError, "Your request rate (#{rate}) is over your limit (#{limit})"
  221 + end
  222 +
  223 + def charge_usage
  224 + EM.next_tick do
  225 + safely(env){ db.collection(:usage_info).update({ :_id => usage_id },
  226 + { '$inc' => { :calls => 1 } }, :upsert => true) }
  227 + end
  228 + end
  229 +
  230 + def inject_headers
  231 + headers.merge!({
  232 + 'X-RateLimit-MaxRequests' => account_info['max_call_rate'].to_s,
  233 + 'X-RateLimit-Requests' => usage_info['calls'].to_i.to_s,
  234 + 'X-RateLimit-Reset' => timebin_end.to_s,
  235 + })
  236 + end
  237 +
  238 + # ===========================================================================
  239 +
  240 + def apikey
  241 + env.params['_apikey']
  242 + end
  243 +
  244 + def usage_id
  245 + "#{apikey}-#{timebin}"
  246 + end
  247 +
  248 + def timebin
  249 + @timebin ||= timebin_beg
  250 + end
  251 +
  252 + def timebin_beg
  253 + ((Time.now.to_i / TIMEBIN_SIZE).floor * TIMEBIN_SIZE)
  254 + end
  255 +
  256 + def timebin_end
  257 + timebin_beg + TIMEBIN_SIZE
  258 + end
  259 +end
  260 +
  261 +class AuthAndRateLimit < HttpLog
  262 + use Goliath::Rack::Tracer, 'X-Tracer'
  263 + use Goliath::Rack::Params # parse & merge query and body parameters
  264 + use Goliath::Rack::BarrierAroundwareFactory, AuthBarrier, 'api_auth_db'
  265 +end
37 examples/chunked_streaming.rb
... ... @@ -0,0 +1,37 @@
  1 +#!/usr/bin/env ruby
  2 +$:<< '../lib' << 'lib'
  3 +
  4 +#
  5 +# A simple HTTP streaming API which returns a 200 response for any GET request
  6 +# and then emits numbers 1 through 10 in 1 second intervals using Chunked
  7 +# transfer encoding, and finally closes the connection.
  8 +#
  9 +# Chunked transfer streaming works transparently with both browsers and
  10 +# streaming consumers.
  11 +#
  12 +
  13 +require 'goliath'
  14 +
  15 +class ChunkedStreaming < Goliath::API
  16 + def on_close(env)
  17 + env.logger.info "Connection closed."
  18 + end
  19 +
  20 + def response(env)
  21 + i = 0
  22 + pt = EM.add_periodic_timer(1) do
  23 + env.chunked_stream_send("#{i}\n")
  24 + i += 1
  25 + end
  26 +
  27 + EM.add_timer(10) do
  28 + pt.cancel
  29 +
  30 + env.chunked_stream_send("!! BOOM !!\n")
  31 + env.chunked_stream_close
  32 + end
  33 +
  34 + headers = { 'Content-Type' => 'text/plain', 'X-Stream' => 'Goliath' }
  35 + chunked_streaming_response(200, headers)
  36 + end
  37 +end
5 examples/conf_test.rb
@@ -10,11 +10,8 @@
10 10 require 'goliath'
11 11
12 12 class ConfTest < Goliath::API
13   -
14 13 use Goliath::Rack::Params
15   - use Goliath::Rack::DefaultMimeType
16   - use Goliath::Rack::Formatters::JSON
17   - use Goliath::Rack::Render
  14 + use Goliath::Rack::Render, 'json'
18 15
19 16 def options_parser(opts, options)
20 17 options[:test] = 0
33 examples/config/auth_and_rate_limit.rb
... ... @@ -0,0 +1,33 @@
  1 +import 'http_log'
  2 +
  3 +environment(:development) do
  4 +
  5 + config['api_auth_db'] = EventMachine::Synchrony::ConnectionPool.new(:size => 20) do
  6 + conn = EM::Mongo::Connection.new('localhost', 27017, 1, {:reconnect_in => 1})
  7 + conn.db('buzzkill_test')
  8 + end
  9 +
  10 + # for demo purposes, some dummy accounts
  11 + timebin = ((Time.now.to_i / 3600).floor * 3600)
  12 +
  13 + # This user's calls should all go through
  14 + config['api_auth_db'].collection(:account_info).save({
  15 + :_id => 'i_am_awesome', 'valid' => true, 'max_call_rate' => 1_000_000 })
  16 +
  17 + # this user's account is disabled
  18 + config['api_auth_db'].collection(:account_info).save({
  19 + :_id => 'i_am_lame', 'valid' => false, 'max_call_rate' => 1_000 })
  20 +
  21 + # this user has not been seen, but will very quickly hit their limit
  22 + config['api_auth_db'].collection(:account_info).save({
  23 + :_id => 'i_am_limited', 'valid' => true, 'max_call_rate' => 10 })
  24 + config['api_auth_db'].collection(:usage_info).save({
  25 + :_id => "i_am_limited-#{timebin}", 'calls' => 0 })
  26 +
  27 + # fakes a user with a bunch of calls already made this hour -- two more = no yuo
  28 + config['api_auth_db'].collection(:account_info).save({
  29 + :_id => 'i_am_busy', 'valid' => true, 'max_call_rate' => 1_000 })
  30 + config['api_auth_db'].collection(:usage_info).save({
  31 + :_id => "i_am_busy-#{timebin}", 'calls' => 999 })
  32 +
  33 +end
29 examples/config/content_stream.rb
... ... @@ -0,0 +1,29 @@
  1 +require 'amqp'
  2 +
  3 +config['channel'] = EM::Channel.new
  4 +
  5 +amqp_config = {
  6 + :host => 'localhost',
  7 + :user => 'test',
  8 + :pass => 'test',
  9 + :vhost => '/test'
  10 +}
  11 +
  12 +conn = AMQP.connect(amqp_config)
  13 +xchange = AMQP::Channel.new(conn).fanout('stream')
  14 +
  15 +q = AMQP::Channel.new(conn).queue('stream/StreamAPI')
  16 +q.bind(xchange)
  17 +
  18 +def handle_message(metadata, payload)
  19 + config['channel'].push(payload)
  20 +end
  21 +
  22 +q.subscribe(&method(:handle_message))
  23 +
  24 +# push data into the stream. (Just so we have stuff going in)
  25 +count = 0
  26 +EM.add_periodic_timer(2) do
  27 + xchange.publish("Iteration #{count}\n")
  28 + count += 1
  29 +end
12 examples/config/http_log.rb
... ... @@ -1,7 +1,9 @@
1 1 config['forwarder'] = 'http://localhost:8080'
2 2
3   -config['mongo'] = EventMachine::Synchrony::ConnectionPool.new(size: 20) do
4   - # Need to deal with this just never connecting ... ?
5   - conn = EM::Mongo::Connection.new('localhost', 27017, 1, {:reconnect_in => 1})
6   - conn.db('http_log').collection('aggregators')
7   -end
  3 +environment(:development) do
  4 + config['mongo'] = EventMachine::Synchrony::ConnectionPool.new(size: 20) do
  5 + # Need to deal with this just never connecting ... ?
  6 + conn = EM::Mongo::Connection.new('localhost', 27017, 1, {:reconnect_in => 1})
  7 + conn.db('http_log').collection('aggregators')
  8 + end
  9 +end
8 examples/config/template.rb
... ... @@ -0,0 +1,8 @@
  1 +config[:template] = {
  2 + :layout_engine => :haml,
  3 +}
  4 +config[:template_engines] = {
  5 + :haml => {
  6 + :escape_html => true
  7 + }
  8 +}
39 examples/content_stream.rb
... ... @@ -0,0 +1,39 @@
  1 +#!/usr/bin/env ruby
  2 +$:<< '../lib' << 'lib'
  3 +
  4 +require 'goliath'
  5 +
  6 +# This example assumes you have an AMQP server up and running with the
  7 +# following config (using rabbit-mq as an example)
  8 +#
  9 +# rabbitmq-server start
  10 +# rabbitmqctl add_vhost /test
  11 +# rabbitmqctl add_user test test
  12 +# rabbitmqctl set_permissions -p /test test ".*" ".*" ".*"
  13 +
  14 +class ContentStream < Goliath::API
  15 + use Goliath::Rack::Params
  16 +
  17 + use Goliath::Rack::Render, 'json'
  18 + use Goliath::Rack::Heartbeat
  19 + use Goliath::Rack::Validation::RequestMethod, %w(GET)
  20 +
  21 + def on_close(env)
  22 + # This is just to make sure if the Heartbeat fires we don't try
  23 + # to close a connection.
  24 + return unless env['subscription']
  25 +
  26 + env.channel.unsubscribe(env['subscription'])
  27 + env.logger.info "Stream connection closed."
  28 + end
  29 +
  30 + def response(env)
  31 + env.logger.info "Stream connection opened"
  32 +
  33 + env['subscription'] = env.channel.subscribe do |msg|
  34 + env.stream_send(msg)
  35 + end
  36 +
  37 + [200, {}, Goliath::Response::STREAMING]
  38 + end
  39 +end
37 examples/early_abort.rb
... ... @@ -0,0 +1,37 @@
  1 +#!/usr/bin/env ruby
  2 +$:<< '../lib' << 'lib'
  3 +require 'goliath'
  4 +
  5 +class EarlyAbort < Goliath::API
  6 + include Goliath::Validation
  7 + MAX_SIZE = 10
  8 + TEST_FILE = "/tmp/goliath-test-error.log"
  9 +
  10 + def on_headers(env, headers)
  11 + env.logger.info 'received headers: ' + headers.inspect
  12 + env['async-headers'] = headers
  13 +
  14 + if env['HTTP_X_CRASH'] && env['HTTP_X_CRASH'] == 'true'
  15 + raise Goliath::Validation::NotImplementedError.new("Can't handle requests with X-Crash: true.")
  16 + end
  17 + end
  18 +
  19 + def on_body(env, data)
  20 + env.logger.info 'received data: ' + data
  21 + (env['async-body'] ||= '') << data
  22 + size = env['async-body'].size
  23 +
  24 + if size >= MAX_SIZE
  25 + raise Goliath::Validation::BadRequestError.new("Payload size can't exceed #{MAX_SIZE} bytes. Received #{size.inspect} bytes.")
  26 + end
  27 + end
  28 +
  29 + def on_close(env)
  30 + env.logger.info 'closing connection'
  31 + end
  32 +
  33 + def response(env)
  34 + File.open(TEST_FILE, "w+") { |f| f << "response that should not be here"}
  35 + [200, {}, "OK"]
  36 + end
  37 +end
20 examples/echo.rb 100755 → 100644
@@ -4,23 +4,21 @@
4 4 require 'goliath'
5 5 require 'goliath/plugins/latency'
6 6
7   -# Goliath uses multi-jon, so pick your favorite JSON serializer
  7 +# Goliath uses multi-json, so pick your favorite JSON serializer
8 8 # require 'json'
9 9 require 'yajl'
10 10
11 11 class Echo < Goliath::API
12   -
13   - # reload code on every request in dev environment
14   - use ::Rack::Reloader, 0 if Goliath.dev?
15   -
16   - use Goliath::Rack::Params # parse & merge query and body parameters
  12 + use Goliath::Rack::Tracer # log trace statistics
17 13 use Goliath::Rack::DefaultMimeType # cleanup accepted media types
18   - use Goliath::Rack::Formatters::JSON # JSON output formatter
19   - use Goliath::Rack::Render # auto-negotiate response format
  14 + use Goliath::Rack::Render, 'json' # auto-negotiate response format
  15 + use Goliath::Rack::Params # parse & merge query and body parameters
20 16 use Goliath::Rack::Heartbeat # respond to /status with 200, OK (monitoring, etc)
21   - use Goliath::Rack::ValidationError # catch and render validation errors
22 17
23   - use Goliath::Rack::Validation::RequestMethod, %w(GET) # allow GET requests only
  18 + # If you are using Golaith version <=0.9.1 you need to Goliath::Rack::ValidationError
  19 + # to prevent the request from remaining open after an error occurs
  20 + #use Goliath::Rack::ValidationError
  21 + use Goliath::Rack::Validation::RequestMethod, %w(GET POST) # allow GET and POST requests only
24 22 use Goliath::Rack::Validation::RequiredParam, {:key => 'echo'} # must provide ?echo= query or body param
25 23
26 24 plugin Goliath::Plugin::Latency # output reactor latency every second
@@ -34,4 +32,4 @@ def process_request
34 32 def response(env)
35 33 [200, {}, process_request]
36 34 end
37   -end
  35 +end
20 examples/env_use_statements.rb
... ... @@ -0,0 +1,20 @@
  1 +#!/usr/bin/env ruby
  2 +$:<< '../lib' << 'lib'
  3 +
  4 +require 'goliath'
  5 +require 'yajl'
  6 +
  7 +# API must be started with -e [production, development, ...]
  8 +# or set your ENV['RACK_ENV'] to specify the environemtn
  9 +
  10 +class EnvUseStatements < Goliath::API
  11 + if Goliath.dev?
  12 + use Goliath::Rack::Render, 'json'
  13 + elsif Goliath.prod?
  14 + use Goliath::Rack::Render, 'xml'
  15 + end
  16 +
  17 + def response(env)
  18 + [200, {}, {'Test' => 'Response'}]
  19 + end
  20 +end
40 examples/favicon.rb
... ... @@ -0,0 +1,40 @@
  1 +#!/usr/bin/env ruby
  2 +require 'time'
  3 +
  4 +#
  5 +# Reads a favicon.ico statically at load time, renders it on any request for
  6 +# '/favicon.ico', and sends every other request on downstream.
  7 +#
  8 +# If you will be serving even one more file than this one, you should instead
  9 +# use Rack::Static:
  10 +#
  11 +# use(Rack::Static, # render static files from ./public
  12 +# :root => Goliath::Application.app_path("public"),
  13 +# :urls => ["/favicon.ico", '/stylesheets', '/javascripts', '/images'])
  14 +#
  15 +class Favicon
  16 + def initialize(app, filename)
  17 + @@favicon = File.read(filename)
  18 + @@last_mod = File.mtime(filename).utc.rfc822
  19 + @@expires = Time.at(Time.now + 604800).utc.rfc822 # 1 week from now
  20 + @app = app
  21 + end
  22 +
  23 + def call(env, *args)
  24 + if env['REQUEST_PATH'] == '/favicon.ico'
  25 + return [200, {"Last-Modified"=> @@last_mod.to_s, "Expires" => @@expires, "Content-Type"=>"image/vnd.microsoft.icon"}, @@favicon]
  26 + else
  27 + return @app.call(env)
  28 + end
  29 + end
  30 +end
  31 +
  32 +if File.expand_path($0) == File.expand_path(__FILE__)
  33 + $:<< '../lib' << 'lib'
  34 + require 'goliath'
  35 + puts "starting hello world!"
  36 + class HelloWorld < Goliath::API
  37 + HelloWorld.use(Favicon, File.expand_path(File.dirname(__FILE__)+"/public/favicon.ico"))
  38 + end
  39 + require(File.dirname(__FILE__)+'/hello_world.rb')
  40 +end
4 examples/gziped.rb
@@ -27,9 +27,7 @@ class Gziped < Goliath::API
27 27 end
28 28
29 29 use Goliath::Rack::Params # parse & merge query and body parameters
30   - use Goliath::Rack::Formatters::JSON # JSON output formatter
31   - use Goliath::Rack::Render # auto-negotiate response format
32   - use Goliath::Rack::ValidationError # catch and render validation errors
  30 + use Goliath::Rack::Render, 'json' # auto-negotiate response format
33 31
34 32 use Goliath::Rack::Validation::RequestMethod, %w(GET) # allow GET requests only
35 33 use Goliath::Rack::Validation::RequiredParam, {:key => 'echo'} # must provide ?echo= query or body param
4 examples/http_log.rb
@@ -15,7 +15,6 @@
15 15 require 'pp'
16 16
17 17 class HttpLog < Goliath::API
18   - use ::Rack::Reloader, 0 if Goliath.dev?
19 18 use Goliath::Rack::Params
20 19
21 20 def on_headers(env, headers)
@@ -57,6 +56,7 @@ def to_http_header(k)
57 56 # Write the request information into mongo
58 57 def record(process_time, resp, client_headers, response_headers)
59 58 e = env
  59 + e.trace('http_log_record')
60 60 EM.next_tick do
61 61 doc = {
62 62 request: {
@@ -82,4 +82,4 @@ def record(process_time, resp, client_headers, response_headers)
82 82 e.mongo.insert(doc)
83 83 end
84 84 end
85   -end
  85 +end
BIN  examples/public/favicon.ico
Binary file not shown
296 examples/public/stylesheets/style.css
... ... @@ -0,0 +1,296 @@
  1 +
  2 +/* ==== Scroll down to find where to put your styles :) ==== */
  3 +
  4 +/* HTML5 - Boilerplate */
  5 +
  6 +html, body, div, span, object, iframe,
  7 +h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  8 +abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
  9 +small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
  10 +fieldset, form, label, legend,
  11 +table, caption, tbody, tfoot, thead, tr, th, td,
  12 +article, aside, canvas, details, figcaption, figure,
  13 +footer, header, hgroup, menu, nav, section, summary,
  14 +time, mark, audio, video {
  15 + margin: 0;
  16 + padding: 0;
  17 + border: 0;
  18 + font-size: 100%;
  19 + font: inherit;
  20 + vertical-align: baseline;
  21 +}
  22 +
  23 +article, aside, details, figcaption, figure,
  24 +footer, header, hgroup, menu, nav, section {
  25 + display: block;
  26 +}
  27 +
  28 +blockquote, q { quotes: none; }
  29 +blockquote:before, blockquote:after,
  30 +q:before, q:after { content: ''; content: none; }
  31 +ins { background-color: #ff9; color: #000; text-decoration: none; }
  32 +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
  33 +del { text-decoration: line-through; }
  34 +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
  35 +table { border-collapse: collapse; border-spacing: 0; }
  36 +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
  37 +input, select { vertical-align: middle; }
  38 +
  39 +body { font:13px/1.231 sans-serif; *font-size:small; }
  40 +select, input, textarea, button { font:99% sans-serif; }
  41 +pre, code, kbd, samp { font-family: monospace, sans-serif; }
  42 +
  43 +html { overflow-y: scroll; }
  44 +a:hover, a:active { outline: none; }
  45 +ul, ol { margin-left: 2em; }
  46 +ol { list-style-type: decimal; }
  47 +nav ul, nav li { margin: 0; list-style:none; list-style-image: none; }
  48 +small { font-size: 85%; }
  49 +strong, th { font-weight: bold; }
  50 +td { vertical-align: top; }
  51 +
  52 +sub, sup { font-size: 75%; line-height: 0; position: relative; }
  53 +sup { top: -0.5em; }
  54 +sub { bottom: -0.25em; }
  55 +
  56 +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; }
  57 +textarea { overflow: auto; }
  58 +.ie6 legend, .ie7 legend { margin-left: -7px; }
  59 +input[type="radio"] { vertical-align: text-bottom; }
  60 +input[type="checkbox"] { vertical-align: bottom; }
  61 +.ie7 input[type="checkbox"] { vertical-align: baseline; }
  62 +.ie6 input { vertical-align: text-bottom; }
  63 +label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; }
  64 +button, input, select, textarea { margin: 0; }
  65 +input:valid, textarea:valid { }
  66 +input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; }
  67 +.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; }
  68 +
  69 +::-moz-selection{ background: #eFdEe9; color:#fff; text-shadow: none; }
  70 +::selection { background:#eFdEe9; color:#fff; text-shadow: none; }
  71 +a:link { -webkit-tap-highlight-color: #eFdEe9; }
  72 +
  73 +button { width: auto; overflow: visible; }
  74 +.ie7 img { -ms-interpolation-mode: bicubic; }
  75 +
  76 +body, select, input, textarea { color: #444; }
  77 +h1, h2, h3, h4, h5, h6 { font-weight: bold; }
  78 +a, a:active, a:visited { color: #607890; }
  79 +a:hover { color: #036; }
  80 +
  81 +/*
  82 + // ========================================== \\
  83 + || ||
  84 + || Your styles ! ||
  85 + || ||
  86 + \\ ========================================== //
  87 +*/
  88 +
  89 +
  90 +body{
  91 + font-family:Helvetica, Helvetica Neue, Arial, sans-serif;
  92 +}
  93 +
  94 +.wrapper{
  95 + margin:auto;
  96 + width:960px;
  97 +}
  98 +
  99 +#logo {
  100 + float: left;
  101 + margin-top: 10px;
  102 + margin-right: 20px;
  103 +}
  104 +
  105 +#header-container{
  106 + background-color:#e4edf6;
  107 + height:80px;
  108 + border-bottom:20px solid #f1e5d9;
  109 + margin-bottom:50px;
  110 +}
  111 +
  112 +#title{
  113 + font-weight:normal;
  114 + font-size: 50px;
  115 + color:white;
  116 + padding: 15px 5px 10px 0;
  117 + float:left;
  118 + clear: none;