webmachine 1.2.2 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +57 -0
- data/Gemfile +20 -15
- data/README.md +89 -91
- data/RELEASING.md +21 -0
- data/Rakefile +5 -21
- data/documentation/adapters.md +41 -0
- data/documentation/authentication-and-authorization.md +37 -0
- data/documentation/configurator.md +19 -0
- data/documentation/error-handling.md +86 -0
- data/documentation/examples.md +224 -0
- data/documentation/how-it-works.md +76 -0
- data/documentation/routes.md +112 -0
- data/documentation/validation.md +159 -0
- data/documentation/versioning-apis.md +74 -0
- data/documentation/visual-debugger.md +38 -0
- data/examples/application.rb +2 -2
- data/examples/debugger.rb +1 -1
- data/lib/webmachine.rb +3 -1
- data/lib/webmachine/adapter.rb +7 -13
- data/lib/webmachine/adapters.rb +1 -2
- data/lib/webmachine/adapters/httpkit.rb +74 -0
- data/lib/webmachine/adapters/lazy_request_body.rb +1 -2
- data/lib/webmachine/adapters/rack.rb +70 -25
- data/lib/webmachine/adapters/rack_mapped.rb +42 -0
- data/lib/webmachine/adapters/reel.rb +22 -23
- data/lib/webmachine/adapters/webrick.rb +16 -16
- data/lib/webmachine/application.rb +2 -2
- data/lib/webmachine/chunked_body.rb +3 -4
- data/lib/webmachine/configuration.rb +1 -1
- data/lib/webmachine/constants.rb +75 -0
- data/lib/webmachine/decision/conneg.rb +12 -10
- data/lib/webmachine/decision/flow.rb +42 -32
- data/lib/webmachine/decision/fsm.rb +14 -21
- data/lib/webmachine/decision/helpers.rb +10 -38
- data/lib/webmachine/dispatcher.rb +13 -10
- data/lib/webmachine/dispatcher/route.rb +45 -9
- data/lib/webmachine/errors.rb +9 -3
- data/lib/webmachine/events.rb +2 -2
- data/lib/webmachine/header_negotiation.rb +25 -0
- data/lib/webmachine/headers.rb +8 -3
- data/lib/webmachine/locale/en.yml +7 -5
- data/lib/webmachine/media_type.rb +10 -8
- data/lib/webmachine/request.rb +67 -26
- data/lib/webmachine/rescueable_exception.rb +62 -0
- data/lib/webmachine/resource.rb +1 -1
- data/lib/webmachine/resource/callbacks.rb +11 -9
- data/lib/webmachine/response.rb +3 -5
- data/lib/webmachine/spec/IO_response.body +1 -0
- data/lib/webmachine/spec/adapter_lint.rb +83 -37
- data/lib/webmachine/spec/test_resource.rb +15 -4
- data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
- data/lib/webmachine/streaming/io_encoder.rb +7 -1
- data/lib/webmachine/trace.rb +1 -0
- data/lib/webmachine/trace/fsm.rb +20 -10
- data/lib/webmachine/trace/resource_proxy.rb +2 -0
- data/lib/webmachine/translation.rb +2 -1
- data/lib/webmachine/version.rb +3 -3
- data/memory_test.rb +37 -0
- data/spec/spec_helper.rb +17 -9
- data/spec/webmachine/adapter_spec.rb +14 -15
- data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
- data/spec/webmachine/adapters/rack_mapped_spec.rb +71 -0
- data/spec/webmachine/adapters/rack_spec.rb +32 -6
- data/spec/webmachine/adapters/reel_spec.rb +16 -12
- data/spec/webmachine/adapters/webrick_spec.rb +2 -2
- data/spec/webmachine/application_spec.rb +18 -17
- data/spec/webmachine/chunked_body_spec.rb +3 -3
- data/spec/webmachine/configuration_spec.rb +5 -5
- data/spec/webmachine/cookie_spec.rb +13 -13
- data/spec/webmachine/decision/conneg_spec.rb +49 -43
- data/spec/webmachine/decision/falsey_spec.rb +4 -4
- data/spec/webmachine/decision/flow_spec.rb +195 -145
- data/spec/webmachine/decision/fsm_spec.rb +81 -19
- data/spec/webmachine/decision/helpers_spec.rb +20 -20
- data/spec/webmachine/dispatcher/rfc3986_percent_decode_spec.rb +22 -0
- data/spec/webmachine/dispatcher/route_spec.rb +114 -32
- data/spec/webmachine/dispatcher_spec.rb +49 -24
- data/spec/webmachine/errors_spec.rb +1 -1
- data/spec/webmachine/etags_spec.rb +19 -19
- data/spec/webmachine/events_spec.rb +6 -6
- data/spec/webmachine/headers_spec.rb +14 -14
- data/spec/webmachine/media_type_spec.rb +36 -36
- data/spec/webmachine/request_spec.rb +70 -39
- data/spec/webmachine/rescueable_exception_spec.rb +15 -0
- data/spec/webmachine/resource/authentication_spec.rb +6 -6
- data/spec/webmachine/response_spec.rb +18 -12
- data/spec/webmachine/trace/fsm_spec.rb +8 -8
- data/spec/webmachine/trace/resource_proxy_spec.rb +9 -9
- data/spec/webmachine/trace/trace_store_spec.rb +5 -5
- data/spec/webmachine/trace_spec.rb +3 -3
- data/webmachine.gemspec +2 -6
- metadata +78 -228
- data/lib/webmachine/adapters/hatetepe.rb +0 -108
- data/lib/webmachine/adapters/mongrel.rb +0 -127
- data/lib/webmachine/dispatcher/not_found_resource.rb +0 -5
- data/lib/webmachine/fiber18.rb +0 -88
- data/spec/webmachine/adapters/hatetepe_spec.rb +0 -60
- 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
|
data/lib/webmachine/fiber18.rb
DELETED
@@ -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
|