webmachine 1.2.2 → 1.6.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 (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
@@ -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", :to_io],
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 to_io
62
- StringIO.new("IO response body")
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
@@ -1,8 +1,4 @@
1
- begin
2
- require 'fiber'
3
- rescue LoadError
4
- require 'webmachine/fiber18'
5
- end
1
+ require 'fiber'
6
2
 
7
3
  module Webmachine
8
4
  module Streaming
@@ -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 [Fixnum] the size, in bytes, of the underlying IO, or
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?
@@ -1,3 +1,4 @@
1
+ require 'webmachine/events'
1
2
  require 'webmachine/trace/resource_proxy'
2
3
  require 'webmachine/trace/fsm'
3
4
  require 'webmachine/trace/pstore_trace_store'
@@ -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
@@ -1,3 +1,5 @@
1
+ require 'webmachine/resource'
2
+
1
3
  module Webmachine
2
4
  module Trace
3
5
  # This class is injected into the decision FSM as a stand-in for
@@ -1,5 +1,6 @@
1
+ require 'set'
1
2
  require 'i18n'
2
-
3
+ I18n.enforce_available_locales = true if I18n.respond_to?(:enforce_available_locales)
3
4
  I18n.config.load_path << File.expand_path("../locale/en.yml", __FILE__)
4
5
 
5
6
  module Webmachine
@@ -1,8 +1,8 @@
1
- module Webmachine
1
+ module Webmachine
2
2
  # Library version
3
- VERSION = "1.2.2"
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
- $LOAD_PATH << File.expand_path("..", __FILE__)
2
- $LOAD_PATH << File.expand_path("../../lib", __FILE__)
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 => Logger.new("/dev/null"),
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(:configuration) { Webmachine::Configuration.default }
5
- let(:dispatcher) { Webmachine::Dispatcher.new }
4
+ let(:application) { Webmachine::Application.new }
6
5
  let(:adapter) do
7
- described_class.new(configuration, dispatcher)
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 configuration" do
12
- adapter.configuration.should eql configuration
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 = mock(described_class)
21
+ adapter = double(described_class)
23
22
 
24
- described_class.should_receive(:new).
25
- with(configuration, dispatcher).
23
+ expect(described_class).to receive(:new).
24
+ with(application).
26
25
  and_return(adapter)
27
26
 
28
- adapter.should_receive(:run)
27
+ expect(adapter).to receive(:run)
29
28
 
30
- described_class.run(configuration, dispatcher)
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
- lambda { adapter.run }.should raise_exception(NotImplementedError)
35
+ expect { adapter.run }.to raise_exception(NotImplementedError)
37
36
  end
38
37
  end
39
38
 
@@ -0,0 +1,10 @@
1
+ require "spec_helper"
2
+ require "webmachine/spec/adapter_lint"
3
+
4
+ begin
5
+ describe Webmachine::Adapters::HTTPkit do
6
+ it_should_behave_like :adapter_lint
7
+ end
8
+ rescue LoadError
9
+ warn "Platform is #{RUBY_PLATFORM}: skipping httpkit adapter spec."
10
+ end
@@ -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"].should match(/Webmachine/)
11
- response["Server"].should match(/Rack/)
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.stub(:release => "1.4") }
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.should have_key("Content-Type")
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.stub(:release => "1.5") }
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.should_not have_key("Content-Type")
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