rackful 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|