webmachine 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/CHANGELOG.md +4 -0
  4. data/Gemfile +13 -11
  5. data/README.md +85 -89
  6. data/Rakefile +0 -1
  7. data/documentation/adapters.md +39 -0
  8. data/documentation/authentication-and-authorization.md +37 -0
  9. data/documentation/configurator.md +19 -0
  10. data/documentation/error-handling.md +86 -0
  11. data/documentation/examples.md +215 -0
  12. data/documentation/how-it-works.md +76 -0
  13. data/documentation/routes.md +97 -0
  14. data/documentation/validation.md +159 -0
  15. data/documentation/versioning-apis.md +74 -0
  16. data/documentation/visual-debugger.md +38 -0
  17. data/examples/application.rb +2 -2
  18. data/examples/debugger.rb +1 -1
  19. data/lib/webmachine.rb +3 -1
  20. data/lib/webmachine/adapter.rb +7 -13
  21. data/lib/webmachine/adapters.rb +1 -2
  22. data/lib/webmachine/adapters/httpkit.rb +74 -0
  23. data/lib/webmachine/adapters/lazy_request_body.rb +1 -2
  24. data/lib/webmachine/adapters/rack.rb +37 -21
  25. data/lib/webmachine/adapters/reel.rb +21 -23
  26. data/lib/webmachine/adapters/webrick.rb +16 -16
  27. data/lib/webmachine/application.rb +2 -2
  28. data/lib/webmachine/chunked_body.rb +3 -4
  29. data/lib/webmachine/constants.rb +75 -0
  30. data/lib/webmachine/decision/conneg.rb +12 -10
  31. data/lib/webmachine/decision/flow.rb +31 -21
  32. data/lib/webmachine/decision/fsm.rb +10 -18
  33. data/lib/webmachine/decision/helpers.rb +9 -37
  34. data/lib/webmachine/dispatcher.rb +13 -10
  35. data/lib/webmachine/dispatcher/route.rb +18 -8
  36. data/lib/webmachine/errors.rb +7 -1
  37. data/lib/webmachine/header_negotiation.rb +25 -0
  38. data/lib/webmachine/headers.rb +7 -2
  39. data/lib/webmachine/locale/en.yml +7 -5
  40. data/lib/webmachine/media_type.rb +10 -8
  41. data/lib/webmachine/request.rb +44 -15
  42. data/lib/webmachine/resource.rb +1 -1
  43. data/lib/webmachine/resource/callbacks.rb +6 -4
  44. data/lib/webmachine/spec/IO_response.body +1 -0
  45. data/lib/webmachine/spec/adapter_lint.rb +70 -36
  46. data/lib/webmachine/spec/test_resource.rb +10 -4
  47. data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
  48. data/lib/webmachine/streaming/io_encoder.rb +6 -0
  49. data/lib/webmachine/trace.rb +1 -0
  50. data/lib/webmachine/trace/fsm.rb +20 -10
  51. data/lib/webmachine/trace/resource_proxy.rb +2 -0
  52. data/lib/webmachine/translation.rb +2 -1
  53. data/lib/webmachine/version.rb +3 -3
  54. data/memory_test.rb +37 -0
  55. data/spec/spec_helper.rb +9 -9
  56. data/spec/webmachine/adapter_spec.rb +14 -15
  57. data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
  58. data/spec/webmachine/adapters/rack_spec.rb +6 -6
  59. data/spec/webmachine/adapters/reel_spec.rb +15 -11
  60. data/spec/webmachine/adapters/webrick_spec.rb +2 -2
  61. data/spec/webmachine/application_spec.rb +18 -17
  62. data/spec/webmachine/chunked_body_spec.rb +3 -3
  63. data/spec/webmachine/configuration_spec.rb +5 -5
  64. data/spec/webmachine/cookie_spec.rb +13 -13
  65. data/spec/webmachine/decision/conneg_spec.rb +48 -42
  66. data/spec/webmachine/decision/falsey_spec.rb +4 -4
  67. data/spec/webmachine/decision/flow_spec.rb +194 -144
  68. data/spec/webmachine/decision/fsm_spec.rb +17 -17
  69. data/spec/webmachine/decision/helpers_spec.rb +20 -20
  70. data/spec/webmachine/dispatcher/route_spec.rb +73 -27
  71. data/spec/webmachine/dispatcher_spec.rb +34 -24
  72. data/spec/webmachine/errors_spec.rb +1 -1
  73. data/spec/webmachine/etags_spec.rb +19 -19
  74. data/spec/webmachine/events_spec.rb +6 -6
  75. data/spec/webmachine/headers_spec.rb +14 -14
  76. data/spec/webmachine/media_type_spec.rb +36 -36
  77. data/spec/webmachine/request_spec.rb +33 -33
  78. data/spec/webmachine/resource/authentication_spec.rb +6 -6
  79. data/spec/webmachine/response_spec.rb +12 -12
  80. data/spec/webmachine/trace/fsm_spec.rb +8 -8
  81. data/spec/webmachine/trace/resource_proxy_spec.rb +9 -9
  82. data/spec/webmachine/trace/trace_store_spec.rb +5 -5
  83. data/spec/webmachine/trace_spec.rb +3 -3
  84. data/webmachine.gemspec +2 -6
  85. metadata +48 -206
  86. data/lib/webmachine/adapters/hatetepe.rb +0 -108
  87. data/lib/webmachine/adapters/mongrel.rb +0 -127
  88. data/lib/webmachine/dispatcher/not_found_resource.rb +0 -5
  89. data/lib/webmachine/fiber18.rb +0 -88
  90. data/spec/webmachine/adapters/hatetepe_spec.rb +0 -60
  91. 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['Charset']
30
- chosen_encoding = metadata['Content-Encoding']
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
- set_content_length
53
+ ensure_content_length(response)
51
54
  else
52
- response.headers.delete 'Content-Length'
53
- response.headers['Transfer-Encoding'] = 'chunked'
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
- Webmachine::Events.instrument('wm.dispatch') do |payload|
45
- Webmachine::Decision::FSM.new(resource, request, response).run
46
- payload[:resource] = resource.class.name
47
- payload[:request] = request.dup
48
- payload[:code] = response.code
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 Webmachine::Translation
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 = '*'.freeze
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(["*"], MyResource)
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(/^\/(.*)/)[1].split('/')
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(/^\/(.*)/)[1]
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
@@ -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['Content-Type'] = "text/html"
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
@@ -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 =~ /^HTTP_(\w+)$/ || k =~ /^(CONTENT_(?:TYPE|LENGTH))$/
10
- h[$1.tr("_", "-")] = v
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><p>%{message}</p>
7
- <address>%{version} server</address></body></html>"
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>%{error}</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 == "*/*" && @params.empty?
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("/").first
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("/").last
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 ["*", "*/*", type].include?(other.type)
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
@@ -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, @uri, @headers, @body = method, uri, headers, body
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.to_s =~ /^(?:[a-z0-9])+(?:_[a-z0-9]+)*$/i
33
+ if m =~ HTTP_HEADERS_MATCH
31
34
  # Access headers more easily as underscored methods.
32
- self[m.to_s.tr('_', '-')]
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 == "GET"
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 == "HEAD"
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 == "POST"
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 == "PUT"
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 == "DELETE"
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 == "TRACE"
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 == "CONNECT"
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 == "OPTIONS"
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