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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/CHANGELOG.md +4 -0
  4. data/Gemfile +13 -11
  5. data/README.md +85 -89
  6. data/Rakefile +0 -1
  7. data/documentation/adapters.md +39 -0
  8. data/documentation/authentication-and-authorization.md +37 -0
  9. data/documentation/configurator.md +19 -0
  10. data/documentation/error-handling.md +86 -0
  11. data/documentation/examples.md +215 -0
  12. data/documentation/how-it-works.md +76 -0
  13. data/documentation/routes.md +97 -0
  14. data/documentation/validation.md +159 -0
  15. data/documentation/versioning-apis.md +74 -0
  16. data/documentation/visual-debugger.md +38 -0
  17. data/examples/application.rb +2 -2
  18. data/examples/debugger.rb +1 -1
  19. data/lib/webmachine.rb +3 -1
  20. data/lib/webmachine/adapter.rb +7 -13
  21. data/lib/webmachine/adapters.rb +1 -2
  22. data/lib/webmachine/adapters/httpkit.rb +74 -0
  23. data/lib/webmachine/adapters/lazy_request_body.rb +1 -2
  24. data/lib/webmachine/adapters/rack.rb +37 -21
  25. data/lib/webmachine/adapters/reel.rb +21 -23
  26. data/lib/webmachine/adapters/webrick.rb +16 -16
  27. data/lib/webmachine/application.rb +2 -2
  28. data/lib/webmachine/chunked_body.rb +3 -4
  29. data/lib/webmachine/constants.rb +75 -0
  30. data/lib/webmachine/decision/conneg.rb +12 -10
  31. data/lib/webmachine/decision/flow.rb +31 -21
  32. data/lib/webmachine/decision/fsm.rb +10 -18
  33. data/lib/webmachine/decision/helpers.rb +9 -37
  34. data/lib/webmachine/dispatcher.rb +13 -10
  35. data/lib/webmachine/dispatcher/route.rb +18 -8
  36. data/lib/webmachine/errors.rb +7 -1
  37. data/lib/webmachine/header_negotiation.rb +25 -0
  38. data/lib/webmachine/headers.rb +7 -2
  39. data/lib/webmachine/locale/en.yml +7 -5
  40. data/lib/webmachine/media_type.rb +10 -8
  41. data/lib/webmachine/request.rb +44 -15
  42. data/lib/webmachine/resource.rb +1 -1
  43. data/lib/webmachine/resource/callbacks.rb +6 -4
  44. data/lib/webmachine/spec/IO_response.body +1 -0
  45. data/lib/webmachine/spec/adapter_lint.rb +70 -36
  46. data/lib/webmachine/spec/test_resource.rb +10 -4
  47. data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
  48. data/lib/webmachine/streaming/io_encoder.rb +6 -0
  49. data/lib/webmachine/trace.rb +1 -0
  50. data/lib/webmachine/trace/fsm.rb +20 -10
  51. data/lib/webmachine/trace/resource_proxy.rb +2 -0
  52. data/lib/webmachine/translation.rb +2 -1
  53. data/lib/webmachine/version.rb +3 -3
  54. data/memory_test.rb +37 -0
  55. data/spec/spec_helper.rb +9 -9
  56. data/spec/webmachine/adapter_spec.rb +14 -15
  57. data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
  58. data/spec/webmachine/adapters/rack_spec.rb +6 -6
  59. data/spec/webmachine/adapters/reel_spec.rb +15 -11
  60. data/spec/webmachine/adapters/webrick_spec.rb +2 -2
  61. data/spec/webmachine/application_spec.rb +18 -17
  62. data/spec/webmachine/chunked_body_spec.rb +3 -3
  63. data/spec/webmachine/configuration_spec.rb +5 -5
  64. data/spec/webmachine/cookie_spec.rb +13 -13
  65. data/spec/webmachine/decision/conneg_spec.rb +48 -42
  66. data/spec/webmachine/decision/falsey_spec.rb +4 -4
  67. data/spec/webmachine/decision/flow_spec.rb +194 -144
  68. data/spec/webmachine/decision/fsm_spec.rb +17 -17
  69. data/spec/webmachine/decision/helpers_spec.rb +20 -20
  70. data/spec/webmachine/dispatcher/route_spec.rb +73 -27
  71. data/spec/webmachine/dispatcher_spec.rb +34 -24
  72. data/spec/webmachine/errors_spec.rb +1 -1
  73. data/spec/webmachine/etags_spec.rb +19 -19
  74. data/spec/webmachine/events_spec.rb +6 -6
  75. data/spec/webmachine/headers_spec.rb +14 -14
  76. data/spec/webmachine/media_type_spec.rb +36 -36
  77. data/spec/webmachine/request_spec.rb +33 -33
  78. data/spec/webmachine/resource/authentication_spec.rb +6 -6
  79. data/spec/webmachine/response_spec.rb +12 -12
  80. data/spec/webmachine/trace/fsm_spec.rb +8 -8
  81. data/spec/webmachine/trace/resource_proxy_spec.rb +9 -9
  82. data/spec/webmachine/trace/trace_store_spec.rb +5 -5
  83. data/spec/webmachine/trace_spec.rb +3 -3
  84. data/webmachine.gemspec +2 -6
  85. metadata +48 -206
  86. data/lib/webmachine/adapters/hatetepe.rb +0 -108
  87. data/lib/webmachine/adapters/mongrel.rb +0 -127
  88. data/lib/webmachine/dispatcher/not_found_resource.rb +0 -5
  89. data/lib/webmachine/fiber18.rb +0 -88
  90. data/spec/webmachine/adapters/hatetepe_spec.rb +0 -60
  91. data/spec/webmachine/adapters/mongrel_spec.rb +0 -16
