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
@@ -1,15 +1,19 @@
1
- require 'cgi'
1
+ require 'cgi'
2
2
  require 'forwardable'
3
+ require 'webmachine/constants'
4
+ require 'ipaddr'
3
5
 
4
6
  module Webmachine
5
7
  # Request represents a single HTTP request sent from a client. It
6
8
  # should be instantiated by {Adapters} when a request is received
7
9
  class Request
10
+ HTTP_HEADERS_MATCH = /^(?:[a-z0-9])+(?:_[a-z0-9]+)*$/i.freeze
11
+ ROUTING_PATH_MATCH = /^\/(.*)/.freeze
12
+
8
13
  extend Forwardable
9
- attr_reader :method, :uri, :headers, :body
10
- attr_accessor :disp_path, :path_info, :path_tokens
11
14
 
12
- STANDARD_HTTP_METHODS = %w[GET HEAD POST PUT DELETE TRACE CONNECT OPTIONS]
15
+ attr_reader :method, :uri, :headers, :body, :routing_tokens, :base_uri
16
+ attr_accessor :disp_path, :path_info, :path_tokens
13
17
 
14
18
  # @param [String] method the HTTP request method
15
19
  # @param [URI] uri the requested URI, including host, scheme and
@@ -17,8 +21,14 @@ module Webmachine
17
21
  # @param [Headers] headers the HTTP request headers
18
22
  # @param [String,#to_s,#each,nil] body the entity included in the
19
23
  # request, if present
20
- def initialize(method, uri, headers, body)
21
- @method, @uri, @headers, @body = method, uri, headers, body
24
+ def initialize(method, uri, headers, body, routing_tokens=nil, base_uri=nil)
25
+ @method, @headers, @body = method, headers, body
26
+ @uri = build_uri(uri, headers)
27
+ @routing_tokens = routing_tokens || @uri.path.match(ROUTING_PATH_MATCH)[1].split(SLASH)
28
+ @base_uri = base_uri || @uri.dup.tap do |u|
29
+ u.path = SLASH
30
+ u.query = nil
31
+ end
22
32
  end
23
33
 
24
34
  def_delegators :headers, :[]
@@ -27,9 +37,18 @@ module Webmachine
27
37
  # lowercased-underscored version of the header name, e.g.
28
38
  # `if_unmodified_since`.
29
39
  def method_missing(m, *args, &block)
30
- if m.to_s =~ /^(?:[a-z0-9])+(?:_[a-z0-9]+)*$/i
40
+ if m =~ HTTP_HEADERS_MATCH
31
41
  # Access headers more easily as underscored methods.
32
- self[m.to_s.tr('_', '-')]
42
+ header_name = m.to_s.tr(UNDERSCORE, DASH)
43
+ if (header_value = @headers[header_name])
44
+ # Make future lookups faster.
45
+ self.class.class_eval <<-RUBY, __FILE__, __LINE__
46
+ def #{m}
47
+ @headers["#{header_name}"]
48
+ end
49
+ RUBY
50
+ end
51
+ header_value
33
52
  else
34
53
  super
35
54
  end
@@ -40,16 +59,6 @@ module Webmachine
40
59
  !(body.nil? || body.empty?)
41
60
  end
42
61
 
43
- # The root URI for the request, ignoring path and query. This is
44
- # useful for calculating relative paths to resources.
45
- # @return [URI]
46
- def base_uri
47
- @base_uri ||= uri.dup.tap do |u|
48
- u.path = "/"
49
- u.query = nil
50
- end
51
- end
52
-
53
62
  # Returns a hash of query parameters (they come after the ? in the
54
63
  # URI). Note that this does NOT work in the same way as Rails,
55
64
  # i.e. it does not support nested arrays and hashes.
@@ -92,7 +101,7 @@ module Webmachine
92
101
  # @return [Boolean]
93
102
  # true if this request was made with the GET method
94
103
  def get?
95
- method == "GET"
104
+ method == GET_METHOD
96
105
  end
97
106
 
98
107
  # Is this a HEAD request?
@@ -100,7 +109,7 @@ module Webmachine
100
109
  # @return [Boolean]
