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
@@ -17,8 +17,10 @@ module Test
|
|
17
17
|
["test/response.enumbody", :to_enum],
|
18
18
|
["test/response.procbody", :to_proc],
|
19
19
|
["test/response.fiberbody", :to_fiber],
|
20
|
-
["test/response.iobody", :
|
21
|
-
["test/response.cookies", :to_cookies]
|
20
|
+
["test/response.iobody", :to_io_body],
|
21
|
+
["test/response.cookies", :to_cookies],
|
22
|
+
["test/response.request_uri", :to_request_uri],
|
23
|
+
["test/response.rack_env", :to_rack_env]
|
22
24
|
]
|
23
25
|
end
|
24
26
|
|
@@ -58,8 +60,8 @@ module Test
|
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
61
|
-
def
|
62
|
-
|
63
|
+
def to_io_body
|
64
|
+
File.new(File.expand_path('../IO_response.body', __FILE__))
|
63
65
|
end
|
64
66
|
|
65
67
|
def to_cookies
|
@@ -67,7 +69,16 @@ module Test
|
|
67
69
|
response.set_cookie("rodeo", "clown")
|
68
70
|
# FIXME: Mongrel/WEBrick fail if this method returns nil
|
69
71
|
# Might be a net/http issue. Is this a bug?
|
72
|
+
# @see Flow#o18, Helpers#encode_body_if_set
|
70
73
|
request.cookies["echo"] || ""
|
71
74
|
end
|
75
|
+
|
76
|
+
def to_request_uri
|
77
|
+
request.uri.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_rack_env
|
81
|
+
request.env.to_json
|
82
|
+
end
|
72
83
|
end
|
73
84
|
end
|
@@ -30,10 +30,16 @@ module Webmachine
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
# Allows the response body to be converted to a IO object.
|
34
|
+
# @return [IO,nil] the body as a IO object, or nil.
|
35
|
+
def to_io
|
36
|
+
IO.try_convert(body)
|
37
|
+
end
|
38
|
+
|
33
39
|
# Returns the length of the IO stream, if known. Returns nil if
|
34
40
|
# the stream uses an encoder or charsetter that might modify the
|
35
41
|
# length of the stream, or the stream size is unknown.
|
36
|
-
# @return [
|
42
|
+
# @return [Integer] the size, in bytes, of the underlying IO, or
|
37
43
|
# nil if unsupported
|
38
44
|
def size
|
39
45
|
if is_unencoded?
|
data/lib/webmachine/trace.rb
CHANGED
data/lib/webmachine/trace/fsm.rb
CHANGED
@@ -4,6 +4,22 @@ module Webmachine
|
|
4
4
|
# tracing is enabled for a resource, enabling the capturing of
|
5
5
|
# traces.
|
6
6
|
module FSM
|
7
|
+
# Overrides the default resource accessor so that incoming
|
8
|
+
# callbacks are traced.
|
9
|
+
def initialize(_resource, _request, _response)
|
10
|
+
if trace?
|
11
|
+
class << self
|
12
|
+
def resource
|
13
|
+
@resource_proxy ||= ResourceProxy.new(@resource)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def trace?
|
20
|
+
Trace.trace?(@resource)
|
21
|
+
end
|
22
|
+
|
7
23
|
# Adds the request to the trace.
|
8
24
|
# @param [Webmachine::Request] request the request to be traced
|
9
25
|
def trace_request(request)
|
@@ -13,7 +29,7 @@ module Webmachine
|
|
13
29
|
:path => request.uri.request_uri.to_s,
|
14
30
|
:headers => request.headers,
|
15
31
|
:body => request.body.to_s
|
16
|
-
}
|
32
|
+
} if trace?
|
17
33
|
end
|
18
34
|
|
19
35
|
# Adds the response to the trace and then commits the trace to
|
@@ -25,24 +41,18 @@ module Webmachine
|
|
25
41
|
:code => response.code.to_s,
|
26
42
|
:headers => response.headers,
|
27
43
|
:body => trace_response_body(response.body)
|
28
|
-
}
|
44
|
+
} if trace?
|
29
45
|
ensure
|
30
46
|
Webmachine::Events.publish('wm.trace.record', {
|
31
47
|
:trace_id => resource.object_id.to_s,
|
32
48
|
:trace => response.trace
|
33
|
-
})
|
49
|
+
}) if trace?
|
34
50
|
end
|
35
51
|
|
36
52
|
# Adds a decision to the trace.
|
37
53
|
# @param [Symbol] decision the decision being processed
|
38
54
|
def trace_decision(decision)
|
39
|
-
response.trace << {:type => :decision, :decision => decision}
|
40
|
-
end
|
41
|
-
|
42
|
-
# Overrides the default resource accessor so that incoming
|
43
|
-
# callbacks are traced.
|
44
|
-
def resource
|
45
|
-
@resource_proxy ||= ResourceProxy.new(@resource)
|
55
|
+
response.trace << {:type => :decision, :decision => decision} if trace?
|
46
56
|
end
|
47
57
|
|
48
58
|
private
|
data/lib/webmachine/version.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
module Webmachine
|
1
|
+
module Webmachine
|
2
2
|
# Library version
|
3
|
-
VERSION = "1.
|
3
|
+
VERSION = "1.6.0".freeze
|
4
4
|
|
5
5
|
# String for use in "Server" HTTP response header, which includes
|
6
6
|
# the {VERSION}.
|
7
|
-
SERVER_STRING = "Webmachine-Ruby/#{VERSION}"
|
7
|
+
SERVER_STRING = "Webmachine-Ruby/#{VERSION}".freeze
|
8
8
|
end
|
data/memory_test.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'webmachine'
|
3
|
+
|
4
|
+
class Constantized < Webmachine::Resource
|
5
|
+
HELLO_WORLD = "Hello World".freeze
|
6
|
+
ALLOWED_METHODS = ['GET'.freeze].freeze
|
7
|
+
CONTENT_TYPES_PROVIDED = [['text/html'.freeze, :to_html].freeze].freeze
|
8
|
+
|
9
|
+
def allowed_methods
|
10
|
+
ALLOWED_METHODS
|
11
|
+
end
|
12
|
+
|
13
|
+
def content_types_provided
|
14
|
+
CONTENT_TYPES_PROVIDED
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_html
|
18
|
+
HELLO_WORLD
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Webmachine.application.routes do
|
23
|
+
add ['constantized'], Constantized
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'webmachine/test'
|
27
|
+
session = Webmachine::Test::Session.new(Webmachine.application)
|
28
|
+
CONSTANTIZED = '/constantized'.freeze
|
29
|
+
require 'memory_profiler'
|
30
|
+
report = MemoryProfiler.report do
|
31
|
+
100.times do
|
32
|
+
session.get(CONSTANTIZED)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
report.pretty_print
|
37
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require 'webmachine'
|
6
|
-
require 'rspec'
|
1
|
+
require "bundler/setup"
|
2
|
+
Bundler.require :default, :test, :webservers
|
7
3
|
require 'logger'
|
8
4
|
|
5
|
+
class NullLogger < Logger
|
6
|
+
def add(severity, message=nil, progname=nil, &block)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
9
10
|
RSpec.configure do |config|
|
10
11
|
config.mock_with :rspec
|
11
12
|
config.filter_run :focus => true
|
12
13
|
config.run_all_when_everything_filtered = true
|
13
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
14
14
|
config.formatter = :documentation if ENV['CI']
|
15
15
|
if defined?(::Java)
|
16
16
|
config.seed = Time.now.utc
|
@@ -18,13 +18,21 @@ RSpec.configure do |config|
|
|
18
18
|
config.order = :random
|
19
19
|
end
|
20
20
|
|
21
|
+
config.before :each do
|
22
|
+
Webmachine::RescuableException.remove(RSpec::Mocks::MockExpectationError)
|
23
|
+
end
|
24
|
+
|
25
|
+
config.after :each do
|
26
|
+
Webmachine::RescuableException.default!
|
27
|
+
end
|
28
|
+
|
21
29
|
config.before(:suite) do
|
22
30
|
options = {
|
23
|
-
:Logger =>
|
31
|
+
:Logger => NullLogger.new(STDERR),
|
24
32
|
:AccessLog => []
|
25
33
|
}
|
26
34
|
Webmachine::Adapters::WEBrick::DEFAULT_OPTIONS.merge! options
|
27
|
-
Webmachine::Adapters::Rack::DEFAULT_OPTIONS.merge! options
|
35
|
+
Webmachine::Adapters::Rack::DEFAULT_OPTIONS.merge! options if defined?(Webmachine::Adapters::Rack)
|
28
36
|
end
|
29
37
|
end
|
30
38
|
|
@@ -1,39 +1,38 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Webmachine::Adapter do
|
4
|
-
let(:
|
5
|
-
let(:dispatcher) { Webmachine::Dispatcher.new }
|
4
|
+
let(:application) { Webmachine::Application.new }
|
6
5
|
let(:adapter) do
|
7
|
-
|
6
|
+
server = TCPServer.new('0.0.0.0', 0)
|
7
|
+
application.configuration.port = server.addr[1]
|
8
|
+
server.close
|
9
|
+
|
10
|
+
described_class.new(application)
|
8
11
|
end
|
9
12
|
|
10
13
|
describe "#initialize" do
|
11
|
-
it "stores the provided
|
12
|
-
adapter.
|
13
|
-
end
|
14
|
-
|
15
|
-
it "stores the provided dispatcher" do
|
16
|
-
adapter.dispatcher.should eql dispatcher
|
14
|
+
it "stores the provided application" do
|
15
|
+
expect(adapter.application).to eq(application)
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
20
19
|
describe ".run" do
|
21
20
|
it "creates a new adapter and runs it" do
|
22
|
-
adapter =
|
21
|
+
adapter = double(described_class)
|
23
22
|
|
24
|
-
described_class.
|
25
|
-
with(
|
23
|
+
expect(described_class).to receive(:new).
|
24
|
+
with(application).
|
26
25
|
and_return(adapter)
|
27
26
|
|
28
|
-
adapter.
|
27
|
+
expect(adapter).to receive(:run)
|
29
28
|
|
30
|
-
described_class.run(
|
29
|
+
described_class.run(application)
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
34
33
|
describe "#run" do
|
35
34
|
it "raises a NotImplementedError" do
|
36
|
-
|
35
|
+
expect { adapter.run }.to raise_exception(NotImplementedError)
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'webmachine/adapter'
|
2
|
+
require 'webmachine/adapters/rack_mapped'
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'webmachine/spec/adapter_lint'
|
5
|
+
require 'rack/test'
|
6
|
+
|
7
|
+
describe Webmachine::Adapters::RackMapped do
|
8
|
+
it_should_behave_like :adapter_lint do
|
9
|
+
it "should set Server header" do
|
10
|
+
response = client.request(Net::HTTP::Get.new("/test"))
|
11
|
+
expect(response["Server"]).to match(/Webmachine/)
|
12
|
+
expect(response["Server"]).to match(/Rack/)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Webmachine::Adapters::RackMapped do
|
18
|
+
class CreateResource < Webmachine::Resource
|
19
|
+
def allowed_methods
|
20
|
+
["POST"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def content_types_accepted
|
24
|
+
[["application/json", :from_json]]
|
25
|
+
end
|
26
|
+
|
27
|
+
def content_types_provided
|
28
|
+
[["application/json", :to_json]]
|
29
|
+
end
|
30
|
+
|
31
|
+
def post_is_create?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_path
|
36
|
+
"created_path_here/123"
|
37
|
+
end
|
38
|
+
|
39
|
+
def from_json
|
40
|
+
response.body = %{ {"foo": "bar"} }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
let(:app) do
|
45
|
+
Rack::Builder.new do
|
46
|
+
map '/some/route' do
|
47
|
+
run(Webmachine::Application.new do |app|
|
48
|
+
app.add_route(["test"], Test::Resource)
|
49
|
+
app.add_route(["create_test"], CreateResource)
|
50
|
+
app.configure do | config |
|
51
|
+
config.adapter = :RackMapped
|
52
|
+
end
|
53
|
+
end.adapter)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "using Rack::Test" do
|
59
|
+
include Rack::Test::Methods
|
60
|
+
|
61
|
+
it "provides the full request URI" do
|
62
|
+
rack_response = get "some/route/test", nil, {"HTTP_ACCEPT" => "test/response.request_uri"}
|
63
|
+
expect(rack_response.body).to eq "http://example.org/some/route/test"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "provides LOCATION header using custom base_uri when creating from POST request" do
|
67
|
+
rack_response = post "/some/route/create_test", %{{"foo": "bar"}}, {"HTTP_ACCEPT" => "application/json", "CONTENT_TYPE" => "application/json"}
|
68
|
+
expect(rack_response.headers["Location"]).to eq("http://example.org/some/route/created_path_here/123")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -2,35 +2,61 @@ require 'webmachine/adapter'
|
|
2
2
|
require 'webmachine/adapters/rack'
|
3
3
|
require 'spec_helper'
|
4
4
|
require 'webmachine/spec/adapter_lint'
|
5
|
+
require 'rack/test'
|
5
6
|
|
6
7
|
describe Webmachine::Adapters::Rack do
|
7
8
|
it_should_behave_like :adapter_lint do
|
8
9
|
it "should set Server header" do
|
9
10
|
response = client.request(Net::HTTP::Get.new("/test"))
|
10
|
-
response["Server"].
|
11
|
-
response["Server"].
|
11
|
+
expect(response["Server"]).to match(/Webmachine/)
|
12
|
+
expect(response["Server"]).to match(/Rack/)
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
17
|
describe Webmachine::Adapters::Rack::RackResponse do
|
17
18
|
context "on Rack < 1.5 release" do
|
18
|
-
before { Rack.
|
19
|
+
before { allow(Rack).to receive_messages(:release => "1.4") }
|
19
20
|
|
20
21
|
it "should add Content-Type header on not acceptable response" do
|
21
22
|
rack_response = described_class.new(double(:body), 406, {})
|
22
23
|
rack_status, rack_headers, rack_body = rack_response.finish
|
23
|
-
rack_headers.
|
24
|
+
expect(rack_headers).to have_key("Content-Type")
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
27
28
|
context "on Rack >= 1.5 release" do
|
28
|
-
before { Rack.
|
29
|
+
before { allow(Rack).to receive_messages(:release => "1.5") }
|
29
30
|
|
30
31
|
it "should not add Content-Type header on not acceptable response" do
|
31
32
|
rack_response = described_class.new(double(:body), 406, {})
|
32
33
|
rack_status, rack_headers, rack_body = rack_response.finish
|
33
|
-
rack_headers.
|
34
|
+
expect(rack_headers).not_to have_key("Content-Type")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe Webmachine::Adapters::Rack do
|
40
|
+
let(:app) do
|
41
|
+
Webmachine::Application.new do |app|
|
42
|
+
app.add_route(["test"], Test::Resource)
|
43
|
+
app.configure do | config |
|
44
|
+
config.adapter = :Rack
|
45
|
+
end
|
46
|
+
end.adapter
|
47
|
+
end
|
48
|
+
|
49
|
+
context "using Rack::Test" do
|
50
|
+
include Rack::Test::Methods
|
51
|
+
|
52
|
+
it "provides the full request URI" do
|
53
|
+
rack_response = get "test", nil, {"HTTP_ACCEPT" => "test/response.request_uri"}
|
54
|
+
expect(rack_response.body).to eq "http://example.org/test"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "provides the rack env on the request" do
|
58
|
+
rack_response = get "test", nil, {"HTTP_ACCEPT" => "test/response.rack_env"}
|
59
|
+
expect(JSON.parse(rack_response.body).keys).to include "rack.input"
|
34
60
|
end
|
35
61
|
end
|
36
62
|
end
|