@@ -53,7 +53,7 @@ module Webmachine
53
53
  resource = self
54
54
  Application.new do |app|
55
55
  app.routes do |router|
56
- router.add ["*"], resource
56
+ router.add [:*], resource
57
57
  end
58
58
  end.run
59
59
  end
@@ -1,3 +1,5 @@
1
+ require 'webmachine/constants'
2
+
1
3
  module Webmachine
2
4
  class Resource
3
5
  # These methods are the primary way your {Webmachine::Resource}
@@ -123,7 +125,7 @@ module Webmachine
123
125
  # @return [Array<String>] allowed methods on this resource
124
126
  # @api callback
125
127
  def allowed_methods
126
- ['GET', 'HEAD']
128
+ [GET_METHOD, HEAD_METHOD]
127
129
  end
128
130
 
129
131
  # HTTP methods that are known to the resource. Like
@@ -134,7 +136,7 @@ module Webmachine
134
136
  # @return [Array<String>] known methods
135
137
  # @api callback
136
138
  def known_methods
137
- ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS']
139
+ STANDARD_HTTP_METHODS
138
140
  end
139
141
 
140
142
  # This method is called when a DELETE request should be enacted,
@@ -209,7 +211,7 @@ module Webmachine
209
211
  # @return an array of mediatype/handler pairs
210
212
  # @api callback
211
213
  def content_types_provided
212
- [['text/html', :to_html]]
214
+ [[TEXT_HTML, :to_html]]
213
215
  end
214
216
 
215
217
  # Similarly to content_types_provided, this should return an array
@@ -263,7 +265,7 @@ module Webmachine
263
265
  # @api callback
264
266
  # @see Encodings
265
267
  def encodings_provided
266
- {"identity" => :encode_identity }
268
+ {IDENTITY => :encode_identity }
267
269
  end
268
270
 
269
271
  # If this method is implemented, it should return a list of
@@ -0,0 +1 @@
1
+ IO response body
@@ -4,32 +4,68 @@ require "net/http"
4
4
  shared_examples_for :adapter_lint do
5
5
  attr_accessor :client
6
6
 
7
- before(:all) do
8
- configuration = Webmachine::Configuration.default
9
- dispatcher = Webmachine::Dispatcher.new
10
- dispatcher.add_route ["test"], Test::Resource
7
+ let(:address) { "127.0.0.1" }
8
+ let(:port) { s = TCPServer.new(address, 0); p = s.addr[1]; s.close; p }
11
9
 
12
- @adapter = described_class.new(configuration, dispatcher)
13
- @client = Net::HTTP.new(configuration.ip, configuration.port)
10
+ let(:application) do
11
+ application = Webmachine::Application.new
12
+ application.dispatcher.add_route ["test"], Test::Resource
14
13
 
15
- Thread.abort_on_exception = true
16
- @server_thread = Thread.new { @adapter.run }
14
+ application.configure do |c|
15
+ c.ip = address
16
+ c.port = port
17
+ end
18
+
19
+ application
20
+ end
17
21
 
