webbed 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +22 -0
  2. data/.travis.yml +7 -0
  3. data/.yardopts +6 -0
  4. data/Gemfile +5 -13
  5. data/Guardfile +5 -0
  6. data/README.md +36 -31
  7. data/Rakefile +7 -14
  8. data/lib/webbed.rb +28 -6
  9. data/lib/webbed/generic_message.rb +27 -15
  10. data/lib/webbed/headers.rb +2 -0
  11. data/lib/webbed/helpers/entity_headers_helper.rb +62 -0
  12. data/lib/webbed/helpers/method_helper.rb +83 -0
  13. data/lib/webbed/helpers/rack_request_helper.rb +86 -0
  14. data/lib/webbed/helpers/rack_response_helper.rb +56 -0
  15. data/lib/webbed/helpers/request_headers_helper.rb +62 -0
  16. data/lib/webbed/helpers/request_uri_helper.rb +34 -0
  17. data/lib/webbed/helpers/response_headers_helper.rb +48 -0
  18. data/lib/webbed/helpers/scheme_helper.rb +26 -0
  19. data/lib/webbed/http_version.rb +88 -33
  20. data/lib/webbed/media_type.rb +160 -0
  21. data/lib/webbed/method.rb +63 -33
  22. data/lib/webbed/request.rb +65 -21
  23. data/lib/webbed/response.rb +65 -24
  24. data/lib/webbed/status_code.rb +84 -17
  25. data/lib/webbed/version.rb +1 -1
  26. data/test/support/assertions.rb +17 -0
  27. data/test/support/runner.rb +326 -0
  28. data/test/test_helper.rb +13 -0
  29. data/test/webbed/generic_message_test.rb +44 -0
  30. data/test/webbed/headers_test.rb +31 -0
  31. data/test/webbed/helpers/entity_headers_helper_test.rb +68 -0
  32. data/test/webbed/helpers/method_helper_test.rb +151 -0
  33. data/test/webbed/helpers/rack_request_helper_test.rb +108 -0
  34. data/test/webbed/helpers/rack_response_helper_test.rb +33 -0
  35. data/test/webbed/helpers/request_headers_helper_test.rb +57 -0
  36. data/test/webbed/helpers/request_uri_helper_test.rb +32 -0
  37. data/test/webbed/helpers/response_headers_helper_test.rb +46 -0
  38. data/test/webbed/helpers/scheme_helper_test.rb +28 -0
  39. data/test/webbed/http_version_test.rb +52 -0
  40. data/test/webbed/media_type_test.rb +100 -0
  41. data/test/webbed/method_test.rb +160 -0
  42. data/test/webbed/request_test.rb +74 -0
  43. data/test/webbed/response_test.rb +86 -0
  44. data/test/webbed/status_code_test.rb +105 -0
  45. data/webbed.gemspec +31 -0
  46. metadata +128 -41
data/lib/webbed/method.rb CHANGED
@@ -1,64 +1,94 @@
1
1
  module Webbed
2
+ # Representation of an HTTP Method.
2
3
  class Method
3
-
4
- attr_reader :name
5
- alias :to_s :name
6
- alias :inspect :name
7
-
8
4
  DEFAULTS = {
9
5
  :safe => false,
10
6
  :idempotent => false,
11
- :entities => [:request, :response]
7
+ :allowable_entities => [:request, :response]
12
8
  }
13
9
 
14
- def self.new(name, options = {})
15
- if const_defined? name
16
- const_get(name)
10
+ # The allowable entities of a message.
11
+ #
12
+ # It can contain any combination of `:request` and `:response`.
13
+ #
14
+ # @return [Array<:request, :response>]
15
+ attr_reader :allowable_entities
16
+
17
+ # Checks for a cached Method or creates a new one.
18
+ #
19
+ # It caches the standard HTTP Methods that are in RFC 2616 as well as the
20
+ # new method `PATCH`. If the Method cannot be found, it calls `#initialize`.
21
+ #
22
+ # @example
23
+ # Webbed::Method.new('GET') # => Webbed::Method::GET
24
+ #
25
+ # @param (see #initialize)
26
+ # @return [Method] the new or cached Method
27
+ # @see #initialize
28
+ def self.new(value, options = {})
29
+ if const_defined?(value)
30
+ const_get(value)
17
31
  else
18
- super(name, options)
32
+ super(value, options)
19
33
  end
