rackful 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +14 -2
- data/example/config.ru +19 -13
- data/example/config2.ru +41 -0
- data/lib/rackful/header_spoofing.rb +39 -32
- data/lib/rackful/method_spoofing.rb +56 -58
- data/lib/rackful/relative_location.rb +35 -21
- data/lib/rackful.rb +6 -934
- data/lib/rackful_http_status.rb +288 -0
- data/lib/rackful_path.rb +112 -0
- data/lib/rackful_request.rb +268 -0
- data/lib/rackful_resource.rb +454 -0
- data/lib/rackful_serializer.rb +318 -0
- data/lib/rackful_server.rb +124 -0
- data/rackful.gemspec +3 -1
- metadata +49 -52
data/README.md
CHANGED
@@ -1,2 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
Rackful
|
2
|
+
=======
|
3
|
+
|
4
|
+
Library for creating Rackful web services
|
5
|
+
|
6
|
+
The latest documentation is always available
|
7
|
+
[here, as GitHub pages](http://pieterb.github.com/Rackful/).
|
8
|
+
|
9
|
+
Licensing
|
10
|
+
---------
|
11
|
+
Copyright ©2011-2012 Pieter van Beek <pieterb@sara.nl>
|
12
|
+
|
13
|
+
Licensed under the Apache License 2.0. You should have received a copy of the
|
14
|
+
license as part of this distribution.
|
data/example/config.ru
CHANGED
@@ -1,26 +1,28 @@
|
|
1
|
-
require
|
1
|
+
require 'rackful'
|
2
|
+
require 'rackful/header_spoofing'
|
3
|
+
require 'rackful/method_spoofing'
|
4
|
+
require 'rackful/relative_location'
|
2
5
|
require 'digest/md5'
|
3
6
|
|
4
7
|
# The class of the object we're going to serve:
|
5
8
|
class Root
|
6
9
|
include Rackful::Resource
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def do_GET request, response
|
12
|
-
response['Content-Type'] = 'text/plain'
|
13
|
-
response.write @content
|
10
|
+
attr_reader :to_rackful
|
11
|
+
def initialize
|
12
|
+
self.path = '/'
|
13
|
+
@to_rackful = 'Hello world!'
|
14
14
|
end
|
15
15
|
def do_PUT request, response
|
16
|
-
@
|
17
|
-
response.status = status_code :no_content
|
16
|
+
@to_rackful = request.body.read.encode( Encoding::UTF_8 )
|
18
17
|
end
|
19
|
-
def
|
20
|
-
'"' + Digest::MD5.new.update(
|
18
|
+
def get_etag
|
19
|
+
'"' + Digest::MD5.new.update(to_rackful).to_s + '"'
|
21
20
|
end
|
21
|
+
add_serializer Rackful::XHTML, 1.0
|
22
|
+
add_serializer Rackful::JSON, 1.0
|
23
|
+
add_media_type 'text/plain'
|
22
24
|
end
|
23
|
-
$root_resource = Root.new
|
25
|
+
$root_resource = Root.new
|
24
26
|
|
25
27
|
# Rackful::Server needs a resource factory which can map URIs to resource objects:
|
26
28
|
class ResourceFactory
|
@@ -32,4 +34,8 @@ class ResourceFactory
|
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
37
|
+
use Rackful::MethodSpoofing
|
38
|
+
use Rackful::HeaderSpoofing
|
39
|
+
use Rackful::RelativeLocation
|
40
|
+
|
35
41
|
run Rackful::Server.new ResourceFactory.new
|
data/example/config2.ru
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rackful/header_spoofing'
|
3
|
+
require 'rackful/method_spoofing'
|
4
|
+
require 'rackful/relative_location'
|
5
|
+
|
6
|
+
# The class of the object we're going to serve:
|
7
|
+
class Root
|
8
|
+
include Rackful::Resource
|
9
|
+
def initialize *args
|
10
|
+
super
|
11
|
+
@content = 'Hello world!'
|
12
|
+
end
|
13
|
+
def do_GET request, response
|
14
|
+
response['Content-Type'] = 'text/plain'
|
15
|
+
response.write @content
|
16
|
+
end
|
17
|
+
def do_PUT request, response
|
18
|
+
@content = request.body.read
|
19
|
+
response.status = status_code :no_content
|
20
|
+
end
|
21
|
+
def etag
|
22
|
+
'"' + Digest::MD5.new.update(@content).to_s + '"'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
$root_resource = Root.new '/'
|
26
|
+
|
27
|
+
# Rackful::Server needs a resource factory which can map URIs to resource objects:
|
28
|
+
class ResourceFactory
|
29
|
+
def [] uri
|
30
|
+
case uri
|
31
|
+
when '/'; $root_resource
|
32
|
+
else; nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
use Rackful::MethodSpoofing
|
38
|
+
use Rackful::HeaderSpoofing
|
39
|
+
use Rackful::RelativeLocation
|
40
|
+
|
41
|
+
run Rackful::Server.new ResourceFactory.new
|
@@ -12,9 +12,9 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
|
15
|
+
require 'rackful'
|
16
16
|
|
17
|
-
=begin
|
17
|
+
=begin markdown
|
18
18
|
Rack middleware that provides header spoofing.
|
19
19
|
|
20
20
|
If you use this middleware, then clients are allowed to spoof an HTTP header
|
@@ -26,41 +26,48 @@ This can be useful if you want to specify certain request headers from within
|
|
26
26
|
a normal web browser.
|
27
27
|
@example Using this middleware
|
28
28
|
use Rackful::HeaderSpoofing
|
29
|
+
@since 0.0.1
|
29
30
|
=end
|
30
|
-
|
31
|
+
class Rackful::HeaderSpoofing
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
def initialize app
|
34
|
+
@app = app
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
def call env
|
38
|
+
before_call env
|
39
|
+
r = @app.call env
|
40
|
+
after_call env
|
41
|
+
r
|
42
|
+
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
56
|
-
}.
|
57
|
-
collect { |p| p.join('=') }.
|
58
|
-
join('&')
|
59
|
-
if original_query_string != env['QUERY_STRING']
|
60
|
-
env['rackful.header_spoofing.query_string'] ||= original_query_string
|
44
|
+
def before_call env
|
45
|
+
original_query_string = env['QUERY_STRING']
|
46
|
+
env['QUERY_STRING'] = original_query_string.
|
47
|
+
split('&', -1).
|
48
|
+
collect { |s| s.split('=', -1) }.
|
49
|
+
select {
|
50
|
+
|p|
|
51
|
+
if /\A_http_([a-z]+(?:[\-_][a-z]+)*)\z/i === p[0]
|
52
|
+
header_name = p.shift.gsub('-', '_').upcase[1..-1]
|
53
|
+
env[header_name] = p.join('=')
|
54
|
+
false
|
55
|
+
else
|
56
|
+
true
|
61
57
|
end
|
62
|
-
|
58
|
+
}.
|
59
|
+
collect { |p| p.join('=') }.
|
60
|
+
join('&')
|
61
|
+
if original_query_string != env['QUERY_STRING']
|
62
|
+
env['rackful.header_spoofing.query_string'] ||= original_query_string
|
63
|
+
end
|
64
|
+
end
|
63
65
|
|
66
|
+
def after_call env
|
67
|
+
if original_query_string = env['rackful.header_spoofing.query_string']
|
68
|
+
env['rackful.header_spoofing.query_string'] = env['QUERY_STRING']
|
69
|
+
env['QUERY_STRING'] = original_query_string
|
64
70
|
end
|
71
|
+
end
|
65
72
|
|
66
|
-
end
|
73
|
+
end # Rackful::HeaderSpoofing
|
@@ -14,9 +14,7 @@
|
|
14
14
|
|
15
15
|
require 'rackful'
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
=begin
|
17
|
+
=begin markdown
|
20
18
|
Rack middleware that provides method spoofing.
|
21
19
|
|
22
20
|
If you use this middleware, then clients are allowed to spoof an HTTP method
|
@@ -37,68 +35,68 @@ In that case, you can put the parameters in a `POST` body, like this:
|
|
37
35
|
param_1=hello¶m_2=world¶m_3=...
|
38
36
|
@example Using this middleware
|
39
37
|
use Rackful::MethodSpoofing
|
38
|
+
@since 0.0.1
|
40
39
|
=end
|
41
|
-
|
40
|
+
class Rackful::MethodSpoofing
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
def initialize app
|
43
|
+
@app = app
|
44
|
+
end
|
46
45
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
46
|
+
def call env
|
47
|
+
before_call env
|
48
|
+
r = @app.call env
|
49
|
+
after_call env
|
50
|
+
r
|
51
|
+
end
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
-
}.
|
71
|
-
collect { |p| p.join('=') }.
|
72
|
-
join('&')
|
73
|
-
if new_method
|
74
|
-
if 'GET' == new_method &&
|
75
|
-
'POST' == env['REQUEST_METHOD'] &&
|
76
|
-
'application/x-www-form-urlencoded' == env['CONTENT_TYPE']
|
77
|
-
unless env['QUERY_STRING'].empty
|
78
|
-
env['QUERY_STRING'] = env['QUERY_STRING'] + '&'
|
79
|
-
end
|
80
|
-
begin
|
81
|
-
env['QUERY_STRING'] = env['QUERY_STRING'] + env['rack.input'].read
|
82
|
-
env['rack.input'].rewind
|
83
|
-
end
|
84
|
-
env['rackful.method_spoofing.input'] = env['rack.input']
|
85
|
-
env.delete 'rack.input'
|
86
|
-
env.delete 'CONTENT_TYPE'
|
87
|
-
env.delete 'CONTENT_LENGTH' if env.key? 'CONTENT_LENGTH'
|
88
|
-
end
|
89
|
-
env['rackful.method_spoofing.QUERY_STRING'] ||= original_query_string
|
90
|
-
env['rackful.method_spoofing.REQUEST_METHOD'] = env['REQUEST_METHOD']
|
91
|
-
env['REQUEST_METHOD'] = new_method
|
53
|
+
def before_call env
|
54
|
+
return unless ['GET', 'POST'].include? env['REQUEST_METHOD']
|
55
|
+
original_query_string = env['QUERY_STRING']
|
56
|
+
new_method = nil
|
57
|
+
env['QUERY_STRING'] = original_query_string.
|
58
|
+
split('&', -1).
|
59
|
+
collect { |s| s.split('=', -1) }.
|
60
|
+
select {
|
61
|
+
|p|
|
62
|
+
if /_method/i === p[0] &&
|
63
|
+
/\A[A-Z]+\z/ === ( method = p[1..-1].join('=').upcase ) &&
|
64
|
+
! new_method
|
65
|
+
new_method = method
|
66
|
+
false
|
67
|
+
else
|
68
|
+
true
|
92
69
|
end
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
env
|
70
|
+
}.
|
71
|
+
collect { |p| p.join('=') }.
|
72
|
+
join('&')
|
73
|
+
if new_method
|
74
|
+
if 'GET' == new_method &&
|
75
|
+
'POST' == env['REQUEST_METHOD'] &&
|
76
|
+
'application/x-www-form-urlencoded' == env['CONTENT_TYPE']
|
77
|
+
unless env['QUERY_STRING'].empty
|
78
|
+
env['QUERY_STRING'] = env['QUERY_STRING'] + '&'
|
79
|
+
end
|
80
|
+
begin
|
81
|
+
env['QUERY_STRING'] = env['QUERY_STRING'] + env['rack.input'].read
|
82
|
+
env['rack.input'].rewind
|
99
83
|
end
|
84
|
+
env['rackful.method_spoofing.input'] = env['rack.input']
|
85
|
+
env.delete 'rack.input'
|
86
|
+
env.delete 'CONTENT_TYPE'
|
87
|
+
env.delete 'CONTENT_LENGTH' if env.key? 'CONTENT_LENGTH'
|
100
88
|
end
|
101
|
-
|
89
|
+
env['rackful.method_spoofing.QUERY_STRING'] ||= original_query_string
|
90
|
+
env['rackful.method_spoofing.REQUEST_METHOD'] = env['REQUEST_METHOD']
|
91
|
+
env['REQUEST_METHOD'] = new_method
|
102
92
|
end
|
93
|
+
end
|
103
94
|
|
95
|
+
def after_call env
|
96
|
+
if env.key? 'rackful.method_spoofing.input'
|
97
|
+
env['rack.input'] = env['rackful.method_spoofing.input']
|
98
|
+
env.delete 'rackful.method_spoofing.input'
|
99
|
+
end
|
104
100
|
end
|
101
|
+
|
102
|
+
end # Rackful::MethodSpoofing
|
@@ -12,11 +12,9 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
|
15
|
+
require 'rackful'
|
16
16
|
require 'rack/request'
|
17
17
|
|
18
|
-
module Rackful
|
19
|
-
|
20
18
|
=begin markdown
|
21
19
|
Rack middleware, inspired by {Rack::RelativeRedirect}.
|
22
20
|
|
@@ -31,28 +29,44 @@ Differences with {Rack::RelativeRedirect}:
|
|
31
29
|
- uses Rack::Request::base_url for creating absolute URIs.
|
32
30
|
- the `Location:` header, if present, is always rectified, independent of the
|
33
31
|
HTTP status code.
|
32
|
+
@since 0.0.1
|
34
33
|
=end
|
35
|
-
class RelativeLocation
|
34
|
+
class Rackful::RelativeLocation
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
def initialize(app)
|
37
|
+
@app = app
|
38
|
+
end
|
40
39
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
res[1]['Location'] = request.base_url + location
|
40
|
+
def call_old(env)
|
41
|
+
res = @app.call(env)
|
42
|
+
if ( location = res[1]['Location'] ) and
|
43
|
+
! %r{\A[a-z]+://}.match(location)
|
44
|
+
request = Rack::Request.new env
|
45
|
+
unless '/' == location[0, 1]
|
46
|
+
path = ( res[1]['Content-Location'] || request.path ).dup
|
47
|
+
path[ %r{[^/]*\z} ] = ''
|
48
|
+
location = File.expand_path( location, path )
|
52
49
|
end
|
53
|
-
|
50
|
+
if '/' == location[0, 1]
|
51
|
+
location = request.base_url + location
|
52
|
+
end
|
53
|
+
res[1]['Location'] = location
|
54
54
|
end
|
55
|
+
res
|
56
|
+
end
|
55
57
|
|
56
|
-
|
58
|
+
def call(env)
|
59
|
+
res = @app.call(env)
|
60
|
+
request = nil
|
61
|
+
['Location', 'Content-Location'].each do
|
62
|
+
|header|
|
63
|
+
if ( location = res[1][header] ) and
|
64
|
+
'/' == location[0,1]
|
65
|
+
request ||= Rack::Request.new env
|
66
|
+
res[1][header] = request.base_url + location
|
67
|
+
end
|
68
|
+
end
|
69
|
+
res
|
70
|
+
end
|
57
71
|
|
58
|
-
end # Rackful
|
72
|
+
end # Rackful::RelativeLocation
|