22
+ let(:client) do
23
+ client = Net::HTTP.new(application.configuration.ip, port)
18
24
  # Wait until the server is responsive
19
25
  timeout(5) do
20
26
  begin
21
27
  client.start
22
28
  rescue Errno::ECONNREFUSED
23
- sleep(0.1)
29
+ sleep(0.01)
24
30
  retry
25
31
  end
26
32
  end
33
+ client
34
+ end
35
+
36
+ before do
37
+ @adapter = described_class.new(application)
38
+
39
+ Thread.abort_on_exception = true
40
+ @server_thread = Thread.new { @adapter.run }
41
+ sleep(0.01)
27
42
  end
28
43
 
29
- after(:all) do
30
- @adapter.shutdown
31
- @client.finish
32
- @server_thread.join
44
+ after do
45
+ client.finish
46
+ @server_thread.kill
47
+ end
48
+
49
+ it "provides the request URI" do
50
+ request = Net::HTTP::Get.new("/test")
51
+ request["Accept"] = "test/response.request_uri"
52
+ response = client.request(request)
53
+ expect(response.body).to eq("http://#{address}:#{port}/test")
54
+ end
55
+
56
+ context do
57
+ let(:address) { "::1" }
58
+
59
+ it "provides the IPv6 request URI" do
60
+ if RUBY_VERSION =~ /^2\.(0|1)\./
61
+ skip "Net::HTTP regression in Ruby 2.(0|1)"
62
+ end
63
+
64
+ request = Net::HTTP::Get.new("/test")
65
+ request["Accept"] = "test/response.request_uri"
66
+ response = client.request(request)
67
+ expect(response.body).to eq("http://[#{address}]:#{port}/test")
68
+ end
33
69
  end
34
70
 
35
71
  it "provides a string-like request body" do
@@ -37,8 +73,8 @@ shared_examples_for :adapter_lint do
37
73
  request.body = "Hello, World!"
38
74
  request["Content-Type"] = "test/request.stringbody"
39
75
  response = client.request(request)
40
- response["Content-Length"].should eq("21")
41
- response.body.should eq("String: Hello, World!")
76
+ expect(response["Content-Length"]).to eq("21")
77
+ expect(response.body).to eq("String: Hello, World!")
42
78
  end
43
79
 
44
80
  it "provides an enumerable request body" do
@@ -46,66 +82,64 @@ shared_examples_for :adapter_lint do
46
82
  request.body = "Hello, World!"
47
83
  request["Content-Type"] = "test/request.enumbody"
48
84
  response = client.request(request)
49
- response["Content-Length"].should eq("19")
50
- response.body.should eq("Enum: Hello, World!")
85
+ expect(response["Content-Length"]).to eq("19")
86
+ expect(response.body).to eq("Enum: Hello, World!")
51
87
  end
52
88
 
53
89
  it "handles missing pages" do
54
90
  request = Net::HTTP::Get.new("/missing")
55
91
  response = client.request(request)
56
- response.code.should eq("404")
57
- response["Content-Type"].should eq("text/html")
92
+ expect(response.code).to eq("404")
93
+ expect(response["Content-Type"]).to eq("text/html")
58
94
  end
59
95
 
60
96
  it "handles empty response bodies" do
61
97
  request = Net::HTTP::Post.new("/test")
62
98
  request.body = ""
63
99
  response = client.request(request)
64
- response.code.should eq("204")
65
- # FIXME: Mongrel/WEBrick fail this test. Is there a bug?
66
- #response["Content-Type"].should be_nil
67
- response["Content-Length"].should be_nil
68
- response.body.should be_nil
100
+ expect(response.code).to eq("204")
101
+ expect(["0", nil]).to include(response["Content-Length"])
102
+ expect(response.body).to be_nil
69
103
  end
70
104
 
71
105
  it "handles string response bodies" do
72
106
  request = Net::HTTP::Get.new("/test")
73
107
  request["Accept"] = "test/response.stringbody"
74
108
  response = client.request(request)
75
- response["Content-Length"].should eq("20")
76
- response.body.should eq("String response body")
109
+ expect(response["Content-Length"]).to eq("20")
110
+ expect(response.body).to eq("String response body")
77
111
  end
78
112
 
79
113
  it "handles enumerable response bodies" do
