webmachine 1.2.2 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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