webmachine 1.2.2 → 1.3.0

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