101
110
  # true if this request was made with the HEAD method
102
111
  def head?
103
- method == "HEAD"
112
+ method == HEAD_METHOD
104
113
  end
105
114
 
106
115
  # Is this a POST request?
@@ -108,7 +117,7 @@ module Webmachine
108
117
  # @return [Boolean]
109
118
  # true if this request was made with the GET method
110
119
  def post?
111
- method == "POST"
120
+ method == POST_METHOD
112
121
  end
113
122
 
114
123
  # Is this a PUT request?
@@ -116,7 +125,7 @@ module Webmachine
116
125
  # @return [Boolean]
117
126
  # true if this request was made with the PUT method
118
127
  def put?
119
- method == "PUT"
128
+ method == PUT_METHOD
120
129
  end
121
130
 
122
131
  # Is this a DELETE request?
@@ -124,7 +133,7 @@ module Webmachine
124
133
  # @return [Boolean]
125
134
  # true if this request was made with the DELETE method
126
135
  def delete?
127
- method == "DELETE"
136
+ method == DELETE_METHOD
128
137
  end
129
138
 
130
139
  # Is this a TRACE request?
@@ -132,7 +141,7 @@ module Webmachine
132
141
  # @return [Boolean]
133
142
  # true if this request was made with the TRACE method
134
143
  def trace?
135
- method == "TRACE"
144
+ method == TRACE_METHOD
136
145
  end
137
146
 
138
147
  # Is this a CONNECT request?
@@ -140,7 +149,7 @@ module Webmachine
140
149
  # @return [Boolean]
141
150
  # true if this request was made with the CONNECT method
142
151
  def connect?
143
- method == "CONNECT"
152
+ method == CONNECT_METHOD
144
153
  end
145
154
 
146
155
  # Is this an OPTIONS request?
@@ -148,7 +157,39 @@ module Webmachine
148
157
  # @return [Boolean]
149
158
  # true if this request was made with the OPTIONS method
150
159
  def options?
151
- method == "OPTIONS"
160
+ method == OPTIONS_METHOD
161
+ end
162
+
163
+ private
164
+
165
+ IPV6_MATCH = /\A\[(?<address> .* )\]:(?<port> \d+ )\z/x.freeze # string like "[::1]:80"
166
+ HOST_MATCH = /\A(?<host> [^:]+ ):(?<port> \d+ )\z/x.freeze # string like "www.example.com:80"
167
+
168
+ def parse_host(uri, host_string)
169
+ # Split host and port number from string.
170
+ case host_string
171
+ when IPV6_MATCH
172
+ uri.host = IPAddr.new($~[:address], Socket::AF_INET6).to_s
173
+ uri.port = $~[:port].to_i
174
+ when HOST_MATCH
175
+ uri.host = $~[:host]
176
+ uri.port = $~[:port].to_i
177
+ else # string with no port number
178
+ uri.host = host_string
179
+ end
180
+
181
+ uri
182
+ end
183
+
184
+ def build_uri(uri, headers)
185
+ uri = URI(uri)
186
+ uri.port ||= 80
187
+ uri.scheme ||= HTTP
188
+ if uri.host
189
+ return uri
190
+ end
191
+
192
+ parse_host(uri, headers.fetch(HOST))
152
193
  end
153
194
 
154
195
  end # class Request