20
34
  end
21
35
 
36
+ # Creates a new Method.
37
+ #
38
+ # @param [String] name the name of the Method to create
39
+ # @param [Hash] options the options to create the Method with
40
+ # @option options [Boolean] :safe (false) whether or not the Method is safe
41
+ # @option options [Boolean] :idempotent (false) whether or not the Method is
42
+ # idempotent
43
+ # @option options [Array<:request, :response>] :allowable_entities
44
+ # ([:request, :response]) the allowable entites of a message
22
45
  def initialize(name, options = {})
23
- options = DEFAULTS.merge options
46
+ options = DEFAULTS.merge(options)
24
47
  @name = name
25
48
  @safe = options[:safe]
26
49
  @idempotent = options[:safe] || options[:idempotent]
27
- @has_request_entity = options[:entities].include? :request
28
- @has_response_entity = options[:entities].include? :response
50
+ @allowable_entities = options[:allowable_entities]
29
51
  end
30
52
 
53
+ # Whether or not the Method is safe.
54
+ #
55
+ # @return [Boolean]
31
56
  def safe?
32
57
  @safe
33
58
  end
34
59
 
60
+ # Whether or not the Method is idempotent.
61
+ #
62
+ # @return [Boolean]
35
63
  def idempotent?
36
64
  @idempotent
37
65
  end
38
66
 
39
- def has_request_entity?
40
- @has_request_entity
41
- end
42
-
43
- def has_response_entity?
44
- @has_response_entity
67
+ # Converts the Method to a string.
68
+ #
69
+ # @return [String]
70
+ def to_s
71
+ @name
45
72
  end
46
73
 
74
+ # Compares the Method to another Method.
75
+ #
76
+ # Two Methods are equal if they have the same name.
77
+ #
78
+ # @param [#to_s] other_method the other Method
79
+ # @return [Boolean]
47
80
  def ==(other_method)
48
- name == other_method.to_s
81
+ to_s == other_method.to_s
49
82
  end
50
83
 
51
- # Common methods used and their settings. Most are defined in RFC 2616 with
52
- # the exception of PATCH which is defined in RFC 5789. These are for caching
53
- # purposes, so that new objects don't need to be created on each request.
54
- OPTIONS = new 'OPTIONS', :safe => true, :idempotent => true, :entities => [:response]
55
- GET = new 'GET', :safe => true, :idempotent => true, :entities => [:response]
56
- HEAD = new 'HEAD', :safe => true, :idempotent => true, :entities => []
57
- POST = new 'POST', :safe => false, :idempotent => false, :entities => [:request, :response]
58
- PUT = new 'PUT', :safe => false, :idempotent => true, :entities => [:request, :response]
59
- DELETE = new 'DELETE', :safe => false, :idempotent => true, :entities => [:response]
60
- TRACE = new 'TRACE', :safe => true, :idempotent => true, :entities => [:response]
61
- CONNECT = new 'CONNECT', :safe => false, :idempotent => false, :entities => [:request, :response]
62
- PATCH = new 'PATCH', :safe => false, :idempotent => false, :entities => [:request, :response]
84
+ OPTIONS = new('OPTIONS', :safe => true, :idempotent => true, :allowable_entities => [:response])
85
+ GET = new('GET', :safe => true, :idempotent => true, :allowable_entities => [:response])
86
+ HEAD = new('HEAD', :safe => true, :idempotent => true, :allowable_entities => [])
87
+ POST = new('POST', :safe => false, :idempotent => false, :allowable_entities => [:request, :response])
88
+ PUT = new('PUT', :safe => false, :idempotent => true, :allowable_entities => [:request, :response])
89
+ DELETE = new('DELETE', :safe => false, :idempotent => true, :allowable_entities => [:response])
90
+ TRACE = new('TRACE', :safe => true, :idempotent => true, :allowable_entities => [:response])
91
+ CONNECT = new('CONNECT', :safe => false, :idempotent => false, :allowable_entities => [:request, :response])
92
+ PATCH = new('PATCH', :safe => false, :idempotent => false, :allowable_entities => [:request, :response])
63
93
  end
64
- end
94
+ end
@@ -1,44 +1,88 @@
1
1
  require 'addressable/uri'
2
2
 
3
3
  module Webbed
4
+ # Representation of an HTTP Request.
5
+ #
6
+ # This class contains the absolute minimum for accessing the different parts
7
+ # of an HTTP Request. Helper modules provide far more functionality.
4
8
  class Request
