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/http_status.rb
DELETED
@@ -1,285 +0,0 @@
|
|
1
|
-
# Required for parsing:
|
2
|
-
require 'rackful/resource.rb'
|
3
|
-
require 'rackful/serializer.rb'
|
4
|
-
|
5
|
-
# Required for running
|
6
|
-
require 'rexml/rexml'
|
7
|
-
|
8
|
-
|
9
|
-
module Rackful
|
10
|
-
|
11
|
-
=begin markdown
|
12
|
-
Exception which represents an HTTP Status response.
|
13
|
-
@abstract
|
14
|
-
=end
|
15
|
-
class HTTPStatus < RuntimeError
|
16
|
-
|
17
|
-
|
18
|
-
include Resource
|
19
|
-
|
20
|
-
|
21
|
-
attr_reader :status, :headers, :to_rackful
|
22
|
-
|
23
|
-
|
24
|
-
=begin markdown
|
25
|
-
@param [Symbol, Integer] status e.g. `404` or `:not_found`
|
26
|
-
@param [String] message XHTML
|
27
|
-
@param [ { Symbol => Object }, { String => String } ] info
|
28
|
-
* If the Hash is indexed by {Symbol}s, then the values will be returned in
|
29
|
-
the response body.
|
30
|
-
* If the Hash is indexed by {String}s, the +key => value+ pairs are returned
|
31
|
-
as response headers.
|
32
|
-
=end
|
33
|
-
def initialize status, message = nil, info = {}
|
34
|
-
self.path = Request.current.path
|
35
|
-
@status = status_code status
|
36
|
-
raise "Wrong status: #{status}" if 0 === @status
|
37
|
-
message ||= ''
|
38
|
-
@headers = {}
|
39
|
-
@to_rackful = {}
|
40
|
-
info.each do
|
41
|
-
|k, v|
|
42
|
-
if k.kind_of? Symbol then @to_rackful[k] = v
|
43
|
-
else @headers[k] = v end
|
44
|
-
end
|
45
|
-
@to_rackful = nil if @to_rackful.empty?
|
46
|
-
begin
|
47
|
-
REXML::Document.new \
|
48
|
-
'<?xml version="1.0" encoding="UTF-8" ?>' +
|
49
|
-
"<div>#{message}</div>"
|
50
|
-
rescue
|
51
|
-
message = Rack::Utils.escape_html(message)
|
52
|
-
end
|
53
|
-
super message
|
54
|
-
if 500 <= @status
|
55
|
-
errors = Request.current.env['rack.errors']
|
56
|
-
errors.puts self.inspect
|
57
|
-
errors.puts "Headers: #{@headers.inspect}"
|
58
|
-
errors.puts "Info: #{@to_rackful.inspect}"
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
def title
|
64
|
-
"#{status} #{HTTP_STATUS_CODES[status]}"
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
class XHTML < ::Rackful::XHTML
|
69
|
-
|
70
|
-
|
71
|
-
def header
|
72
|
-
super + <<EOS
|
73
|
-
<h1>HTTP/1.1 #{Rack::Utils.escape_html(resource.title)}</h1>
|
74
|
-
<div id="rackful_description">#{resource.message}</div>
|
75
|
-
EOS
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
def headers; self.resource.headers; end
|
80
|
-
|
81
|
-
|
82
|
-
end # class HTTPStatus::XHTML
|
83
|
-
|
84
|
-
|
85
|
-
#~ class JSON < ::Rackful::JSON
|
86
|
-
#~
|
87
|
-
#~
|
88
|
-
#~ def headers; self.resource.headers; end
|
89
|
-
#~
|
90
|
-
#~
|
91
|
-
#~ def each &block
|
92
|
-
#~ super( [ self.resource.message, self.resource.to_rackful ], &block )
|
93
|
-
#~ end
|
94
|
-
#~
|
95
|
-
#~
|
96
|
-
#~ end # class HTTPStatus::XHTML
|
97
|
-
|
98
|
-
|
99
|
-
add_serializer XHTML, 1.0
|
100
|
-
|
101
|
-
|
102
|
-
end # class Rackful::HTTPStatus
|
103
|
-
|
104
|
-
|
105
|
-
=begin markdown
|
106
|
-
@abstract Base class for HTTP status codes with only a simple text message, or
|
107
|
-
no message at all.
|
108
|
-
=end
|
109
|
-
class HTTPSimpleStatus < HTTPStatus
|
110
|
-
|
111
|
-
def initialize message = nil
|
112
|
-
/HTTP(\d\d\d)\w+\z/ === self.class.to_s
|
113
|
-
status = $1.to_i
|
114
|
-
super( status, message )
|
115
|
-
end
|
116
|
-
|
117
|
-
end
|
118
|
-
|
119
|
-
|
120
|
-
class HTTP201Created < HTTPStatus
|
121
|
-
|
122
|
-
def initialize locations
|
123
|
-
locations = [ locations ] unless locations.kind_of? Array
|
124
|
-
locations = locations.collect { |l| l.to_path }
|
125
|
-
rf = Request.current.resource_factory
|
126
|
-
if locations.size > 1
|
127
|
-
locations = locations.collect {
|
128
|
-
|l|
|
129
|
-
resource = rf[l]
|
130
|
-
{ :location => l }.merge( resource.default_headers )
|
131
|
-
rf.uncache l if rf.respond_to? :uncache
|
132
|
-
}
|
133
|
-
super(
|
134
|
-
201, 'New resources were created:', :locations => locations
|
135
|
-
)
|
136
|
-
else
|
137
|
-
location = locations[0]
|
138
|
-
resource = rf[location]
|
139
|
-
super(
|
140
|
-
201, 'A new resource was created:', {
|
141
|
-
:location => location,
|
142
|
-
'Location' => location
|
143
|
-
}.merge( resource.default_headers )
|
144
|
-
)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
end # class HTTP201Created
|
149
|
-
|
150
|
-
|
151
|
-
class HTTP202Accepted < HTTPStatus
|
152
|
-
|
153
|
-
def initialize location = nil
|
154
|
-
if location
|
155
|
-
super(
|
156
|
-
202, '', {
|
157
|
-
:'Job status location:' => Path.new(locations),
|
158
|
-
'Location' => locations
|
159
|
-
}
|
160
|
-
)
|
161
|
-
else
|
162
|
-
super 202
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
end # class HTTP202Accepted
|
167
|
-
|
168
|
-
|
169
|
-
class HTTP301MovedPermanently < HTTPStatus
|
170
|
-
|
171
|
-
def initialize location
|
172
|
-
super(
|
173
|
-
301, '', {
|
174
|
-
:'New location:' => Path.new(location),
|
175
|
-
'Location' => location
|
176
|
-
}
|
177
|
-
)
|
178
|
-
end
|
179
|
-
|
180
|
-
end
|
181
|
-
|
182
|
-
|
183
|
-
class HTTP303SeeOther < HTTPStatus
|
184
|
-
|
185
|
-
def initialize location
|
186
|
-
super(
|
187
|
-
303, '', {
|
188
|
-
:'See:' => Path.new(location),
|
189
|
-
'Location' => location
|
190
|
-
}
|
191
|
-
)
|
192
|
-
end
|
193
|
-
|
194
|
-
end
|
195
|
-
|
196
|
-
|
197
|
-
class HTTP304NotModified < HTTPStatus
|
198
|
-
|
199
|
-
def initialize
|
200
|
-
super( 304 )
|
201
|
-
end
|
202
|
-
|
203
|
-
end
|
204
|
-
|
205
|
-
|
206
|
-
class HTTP307TemporaryRedirect < HTTPStatus
|
207
|
-
|
208
|
-
def initialize location
|
209
|
-
super(
|
210
|
-
301, '', {
|
211
|
-
:'Current location:' => Path.new(location),
|
212
|
-
'Location' => location
|
213
|
-
}
|
214
|
-
)
|
215
|
-
end
|
216
|
-
|
217
|
-
end
|
218
|
-
|
219
|
-
|
220
|
-
class HTTP400BadRequest < HTTPSimpleStatus; end
|
221
|
-
|
222
|
-
class HTTP403Forbidden < HTTPSimpleStatus; end
|
223
|
-
|
224
|
-
class HTTP404NotFound < HTTPSimpleStatus; end
|
225
|
-
|
226
|
-
|
227
|
-
class HTTP405MethodNotAllowed < HTTPStatus
|
228
|
-
|
229
|
-
def initialize methods
|
230
|
-
super 405, '', 'Allow' => methods.join(', '),
|
231
|
-
:'Allowed methods:' => methods
|
232
|
-
end
|
233
|
-
|
234
|
-
end
|
235
|
-
|
236
|
-
|
237
|
-
class HTTP406NotAcceptable < HTTPStatus
|
238
|
-
|
239
|
-
def initialize content_types
|
240
|
-
super 406, '',
|
241
|
-
:'Available content-type(s):' => content_types
|
242
|
-
end
|
243
|
-
|
244
|
-
end
|
245
|
-
|
246
|
-
|
247
|
-
class HTTP409Conflict < HTTPSimpleStatus; end
|
248
|
-
|
249
|
-
class HTTP410Gone < HTTPSimpleStatus; end
|
250
|
-
|
251
|
-
class HTTP411LengthRequired < HTTPSimpleStatus; end
|
252
|
-
|
253
|
-
|
254
|
-
class HTTP412PreconditionFailed < HTTPStatus
|
255
|
-
|
256
|
-
def initialize header = nil
|
257
|
-
info =
|
258
|
-
if header
|
259
|
-
{ header.to_sym => Request.current.env[ 'HTTP_' + header.gsub('-', '_').upcase ] }
|
260
|
-
else
|
261
|
-
{}
|
262
|
-
end
|
263
|
-
super 412, 'Failed precondition:', info
|
264
|
-
end
|
265
|
-
|
266
|
-
end
|
267
|
-
|
268
|
-
|
269
|
-
class HTTP415UnsupportedMediaType < HTTPStatus
|
270
|
-
|
271
|
-
def initialize media_types
|
272
|
-
super 415, '',
|
273
|
-
:'Supported media-type(s):' => media_types
|
274
|
-
end
|
275
|
-
|
276
|
-
end
|
277
|
-
|
278
|
-
|
279
|
-
class HTTP422UnprocessableEntity < HTTPSimpleStatus; end
|
280
|
-
|
281
|
-
class HTTP501NotImplemented < HTTPSimpleStatus; end
|
282
|
-
|
283
|
-
class HTTP503ServiceUnavailable < HTTPSimpleStatus; end
|
284
|
-
|
285
|
-
end # module Rackful
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# Copyright ©2011-2012 Pieter van Beek <pieterb@sara.nl>
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
require 'rackful'
|
16
|
-
|
17
|
-
=begin markdown
|
18
|
-
Rack middleware that provides header spoofing.
|
19
|
-
|
20
|
-
If you use this middleware, then clients are allowed to spoof an HTTP header
|
21
|
-
by specifying a `_http_SOME_HEADER=...` request parameter, for example:
|
22
|
-
|
23
|
-
http://example.com/some_resource?_http_DEPTH=infinity
|
24
|
-
|
25
|
-
This can be useful if you want to specify certain request headers from within
|
26
|
-
a normal web browser.
|
27
|
-
@example Using this middleware
|
28
|
-
use Rackful::HeaderSpoofing
|
29
|
-
=end
|
30
|
-
class Rackful::HeaderSpoofing
|
31
|
-
|
32
|
-
def initialize app
|
33
|
-
@app = app
|
34
|
-
end
|
35
|
-
|
36
|
-
def call env
|
37
|
-
before_call env
|
38
|
-
r = @app.call env
|
39
|
-
after_call env
|
40
|
-
r
|
41
|
-
end
|
42
|
-
|
43
|
-
def before_call env
|
44
|
-
original_query_string = env['QUERY_STRING']
|
45
|
-
env['QUERY_STRING'] = original_query_string.
|
46
|
-
split('&', -1).
|
47
|
-
collect { |s| s.split('=', -1) }.
|
48
|
-
select {
|
49
|
-
|p|
|
50
|
-
if /\A_http_([a-z]+(?:[\-_][a-z]+)*)\z/i === p[0]
|
51
|
-
header_name = p.shift.gsub('-', '_').upcase[1..-1]
|
52
|
-
env[header_name] = p.join('=')
|
53
|
-
false
|
54
|
-
else
|
55
|
-
true
|
56
|
-
end
|
57
|
-
}.
|
58
|
-
collect { |p| p.join('=') }.
|
59
|
-
join('&')
|
60
|
-
if original_query_string != env['QUERY_STRING']
|
61
|
-
env['rackful.header_spoofing.query_string'] ||= original_query_string
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def after_call env
|
66
|
-
#if original_query_string = env['rackful.header_spoofing.query_string']
|
67
|
-
#env['rackful.header_spoofing.query_string'] = env['QUERY_STRING']
|
68
|
-
#env['QUERY_STRING'] = original_query_string
|
69
|
-
#end
|
70
|
-
end
|
71
|
-
|
72
|
-
end # Rackful::HeaderSpoofing
|
@@ -1,101 +0,0 @@
|
|
1
|
-
# Copyright ©2011-2012 Pieter van Beek <pieterb@sara.nl>
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
|
15
|
-
require 'rackful'
|
16
|
-
|
17
|
-
=begin markdown
|
18
|
-
Rack middleware that provides method spoofing.
|
19
|
-
|
20
|
-
If you use this middleware, then clients are allowed to spoof an HTTP method
|
21
|
-
by specifying a `_method=...` request parameter, for example:
|
22
|
-
|
23
|
-
http://example.com/some_resource?_method=DELETE
|
24
|
-
|
25
|
-
This can be useful if you want to perform `PUT` and `DELETE` requests from
|
26
|
-
within a browser, of when you want to perform a `GET` requests with (too)
|
27
|
-
many parameters, exceeding the maximum URI length in your client or server.
|
28
|
-
In that case, you can put the parameters in a `POST` body, like this:
|
29
|
-
|
30
|
-
POST /some_resource HTTP/1.1
|
31
|
-
Host: example.com
|
32
|
-
Content-Type: application/x-www-form-urlencoded
|
33
|
-
Content-Length: 123456789
|
34
|
-
|
35
|
-
param_1=hello¶m_2=world¶m_3=...
|
36
|
-
@example Using this middleware
|
37
|
-
use Rackful::MethodSpoofing
|
38
|
-
=end
|
39
|
-
class Rackful::MethodSpoofing
|
40
|
-
|
41
|
-
def initialize app
|
42
|
-
@app = app
|
43
|
-
end
|
44
|
-
|
45
|
-
def call env
|
46
|
-
before_call env
|
47
|
-
r = @app.call env
|
48
|
-
after_call env
|
49
|
-
r
|
50
|
-
end
|
51
|
-
|
52
|
-
def before_call env
|
53
|
-
return unless ['GET', 'POST'].include? env['REQUEST_METHOD']
|
54
|
-
original_query_string = env['QUERY_STRING']
|
55
|
-
new_method = nil
|
56
|
-
env['QUERY_STRING'] = original_query_string.
|
57
|
-
split('&', -1).
|
58
|
-
collect { |s| s.split('=', -1) }.
|
59
|
-
select {
|
60
|
-
|p|
|
61
|
-
if /_method/i === p[0] &&
|
62
|
-
/\A[A-Z]+\z/ === ( method = p[1..-1].join('=').upcase ) &&
|
63
|
-
! new_method
|
64
|
-
new_method = method
|
65
|
-
false
|
66
|
-
else
|
67
|
-
true
|
68
|
-
end
|
69
|
-
}.
|
70
|
-
collect { |p| p.join('=') }.
|
71
|
-
join('&')
|
72
|
-
if new_method
|
73
|
-
if 'GET' == new_method &&
|
74
|
-
'POST' == env['REQUEST_METHOD'] &&
|
75
|
-
'application/x-www-form-urlencoded' == env['CONTENT_TYPE']
|
76
|
-
unless env['QUERY_STRING'].empty
|
77
|
-
env['QUERY_STRING'] = env['QUERY_STRING'] + '&'
|
78
|
-
end
|
79
|
-
begin
|
80
|
-
env['QUERY_STRING'] = env['QUERY_STRING'] + env['rack.input'].read
|
81
|
-
env['rack.input'].rewind
|
82
|
-
end
|
83
|
-
env['rackful.method_spoofing.input'] = env['rack.input']
|
84
|
-
env.delete 'rack.input'
|
85
|
-
env.delete 'CONTENT_TYPE'
|
86
|
-
env.delete 'CONTENT_LENGTH' if env.key? 'CONTENT_LENGTH'
|
87
|
-
end
|
88
|
-
env['rackful.method_spoofing.QUERY_STRING'] ||= original_query_string
|
89
|
-
env['rackful.method_spoofing.REQUEST_METHOD'] = env['REQUEST_METHOD']
|
90
|
-
env['REQUEST_METHOD'] = new_method
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def after_call env
|
95
|
-
if env.key? 'rackful.method_spoofing.input'
|
96
|
-
env['rack.input'] = env['rackful.method_spoofing.input']
|
97
|
-
env.delete 'rackful.method_spoofing.input'
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
end # Rackful::MethodSpoofing
|