@@ -0,0 +1,62 @@
1
+ module Webmachine::RescuableException
2
+ require_relative 'errors'
3
+ require 'set'
4
+
5
+ UNRESCUABLE_DEFAULTS = [
6
+ Webmachine::MalformedRequest,
7
+ NoMemoryError, SystemExit, SignalException
8
+ ].freeze
9
+
10
+ UNRESCUABLE = Set.new UNRESCUABLE_DEFAULTS.dup
11
+ private_constant :UNRESCUABLE
12
+
13
+ def self.===(e)
14
+ case e
15
+ when *UNRESCUABLE then false
16
+ else true
17
+ end
18
+ end
19
+
20
+ #
21
+ # Remove modifications to Webmachine::RescuableException.
22
+ # Restores default list of unrescue-able exceptions.
23
+ #
24
+ # @return [nil]
25
+ #
26
+ def self.default!
27
+ UNRESCUABLE.replace Set.new(UNRESCUABLE_DEFAULTS.dup)
28
+ nil
29
+ end
30
+
31
+ #
32
+ # @return [Array<Exception>]
33
+ # Returns an Array of exceptions that will not be
34
+ # rescued by {Webmachine::Resource#handle_exception}.
35
+ #
36
+ def self.UNRESCUABLEs
37
+ UNRESCUABLE.to_a
38
+ end
39
+
40
+ #
41
+ # Add a variable number of exceptions that should be rescued by
42
+ # {Webmachine::Resource#handle_exception}. See {UNRESCUABLE_DEFAULTS}
43
+ # for a list of exceptions that are not caught by default.
44
+ #
45
+ # @param (see #remove)
46
+ #
47
+ def self.add(*exceptions)
48
+ exceptions.each{|e| UNRESCUABLE.delete(e)}
49
+ end
50
+
51
+ #
52
+ # Remove a variable number of exceptions from being rescued by
53
+ # {Webmachine::Resource#handle_exception}. See {UNRESCUABLE_DEFAULTS}
54
+ # for a list of exceptions that are not caught by default.
55
+ #
56
+ # @param [Exception] *exceptions
57
+ # A subclass of Exception.
58
+ #
59
+ def self.remove(*exceptions)
60
+ exceptions.each{|e| UNRESCUABLE.add(e)}
61
+ end
62
+ end
@@ -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}
@@ -101,7 +103,7 @@ module Webmachine
101
103
  # If the entity length on PUT or POST is invalid, this should
102
104
  # return false, which will result in a '413 Request Entity Too
103
105
  # Large' response. Defaults to true.
104
- # @param [Fixnum] length the size of the request body (entity)
106
+ # @param [Integer] length the size of the request body (entity)
105
107
  # @return [true,false] Whether the body is a valid length (not too
106
108
  # large)
107
109
  # @api callback
@@ -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,
@@ -190,7 +192,7 @@ module Webmachine
190
192
 
191
193
  # If post_is_create? returns false, then this will be called to
192
194
  # process any POST request. If it succeeds, it should return true.
193
- # @return [true,false,Fixnum] Whether the POST was successfully
195
+ # @return [true,false,Integer] Whether the POST was successfully
194
196
  # processed, or an alternate response code
195
197
  # @api callback
196
198
  def process_post
@@ -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
@@ -361,11 +363,11 @@ module Webmachine
361
363
  def finish_request; end
362
364
 
363
365
  #
364
- # This method is called when an exception is raised within a subclass of
366
+ # This method is called when an error is raised within a subclass of
365
367
  # {Webmachine::Resource}.
366
368
  #
367
- # @param [Exception] e
368
- # The exception.
369
+ # @param [StandardError] e
370
+ # The error.
369
371
  #
370
372
  # @return [void]
371
373
  #
@@ -4,7 +4,7 @@ module Webmachine
4
4
  # @return [HeaderHash] Response headers that will be sent to the client
5
5
  attr_reader :headers
6
6
 
7
- # @return [Fixnum] The HTTP status code of the response
7
+ # @return [Integer] The HTTP status code of the response
8
8
  attr_accessor :code
9
9
 
10
10
  # @return [String, #each] The response body
@@ -48,11 +48,9 @@ module Webmachine
48
48
  cookie = Webmachine::Cookie.new(name, value, attributes).to_s
49
49
  case headers['Set-Cookie']
50
50
  when nil
51
- headers['Set-Cookie'] = cookie
52
- when String
53
- headers['Set-Cookie'] = [headers['Set-Cookie'], cookie]
51
+ headers['Set-Cookie'] = [cookie]
54
52
  when Array
55
- headers['Set-Cookie'] = headers['Set-Cookie'] + cookie
53
+ headers['Set-Cookie'] << cookie
56
54
  end
57
55
  end
58
56
 
@@ -0,0 +1 @@
1
+ IO response body
@@ -1,44 +1,92 @@
1
- require "webmachine/spec/test_resource"
1
+ require "webmachine/spec/test_resource"
2
2
  require "net/http"
