webmachine 1.2.2 → 1.3.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 +4 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +13 -11
- data/README.md +85 -89
- data/Rakefile +0 -1
- data/documentation/adapters.md +39 -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 +215 -0
- data/documentation/how-it-works.md +76 -0
- data/documentation/routes.md +97 -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 +37 -21
- data/lib/webmachine/adapters/reel.rb +21 -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/constants.rb +75 -0
- data/lib/webmachine/decision/conneg.rb +12 -10
- data/lib/webmachine/decision/flow.rb +31 -21
- data/lib/webmachine/decision/fsm.rb +10 -18
- data/lib/webmachine/decision/helpers.rb +9 -37
- data/lib/webmachine/dispatcher.rb +13 -10
- data/lib/webmachine/dispatcher/route.rb +18 -8
- data/lib/webmachine/errors.rb +7 -1
- data/lib/webmachine/header_negotiation.rb +25 -0
- data/lib/webmachine/headers.rb +7 -2
- data/lib/webmachine/locale/en.yml +7 -5
- data/lib/webmachine/media_type.rb +10 -8
- data/lib/webmachine/request.rb +44 -15
- data/lib/webmachine/resource.rb +1 -1
- data/lib/webmachine/resource/callbacks.rb +6 -4
- data/lib/webmachine/spec/IO_response.body +1 -0
- data/lib/webmachine/spec/adapter_lint.rb +70 -36
- data/lib/webmachine/spec/test_resource.rb +10 -4
- data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
- data/lib/webmachine/streaming/io_encoder.rb +6 -0
- 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 +9 -9
- data/spec/webmachine/adapter_spec.rb +14 -15
- data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
- data/spec/webmachine/adapters/rack_spec.rb +6 -6
- data/spec/webmachine/adapters/reel_spec.rb +15 -11
- 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 +48 -42
- data/spec/webmachine/decision/falsey_spec.rb +4 -4
- data/spec/webmachine/decision/flow_spec.rb +194 -144
- data/spec/webmachine/decision/fsm_spec.rb +17 -17
- data/spec/webmachine/decision/helpers_spec.rb +20 -20
- data/spec/webmachine/dispatcher/route_spec.rb +73 -27
- data/spec/webmachine/dispatcher_spec.rb +34 -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 +33 -33
- data/spec/webmachine/resource/authentication_spec.rb +6 -6
- data/spec/webmachine/response_spec.rb +12 -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 +48 -206
- 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
@@ -1,9 +1,11 @@
|
|
1
|
-
require 'stringio'
|
1
|
+
require 'stringio'
|
2
2
|
require 'time'
|
3
3
|
require 'webmachine/streaming'
|
4
4
|
require 'webmachine/media_type'
|
5
5
|
require 'webmachine/quoted_string'
|
6
6
|
require 'webmachine/etags'
|
7
|
+
require 'webmachine/header_negotiation'
|
8
|
+
require 'webmachine/constants'
|
7
9
|
|
8
10
|
module Webmachine
|
9
11
|
module Decision
|
@@ -11,6 +13,7 @@ module Webmachine
|
|
11
13
|
module Helpers
|
12
14
|
include QuotedString
|
13
15
|
include Streaming
|
16
|
+
include HeaderNegotiation
|
14
17
|
|
15
18
|
# Determines if the response has a body/entity set.
|
16
19
|
def has_response_body?
|
@@ -26,8 +29,8 @@ module Webmachine
|
|
26
29
|
# Encodes the body in the selected charset and encoding.
|
27
30
|
def encode_body
|
28
31
|
body = response.body
|
29
|
-
chosen_charset = metadata[
|
30
|
-
chosen_encoding = metadata[
|
32
|
+
chosen_charset = metadata[CHARSET]
|
33
|
+
chosen_encoding = metadata[CONTENT_ENCODING]
|
31
34
|
charsetter = resource.charsets_provided && resource.charsets_provided.find {|c,_| c == chosen_charset }.last || :charset_nop
|
32
35
|
encoder = resource.encodings_provided[chosen_encoding]
|
33
36
|
response.body = case body
|
@@ -47,10 +50,10 @@ module Webmachine
|
|
47
50
|
end
|
48
51
|
end
|
49
52
|
if body_is_fixed_length?
|
50
|
-
|
53
|
+
ensure_content_length(response)
|
51
54
|
else
|
52
|
-
response.headers.delete
|
53
|
-
response.headers[
|
55
|
+
response.headers.delete CONTENT_LENGTH
|
56
|
+
response.headers[TRANSFER_ENCODING] = 'chunked'
|
54
57
|
end
|
55
58
|
end
|
56
59
|
|
@@ -88,37 +91,6 @@ module Webmachine
|
|
88
91
|
end
|
89
92
|
end
|
90
93
|
|
91
|
-
# Ensures that responses have an appropriate Content-Length
|
92
|
-
# header
|
93
|
-
def ensure_content_length
|
94
|
-
case
|
95
|
-
when response.headers['Transfer-Encoding']
|
96
|
-
return
|
97
|
-
when [204, 205, 304].include?(response.code)
|
98
|
-
response.headers.delete 'Content-Length'
|
99
|
-
when has_response_body?
|
100
|
-
set_content_length
|
101
|
-
else
|
102
|
-
response.headers['Content-Length'] = '0'
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# Ensures that responses have an appropriate Date header
|
107
|
-
def ensure_date_header
|
108
|
-
if (200..499).include?(response.code)
|
109
|
-
response.headers['Date'] ||= Time.now.httpdate
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# Sets the Content-Length header on the response
|
114
|
-
def set_content_length
|
115
|
-
if response.body.respond_to?(:bytesize)
|
116
|
-
response.headers['Content-Length'] = response.body.bytesize.to_s
|
117
|
-
else
|
118
|
-
response.headers['Content-Length'] = response.body.length.to_s
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
94
|
# Determines whether the response is of a fixed lenghth, i.e. it
|
123
95
|
# is a String or IO with known size.
|
124
96
|
def body_is_fixed_length?
|
@@ -1,12 +1,13 @@
|
|
1
|
-
require 'forwardable'
|
1
|
+
require 'forwardable'
|
2
2
|
require 'webmachine/decision'
|
3
3
|
require 'webmachine/dispatcher/route'
|
4
|
-
require 'webmachine/dispatcher/not_found_resource'
|
5
4
|
|
6
5
|
module Webmachine
|
7
6
|
# Handles dispatching incoming requests to the proper registered
|
8
7
|
# resources and initializing the decision logic.
|
9
8
|
class Dispatcher
|
9
|
+
WM_DISPATCH = 'wm.dispatch'.freeze
|
10
|
+
|
10
11
|
# @return [Array<Route>] the list of routes that will be
|
11
12
|
# dispatched to
|
12
13
|
# @see #add_route
|
@@ -40,12 +41,16 @@ module Webmachine
|
|
40
41
|
# @param [Request] request the request object
|
41
42
|
# @param [Response] response the response object
|
42
43
|
def dispatch(request, response)
|
43
|
-
resource = find_resource(request, response)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
if resource = find_resource(request, response)
|
45
|
+
Webmachine::Events.instrument(WM_DISPATCH) do |payload|
|
46
|
+
Webmachine::Decision::FSM.new(resource, request, response).run
|
47
|
+
|
48
|
+
payload[:resource] = resource.class.name
|
49
|
+
payload[:request] = request.dup
|
50
|
+
payload[:code] = response.code
|
51
|
+
end
|
52
|
+
else
|
53
|
+
Webmachine.render_error(404, request, response)
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
@@ -61,8 +66,6 @@ module Webmachine
|
|
61
66
|
def find_resource(request, response)
|
62
67
|
if route = find_route(request)
|
63
68
|
prepare_resource(route, request, response)
|
64
|
-
else
|
65
|
-
NotFoundResource.new(request, response)
|
66
69
|
end
|
67
70
|
end
|
68
71
|
|
@@ -1,12 +1,13 @@
|
|
1
|
-
require 'webmachine/resource'
|
1
|
+
require 'webmachine/resource'
|
2
2
|
require 'webmachine/translation'
|
3
|
+
require 'webmachine/constants'
|
3
4
|
|
4
5
|
module Webmachine
|
5
6
|
class Dispatcher
|
6
7
|
# Pairs URIs with {Resource} classes in the {Dispatcher}. To
|
7
8
|
# create routes, use {Dispatcher#add_route}.
|
8
9
|
class Route
|
9
|
-
include
|
10
|
+
include Translation
|
10
11
|
|
11
12
|
# @return [Class] the resource this route will dispatch to, a
|
12
13
|
# subclass of {Resource}
|
@@ -22,13 +23,16 @@ module Webmachine
|
|
22
23
|
|
23
24
|
# When used in a path specification, will match all remaining
|
24
25
|
# segments
|
25
|
-
MATCH_ALL =
|
26
|
+
MATCH_ALL = :*
|
27
|
+
|
28
|
+
# String version of MATCH_ALL, deprecated. Use the symbol instead.
|
29
|
+
MATCH_ALL_STR = '*'.freeze
|
26
30
|
|
27
31
|
# Creates a new Route that will associate a pattern to a
|
28
32
|
# {Resource}.
|
29
33
|
#
|
30
34
|
# @example Standard route
|
31
|
-
# Route.new([
|
35
|
+
# Route.new([:*], MyResource)
|
32
36
|
#
|
33
37
|
# @example Guarded route
|
34
38
|
# Route.new ["/notes"],
|
@@ -65,6 +69,8 @@ module Webmachine
|
|
65
69
|
guards = args
|
66
70
|
guards << Proc.new if block_given?
|
67
71
|
|
72
|
+
warn t('match_all_symbol') if path_spec.include? MATCH_ALL_STR
|
73
|
+
|
68
74
|
@path_spec = path_spec
|
69
75
|
@guards = guards
|
70
76
|
@resource = resource
|
@@ -73,11 +79,13 @@ module Webmachine
|
|
73
79
|
raise ArgumentError, t('not_resource_class', :class => resource.name) unless resource < Resource
|
74
80
|
end
|
75
81
|
|
82
|
+
PATH_MATCH = /^\/(.*)/.freeze
|
83
|
+
|
76
84
|
# Determines whether the given request matches this route and
|
77
85
|
# should be dispatched to the {#resource}.
|
78
86
|
# @param [Reqeust] request the request object
|
79
87
|
def match?(request)
|
80
|
-
tokens = request.uri.path.match(
|
88
|
+
tokens = request.uri.path.match(PATH_MATCH)[1].split(SLASH)
|
81
89
|
bind(tokens, {}) && guards.all? { |guard| guard.call(request) }
|
82
90
|
end
|
83
91
|
|
@@ -85,9 +93,9 @@ module Webmachine
|
|
85
93
|
# route, including path bindings.
|
86
94
|
# @param [Request] request the request object
|
87
95
|
def apply(request)
|
88
|
-
request.disp_path = request.uri.path.match(
|
96
|
+
request.disp_path = request.uri.path.match(PATH_MATCH)[1]
|
89
97
|
request.path_info = @bindings.dup
|
90
|
-
tokens = request.disp_path.split(
|
98
|
+
tokens = request.disp_path.split(SLASH)
|
91
99
|
depth, trailing = bind(tokens, request.path_info)
|
92
100
|
request.path_tokens = trailing || []
|
93
101
|
end
|
@@ -107,12 +115,14 @@ module Webmachine
|
|
107
115
|
case
|
108
116
|
when spec.empty? && tokens.empty?
|
109
117
|
return depth
|
118
|
+
when spec == [MATCH_ALL_STR]
|
119
|
+
return [depth, tokens]
|
110
120
|
when spec == [MATCH_ALL]
|
111
121
|
return [depth, tokens]
|
112
122
|
when tokens.empty?
|
113
123
|
return false
|
114
124
|
when Symbol === spec.first
|
115
|
-
bindings[spec.first] = tokens.first
|
125
|
+
bindings[spec.first] = URI.decode(tokens.first)
|
116
126
|
when spec.first == tokens.first
|
117
127
|
else
|
118
128
|
return false
|
data/lib/webmachine/errors.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
require 'webmachine/header_negotiation'
|
1
2
|
require 'webmachine/translation'
|
3
|
+
require 'webmachine/constants'
|
2
4
|
require 'webmachine/version'
|
3
5
|
|
4
6
|
module Webmachine
|
7
|
+
extend HeaderNegotiation
|
5
8
|
extend Translation
|
6
9
|
|
7
10
|
# Renders a standard error message body for the response. The
|
@@ -21,10 +24,13 @@ module Webmachine
|
|
21
24
|
{:title => title,
|
22
25
|
:message => message,
|
23
26
|
:version => Webmachine::SERVER_STRING}.merge(options))
|
24
|
-
res.headers[
|
27
|
+
res.headers[CONTENT_TYPE] = TEXT_HTML
|
25
28
|
end
|
29
|
+
ensure_content_length(res)
|
30
|
+
ensure_date_header(res)
|
26
31
|
end
|
27
32
|
|
33
|
+
|
28
34
|
# Superclass of all errors generated by Webmachine.
|
29
35
|
class Error < ::StandardError; end
|
30
36
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'webmachine/constants'
|
2
|
+
|
3
|
+
module Webmachine
|
4
|
+
module HeaderNegotiation
|
5
|
+
def ensure_date_header(res)
|
6
|
+
if (200..499).include?(res.code)
|
7
|
+
res.headers[DATE] ||= Time.now.httpdate
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def ensure_content_length(res)
|
12
|
+
body = res.body
|
13
|
+
case
|
14
|
+
when res.headers[TRANSFER_ENCODING]
|
15
|
+
return
|
16
|
+
when [204, 205, 304].include?(res.code)
|
17
|
+
res.headers.delete CONTENT_LENGTH
|
18
|
+
when body != nil
|
19
|
+
res.headers[CONTENT_LENGTH] = body.respond_to?(:bytesize) ? body.bytesize.to_s : body.length.to_s
|
20
|
+
else
|
21
|
+
res.headers[CONTENT_LENGTH] = '0'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/webmachine/headers.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
|
+
require 'webmachine/constants'
|
2
|
+
|
1
3
|
module Webmachine
|
2
4
|
# Case-insensitive Hash of Request headers
|
3
5
|
class Headers < ::Hash
|
6
|
+
CGI_HTTP_MATCH = /^HTTP_(\w+)$/.freeze
|
7
|
+
CONTENT_TYPE_LENGTH_MATCH = /^(CONTENT_(?:TYPE|LENGTH))$/.freeze
|
8
|
+
|
4
9
|
# Convert CGI-style Hash into Request headers
|
5
10
|
# @param [Hash] env a hash of CGI-style env/headers
|
6
11
|
# @return [Webmachine::Headers]
|
7
12
|
def self.from_cgi(env)
|
8
13
|
env.inject(new) do |h,(k,v)|
|
9
|
-
if k =~
|
10
|
-
h[$1.tr(
|
14
|
+
if k =~ CGI_HTTP_MATCH || k =~ CONTENT_TYPE_LENGTH_MATCH
|
15
|
+
h[$1.tr(UNDERSCORE, DASH)] = v
|
11
16
|
end
|
12
17
|
h
|
13
18
|
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
en:
|
2
2
|
webmachine:
|
3
3
|
errors:
|
4
|
-
standard_body: "<!DOCTYPE html><html
|
5
|
-
<head><title>%{title}</title></head
|
6
|
-
<body><h1>%{title}</h1
|
7
|
-
<
|
4
|
+
standard_body: "<!DOCTYPE html><html>\n
|
5
|
+
<head><title>%{title}</title></head>\n
|
6
|
+
<body><h1>%{title}</h1>\n
|
7
|
+
<p>%{message}</p>\n
|
8
|
+
<address>%{version} server</address></body></html>\n"
|
8
9
|
"400":
|
9
10
|
title: 400 Malformed Request
|
10
11
|
message: The request was malformed and could not be processed.
|
@@ -13,7 +14,7 @@ en:
|
|
13
14
|
message: The requested document was not found on this server.
|
14
15
|
"500":
|
15
16
|
title: 500 Internal Server Error
|
16
|
-
message: "The server encountered an error while processing this request: <pre
|
17
|
+
message: "The server encountered an error while processing this request: <pre>\n%{error}</pre>"
|
17
18
|
"501":
|
18
19
|
title: 501 Not Implemented
|
19
20
|
message: "The server does not support the %{method} method."
|
@@ -26,3 +27,4 @@ en:
|
|
26
27
|
invalid_media_type: "Invalid media type: %{type}"
|
27
28
|
not_resource_class: "%{class} is not a subclass of Webmachine::Resource"
|
28
29
|
process_post_invalid: "process_post returned %{result}"
|
30
|
+
match_all_symbol: '"*" as a path segment is deprecated and will be removed in a future release. Please use :*'
|
@@ -1,14 +1,16 @@
|
|
1
|
-
require 'webmachine/translation'
|
1
|
+
require 'webmachine/translation'
|
2
|
+
require 'webmachine/constants'
|
3
|
+
require 'webmachine/dispatcher/route'
|
2
4
|
|
3
5
|
module Webmachine
|
4
6
|
# Encapsulates a MIME media type, with logic for matching types.
|
5
7
|
class MediaType
|
6
8
|
extend Translation
|
7
9
|
# Matches valid media types
|
8
|
-
MEDIA_TYPE_REGEX = /^\s*([^;\s]+)\s*((?:;\s*\S+\s*)*)\s
|
10
|
+
MEDIA_TYPE_REGEX = /^\s*([^;\s]+)\s*((?:;\s*\S+\s*)*)\s*$/.freeze
|
9
11
|
|
10
12
|
# Matches sub-type parameters
|
11
|
-
PARAMS_REGEX = /;\s*([^=]+)(=([^;=\s]*))
|
13
|
+
PARAMS_REGEX = /;\s*([^=]+)(=([^;=\s]*))?/.freeze
|
12
14
|
|
13
15
|
# Creates a new MediaType by parsing an alternate representation.
|
14
16
|
# @param [MediaType, String, Array<String,Hash>] obj the raw type
|
@@ -48,7 +50,7 @@ module Webmachine
|
|
48
50
|
# Detects whether the {MediaType} represents an open wildcard
|
49
51
|
# type, that is, "*/*" without any {#params}.
|
50
52
|
def matches_all?
|
51
|
-
@type ==
|
53
|
+
@type == MATCHES_ALL && @params.empty?
|
52
54
|
end
|
53
55
|
|
54
56
|
# @return [true,false] Are these two types strictly equal?
|
@@ -97,12 +99,12 @@ module Webmachine
|
|
97
99
|
|
98
100
|
# @return [String] The major type, e.g. "application", "text", "image"
|
99
101
|
def major
|
100
|
-
type.split(
|
102
|
+
type.split(SLASH).first
|
101
103
|
end
|
102
104
|
|
103
105
|
# @return [String] the minor or sub-type, e.g. "json", "html", "jpeg"
|
104
106
|
def minor
|
105
|
-
type.split(
|
107
|
+
type.split(SLASH).last
|
106
108
|
end
|
107
109
|
|
108
110
|
# @param [MediaType] other the other type
|
@@ -110,10 +112,10 @@ module Webmachine
|
|
110
112
|
# ignoring params and taking into account wildcards
|
111
113
|
def type_matches?(other)
|
112
114
|
other = self.class.parse(other)
|
113
|
-
if [
|
115
|
+
if [Dispatcher::Route::MATCH_ALL_STR, MATCHES_ALL, type].include?(other.type)
|
114
116
|
true
|
115
117
|
else
|
116
|
-
other.major == major && other.minor ==
|
118
|
+
other.major == major && other.minor == Dispatcher::Route::MATCH_ALL_STR
|
117
119
|
end
|
118
120
|
end
|
119
121
|
end # class MediaType
|
data/lib/webmachine/request.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
-
require 'cgi'
|
1
|
+
require 'cgi'
|
2
2
|
require 'forwardable'
|
3
|
+
require 'webmachine/constants'
|
3
4
|
|
4
5
|
module Webmachine
|
5
6
|
# Request represents a single HTTP request sent from a client. It
|
6
7
|
# should be instantiated by {Adapters} when a request is received
|
7
8
|
class Request
|
9
|
+
HTTP_HEADERS_MATCH = /^(?:[a-z0-9])+(?:_[a-z0-9]+)*$/i.freeze
|
10
|
+
|
8
11
|
extend Forwardable
|
12
|
+
|
9
13
|
attr_reader :method, :uri, :headers, :body
|
10
14
|
attr_accessor :disp_path, :path_info, :path_tokens
|
11
15
|
|
12
|
-
STANDARD_HTTP_METHODS = %w[GET HEAD POST PUT DELETE TRACE CONNECT OPTIONS]
|
13
|
-
|
14
16
|
# @param [String] method the HTTP request method
|
15
17
|
# @param [URI] uri the requested URI, including host, scheme and
|
16
18
|
# port
|
@@ -18,7 +20,8 @@ module Webmachine
|
|
18
20
|
# @param [String,#to_s,#each,nil] body the entity included in the
|
19
21
|
# request, if present
|
20
22
|
def initialize(method, uri, headers, body)
|
21
|
-
@method, @
|
23
|
+
@method, @headers, @body = method, headers, body
|
24
|
+
@uri = build_uri(uri, headers)
|
22
25
|
end
|
23
26
|
|
24
27
|
def_delegators :headers, :[]
|
@@ -27,9 +30,18 @@ module Webmachine
|
|
27
30
|
# lowercased-underscored version of the header name, e.g.
|
28
31
|
# `if_unmodified_since`.
|
29
32
|
def method_missing(m, *args, &block)
|
30
|
-
if m
|
33
|
+
if m =~ HTTP_HEADERS_MATCH
|
31
34
|
# Access headers more easily as underscored methods.
|
32
|
-
|
35
|
+
header_name = m.to_s.tr(UNDERSCORE, DASH)
|
36
|
+
if (header_value = headers[header_name])
|
37
|
+
# Make future lookups faster.
|
38
|
+
self.class.class_eval <<-RUBY, __FILE__, __LINE__
|
39
|
+
def #{m}
|
40
|
+
headers["#{header_name}"]
|
41
|
+
end
|
42
|
+
RUBY
|
43
|
+
end
|
44
|
+
header_value
|
33
45
|
else
|
34
46
|
super
|
35
47
|
end
|
@@ -45,7 +57,7 @@ module Webmachine
|
|
45
57
|
# @return [URI]
|
46
58
|
def base_uri
|
47
59
|
@base_uri ||= uri.dup.tap do |u|
|
48
|
-
u.path =
|
60
|
+
u.path = SLASH
|
49
61
|
u.query = nil
|
50
62
|
end
|
51
63
|
end
|
@@ -92,7 +104,7 @@ module Webmachine
|
|
92
104
|
# @return [Boolean]
|
93
105
|
# true if this request was made with the GET method
|
94
106
|
def get?
|
95
|
-
method ==
|
107
|
+
method == GET_METHOD
|
96
108
|
end
|
97
109
|
|
98
110
|
# Is this a HEAD request?
|
@@ -100,7 +112,7 @@ module Webmachine
|
|
100
112
|
# @return [Boolean]
|
101
113
|
# true if this request was made with the HEAD method
|
102
114
|
def head?
|
103
|
-
method ==
|
115
|
+
method == HEAD_METHOD
|
104
116
|
end
|
105
117
|
|
106
118
|
# Is this a POST request?
|
@@ -108,7 +120,7 @@ module Webmachine
|
|
108
120
|
# @return [Boolean]
|
109
121
|
# true if this request was made with the GET method
|
110
122
|
def post?
|
111
|
-
method ==
|
123
|
+
method == POST_METHOD
|
112
124
|
end
|
113
125
|
|
114
126
|
# Is this a PUT request?
|
@@ -116,7 +128,7 @@ module Webmachine
|
|
116
128
|
# @return [Boolean]
|
117
129
|
# true if this request was made with the PUT method
|
118
130
|
def put?
|
119
|
-
method ==
|
131
|
+
method == PUT_METHOD
|
120
132
|
end
|
121
133
|
|
122
134
|
# Is this a DELETE request?
|
@@ -124,7 +136,7 @@ module Webmachine
|
|
124
136
|
# @return [Boolean]
|
125
137
|
# true if this request was made with the DELETE method
|
126
138
|
def delete?
|
127
|
-
method ==
|
139
|
+
method == DELETE_METHOD
|
128
140
|
end
|
129
141
|
|
130
142
|
# Is this a TRACE request?
|
@@ -132,7 +144,7 @@ module Webmachine
|
|
132
144
|
# @return [Boolean]
|
133
145
|
# true if this request was made with the TRACE method
|
134
146
|
def trace?
|
135
|
-
method ==
|
147
|
+
method == TRACE_METHOD
|
136
148
|
end
|
137
149
|
|
138
150
|
# Is this a CONNECT request?
|
@@ -140,7 +152,7 @@ module Webmachine
|
|
140
152
|
# @return [Boolean]
|
141
153
|
# true if this request was made with the CONNECT method
|
142
154
|
def connect?
|
143
|
-
method ==
|
155
|
+
method == CONNECT_METHOD
|
144
156
|
end
|
145
157
|
|
146
158
|
# Is this an OPTIONS request?
|
@@ -148,7 +160,24 @@ module Webmachine
|
|
148
160
|
# @return [Boolean]
|
149
161
|
# true if this request was made with the OPTIONS method
|
150
162
|
def options?
|
151
|
-
method ==
|
163
|
+
method == OPTIONS_METHOD
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def build_uri(uri, headers)
|
169
|
+
uri = URI(uri)
|
170
|
+
|
171
|
+
host, _, port = headers.fetch(HOST, "").rpartition(COLON)
|
172
|
+
return uri if host.empty?
|
173
|
+
|
174
|
+
host = "[#{host}]" if host.include?(COLON)
|
175
|
+
port = 80 if port.empty?
|
176
|
+
|
177
|
+
uri.scheme = HTTP
|
178
|
+
uri.host, uri.port = host, port.to_i
|
179
|
+
|
180
|
+
URI.parse(uri.to_s)
|
152
181
|
end
|
153
182
|
|
154
183
|
end # class Request
|