9
+ include GenericMessage
5
10
 
6
- include Webbed::GenericMessage
11
+ # The Request-URI of the Request.
12
+ #
13
+ # The method automatically converts the new value to an instance of
14
+ # `Addressable::URI` if it is not already one.
15
+ #
16
+ # @return [Addressable::URI]
17
+ # @note {Helpers::RequestURIHelper} aliases this method to `#request_url`.
7
18
  attr_reader :request_uri
8
- DEFAULTS = {
9
- :method => 'GET',
10
- :request_uri => '*',
11
- :http_version => 'HTTP/1.1',
12
- :headers => {},
13
- :entity_body => ''
14
- }
15
-
16
- def initialize(request_hash = {})
17
- request_hash = DEFAULTS.merge(request_hash)
18
-
19
- self.method = request_hash[:method]
20
- self.request_uri = request_hash[:request_uri]
21
- self.http_version = request_hash[:http_version]
22
- self.headers.merge!(request_hash[:headers])
23
- self.entity_body = request_hash[:entity_body]
19
+
20
+ def request_uri=(request_uri)
21
+ @request_uri = Addressable::URI.parse(request_uri)
22
+ end
23
+
24
+ # The scheme of the Request.
25
+ #
26
+ # @return ['http', 'https']
27
+ attr_accessor :scheme
28
+
29
+ # Creates a new Request.
30
+ #
31
+ # The method converts the values passed in to their proper types.
32
+ #
33
+ # @example
34
+ # Webbed::Request.new('GET', 'http://example.com', {}, '')
35
+ #
36
+ # @param [Method, String] method
37
+ # @param [Addressable::URI, String] request_uri
38
+ # @param [Headers, Hash] headers
39
+ # @param [#to_s] entity_body
40
+ # @param [Array] options the options to create the Request with
41
+ # @option options [#to_s] :http_version (1.1) the HTTP-Version of the
42
+ # Request
43
+ # @option options ['http', 'https'] :scheme ('http') the scheme of the
44
+ # Request
45
+ def initialize(method, request_uri, headers, entity_body, options = {})
46
+ self.method = method
47
+ self.request_uri = request_uri
48
+ self.headers = headers
49
+ self.entity_body = entity_body
50
+ self.http_version = options[:http_version] || 1.1
51
+ self.scheme = options[:scheme] || 'http'
24
52
  end
25
53
 
54
+ # The Method of the Request.
55
+ #
56
+ # @return [Method]
26
57
  def method(*args)
27
58
  return super(*args) unless args.empty?
28
59
  @method
29
60
  end
30
61
 
62
+ # Sets the Method of the Request.
63
+ #
64
+ # @param [Method] method
31
65
  def method=(method)
32
66
  @method = Webbed::Method.new(method)
33
67
  end
34
68
 
35
- def request_uri=(uri)
36
- @request_uri = Addressable::URI.parse(uri)
37
- end
38
-
69
+ # The Request-Line of the Request as defined in RFC 2616.
70
+ #
71
+ # @example
72
+ # request = Webbed::Request.new(['GET', 'http://example.com', {}, ''])
73
+ # request.request_line # => "GET * HTTP/1.1\r\n"
74
+ #
75
+ # @return [String]
39
76
  def request_line
40
77
  "#{method} #{request_uri} #{http_version}\r\n"
41
78
  end
42
79
  alias :start_line :request_line
80
+
81
+ include Helpers::MethodHelper
82
+ include Helpers::RequestURIHelper
83
+ include Helpers::RackRequestHelper
84
+ include Helpers::SchemeHelper
85
+ include Helpers::RequestHeadersHelper
86
+ include Helpers::EntityHeadersHelper
43
87
  end
44
88
  end
@@ -1,42 +1,83 @@
1
1
  module Webbed
2
+ # Representation of an HTTP Response.
3
+ #
4
+ # This class contains the absolute minimum for accessing the different parts
5
+ # of an HTTP Response. Helper modules provide far more functionality.
2
6
  class Response
7
+ include GenericMessage
3
8
 
4
- include Webbed::GenericMessage
9
+ STATUS_CODE_REGEX = /^(\d{3}) (.*)$/
10
+
11
+ # The Status Code of the Response.
12
+ #
13
+ # The method automatically converts the new value to an instance of
14
+ # {StatusCode} if it is not already one.
15
+ #
16
+ # @return [#to_i]
5
17
  attr_reader :status_code
