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