webmachine 1.2.2 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +57 -0
- data/Gemfile +20 -15
- data/README.md +89 -91
- data/RELEASING.md +21 -0
- data/Rakefile +5 -21
- data/documentation/adapters.md +41 -0
- data/documentation/authentication-and-authorization.md +37 -0
- data/documentation/configurator.md +19 -0
- data/documentation/error-handling.md +86 -0
- data/documentation/examples.md +224 -0
- data/documentation/how-it-works.md +76 -0
- data/documentation/routes.md +112 -0
- data/documentation/validation.md +159 -0
- data/documentation/versioning-apis.md +74 -0
- data/documentation/visual-debugger.md +38 -0
- data/examples/application.rb +2 -2
- data/examples/debugger.rb +1 -1
- data/lib/webmachine.rb +3 -1
- data/lib/webmachine/adapter.rb +7 -13
- data/lib/webmachine/adapters.rb +1 -2
- data/lib/webmachine/adapters/httpkit.rb +74 -0
- data/lib/webmachine/adapters/lazy_request_body.rb +1 -2
- data/lib/webmachine/adapters/rack.rb +70 -25
- data/lib/webmachine/adapters/rack_mapped.rb +42 -0
- data/lib/webmachine/adapters/reel.rb +22 -23
- data/lib/webmachine/adapters/webrick.rb +16 -16
- data/lib/webmachine/application.rb +2 -2
- data/lib/webmachine/chunked_body.rb +3 -4
- data/lib/webmachine/configuration.rb +1 -1
- data/lib/webmachine/constants.rb +75 -0
- data/lib/webmachine/decision/conneg.rb +12 -10
- data/lib/webmachine/decision/flow.rb +42 -32
- data/lib/webmachine/decision/fsm.rb +14 -21
- data/lib/webmachine/decision/helpers.rb +10 -38
- data/lib/webmachine/dispatcher.rb +13 -10
- data/lib/webmachine/dispatcher/route.rb +45 -9
- data/lib/webmachine/errors.rb +9 -3
- data/lib/webmachine/events.rb +2 -2
- data/lib/webmachine/header_negotiation.rb +25 -0
- data/lib/webmachine/headers.rb +8 -3
- data/lib/webmachine/locale/en.yml +7 -5
- data/lib/webmachine/media_type.rb +10 -8
- data/lib/webmachine/request.rb +67 -26
- data/lib/webmachine/rescueable_exception.rb +62 -0
- data/lib/webmachine/resource.rb +1 -1
- data/lib/webmachine/resource/callbacks.rb +11 -9
- data/lib/webmachine/response.rb +3 -5
- data/lib/webmachine/spec/IO_response.body +1 -0
- data/lib/webmachine/spec/adapter_lint.rb +83 -37
- data/lib/webmachine/spec/test_resource.rb +15 -4
- data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
- data/lib/webmachine/streaming/io_encoder.rb +7 -1
- data/lib/webmachine/trace.rb +1 -0
- data/lib/webmachine/trace/fsm.rb +20 -10
- data/lib/webmachine/trace/resource_proxy.rb +2 -0
- data/lib/webmachine/translation.rb +2 -1
- data/lib/webmachine/version.rb +3 -3
- data/memory_test.rb +37 -0
- data/spec/spec_helper.rb +17 -9
- data/spec/webmachine/adapter_spec.rb +14 -15
- data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
- data/spec/webmachine/adapters/rack_mapped_spec.rb +71 -0
- data/spec/webmachine/adapters/rack_spec.rb +32 -6
- data/spec/webmachine/adapters/reel_spec.rb +16 -12
- data/spec/webmachine/adapters/webrick_spec.rb +2 -2
- data/spec/webmachine/application_spec.rb +18 -17
- data/spec/webmachine/chunked_body_spec.rb +3 -3
- data/spec/webmachine/configuration_spec.rb +5 -5
- data/spec/webmachine/cookie_spec.rb +13 -13
- data/spec/webmachine/decision/conneg_spec.rb +49 -43
- data/spec/webmachine/decision/falsey_spec.rb +4 -4
- data/spec/webmachine/decision/flow_spec.rb +195 -145
- data/spec/webmachine/decision/fsm_spec.rb +81 -19
- data/spec/webmachine/decision/helpers_spec.rb +20 -20
- data/spec/webmachine/dispatcher/rfc3986_percent_decode_spec.rb +22 -0
- data/spec/webmachine/dispatcher/route_spec.rb +114 -32
- data/spec/webmachine/dispatcher_spec.rb +49 -24
- data/spec/webmachine/errors_spec.rb +1 -1
- data/spec/webmachine/etags_spec.rb +19 -19
- data/spec/webmachine/events_spec.rb +6 -6
- data/spec/webmachine/headers_spec.rb +14 -14
- data/spec/webmachine/media_type_spec.rb +36 -36
- data/spec/webmachine/request_spec.rb +70 -39
- data/spec/webmachine/rescueable_exception_spec.rb +15 -0
- data/spec/webmachine/resource/authentication_spec.rb +6 -6
- data/spec/webmachine/response_spec.rb +18 -12
- data/spec/webmachine/trace/fsm_spec.rb +8 -8
- data/spec/webmachine/trace/resource_proxy_spec.rb +9 -9
- data/spec/webmachine/trace/trace_store_spec.rb +5 -5
- data/spec/webmachine/trace_spec.rb +3 -3
- data/webmachine.gemspec +2 -6
- metadata +78 -228
- data/lib/webmachine/adapters/hatetepe.rb +0 -108
- data/lib/webmachine/adapters/mongrel.rb +0 -127
- data/lib/webmachine/dispatcher/not_found_resource.rb +0 -5
- data/lib/webmachine/fiber18.rb +0 -88
- data/spec/webmachine/adapters/hatetepe_spec.rb +0 -60
- data/spec/webmachine/adapters/mongrel_spec.rb +0 -16
data/lib/webmachine/request.rb
CHANGED
@@ -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
|
-
|
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, @
|
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
|
40
|
+
if m =~ HTTP_HEADERS_MATCH
|
31
41
|
# Access headers more easily as underscored methods.
|
32
|
-
|
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 ==
|
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 ==
|
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 ==
|
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 ==
|
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 ==
|
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 ==
|
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 ==
|
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 ==
|
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
|
data/lib/webmachine/resource.rb
CHANGED
@@ -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 [
|
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
|
-
[
|
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
|
-
|
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,
|
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
|
-
[[
|
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
|
-
{
|
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
|
366
|
+
# This method is called when an error is raised within a subclass of
|
365
367
|
# {Webmachine::Resource}.
|
366
368
|
#
|
367
|
-
# @param [
|
368
|
-
# The
|
369
|
+
# @param [StandardError] e
|
370
|
+
# The error.
|
369
371
|
#
|
370
372
|
# @return [void]
|
371
373
|
#
|
data/lib/webmachine/response.rb
CHANGED
@@ -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 [
|
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']
|
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
|
-
|
7
|
+
attr_reader :client
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
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
|
-
|
35
|
+
Thread.new { adapter.run }
|
36
|
+
end
|
17
37
|
|
18
|
-
|
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.
|
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.
|
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"].
|
41
|
-
response.body.
|
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"].
|
50
|
-
response.body.
|
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.
|
57
|
-
response["Content-Type"].
|
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.
|
65
|
-
|
66
|
-
|
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"].
|
76
|
-
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"].
|
84
|
-
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"].
|
92
|
-
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"].
|
100
|
-
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"].
|
108
|
-
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.
|
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"].
|
169
|
+
expect(response["Set-Cookie"]).to eq("cookie=monster, rodeo=clown")
|
124
170
|
end
|
125
171
|
end
|