6
- attr_writer :reason_phrase
7
- DEFAULTS = {
8
- :http_version => 'HTTP/1.1',
9
- :status_code => 200,
10
- :reason_phrase => nil,
11
- :headers => {},
12
- :entity_body => ''
13
- }
14
-
15
- def initialize(response_hash = {})
16
- response_hash = DEFAULTS.merge(response_hash)
17
-
18
- self.http_version = response_hash[:http_version]
19
- self.status_code = response_hash[:status_code]
20
- self.reason_phrase = response_hash[:reason_phrase]
21
- self.headers.merge!(response_hash[:headers])
22
- self.entity_body = response_hash[:entity_body]
18
+
19
+ def status_code=(status_code)
20
+ @status_code = Webbed::StatusCode.new(status_code)
23
21
  end
24
22
 
23
+ # The Reason Phrase of the Response.
24
+ #
25
+ # The method returns the Status Code's default reason phrase if the Reason
26
+ # Phrase has not already been set.
27
+ #
28
+ # @return [String]
25
29
  def reason_phrase
26
- @reason_phrase || default_reason_phrase
30
+ defined?(@reason_phrase) ? @reason_phrase : @status_code.default_reason_phrase
27
31
  end
28
32
 
29
- def default_reason_phrase
30
- @status_code.default_reason_phrase
31
- end
33
+ attr_writer :reason_phrase
32
34
 
33
- def status_code=(status_code)
34
- @status_code = Webbed::StatusCode.new(status_code)
35
+ # Creates a new Response.
36
+ #
37
+ # The Status Code may be passed in as a string with a Reason Phrase or just
38
+ # a number. If a number is provided, the default Reason Phrase for that
39
+ # Status Code is used.
40
+ #
41
+ # The method converts the values passed in to their proper types.
42
+ #
43
+ # @example
44
+ # Webbed::Response.new(200, {}, '')
45
+ # Webbed::Response.new('404 Missing File', {}, '')
46
+ #
47
+ # @param [Fixnum, String] status_code
48
+ # @param [Headers, Hash] headers
49
+ # @param [#to_s] entity_body
50
+ # @param [Array] options the options to create the Response with @option
51
+ # @options options [#to_s] :http_version (1.1) the HTTP-Version of the
52
+ # Response
53
+ def initialize(status_code, headers, entity_body, options = {})
54
+ if STATUS_CODE_REGEX =~ status_code.to_s
55
+ self.status_code = $1
56
+ self.reason_phrase = $2
57
+ else
58
+ self.status_code = status_code
59
+ end
60
+
61
+ self.headers = headers
62
+ self.entity_body = entity_body
63
+
64
+ self.http_version = options.delete(:http_version) || 1.1
35
65
  end
36
66
 
67
+ # The Status-Line of the Response.
68
+ #
69
+ # @example
70
+ # response = Webbed::Response.new([200, {}, ''])
71
+ # response.status_line # => "HTTP/1.1 200 OK\r\n"
72
+ #
73
+ # @return [String]
37
74
  def status_line
38
75
  "#{http_version} #{status_code} #{reason_phrase}\r\n"
39
76
  end
40
77
  alias :start_line :status_line
78
+
79
+ include Helpers::RackResponseHelper
80
+ include Helpers::ResponseHeadersHelper
81
+ include Helpers::EntityHeadersHelper
41
82
  end
42
83
  end
@@ -1,9 +1,7 @@
1
1
  module Webbed
2
+ # Representation of an HTTP Status Code.
2
3
  class StatusCode
3
-
4
4
  include Comparable
5
- attr_reader :status_code, :default_reason_phrase
6
- CACHED = {}
7
5
 
8
6
  UNKNOWN_REASON_PHRASE = 'Unknown Status Code'
9
7
  REASON_PHRASES = {
@@ -49,54 +47,123 @@ module Webbed
49
47
  505 => 'HTTP Version not supported'
50
48
  }
51
49
 
50
+ @@cached = {}
51
+
52
+ # The default Reason Phrase of the Status Code.
53
+ #
54
+ # @return [String]
55
+ attr_reader :default_reason_phrase
56
+
57
+ # Retrieves a Status Code from the cache or creates a new one.
58
+ #
59
+ # All created Status Codes are cached forever.
60
+ #
61
+ # @param (see #initialize)
62
+ # @return [StatusCode] the new or cached StatusCode
63
+ # @see #initialize
52
64
  def self.new(status_code)
