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/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
|