3
3
 
4
+ ADDRESS = "127.0.0.1"
5
+
4
6
  shared_examples_for :adapter_lint do
5
- attr_accessor :client
7
+ attr_reader :client
6
8
 
7
- before(:all) do
8
- configuration = Webmachine::Configuration.default
9
- dispatcher = Webmachine::Dispatcher.new
10
- dispatcher.add_route ["test"], Test::Resource
9
+ class TestApplicationNotResponsive < Timeout::Error; end
10
+
11
+ def find_free_port
12
+ temp_server = TCPServer.new(ADDRESS, 0)
13
+ port = temp_server.addr[1]
14
+ temp_server.close # only frees Ruby resource, socket is in TIME_WAIT at OS level
15
+ # so we can't have our adapter use it too quickly
11
16
 
12
- @adapter = described_class.new(configuration, dispatcher)
13
- @client = Net::HTTP.new(configuration.ip, configuration.port)
17
+ sleep(0.1) # 'Wait' for temp_server to *really* close, not just TIME_WAIT
18
+ port
19
+ end
20
+
21
+ def create_test_application(port)
22
+ Webmachine::Application.new.tap do |application|
23
+ application.dispatcher.add_route ["test"], Test::Resource
14
24
 
25
+ application.configure do |c|
26
+ c.ip = ADDRESS
27
+ c.port = port
28
+ end
29
+ end
30
+ end
31
+
32
+ def run_application(adapter_class, application)
33
+ adapter = adapter_class.new(application)
15
34
  Thread.abort_on_exception = true
16
- @server_thread = Thread.new { @adapter.run }
35
+ Thread.new { adapter.run }
36
+ end
17
37
 
18
- # Wait until the server is responsive
19
- timeout(5) do
38
+ def wait_until_server_responds_to(client)
39
+ Timeout.timeout(5, TestApplicationNotResponsive) do
20
40
  begin
21
41
  client.start
22
42
  rescue Errno::ECONNREFUSED
23
- sleep(0.1)
43
+ sleep(0.01)
24
44
  retry
25
45
  end
26
46
  end
27
47
  end
28
48
 
49
+ before(:all) do
50
+ @port = find_free_port
51
+ application = create_test_application(@port)
52
+
53
+ adapter_class = described_class
54
+ @server_thread = run_application(adapter_class, application)
55
+
56
+ @client = Net::HTTP.new(application.configuration.ip, @port)
57
+ wait_until_server_responds_to(client)
58
+ end
59
+
29
60
  after(:all) do
30
- @adapter.shutdown
31
61
  @client.finish
32
- @server_thread.join
62
+ @server_thread.kill
63
+ end
64
+
65
+ it "provides the request URI" do
66
+ request = Net::HTTP::Get.new("/test")
67
+ request["Accept"] = "test/response.request_uri"
68
+ response = client.request(request)
69
+ expect(response.body).to eq("http://#{ADDRESS}:#{@port}/test")
33
70
  end
34
71
 
72
+ # context do
73
+ # let(:address) { "::1" }
74
+
75
+ # it "provides the IPv6 request URI" do
76
+ # request = Net::HTTP::Get.new("/test")
77
+ # request["Accept"] = "test/response.request_uri"
78
+ # response = client.request(request)
79
+ # expect(response.body).to eq("http://[#{address}]:#{port}/test")
80
+ # end
81
+ # end
82
+
35
83
  it "provides a string-like request body" do
36
84
  request = Net::HTTP::Put.new("/test")
37
85
  request.body = "Hello, World!"
38
86
  request["Content-Type"] = "test/request.stringbody"
39
87
  response = client.request(request)
40
- response["Content-Length"].should eq("21")
41
- response.body.should eq("String: Hello, World!")
88
+ expect(response["Content-Length"]).to eq("21")
89
+ expect(response.body).to eq("String: Hello, World!")
42
90
  end
43
91
 
44
92
  it "provides an enumerable request body" do
@@ -46,66 +94,64 @@ shared_examples_for :adapter_lint do
46
94
  request.body = "Hello, World!"
47
95
  request["Content-Type"] = "test/request.enumbody"
