rackful 0.1.4 → 0.2.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/CHANGES.md +18 -8
- data/RACKFUL.md +46 -34
- data/README.md +11 -5
- data/example/config.ru +19 -25
- data/lib/rackful.rb +11 -2
- data/lib/rackful/httpstatus.rb +250 -0
- data/lib/rackful/middleware.rb +2 -3
- data/lib/rackful/middleware/headerspoofing.rb +49 -0
- data/lib/rackful/middleware/methodoverride.rb +136 -0
- data/lib/rackful/parser.rb +315 -0
- data/lib/rackful/request.rb +103 -53
- data/lib/rackful/resource.rb +158 -221
- data/lib/rackful/serializer.rb +133 -215
- data/lib/rackful/server.rb +76 -86
- data/lib/rackful/uri.rb +150 -0
- data/mkdoc.sh +4 -2
- data/rackful.gemspec +6 -5
- metadata +66 -58
- data/lib/rackful/http_status.rb +0 -285
- data/lib/rackful/middleware/header_spoofing.rb +0 -72
- data/lib/rackful/middleware/method_spoofing.rb +0 -101
- data/lib/rackful/middleware/relative_location.rb +0 -71
- data/lib/rackful/path.rb +0 -179
data/lib/rackful/request.rb
CHANGED
@@ -1,63 +1,53 @@
|
|
1
|
-
#
|
2
|
-
require 'rack'
|
3
|
-
|
4
|
-
# Required for running:
|
1
|
+
# encoding: utf-8
|
5
2
|
|
6
3
|
|
7
4
|
module Rackful
|
8
5
|
|
9
|
-
|
10
|
-
Subclass of {Rack::Request}, augmented for Rackful requests.
|
11
|
-
=end
|
6
|
+
# Subclass of {Rack::Request}, augmented for Rackful requests.
|
12
7
|
class Request < Rack::Request
|
13
8
|
|
14
9
|
|
15
|
-
|
16
|
-
|
17
|
-
@return [#[]]
|
18
|
-
@see Server#initialize
|
19
|
-
=end
|
20
|
-
def resource_factory; self.env['rackful.resource_factory']; end
|
21
|
-
def base_path
|
22
|
-
self.env['rackful.base_path'] ||= begin
|
23
|
-
r = self.content_path.dup
|
24
|
-
r[%r{[^/]*\z}] = ''
|
25
|
-
r
|
26
|
-
end
|
10
|
+
def initialize *args
|
11
|
+
super( *args )
|
27
12
|
end
|
13
|
+
|
14
|
+
|
28
15
|
=begin markdown
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
=begin markdown
|
35
|
-
Set by {Rackful::Server#call!}
|
36
|
-
@return [Path]
|
37
|
-
=end
|
38
|
-
def content_path= bp; self.env['rackful.content_path'] = bp.to_path; end
|
39
|
-
=begin markdown
|
40
|
-
@return [Path]
|
16
|
+
Shortcut to {Server#resource_at}.
|
17
|
+
|
18
|
+
(see Server#resource_at)
|
19
|
+
@return (see Server#resource_at)
|
20
|
+
@raise (see Server#resource_at)
|
41
21
|
=end
|
42
|
-
def
|
22
|
+
def resource_at *args
|
23
|
+
env['rackful.server'].resource_at( *args )
|
24
|
+
end
|
43
25
|
|
44
26
|
|
45
|
-
|
46
|
-
|
47
|
-
|
27
|
+
=begin markdown
|
28
|
+
Similar to the HTTP/1.1 `Content-Location:` header. Contains the canonical url
|
29
|
+
of the requested resource, which may differ from {#url}.
|
30
|
+
|
31
|
+
If parameter +full_path+ is provided, than this is used instead of the current
|
32
|
+
request’s full path (which is the path plus optional query string).
|
33
|
+
@return [URI::Generic]
|
34
|
+
=end
|
35
|
+
def canonical_uri
|
36
|
+
env['rackful.canonical_uri'] ||= URI( self.url ).normalize
|
48
37
|
end
|
49
38
|
|
50
39
|
|
51
40
|
=begin markdown
|
52
|
-
The
|
41
|
+
The canonical url of the requested resource. This may differ from {#url}.
|
53
42
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@return [Request]
|
43
|
+
@todo Change URI::Generic into URI::HTTP
|
44
|
+
@param uri [URI::Generic, String]
|
45
|
+
@return [URI::Generic, String] `uri`
|
58
46
|
=end
|
59
|
-
def
|
60
|
-
|
47
|
+
def canonical_uri=( uri )
|
48
|
+
env['rackful.canonical_uri'] =
|
49
|
+
uri.kind_of?( URI::Generic ) ? URI(uri) : URI( uri ).normalize
|
50
|
+
uri
|
61
51
|
end
|
62
52
|
|
63
53
|
|
@@ -79,7 +69,7 @@ Assert all <tt>If-*</tt> request headers.
|
|
79
69
|
=end
|
80
70
|
def assert_if_headers resource
|
81
71
|
#raise HTTP501NotImplemented, 'If-Range: request header is not supported.' \
|
82
|
-
# if
|
72
|
+
# if env.key? 'HTTP_IF_RANGE'
|
83
73
|
empty = resource.empty?
|
84
74
|
etag =
|
85
75
|
if ! empty && resource.respond_to?(:get_etag)
|
@@ -106,7 +96,7 @@ Assert all <tt>If-*</tt> request headers.
|
|
106
96
|
elsif cond[:unmodified_since]
|
107
97
|
raise HTTP412PreconditionFailed, 'If-Unmodified-Since'
|
108
98
|
elsif cond[:modified_since]
|
109
|
-
raise HTTP404NotFound
|
99
|
+
raise HTTP404NotFound
|
110
100
|
end
|
111
101
|
else
|
112
102
|
if cond[:none_match] && self.validate_etag( etag, cond[:none_match] )
|
@@ -136,17 +126,77 @@ Assert all <tt>If-*</tt> request headers.
|
|
136
126
|
end
|
137
127
|
|
138
128
|
|
139
|
-
|
140
|
-
|
129
|
+
# Shortcut to {Rack::Utils.q_values}. Well, actually, we reimplemented it
|
130
|
+
# because the implementation in {Rack::Utils} seems incomplete.
|
131
|
+
# @return [Array<Array(type, quality)>]
|
132
|
+
# @see Rack::Utils.q_values
|
133
|
+
def q_values
|
134
|
+
# This would be the “shortcut” implementation:
|
135
|
+
#env['rackful.q_values'] ||= Rack::Utils.q_values(env['HTTP_ACCEPT'])
|
136
|
+
# But here’s a full (and better) implementation:
|
137
|
+
env['rackful.q_values'] ||= env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do
|
138
|
+
|part|
|
139
|
+
value, *parameters = part.split(/\s*;\s*/)
|
140
|
+
quality = 1.0
|
141
|
+
parameters.each do |p|
|
142
|
+
quality = p[2..-1].to_f if p.start_with? 'q='
|
143
|
+
end
|
144
|
+
[value, quality]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
141
148
|
|
142
|
-
|
143
|
-
|
144
|
-
@
|
149
|
+
=begin markdown
|
150
|
+
The best media type to represent a resource, given the current HTTP request.
|
151
|
+
@param resource [Resource] the resource to represent
|
152
|
+
@param require_match [Boolean] this flag determines what must happen if the
|
153
|
+
client sent an `Accept:` header, and we cannot serve any of the acceptable
|
154
|
+
media types. **`TRUE`** means that an {HTTP406NotAcceptable} exception is
|
155
|
+
raised. **`FALSE`** means that the content-type with the highest quality is
|
156
|
+
returned.
|
157
|
+
@return [String] content-type
|
158
|
+
@raise [HTTP406NotAcceptable]
|
159
|
+
@api private
|
145
160
|
=end
|
161
|
+
def best_content_type( resource, require_match = true )
|
162
|
+
q_values = self.q_values
|
163
|
+
if q_values.empty?
|
164
|
+
return resource.all_serializers.values.sort_by(&:last).last[0]::CONTENT_TYPES[0]
|
165
|
+
end
|
166
|
+
matches = []
|
167
|
+
q_values.each {
|
168
|
+
|accept_media_type, accept_quality|
|
169
|
+
resource.all_serializers.each_pair {
|
170
|
+
|content_type, v|
|
171
|
+
quality = v[1]
|
172
|
+
media_type = content_type.split(';').first.strip
|
173
|
+
if File.fnmatch( accept_media_type, media_type )
|
174
|
+
matches << [ content_type, accept_quality * quality ]
|
175
|
+
end
|
176
|
+
}
|
177
|
+
}
|
178
|
+
if matches.empty?
|
179
|
+
if require_match
|
180
|
+
raise( HTTP406NotAcceptable, resource.all_serializers.keys() )
|
181
|
+
else
|
182
|
+
return resource.all_serializers.values.sort_by(&:last).
|
183
|
+
last.first::CONTENT_TYPES.first
|
184
|
+
end
|
185
|
+
end
|
186
|
+
matches.sort_by(&:last).last.first
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
# Hash of acceptable media types and their qualities.
|
191
|
+
#
|
192
|
+
# This method parses the HTTP/1.1 `Accept:` header. If no acceptable media
|
193
|
+
# types are provided, an empty Hash is returned.
|
194
|
+
# @return [Hash{media_type => quality}]
|
195
|
+
# @deprecated Use {#q_values} instead
|
146
196
|
def accept
|
147
|
-
|
197
|
+
env['rackful.accept'] ||= begin
|
148
198
|
Hash[
|
149
|
-
|
199
|
+
env['HTTP_ACCEPT'].to_s.split(',').collect do
|
150
200
|
|entry|
|
151
201
|
type, *options = entry.delete(' ').split(';')
|
152
202
|
quality = 1
|
@@ -170,7 +220,7 @@ Parses the HTTP/1.1 `If-Match:` header.
|
|
170
220
|
@see #if_none_match
|
171
221
|
=end
|
172
222
|
def if_match none = false
|
173
|
-
header =
|
223
|
+
header = env["HTTP_IF_#{ none ? 'NONE_' : '' }MATCH"]
|
174
224
|
return nil unless header
|
175
225
|
envkey = "rackful.if_#{ none ? 'none_' : '' }match"
|
176
226
|
if %r{\A\s*\*\s*\z} === header
|
@@ -200,7 +250,7 @@ Parses the HTTP/1.1 `If-None-Match:` header.
|
|
200
250
|
@see #if_unmodified_since
|
201
251
|
=end
|
202
252
|
def if_modified_since unmodified = false
|
203
|
-
header =
|
253
|
+
header = env["HTTP_IF_#{ unmodified ? 'UN' : '' }MODIFIED_SINCE"]
|
204
254
|
return nil unless header
|
205
255
|
begin
|
206
256
|
header = Time.httpdate( header )
|
data/lib/rackful/resource.rb
CHANGED
@@ -1,177 +1,169 @@
|
|
1
|
-
#
|
2
|
-
require 'rack'
|
3
|
-
|
4
|
-
# Required for running:
|
1
|
+
# encoding: utf-8
|
5
2
|
|
3
|
+
require 'forwardable'
|
6
4
|
|
7
5
|
module Rackful
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
7
|
+
# Abstract superclass for resources served by {Server}.
|
8
|
+
# @see Server
|
9
|
+
# @todo better documentation
|
10
|
+
# @abstract Realizations must implement…
|
11
|
+
class Resource
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
|
16
|
+
# Meta-programmer method.
|
17
|
+
# @example Have your resource rendered in XML and JSON
|
18
|
+
# class MyResource
|
19
|
+
# add_serializer MyResource2XML
|
20
|
+
# add_serializer MyResource2JSON, 0.5
|
21
|
+
# end
|
22
|
+
# @param serializer [Serializer]
|
23
|
+
# @param quality [Float]
|
24
|
+
# @return [self]
|
25
|
+
def add_serializer serializer, quality = 1.0
|
26
|
+
quality = quality.to_f
|
27
|
+
quality = 1.0 if quality > 1.0
|
28
|
+
quality = 0.0 if quality < 0.0
|
29
|
+
s = [serializer, quality]
|
30
|
+
serializer::CONTENT_TYPES.each {
|
31
|
+
|content_type|
|
32
|
+
self.serializers[content_type.to_s] = s
|
33
|
+
}
|
34
|
+
self
|
35
|
+
end
|
20
36
|
|
21
37
|
|
22
|
-
|
38
|
+
# Meta-programmer method.
|
39
|
+
# @example Have your resource accept XHTML in `PUT` requests
|
40
|
+
# class MyResource
|
41
|
+
# include Rackful::Resource
|
42
|
+
# add_parser Rackful::Parser::XHTML, :PUT
|
43
|
+
# end
|
44
|
+
# @param parser [Class] an implementation (ie. subclass) of {Parser}
|
45
|
+
# @param method [#to_sym] For example: `:PUT` or `:POST`
|
46
|
+
# @return [self]
|
47
|
+
def add_parser parser, method = :PUT
|
48
|
+
method = method.to_sym
|
49
|
+
self.parsers[method] ||= []
|
50
|
+
self.parsers[method] << parser
|
51
|
+
self.parsers[method].uniq!
|
52
|
+
self
|
53
|
+
end
|
23
54
|
|
24
55
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
56
|
+
# All parsers added to _this_ class. Ie. not including parsers added
|
57
|
+
# to parent classes.
|
58
|
+
# @return [Hash{Symbol => Array<Class>}] A hash of lists of {Parser} classes,
|
59
|
+
# indexed by HTTP method.
|
60
|
+
# @api private
|
61
|
+
def parsers
|
62
|
+
# The single '@' on the following line is on purpose!
|
63
|
+
@rackful_resource_parsers ||= {}
|
64
|
+
end
|
30
65
|
|
31
66
|
|
32
|
-
|
33
|
-
|
34
|
-
|
67
|
+
# All serializers added to _this_ class. Ie. not including serializers added
|
68
|
+
# to parent classes.
|
69
|
+
# @return [Hash{Serializer => Float}]
|
70
|
+
# @api private
|
71
|
+
def serializers
|
72
|
+
# The single '@' on the following line is on purpose!
|
73
|
+
@rackful_resource_serializers ||= {}
|
35
74
|
end
|
36
75
|
|
37
76
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@
|
43
|
-
|
44
|
-
|
45
|
-
|
77
|
+
# All parsers for this class, including parsers added to parent classes.
|
78
|
+
# The result of this method is cached, which will interfere with code reloading.
|
79
|
+
# @param method [#to_sym] For example: `:PUT` or `:POST`
|
80
|
+
# @return [Hash{Symbol => Array<Class>}]
|
81
|
+
# @api private
|
82
|
+
def all_parsers
|
83
|
+
# The single '@' on the following line is on purpose!
|
84
|
+
@rackful_resource_all_parsers ||=
|
85
|
+
if self.superclass.respond_to?(:all_parsers)
|
86
|
+
self.parsers.merge( self.superclass.all_parsers ) do
|
87
|
+
|key, oldval, newval|
|
88
|
+
( oldval + newval ).uniq
|
89
|
+
end
|
90
|
+
else
|
91
|
+
self.parsers
|
92
|
+
end
|
46
93
|
end
|
47
|
-
@param serializer [Serializer]
|
48
|
-
@param quality [Float]
|
49
|
-
@return [self]
|
50
|
-
=end
|
51
|
-
def add_serializer serializer, quality = 1.0
|
52
|
-
quality = quality.to_f
|
53
|
-
quality = 1.0 if quality > 1.0
|
54
|
-
quality = 0.0 if quality < 0.0
|
55
|
-
s = [serializer, quality]
|
56
|
-
serializer::CONTENT_TYPES.each {
|
57
|
-
|content_type|
|
58
|
-
self.serializers[content_type.to_s] = s
|
59
|
-
}
|
60
|
-
self
|
61
|
-
end
|
62
94
|
|
63
95
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
96
|
+
# All serializers for this class, including those added to parent classes.
|
97
|
+
# The result of this method is cached, which will interfere with code reloading.
|
98
|
+
# @return [Hash{Serializer => Float}] The float indicates the quality of each
|
99
|
+
# serializer in the interval [0,1]
|
100
|
+
# @api private
|
101
|
+
def all_serializers
|
102
|
+
# The single '@' on the following line is on purpose!
|
103
|
+
@rackful_resource_all_serializers ||=
|
104
|
+
if self.superclass.respond_to?(:all_serializers)
|
105
|
+
self.superclass.all_serializers.merge( self.serializers ) do
|
106
|
+
|key, oldval, newval|
|
107
|
+
newval[1] >= oldval[1] ? newval : oldval
|
108
|
+
end
|
109
|
+
else
|
110
|
+
self.serializers
|
111
|
+
end
|
112
|
+
end
|
68
113
|
|
69
114
|
|
70
|
-
|
71
|
-
# The single '@' on the following line is on purpose!
|
72
|
-
@rackful_resource_all_serializers ||=
|
73
|
-
if self.superclass.respond_to?(:all_serializers)
|
74
|
-
self.superclass.all_serializers.merge( self.serializers ) do
|
75
|
-
|key, oldval, newval|
|
76
|
-
newval[1] >= oldval[1] ? newval : oldval
|
77
|
-
end
|
78
|
-
else
|
79
|
-
self.serializers
|
80
|
-
end
|
81
|
-
end
|
115
|
+
end # module ClassMethods
|
82
116
|
|
83
117
|
|
84
|
-
|
85
|
-
|
86
|
-
@example Have your resource accept XML and JSON in `PUT` requests
|
87
|
-
class MyResource
|
88
|
-
add_media_type 'text/xml', :PUT
|
89
|
-
add_media_type 'application/json', :PUT
|
90
|
-
end
|
91
|
-
@param [#to_s] media_type
|
92
|
-
@param [#to_sym] method
|
93
|
-
@return [self]
|
94
|
-
=end
|
95
|
-
def add_media_type media_type, method = :PUT
|
96
|
-
method = method.to_sym
|
97
|
-
self.media_types[method] ||= []
|
98
|
-
self.media_types[method] << media_type.to_s
|
99
|
-
self
|
100
|
-
end
|
118
|
+
extend Forwardable
|
119
|
+
def_delegators 'self.class', :parsers, :serializers, :all_parsers, :all_serializers
|
101
120
|
|
102
121
|
|
103
|
-
|
104
|
-
@
|
105
|
-
=
|
106
|
-
|
107
|
-
@rackful_resource_media_types ||= {}
|
108
|
-
end
|
122
|
+
def initialize( uri )
|
123
|
+
@uri = uri.kind_of?( URI::Generic ) ? uri.dup : URI(uri.to_s).normalize
|
124
|
+
#self.uri = uri
|
125
|
+
end
|
109
126
|
|
110
127
|
|
111
|
-
=begin
|
112
|
-
@
|
128
|
+
=begin markdown
|
129
|
+
@param request [Request]
|
130
|
+
@param content_type [String, nil] If omitted, you get the best serializer
|
131
|
+
available, which may not be acceptable by the client.
|
132
|
+
@return [Serializer]
|
113
133
|
=end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|key, oldval, newval|
|
119
|
-
oldval + newval
|
120
|
-
end
|
121
|
-
else
|
122
|
-
self.media_types
|
123
|
-
end
|
124
|
-
end
|
134
|
+
def serializer request, content_type = nil
|
135
|
+
content_type ||= request.best_content_type( self, false )
|
136
|
+
self.class.all_serializers[content_type][0].new( request, self, content_type )
|
137
|
+
end
|
125
138
|
|
126
139
|
|
127
140
|
=begin markdown
|
128
141
|
The best media type for the response body, given the current HTTP request.
|
129
|
-
@param
|
130
|
-
@
|
131
|
-
@
|
132
|
-
@raise [HTTP406NotAcceptable] if `require_match` is `true` and no match was found.
|
142
|
+
@param request [Rackful::Request]
|
143
|
+
@return [Parser, nil] a {Parser}, or nil if the request entity is empty
|
144
|
+
@raise [HTTP415UnsupportedMediaType] if no parser can be found for the request entity
|
133
145
|
=end
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
matches << [ content_type, accept_quality * quality ]
|
147
|
-
end
|
148
|
-
}
|
149
|
-
}
|
150
|
-
if matches.empty?
|
151
|
-
if require_match
|
152
|
-
raise( HTTP406NotAcceptable, self.all_serializers.keys() )
|
153
|
-
else
|
154
|
-
return self.all_serializers.values.sort_by(&:last).last[0]::CONTENT_TYPES[0]
|
146
|
+
def parser request
|
147
|
+
unless request.content_length ||
|
148
|
+
'chunked' == request.env['HTTP_TRANSFER_ENCODING']
|
149
|
+
raise HTTP411LengthRequired
|
150
|
+
end
|
151
|
+
request_media_type = request.media_type.to_s
|
152
|
+
supported_media_types = []
|
153
|
+
all_parsers = self.class.all_parsers[ request.request_method.to_sym ] || []
|
154
|
+
all_parsers.each do |p|
|
155
|
+
p::MEDIA_TYPES.each do |parser_media_type|
|
156
|
+
if File.fnmatch( parser_media_type, request_media_type )
|
157
|
+
return p.new( request, self )
|
155
158
|
end
|
159
|
+
supported_media_types << parser_media_type
|
156
160
|
end
|
157
|
-
matches.sort_by(&:last).last[0]
|
158
161
|
end
|
159
|
-
|
160
|
-
|
161
|
-
end # module ClassMethods
|
162
|
-
|
163
|
-
|
164
|
-
=begin markdown
|
165
|
-
@return [Serializer]
|
166
|
-
=end
|
167
|
-
def serializer content_type
|
168
|
-
@rackful_resource_serializers ||= {}
|
169
|
-
@rackful_resource_serializers[content_type] ||=
|
170
|
-
self.class.all_serializers[content_type][0].new( self, content_type )
|
162
|
+
raise( HTTP415UnsupportedMediaType, supported_media_types.uniq )
|
171
163
|
end
|
172
164
|
|
173
165
|
|
174
|
-
=begin
|
166
|
+
=begin markdown
|
175
167
|
@!method do_METHOD( Request, Rack::Response )
|
176
168
|
HTTP/1.1 method handler.
|
177
169
|
|
@@ -189,36 +181,23 @@ The best media type for the response body, given the current HTTP request.
|
|
189
181
|
|
190
182
|
|
191
183
|
=begin markdown
|
192
|
-
The path of this resource.
|
193
|
-
@return [
|
194
|
-
@see #initialize
|
184
|
+
The canonical path of this resource.
|
185
|
+
@return [URI]
|
195
186
|
=end
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
def path= path
|
200
|
-
@rackful_resource_path = Path.new(path)
|
201
|
-
end
|
187
|
+
attr_reader :uri
|
202
188
|
|
203
189
|
|
204
190
|
def title
|
205
|
-
|
206
|
-
Request.current.host :
|
207
|
-
File.basename(self.path).to_path.unescape
|
208
|
-
end
|
209
|
-
|
210
|
-
|
211
|
-
def requested?
|
212
|
-
self.path.slashify == Request.current.path.slashify
|
191
|
+
self.uri.segments.last || self.class.to_s
|
213
192
|
end
|
214
193
|
|
215
194
|
|
216
195
|
=begin markdown
|
217
|
-
Does this resource
|
196
|
+
Does this resource _exist_?
|
218
197
|
|
219
198
|
For example, a client can `PUT` to a URL that doesn't refer to a resource
|
220
|
-
yet. In that case, your {Server#
|
221
|
-
produce an empty resource to
|
199
|
+
yet. In that case, your {Server#initialize resource registry} can
|
200
|
+
produce an empty resource to handle the `PUT` request. `HEAD` and `GET`
|
222
201
|
requests will still yield `404 Not Found`.
|
223
202
|
|
224
203
|
@return [Boolean] The default implementation returns `false`.
|
@@ -227,10 +206,7 @@ requests will still yield `404 Not Found`.
|
|
227
206
|
false
|
228
207
|
end
|
229
208
|
|
230
|
-
|
231
|
-
=begin markdown
|
232
|
-
|
233
|
-
=end
|
209
|
+
# @todo documentation
|
234
210
|
def to_rackful
|
235
211
|
self
|
236
212
|
end
|
@@ -286,7 +262,7 @@ List of all HTTP/1.1 methods implemented by this resource.
|
|
286
262
|
|
287
263
|
This works by inspecting all the {#do_METHOD} methods this object implements.
|
288
264
|
@return [Array<Symbol>]
|
289
|
-
@private
|
265
|
+
@api private
|
290
266
|
=end
|
291
267
|
def http_methods
|
292
268
|
r = []
|
@@ -321,8 +297,7 @@ Feel free to override this method at will.
|
|
321
297
|
@raise [HTTP404NotFound] `404 Not Found` if this resource is empty.
|
322
298
|
=end
|
323
299
|
def http_OPTIONS request, response
|
324
|
-
|
325
|
-
response.status = status_code :no_content
|
300
|
+
response.status = Rack::Utils.status_code :no_content
|
326
301
|
response.header['Allow'] = self.http_methods.join ', '
|
327
302
|
end
|
328
303
|
|
@@ -349,21 +324,20 @@ Feel free to override this method at will.
|
|
349
324
|
|
350
325
|
|
351
326
|
=begin markdown
|
352
|
-
@private
|
353
|
-
@param [Rackful::Request]
|
354
|
-
@param [Rack::Response]
|
327
|
+
@api private
|
328
|
+
@param request [Rackful::Request]
|
329
|
+
@param response [Rack::Response]
|
355
330
|
@return [void]
|
356
331
|
@raise [HTTP404NotFound, HTTP405MethodNotAllowed]
|
357
332
|
=end
|
358
333
|
def http_GET request, response
|
359
|
-
raise HTTP404NotFound
|
334
|
+
raise HTTP404NotFound if self.empty?
|
360
335
|
# May throw HTTP406NotAcceptable:
|
361
|
-
content_type =
|
336
|
+
content_type = request.best_content_type( self )
|
362
337
|
response['Content-Type'] = content_type
|
363
|
-
response.status = status_code( :ok )
|
338
|
+
response.status = Rack::Utils.status_code( :ok )
|
364
339
|
response.headers.merge! self.default_headers
|
365
|
-
|
366
|
-
serializer = self.serializer( content_type )
|
340
|
+
serializer = self.serializer( request, content_type )
|
367
341
|
if serializer.respond_to? :headers
|
368
342
|
response.headers.merge!( serializer.headers )
|
369
343
|
end
|
@@ -373,14 +347,15 @@ Feel free to override this method at will.
|
|
373
347
|
|
374
348
|
=begin markdown
|
375
349
|
Wrapper around {#do_METHOD #do_GET}
|
376
|
-
@private
|
350
|
+
@api private
|
377
351
|
@return [void]
|
378
352
|
@raise [HTTP404NotFound, HTTP405MethodNotAllowed]
|
379
353
|
=end
|
380
354
|
def http_DELETE request, response
|
381
|
-
raise HTTP404NotFound
|
382
|
-
raise HTTP405MethodNotAllowed, self.http_methods unless
|
383
|
-
|
355
|
+
raise HTTP404NotFound if self.empty?
|
356
|
+
raise HTTP405MethodNotAllowed, self.http_methods unless
|
357
|
+
self.respond_to?( :destroy )
|
358
|
+
response.status = Rack::Utils.status_code( :no_content )
|
384
359
|
if headers = self.destroy( request, response )
|
385
360
|
response.headers.merge! headers
|
386
361
|
end
|
@@ -388,39 +363,30 @@ Wrapper around {#do_METHOD #do_GET}
|
|
388
363
|
|
389
364
|
|
390
365
|
=begin markdown
|
391
|
-
@private
|
366
|
+
@api private
|
392
367
|
@return [void]
|
393
368
|
@raise [HTTP415UnsupportedMediaType, HTTP405MethodNotAllowed] if the resource doesn't implement the `PUT` method.
|
394
369
|
=end
|
395
370
|
def http_PUT request, response
|
396
371
|
raise HTTP405MethodNotAllowed, self.http_methods unless self.respond_to? :do_PUT
|
397
|
-
|
398
|
-
self.class.media_types[:PUT].include?( request.media_type )
|
399
|
-
raise HTTP415UnsupportedMediaType, self.class.media_types[:PUT]
|
400
|
-
end
|
401
|
-
response.status = status_code( self.empty? ? :created : :no_content )
|
372
|
+
response.status = Rack::Utils.status_code( self.empty? ? :created : :no_content )
|
402
373
|
self.do_PUT( request, response )
|
403
374
|
response.headers.merge! self.default_headers
|
404
375
|
end
|
405
376
|
|
406
377
|
|
407
378
|
=begin markdown
|
408
|
-
Wrapper around {#do_METHOD
|
409
|
-
@private
|
379
|
+
Wrapper around {#do_METHOD}
|
380
|
+
@api private
|
410
381
|
@return [void]
|
411
|
-
@raise [HTTPStatus] `405 Method Not Allowed` if the resource doesn't implement
|
382
|
+
@raise [HTTPStatus] `405 Method Not Allowed` if the resource doesn't implement
|
383
|
+
the request method.
|
412
384
|
=end
|
413
385
|
def http_method request, response
|
414
386
|
method = request.request_method.to_sym
|
415
387
|
if ! self.respond_to?( :"do_#{method}" )
|
416
388
|
raise HTTP405MethodNotAllowed, self.http_methods
|
417
389
|
end
|
418
|
-
if ( request.content_length ||
|
419
|
-
'chunked' == request.env['HTTP_TRANSFER_ENCODING'] ) and
|
420
|
-
! self.class.media_types[method] ||
|
421
|
-
! self.class.media_types[method].include?( request.media_type )
|
422
|
-
raise HTTP415UnsupportedMediaType, self.class.media_types[method]
|
423
|
-
end
|
424
390
|
self.send( :"do_#{method}", request, response )
|
425
391
|
end
|
426
392
|
|
@@ -441,33 +407,4 @@ Adds `ETag:` and `Last-Modified:` response headers.
|
|
441
407
|
end # module Resource
|
442
408
|
|
443
409
|
|
444
|
-
=begin unused
|
445
|
-
module Collection
|
446
|
-
|
447
|
-
|
448
|
-
include Enumerable
|
449
|
-
|
450
|
-
|
451
|
-
def self.included( modul )
|
452
|
-
unless modul.kind_of? Resource
|
453
|
-
raise "module #{self} included in #{modul}, which isn't a Rackful::Resource"
|
454
|
-
end
|
455
|
-
end
|
456
|
-
|
457
|
-
|
458
|
-
def recurse?; false; end
|
459
|
-
|
460
|
-
|
461
|
-
def each_pair
|
462
|
-
self.each do
|
463
|
-
|path|
|
464
|
-
yield [ path, Request.current.resource_factory( path ) ]
|
465
|
-
end
|
466
|
-
end
|
467
|
-
|
468
|
-
|
469
|
-
end # module Collection
|
470
|
-
=end
|
471
|
-
|
472
|
-
|
473
410
|
end # module Rackful
|