webmachine 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/CHANGELOG.md +4 -0
  4. data/Gemfile +13 -11
  5. data/README.md +85 -89
  6. data/Rakefile +0 -1
  7. data/documentation/adapters.md +39 -0
  8. data/documentation/authentication-and-authorization.md +37 -0
  9. data/documentation/configurator.md +19 -0
  10. data/documentation/error-handling.md +86 -0
  11. data/documentation/examples.md +215 -0
  12. data/documentation/how-it-works.md +76 -0
  13. data/documentation/routes.md +97 -0
  14. data/documentation/validation.md +159 -0
  15. data/documentation/versioning-apis.md +74 -0
  16. data/documentation/visual-debugger.md +38 -0
  17. data/examples/application.rb +2 -2
  18. data/examples/debugger.rb +1 -1
  19. data/lib/webmachine.rb +3 -1
  20. data/lib/webmachine/adapter.rb +7 -13
  21. data/lib/webmachine/adapters.rb +1 -2
  22. data/lib/webmachine/adapters/httpkit.rb +74 -0
  23. data/lib/webmachine/adapters/lazy_request_body.rb +1 -2
  24. data/lib/webmachine/adapters/rack.rb +37 -21
  25. data/lib/webmachine/adapters/reel.rb +21 -23
  26. data/lib/webmachine/adapters/webrick.rb +16 -16
  27. data/lib/webmachine/application.rb +2 -2
  28. data/lib/webmachine/chunked_body.rb +3 -4
  29. data/lib/webmachine/constants.rb +75 -0
  30. data/lib/webmachine/decision/conneg.rb +12 -10
  31. data/lib/webmachine/decision/flow.rb +31 -21
  32. data/lib/webmachine/decision/fsm.rb +10 -18
  33. data/lib/webmachine/decision/helpers.rb +9 -37
  34. data/lib/webmachine/dispatcher.rb +13 -10
  35. data/lib/webmachine/dispatcher/route.rb +18 -8
  36. data/lib/webmachine/errors.rb +7 -1
  37. data/lib/webmachine/header_negotiation.rb +25 -0
  38. data/lib/webmachine/headers.rb +7 -2
  39. data/lib/webmachine/locale/en.yml +7 -5
  40. data/lib/webmachine/media_type.rb +10 -8
  41. data/lib/webmachine/request.rb +44 -15
  42. data/lib/webmachine/resource.rb +1 -1
  43. data/lib/webmachine/resource/callbacks.rb +6 -4
  44. data/lib/webmachine/spec/IO_response.body +1 -0
  45. data/lib/webmachine/spec/adapter_lint.rb +70 -36
  46. data/lib/webmachine/spec/test_resource.rb +10 -4
  47. data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
  48. data/lib/webmachine/streaming/io_encoder.rb +6 -0
  49. data/lib/webmachine/trace.rb +1 -0
  50. data/lib/webmachine/trace/fsm.rb +20 -10
  51. data/lib/webmachine/trace/resource_proxy.rb +2 -0
  52. data/lib/webmachine/translation.rb +2 -1
  53. data/lib/webmachine/version.rb +3 -3
  54. data/memory_test.rb +37 -0
  55. data/spec/spec_helper.rb +9 -9
  56. data/spec/webmachine/adapter_spec.rb +14 -15
  57. data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
  58. data/spec/webmachine/adapters/rack_spec.rb +6 -6
  59. data/spec/webmachine/adapters/reel_spec.rb +15 -11
  60. data/spec/webmachine/adapters/webrick_spec.rb +2 -2
  61. data/spec/webmachine/application_spec.rb +18 -17
  62. data/spec/webmachine/chunked_body_spec.rb +3 -3
  63. data/spec/webmachine/configuration_spec.rb +5 -5
  64. data/spec/webmachine/cookie_spec.rb +13 -13
  65. data/spec/webmachine/decision/conneg_spec.rb +48 -42
  66. data/spec/webmachine/decision/falsey_spec.rb +4 -4
  67. data/spec/webmachine/decision/flow_spec.rb +194 -144
  68. data/spec/webmachine/decision/fsm_spec.rb +17 -17
  69. data/spec/webmachine/decision/helpers_spec.rb +20 -20
  70. data/spec/webmachine/dispatcher/route_spec.rb +73 -27
  71. data/spec/webmachine/dispatcher_spec.rb +34 -24
  72. data/spec/webmachine/errors_spec.rb +1 -1
  73. data/spec/webmachine/etags_spec.rb +19 -19
  74. data/spec/webmachine/events_spec.rb +6 -6
  75. data/spec/webmachine/headers_spec.rb +14 -14
  76. data/spec/webmachine/media_type_spec.rb +36 -36
  77. data/spec/webmachine/request_spec.rb +33 -33
  78. data/spec/webmachine/resource/authentication_spec.rb +6 -6
  79. data/spec/webmachine/response_spec.rb +12 -12
  80. data/spec/webmachine/trace/fsm_spec.rb +8 -8
  81. data/spec/webmachine/trace/resource_proxy_spec.rb +9 -9
  82. data/spec/webmachine/trace/trace_store_spec.rb +5 -5
  83. data/spec/webmachine/trace_spec.rb +3 -3
  84. data/webmachine.gemspec +2 -6
  85. metadata +48 -206
  86. data/lib/webmachine/adapters/hatetepe.rb +0 -108
  87. data/lib/webmachine/adapters/mongrel.rb +0 -127
  88. data/lib/webmachine/dispatcher/not_found_resource.rb +0 -5
  89. data/lib/webmachine/fiber18.rb +0 -88
  90. data/spec/webmachine/adapters/hatetepe_spec.rb +0 -60
  91. 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