48
96
  response = client.request(request)
49
- response["Content-Length"].should eq("19")
50
- response.body.should eq("Enum: Hello, World!")
97
+ expect(response["Content-Length"]).to eq("19")
98
+ expect(response.body).to eq("Enum: Hello, World!")
51
99
  end
52
100
 
53
101
  it "handles missing pages" do
54
102
  request = Net::HTTP::Get.new("/missing")
55
103
  response = client.request(request)
56
- response.code.should eq("404")
57
- response["Content-Type"].should eq("text/html")
104
+ expect(response.code).to eq("404")
105
+ expect(response["Content-Type"]).to eq("text/html")
58
106
  end
59
107
 
60
108
  it "handles empty response bodies" do
61
109
  request = Net::HTTP::Post.new("/test")
62
110
  request.body = ""
63
111
  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
112
+ expect(response.code).to eq("204")
113
+ expect(["0", nil]).to include(response["Content-Length"])
114
+ expect(response.body).to be_nil
69
115
  end
70
116
 
71
117
  it "handles string response bodies" do
72
118
  request = Net::HTTP::Get.new("/test")
73
119
  request["Accept"] = "test/response.stringbody"
74
120
  response = client.request(request)
75
- response["Content-Length"].should eq("20")
76
- response.body.should eq("String response body")
121
+ expect(response["Content-Length"]).to eq("20")
122
+ expect(response.body).to eq("String response body")
77
123
  end
78
124
 
79
125
  it "handles enumerable response bodies" do
80
126
  request = Net::HTTP::Get.new("/test")
81
127
  request["Accept"] = "test/response.enumbody"
82
128
  response = client.request(request)
83
- response["Transfer-Encoding"].should eq("chunked")
84
- response.body.should eq("Enumerable response body")
129
+ expect(response["Transfer-Encoding"]).to eq("chunked")
130
+ expect(response.body).to eq("Enumerable response body")
85
131
  end
86
132
 
87
133
  it "handles proc response bodies" do
88
134
  request = Net::HTTP::Get.new("/test")
89
135
  request["Accept"] = "test/response.procbody"
90
136
  response = client.request(request)
91
- response["Transfer-Encoding"].should eq("chunked")
92
- response.body.should eq("Proc response body")
137
+ expect(response["Transfer-Encoding"]).to eq("chunked")
138
+ expect(response.body).to eq("Proc response body")
93
139
  end
94
140
 
95
141
  it "handles fiber response bodies" do
96
142
  request = Net::HTTP::Get.new("/test")
97
143
  request["Accept"] = "test/response.fiberbody"
98
144
  response = client.request(request)
99
- response["Transfer-Encoding"].should eq("chunked")
100
- response.body.should eq("Fiber response body")
145
+ expect(response["Transfer-Encoding"]).to eq("chunked")
146
+ expect(response.body).to eq("Fiber response body")
101
147
  end
102
148
 
103
149
  it "handles io response bodies" do
104
150
  request = Net::HTTP::Get.new("/test")
105
151
  request["Accept"] = "test/response.iobody"
106
152
  response = client.request(request)
107
- response["Content-Length"].should eq("16")
108
- response.body.should eq("IO response body")
153
+ expect(response["Content-Length"]).to eq("17")
154
+ expect(response.body).to eq("IO response body\n")
109
155
  end
110
156
 
111
157
  it "handles request cookies" do
@@ -113,13 +159,13 @@ shared_examples_for :adapter_lint do
113
159
  request["Accept"] = "test/response.cookies"
114
160
  request["Cookie"] = "echo=echocookie"
115
161
  response = client.request(request)
116
- response.body.should eq("echocookie")
162
+ expect(response.body).to eq("echocookie")
117
163
  end
118
164
 
119
165
  it "handles response cookies" do
120
166
  request = Net::HTTP::Get.new("/test")
121
167
  request["Accept"] = "test/response.cookies"
122
168
  response = client.request(request)
123
- response["Set-Cookie"].should eq("cookie=monster, rodeo=clown")
169
+ expect(response["Set-Cookie"]).to eq("cookie=monster, rodeo=clown")
124
170
  end
125
171
  end