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
@@ -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