webmachine 1.2.2 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/CHANGELOG.md +57 -0
  4. data/Gemfile +20 -15
  5. data/README.md +89 -91
  6. data/RELEASING.md +21 -0
  7. data/Rakefile +5 -21
  8. data/documentation/adapters.md +41 -0
  9. data/documentation/authentication-and-authorization.md +37 -0
  10. data/documentation/configurator.md +19 -0
  11. data/documentation/error-handling.md +86 -0
  12. data/documentation/examples.md +224 -0
  13. data/documentation/how-it-works.md +76 -0
  14. data/documentation/routes.md +112 -0
  15. data/documentation/validation.md +159 -0
  16. data/documentation/versioning-apis.md +74 -0
  17. data/documentation/visual-debugger.md +38 -0
  18. data/examples/application.rb +2 -2
  19. data/examples/debugger.rb +1 -1
  20. data/lib/webmachine.rb +3 -1
  21. data/lib/webmachine/adapter.rb +7 -13
  22. data/lib/webmachine/adapters.rb +1 -2
  23. data/lib/webmachine/adapters/httpkit.rb +74 -0
  24. data/lib/webmachine/adapters/lazy_request_body.rb +1 -2
  25. data/lib/webmachine/adapters/rack.rb +70 -25
  26. data/lib/webmachine/adapters/rack_mapped.rb +42 -0
  27. data/lib/webmachine/adapters/reel.rb +22 -23
  28. data/lib/webmachine/adapters/webrick.rb +16 -16
  29. data/lib/webmachine/application.rb +2 -2
  30. data/lib/webmachine/chunked_body.rb +3 -4
  31. data/lib/webmachine/configuration.rb +1 -1
  32. data/lib/webmachine/constants.rb +75 -0
  33. data/lib/webmachine/decision/conneg.rb +12 -10
  34. data/lib/webmachine/decision/flow.rb +42 -32
  35. data/lib/webmachine/decision/fsm.rb +14 -21
  36. data/lib/webmachine/decision/helpers.rb +10 -38
  37. data/lib/webmachine/dispatcher.rb +13 -10
  38. data/lib/webmachine/dispatcher/route.rb +45 -9
  39. data/lib/webmachine/errors.rb +9 -3
  40. data/lib/webmachine/events.rb +2 -2
  41. data/lib/webmachine/header_negotiation.rb +25 -0
  42. data/lib/webmachine/headers.rb +8 -3
  43. data/lib/webmachine/locale/en.yml +7 -5
  44. data/lib/webmachine/media_type.rb +10 -8
  45. data/lib/webmachine/request.rb +67 -26
  46. data/lib/webmachine/rescueable_exception.rb +62 -0
  47. data/lib/webmachine/resource.rb +1 -1
  48. data/lib/webmachine/resource/callbacks.rb +11 -9
  49. data/lib/webmachine/response.rb +3 -5
  50. data/lib/webmachine/spec/IO_response.body +1 -0
  51. data/lib/webmachine/spec/adapter_lint.rb +83 -37
  52. data/lib/webmachine/spec/test_resource.rb +15 -4
  53. data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
  54. data/lib/webmachine/streaming/io_encoder.rb +7 -1
  55. data/lib/webmachine/trace.rb +1 -0
  56. data/lib/webmachine/trace/fsm.rb +20 -10
  57. data/lib/webmachine/trace/resource_proxy.rb +2 -0
  58. data/lib/webmachine/translation.rb +2 -1
  59. data/lib/webmachine/version.rb +3 -3
  60. data/memory_test.rb +37 -0
  61. data/spec/spec_helper.rb +17 -9
  62. data/spec/webmachine/adapter_spec.rb +14 -15
  63. data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
  64. data/spec/webmachine/adapters/rack_mapped_spec.rb +71 -0
  65. data/spec/webmachine/adapters/rack_spec.rb +32 -6
  66. data/spec/webmachine/adapters/reel_spec.rb +16 -12
  67. data/spec/webmachine/adapters/webrick_spec.rb +2 -2
  68. data/spec/webmachine/application_spec.rb +18 -17
  69. data/spec/webmachine/chunked_body_spec.rb +3 -3
  70. data/spec/webmachine/configuration_spec.rb +5 -5
  71. data/spec/webmachine/cookie_spec.rb +13 -13
  72. data/spec/webmachine/decision/conneg_spec.rb +49 -43
  73. data/spec/webmachine/decision/falsey_spec.rb +4 -4
  74. data/spec/webmachine/decision/flow_spec.rb +195 -145
  75. data/spec/webmachine/decision/fsm_spec.rb +81 -19
  76. data/spec/webmachine/decision/helpers_spec.rb +20 -20
  77. data/spec/webmachine/dispatcher/rfc3986_percent_decode_spec.rb +22 -0
  78. data/spec/webmachine/dispatcher/route_spec.rb +114 -32
  79. data/spec/webmachine/dispatcher_spec.rb +49 -24
  80. data/spec/webmachine/errors_spec.rb +1 -1
  81. data/spec/webmachine/etags_spec.rb +19 -19
  82. data/spec/webmachine/events_spec.rb +6 -6
  83. data/spec/webmachine/headers_spec.rb +14 -14
  84. data/spec/webmachine/media_type_spec.rb +36 -36
  85. data/spec/webmachine/request_spec.rb +70 -39
  86. data/spec/webmachine/rescueable_exception_spec.rb +15 -0
  87. data/spec/webmachine/resource/authentication_spec.rb +6 -6
  88. data/spec/webmachine/response_spec.rb +18 -12
  89. data/spec/webmachine/trace/fsm_spec.rb +8 -8
  90. data/spec/webmachine/trace/resource_proxy_spec.rb +9 -9
  91. data/spec/webmachine/trace/trace_store_spec.rb +5 -5
  92. data/spec/webmachine/trace_spec.rb +3 -3
  93. data/webmachine.gemspec +2 -6
  94. metadata +78 -228
  95. data/lib/webmachine/adapters/hatetepe.rb +0 -108
  96. data/lib/webmachine/adapters/mongrel.rb +0 -127
  97. data/lib/webmachine/dispatcher/not_found_resource.rb +0 -5
  98. data/lib/webmachine/fiber18.rb +0 -88
  99. data/spec/webmachine/adapters/hatetepe_spec.rb +0 -60
  100. data/spec/webmachine/adapters/mongrel_spec.rb +0 -16
