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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +13 -11
- data/README.md +85 -89
- data/Rakefile +0 -1
- data/documentation/adapters.md +39 -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 +215 -0
- data/documentation/how-it-works.md +76 -0
- data/documentation/routes.md +97 -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 +37 -21
- data/lib/webmachine/adapters/reel.rb +21 -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/constants.rb +75 -0
- data/lib/webmachine/decision/conneg.rb +12 -10
- data/lib/webmachine/decision/flow.rb +31 -21
- data/lib/webmachine/decision/fsm.rb +10 -18
- data/lib/webmachine/decision/helpers.rb +9 -37
- data/lib/webmachine/dispatcher.rb +13 -10
- data/lib/webmachine/dispatcher/route.rb +18 -8
- data/lib/webmachine/errors.rb +7 -1
- data/lib/webmachine/header_negotiation.rb +25 -0
- data/lib/webmachine/headers.rb +7 -2
- data/lib/webmachine/locale/en.yml +7 -5
- data/lib/webmachine/media_type.rb +10 -8
- data/lib/webmachine/request.rb +44 -15
- data/lib/webmachine/resource.rb +1 -1
- data/lib/webmachine/resource/callbacks.rb +6 -4
- data/lib/webmachine/spec/IO_response.body +1 -0
- data/lib/webmachine/spec/adapter_lint.rb +70 -36
- data/lib/webmachine/spec/test_resource.rb +10 -4
- data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
- data/lib/webmachine/streaming/io_encoder.rb +6 -0
- 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 +9 -9
- data/spec/webmachine/adapter_spec.rb +14 -15
- data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
- data/spec/webmachine/adapters/rack_spec.rb +6 -6
- data/spec/webmachine/adapters/reel_spec.rb +15 -11
- 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 +48 -42
- data/spec/webmachine/decision/falsey_spec.rb +4 -4
- data/spec/webmachine/decision/flow_spec.rb +194 -144
- data/spec/webmachine/decision/fsm_spec.rb +17 -17
- data/spec/webmachine/decision/helpers_spec.rb +20 -20
- data/spec/webmachine/dispatcher/route_spec.rb +73 -27
- data/spec/webmachine/dispatcher_spec.rb +34 -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 +33 -33
- data/spec/webmachine/resource/authentication_spec.rb +6 -6
- data/spec/webmachine/response_spec.rb +12 -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 +48 -206
- 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
|