80
114
  request = Net::HTTP::Get.new("/test")
81
115
  request["Accept"] = "test/response.enumbody"
82
116
  response = client.request(request)
83
- response["Transfer-Encoding"].should eq("chunked")
84
- response.body.should eq("Enumerable response body")
117
+ expect(response["Transfer-Encoding"]).to eq("chunked")
118
+ expect(response.body).to eq("Enumerable response body")
85
119
  end
86
120
 
87
121
  it "handles proc response bodies" do
88
122
  request = Net::HTTP::Get.new("/test")
89
123
  request["Accept"] = "test/response.procbody"
90
124
  response = client.request(request)
91
- response["Transfer-Encoding"].should eq("chunked")
92
- response.body.should eq("Proc response body")
125
+ expect(response["Transfer-Encoding"]).to eq("chunked")
126
+ expect(response.body).to eq("Proc response body")
93
127
  end
94
128
 
95
129
  it "handles fiber response bodies" do
96
130
  request = Net::HTTP::Get.new("/test")
97
131
  request["Accept"] = "test/response.fiberbody"
98
132
  response = client.request(request)
99
- response["Transfer-Encoding"].should eq("chunked")
100
- response.body.should eq("Fiber response body")
133
+ expect(response["Transfer-Encoding"]).to eq("chunked")
134
+ expect(response.body).to eq("Fiber response body")
101
135
  end
102
136
 
103
137
  it "handles io response bodies" do
104
138
  request = Net::HTTP::Get.new("/test")
105
139
  request["Accept"] = "test/response.iobody"
106
140
  response = client.request(request)
107
- response["Content-Length"].should eq("16")
108
- response.body.should eq("IO response body")
141
+ expect(response["Content-Length"]).to eq("17")
142
+ expect(response.body).to eq("IO response body\n")
109
143
  end
110
144
 
111
145
  it "handles request cookies" do
@@ -113,13 +147,13 @@ shared_examples_for :adapter_lint do
113
147
  request["Accept"] = "test/response.cookies"
114
148
  request["Cookie"] = "echo=echocookie"
115
149
  response = client.request(request)
116
- response.body.should eq("echocookie")
150
+ expect(response.body).to eq("echocookie")
117
151
  end
118
152
 
119
153
  it "handles response cookies" do
120
154
  request = Net::HTTP::Get.new("/test")
121
155
  request["Accept"] = "test/response.cookies"
122
156
  response = client.request(request)
123
- response["Set-Cookie"].should eq("cookie=monster, rodeo=clown")
157
+ expect(response["Set-Cookie"]).to eq("cookie=monster, rodeo=clown")
124
158
  end
125
159
  end
@@ -17,8 +17,9 @@ 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]
22
23
  ]
23
24
  end
24
25
 
@@ -58,8 +59,8 @@ module Test
58
59
  end
59
60
  end
60
61
 
61
- def to_io
62
- StringIO.new("IO response body")
62
+ def to_io_body
63
+ File.new(File.expand_path('../IO_response.body', __FILE__))
63
64
  end
64
65
 
65
66
  def to_cookies
@@ -67,7 +68,12 @@ module Test
67
68
  response.set_cookie("rodeo", "clown")
68
69
  # FIXME: Mongrel/WEBrick fail if this method returns nil
69
70
  # Might be a net/http issue. Is this a bug?
71
+ # @see Flow#o18, Helpers#encode_body_if_set
70
72
  request.cookies["echo"] || ""
71
73
  end
74
+
75
+ def to_request_uri
76
+ request.uri.to_s
77
+ end
72
78
  end
73
79
  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,6 +30,12 @@ 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.
@@ -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.3.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
@@ -20,11 +20,11 @@ RSpec.configure do |config|
20
20
 
21
21
  config.before(:suite) do
22
22
  options = {
23
- :Logger => Logger.new("/dev/null"),
23
+ :Logger => NullLogger.new(STDERR),
24
24
  :AccessLog => []
25
25
  }
26
26
  Webmachine::Adapters::WEBrick::DEFAULT_OPTIONS.merge! options
27
- Webmachine::Adapters::Rack::DEFAULT_OPTIONS.merge! options
27
+ Webmachine::Adapters::Rack::DEFAULT_OPTIONS.merge! options if defined?(Webmachine::Adapters::Rack)
28
28
  end
29
29
  end
30
30