@@ -1,108 +0,0 @@
1
- require 'hatetepe/server'
2
-
3
- unless Hatetepe::VERSION >= '0.5.0'
4
- raise LoadError, 'webmachine only supports hatetepe >= 0.5.0'
5
- end
6
-
7
- require 'webmachine/version'
8
- require 'webmachine/headers'
9
- require 'webmachine/request'
10
- require 'webmachine/response'
11
- require 'webmachine/dispatcher'
12
- require 'webmachine/chunked_body'
13
-
14
- module Webmachine
15
- module Adapters
16
- class Hatetepe < Adapter
17
- def options
18
- {
19
- :host => configuration.ip,
20
- :port => configuration.port,
21
- :app => [
22
- ::Hatetepe::Server::Pipeline,
23
- ::Hatetepe::Server::KeepAlive,
24
- method(:call)
25
- ]
26
- }
27
- end
28
-
29
- def run
30
- EM.epoll
31
- EM.synchrony do
32
- ::Hatetepe::Server.start(options)
33
- trap("INT") { shutdown }
34
- end
35
- end
36
-
37
- def shutdown
38
- EM.stop
39
- end
40
-
41
- def call(request, &respond)
42
- response = Webmachine::Response.new
43
- dispatcher.dispatch(convert_request(request), response)
44
-
45
- respond.call(convert_response(response))
46
- end
47
-
48
- private
49
-
50
- def convert_request(request)
51
- args = [
52
- request.verb,
53
- build_request_uri(request),
54
- Webmachine::Headers[request.headers.dup],
55
- Body.new(request.body)
56
- ]
57
- Webmachine::Request.new(*args)
58
- end
59
-
60
- def convert_response(response)
61
- response.headers["Server"] = [
62
- Webmachine::SERVER_STRING,
63
- "hatetepe/#{::Hatetepe::VERSION}"
64
- ].join(" ")
65
-
66
- args = [
67
- response.code.to_i,
68
- response.headers,
69
- convert_body(response.body)
70
- ]
71
- ::Hatetepe::Response.new(*args)
72
- end
73
-
74
- def convert_body(body)
75
- if body.respond_to?(:call)
76
- [ body.call ]
77
- elsif body.respond_to?(:to_s)
78
- [ body.to_s ]
79
- else
80
- body
81
- end
82
- end
83
-
84
- def build_request_uri(request)
85
- uri = URI.parse(request.uri)
86
- uri.scheme = "http"
87
-
88
- host = request.headers.fetch("Host", "").split(":")
89
- uri.host = host[0] || configuration.ip
90
- uri.port = host[1].to_i || configuration.port
91
-
92
- URI.parse(uri.to_s)
93
- end
94
-
95
- class Body < Struct.new(:body)
96
- def each(&block)
97
- body.rewind
98
- body.each(&block)
99
- end
100
-
101
- def to_s
102
- body.rewind
103
- body.read.to_s
104
- end
105
- end
106
- end
107
- end
108
- end
@@ -1,127 +0,0 @@
1
- require 'mongrel'
2
- require 'webmachine/version'
3
- require 'webmachine/headers'
4
- require 'webmachine/request'
5
- require 'webmachine/response'
6
- require 'webmachine/dispatcher'
7
- require 'webmachine/chunked_body'
8
-
9
- module Webmachine
10
- module Adapters
11
- # Connects Webmachine to Mongrel.
12
- class Mongrel < Adapter
13
-
14
- # Starts the Mongrel adapter
15
- def run
16
- defaults = {
17
- :port => configuration.port,
18
- :host => configuration.ip,
19
- :dispatcher => dispatcher
20
- }.merge(configuration.adapter_options)
21
- @config = ::Mongrel::Configurator.new(defaults) do
22
- listener do
23
- uri '/', :handler => Webmachine::Adapters::Mongrel::Handler.new(defaults[:dispatcher])
24
- end
25
- trap("INT") { stop }
26
- run
27
- end
28
- @config.join
29
- end
30
-
31
- def shutdown
32
- # The second argument tells mongrel to block until all listeners are shut down.
33
- # This causes the mongrel tests to be very slow, but faster methods cause errors.
34
- @config.stop(false, true) if @config
35
- end
36
-
37
- # A Mongrel handler for Webmachine
38
- class Handler < ::Mongrel::HttpHandler
39
- def initialize(dispatcher)
40
- @dispatcher = dispatcher
41
- super()
42
- end
43
-
44
- # Processes an individual request from Mongrel through Webmachine.
45
- def process(wreq, wres)
46
- header = Webmachine::Headers.from_cgi(wreq.params)
47
-
48
- request = Webmachine::Request.new(wreq.params["REQUEST_METHOD"],
49
- URI.parse(wreq.params["REQUEST_URI"]),
50
- header,
51
- RequestBody.new(wreq))
52
-
53
- response = Webmachine::Response.new
54
- @dispatcher.dispatch(request, response)
55
-
56
- begin
57
- wres.status = response.code.to_i
58
- wres.send_status(nil)
59
-
60
- response.headers.each do |k, vs|
61
- [*vs].each do |v|
62
- wres.header[k] = v
63
- end
64
- end
65
-
66
- wres.header['Server'] = [Webmachine::SERVER_STRING, "Mongrel/#{::Mongrel::Const::MONGREL_VERSION}"].join(" ")
67
- wres.send_header
68
-
69
- case response.body
70
- when String
71
- wres.write response.body
72
- wres.socket.flush
73
- when Enumerable
74
- # This might be an IOEncoder with a Content-Length, which shouldn't be chunked.
75
- if response.headers["Transfer-Encoding"] == "chunked"
76
- Webmachine::ChunkedBody.new(response.body).each do |part|
77
- wres.write part
78
- wres.socket.flush
79
- end
80
- else
81
- response.body.each do |part|
82
- wres.write part
83
- wres.socket.flush
84
- end
85
- end
86
- else
87
- if response.body.respond_to?(:call)
88
- Webmachine::ChunkedBody.new(Array(response.body.call)).each do |part|
89
- wres.write part
90
- wres.socket.flush
91
- end
92
- end
93
- end
94
- ensure
95
- response.body.close if response.body.respond_to? :close
96
- end
97
- end
98
- end # class Handler
99
-
100
- # Wraps a Mongrel request body so that it can behave like a
101
- # String.
102
- # @api private
103
- class RequestBody
104
- # @return the request from Mongrel
105
- attr_reader :request
106
-
107
- # @param request the request from Mongrel
108
- def initialize(request)
109
- @request = request
110
- end
111
-
112
- # @return [String] the request body as a string
113
- def to_s
114
- request.body.rewind
115
- request.body.read
116
- end
117
-
118
- # @yield [chunk]
119
- # @yieldparam [String] chunk a chunk of the request body
120
- def each(&block)
121
- request.body.each(&block)
122
- end
123
- end # class RequestBody
124
-
125
- end # module Mongrel
126
- end # module Adapters
127
- end # module Webmachine
@@ -1,5 +0,0 @@
1
- class Webmachine::Dispatcher::NotFoundResource < Webmachine::Resource
2
- def resource_exists?
3
- false
4
- end
5
- end
@@ -1,88 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Poor Man's Fiber (API compatible Thread based Fiber implementation for Ruby 1.8)
3
- # (c) 2008 Aman Gupta (tmm1)
4
-
5
- unless defined? Fiber
6
- require 'thread'
7
-
8
- # Raised by {Fiber} when they are used improperly
9
- class FiberError < StandardError; end
10
-
11
- # Implements a reasonably-compatible Fiber class that can be used on
12
- # Rubies that have 1.8-style APIs.
13
- class Fiber
14
- # @yield the block that should be executed inside the Fiber
15
- def initialize
16
- raise ArgumentError, 'new Fiber requires a block' unless block_given?
17
-
18
- @yield = Queue.new
19
- @resume = Queue.new
20
-
21
- @thread = Thread.new{ @yield.push [ *yield(*@resume.pop) ] }
22
- @thread.abort_on_exception = true
23
- @thread[:fiber] = self
24
- end
25
- attr_reader :thread
26
-
27
- # Returns true if the fiber can still be resumed (or transferred
28
- # to). After finishing execution of the fiber block this method
29
- # will always return false.
30
- def alive?
31
- @thread.alive?
32
- end
33
-
34
- # Resumes the fiber from the point at which the last Fiber.yield
35
- # was called, or starts running it if it is the first call to
36
- # resume. Arguments passed to resume will be the value of the
37
- # Fiber.yield expression or will be passed as block parameters to
38
- # the fiber’s block if this is the first resume.
39
- #
40
- # Alternatively, when resume is called it evaluates to the arguments
41
- # passed to the next Fiber.yield statement inside the fiber’s block or
42
- # to the block value if it runs to completion without any Fiber.yield
43
- def resume *args
44
- raise FiberError, 'dead fiber called' unless @thread.alive?
45
- @resume.push(args)
46
- result = @yield.pop
47
- result.size > 1 ? result : result.first
48
- end
49
-
50
- # Yields control back to the context that resumed this fiber,
51
- # passing along any arguments that were passed to it. The fiber
52
- # will resume processing at this point when resume is called
53
- # next. Any arguments passed to the next resume will be the value
54
- # that this Fiber.yield expression evaluates to.
55
- # @note This method is only called internally. In your code, use
56
- # {Fiber.yield}.
57
- def yield *args
58
- @yield.push(args)
59
- result = @resume.pop
60
- result.size > 1 ? result : result.first
61
- end
62
-
63
- # Yields control back to the context that resumed the fiber,
64
- # passing along any arguments that were passed to it. The fiber
65
- # will resume processing at this point when resume is called
66
- # next. Any arguments passed to the next resume will be the value
67
- # that this Fiber.yield expression evaluates to. This will raise
68
- # a {FiberError} if you are not inside a {Fiber}.
69
- # @raise FiberError
70
- def self.yield *args
71
- raise FiberError, "can't yield from root fiber" unless fiber = Thread.current[:fiber]
72
- fiber.yield(*args)
73
- end
74
-
75
- # Returns the current fiber. If you are not running in the
76
- # context of a fiber this method will raise a {FiberError}.
77
- # @raise FiberError
78
- def self.current
79
- Thread.current[:fiber] or raise FiberError, 'not inside a fiber'
80
- end
81
-
82
- # Returns a string containing a human-readable representation of
83
- # this Fiber.
84
- def inspect
85
- "#<#{self.class}:0x#{self.object_id.to_s(16)}>"
86
- end
87
- end
88
- end
@@ -1,60 +0,0 @@
1
- require "spec_helper"
2
-
3
- examples = proc do
4
- let(:configuration) { Webmachine::Configuration.default }
5
- let(:dispatcher) { Webmachine::Dispatcher.new }
6
- let(:adapter) { described_class.new(configuration, dispatcher) }
7
-
8
- it "inherits from Webmachine::Adapter" do
9
- adapter.should be_a(Webmachine::Adapter)
10
- end
11
-
12
- describe "#run" do
13
- it "starts a server" do
14
- Hatetepe::Server.should_receive(:start).with(adapter.options) { adapter.shutdown }
15
- adapter.run
16
- end
17
- end
18
-
19
- describe "#call" do
20
- let :request do
21
- Hatetepe::Request.new(:get, "/", {}, StringIO.new("hello, world!"))
22
- end
23
-
24
- it "builds a string-like and enumerable request body" do
25
- dispatcher.should_receive(:dispatch) do |req, res|
26
- req.body.to_s.should eq("hello, world!")
27
- enum_to_s(req.body).should eq("hello, world!")
28
- end
29
- adapter.call(request) {}
30
- end
31
-
32
- shared_examples "enumerable response body" do
33
- before do
34
- dispatcher.stub(:dispatch) {|_, response| response.body = body }
35
- end
36
-
37
- it "builds an enumerable response body" do
38
- adapter.call(request) do |response|
39
- enum_to_s(response.body).should eq("bye, world!")
40
- end
41
- end
42
- end
43
-
44
- describe "with normal response" do
45
- let(:body) { "bye, world!" }
46
-
47
- it_behaves_like "enumerable response body"
48
- end
49
-
50
- describe "with streaming response" do
51
- let(:body) { proc { "bye, world!" } }
52
-
53
- it_behaves_like "enumerable response body"
54
- end
55
- end
56
-
57
- def enum_to_s(enum)
58
- enum.to_enum.to_a.join
59
- end
60
- end
@@ -1,16 +0,0 @@
1
- require "spec_helper"
2
- require "webmachine/spec/adapter_lint"
3
-
4
- begin
5
- describe Webmachine::Adapters::Mongrel do
6
- it_should_behave_like :adapter_lint do
7
- it "should set Server header" do
8
- response = client.request(Net::HTTP::Get.new("/test"))
9
- response["Server"].should match(/Webmachine/)
10
- response["Server"].should match(/Mongrel/)
11
- end
12
- end
13
- end
14
- rescue LoadError
15
- warn "Platform is #{RUBY_PLATFORM}: skipping mongrel adapter spec."
16
- end