53
- CACHED[status_code] ||= super(status_code)
65
+ status_code = status_code.to_i
66
+ @@cached[status_code] ||= super(status_code)
54
67
  end
55
68
 
69
+ # Creates a new Status Code.
70
+ #
71
+ # @param [Fixnum] status_code
56
72
  def initialize(status_code)
57
- status_code = status_code.to_i
58
73
  @status_code = status_code
59
- @default_reason_phrase = REASON_PHRASES[status_code] || UNKNOWN_REASON_PHRASE
74
+ @default_reason_phrase = REASON_PHRASES[@status_code] || UNKNOWN_REASON_PHRASE
60
75
  end
61
76
 
77
+ # Comparse the Status Code to another Status Code.
78
+ #
79
+ # @param [#to_i] other_status_code the other Status Code
80
+ # @return [Fixnum] the sign of the comparison (either `1`, `0`, or `-1`)
62
81
  def <=>(other_status_code)
63
- status_code <=> other_status_code.to_i
82
+ @status_code <=> other_status_code.to_i
64
83
  end
65
84
 
85
+ # Converts the Status Code to an integer.
86
+ #
87
+ # @return [Fixnum]
66
88
  def to_i
67
- status_code
89
+ @status_code
68
90
  end
69
91
 
92
+ # Converts the Status Code to a string.
93
+ #
94
+ # @return [String]
70
95
  def to_s
71
- '%03d' % status_code
96
+ @status_code.to_s
72
97
  end
73
98
 
99
+ # Whether or not the Status Code is informational.
100
+ #
101
+ # According to RFC 2616, informational status codes are in the range of 100
102
+ # to 199, inclusive.
103
+ #
104
+ # @return [Boolean]
74
105
  def informational?
75
- (100...200).include? status_code
106
+ (100...200).include?(@status_code)
76
107
  end
77
108
 
78
- def success?
79
- (200...300).include? status_code
109
+ # Whether or not the Status Code is successful.
110
+ #
111
+ # According to RFC 2616, successful status codes are in the range of 200 to
112
+ # 299, inclusive.
113
+ #
114
+ # @return [Boolean]
115
+ def successful?
116
+ (200...300).include?(@status_code)
80
117
  end
81
118
 
119
+ # Whether or not the Status Code is a redirection.
120
+ #
121
+ # According to RFC 2616, redirection status codes are in the range of 300 to
122
+ # 399, inclusive.
123
+ #
124
+ # @return [Boolean]
82
125
  def redirection?
83
- (300...400).include? status_code
126
+ (300...400).include?(@status_code)
84
127
  end
85
128
 
129
+ # Whether or not the Status Code is a client error.
130
+ #
131
+ # According to RFC 2616, client error status codes are in the range of 400
132
+ # to 499, inclusive.
133
+ #
134
+ # @return [Boolean]
86
135
  def client_error?
87
- (400...500).include? status_code
136
+ (400...500).include?(@status_code)
88
137
  end
89
138
 
139
+ # Whether or not the Status Code is a server error.
140
+ #
141
+ # According to RFC 2616, server error status codes are in the range of 500
142
+ # to 599, inclusive.
143
+ #
144
+ # @return [Boolean]
90
145
  def server_error?
91
- (500...600).include? status_code
146
+ (500...600).include?(@status_code)
92
147
  end
93
148
 
149
+ # Whether or not the Status Code is unknown.
150
+ #
151
+ # According to RFC 2616, the only defined Status Code ranges are from 100 to
152
+ # 599, inclusive. Anything outside that range is an unknown Status Code.
153
+ #
154
+ # @return [Boolean]
94
155
  def unknown?
95
- !(100...600).include? status_code
156
+ !(100...600).include?(@status_code)
96
157
  end
97
158
 
159
+ # Whether or not the Status Code is an error.
160
+ #
161
+ # According to RFC 2616, Status Codes that signify errors are in the range
162
+ # of 400 to 599, inclusive.
163
+ #
164
+ # @return [Boolean]
98
165
  def error?
99
- (400...600).include? status_code
166
+ (400...600).include?(@status_code)
100
167
  end
101
168
  end
102
169
  end