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/server.rb
CHANGED
@@ -1,81 +1,68 @@
|
|
1
|
-
# Required for parsing:
|
2
|
-
#require 'forwardable' # Used to be for ResourceFactoryWrapper.
|
3
|
-
|
4
|
-
# Required for running:
|
5
|
-
|
6
1
|
module Rackful
|
7
2
|
|
8
|
-
|
9
|
-
Rack compliant server class for implementing RESTful web services.
|
10
|
-
=end
|
3
|
+
# Rack compliant server class for implementing RESTful web services.
|
11
4
|
class Server
|
12
5
|
|
13
6
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
a
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
{Resource#empty? empty resource}.
|
35
|
-
@return [#[]]
|
36
|
-
@see #initialize
|
37
|
-
=end
|
38
|
-
attr_reader :resource_factory
|
7
|
+
# Constructor.
|
8
|
+
#
|
9
|
+
# This generic server class has no knowledge, and makes no presumptions,
|
10
|
+
# about your URI namespace. It depends on the code block you provide here
|
11
|
+
# to produce the {Resource} object which lives at a certain URI.
|
12
|
+
# This block will be called with a {URI::Generic#normalize! normalized}
|
13
|
+
# URI, and must return a {Resource}, or `nil` if there’s no
|
14
|
+
# resource at the given URI.
|
15
|
+
#
|
16
|
+
# If there’s no resource at the given URI, but you’d still like to respond to
|
17
|
+
# `POST` or `PUT` requests to this URI, you can return an
|
18
|
+
# {Resource#empty? empty resource}.
|
19
|
+
#
|
20
|
+
# The provided code block must be thread-safe and reentrant.
|
21
|
+
# @yieldparam uri [URI::Generic] The {URI::Generic::normalize! normalized}
|
22
|
+
# URI of the requested resource.
|
23
|
+
# @yieldreturn [Resource] A (possibly {Resource#empty? empty}) resource, or nil.
|
24
|
+
def initialize( &resource_registry )
|
25
|
+
@resource_registry = resource_registry
|
26
|
+
end
|
39
27
|
|
40
28
|
|
41
|
-
|
42
|
-
@param
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
29
|
+
# Calls the code block passed to the {#initialize constructor}.
|
30
|
+
# @param uri [URI::HTTP, String]
|
31
|
+
# @return [Resource]
|
32
|
+
# @raise [HTTP404NotFound]
|
33
|
+
def resource_at(uri)
|
34
|
+
uri = URI(uri) unless uri.kind_of?( URI::Generic )
|
35
|
+
retval = @resource_registry.call( uri.normalize )
|
36
|
+
raise HTTP404NotFound unless retval
|
37
|
+
retval
|
47
38
|
end
|
48
39
|
|
49
40
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
@
|
56
|
-
|
57
|
-
def call(
|
58
|
-
|
59
|
-
retval = dup.call! p_env
|
60
|
-
#$stderr.puts( p_env.inspect )
|
61
|
-
retval
|
41
|
+
# As required by the Rack specification.
|
42
|
+
#
|
43
|
+
# For thread safety, this method clones `self`, which handles the request in
|
44
|
+
# {#call!}.
|
45
|
+
# For reentrancy, the clone is stored in the environment.
|
46
|
+
# @param env [{String => Mixed}]
|
47
|
+
# @return [(status_code, response_headers, response_body)]
|
48
|
+
def call( env )
|
49
|
+
( env['rackful.server'] ||= self.dup ).call!( env )
|
62
50
|
end
|
63
51
|
|
64
52
|
|
65
|
-
|
66
|
-
@return [
|
67
|
-
|
68
|
-
|
69
|
-
request = Request.new( self.resource_factory, p_env )
|
70
|
-
# See also Request::current():
|
71
|
-
Thread.current[:rackful_request] = request
|
53
|
+
# @see #call
|
54
|
+
# @return [(status_code, response_headers, response_body)]
|
55
|
+
def call!( env )
|
56
|
+
request = Request.new( env )
|
72
57
|
response = Rack::Response.new
|
73
58
|
begin
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
59
|
+
resource = resource_at( request.url )
|
60
|
+
request.canonical_uri = resource.uri
|
61
|
+
if request.url != request.canonical_uri.to_s
|
62
|
+
if %w{HEAD GET}.include?( request.request_method )
|
63
|
+
raise HTTP301MovedPermanently, request.canonical_uri
|
64
|
+
end
|
65
|
+
response.header['Content-Location'] = request.canonical_uri.to_s
|
79
66
|
end
|
80
67
|
request.assert_if_headers resource
|
81
68
|
if %w{HEAD GET OPTIONS PUT DELETE}.include?( request.request_method )
|
@@ -84,32 +71,35 @@ For thread safety, this method clones `self`, which handles the request in
|
|
84
71
|
resource.http_method request, response
|
85
72
|
end
|
86
73
|
rescue HTTPStatus => e
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# The next line fixes a small peculiarity in RFC2616: the response body of
|
94
|
-
# a `HEAD` request _must_ be empty, even for responses outside 2xx.
|
95
|
-
if request.head?
|
96
|
-
response.body = []
|
74
|
+
serializer = e.serializer(request)
|
75
|
+
response = Rack::Response.new
|
76
|
+
response['Content-Type'] = serializer.content_type
|
77
|
+
response.status = e.status
|
78
|
+
if serializer.respond_to? :headers
|
79
|
+
response.headers.merge!( serializer.headers )
|
97
80
|
end
|
81
|
+
response.body = serializer
|
98
82
|
end
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
83
|
+
# The next line fixes a small peculiarity in RFC2616: the response body of
|
84
|
+
# a `HEAD` request _must_ be empty, even for responses outside 2xx.
|
85
|
+
if request.head?
|
86
|
+
response.body = []
|
87
|
+
end
|
88
|
+
begin
|
89
|
+
if 201 == response.status &&
|
90
|
+
( location = response['Location'] ) &&
|
91
|
+
( new_resource = resource_at( location ) ) &&
|
92
|
+
! new_resource.empty? \
|
93
|
+
or ( (200...300) === response.status ||
|
94
|
+
304 == response.status ) &&
|
95
|
+
! response['Location'] &&
|
96
|
+
( new_resource = resource_at( request.canonical_uri ) ) &&
|
97
|
+
! new_resource.empty?
|
98
|
+
response.headers.merge! new_resource.default_headers
|
99
|
+
end
|
100
|
+
rescue HTTP404NotFound => e
|
109
101
|
end
|
110
|
-
|
111
|
-
#~ $stderr.puts r.inspect
|
112
|
-
r
|
102
|
+
response.finish
|
113
103
|
end
|
114
104
|
|
115
105
|
|
data/lib/rackful/uri.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
# Extension and monkeypatch of Ruby’s StdLib URI::Generic class.
|
2
|
+
class URI::Generic
|
3
|
+
|
4
|
+
unless method_defined? :uri_generic_normalize!
|
5
|
+
# Copy of the original StdLib
|
6
|
+
# [URI::Generic::normalize!](http://ruby-doc.org/stdlib/libdoc/uri/rdoc/URI/Generic.html#method-i-normalize-21)
|
7
|
+
# method.
|
8
|
+
alias_method :uri_generic_normalize!, :normalize!
|
9
|
+
end
|
10
|
+
|
11
|
+
unless method_defined? :uri_generic_normalize
|
12
|
+
# Copy of the original StdLib
|
13
|
+
# [URI::Generic::normalize!](http://ruby-doc.org/stdlib/libdoc/uri/rdoc/URI/Generic.html#method-i-normalize-21)
|
14
|
+
# method.
|
15
|
+
alias_method :uri_generic_normalize, :normalize
|
16
|
+
end
|
17
|
+
|
18
|
+
# (see #normalize!)
|
19
|
+
# @return [URI::Generic] a normalized copy of `self`.
|
20
|
+
def normalize
|
21
|
+
r = self.dup
|
22
|
+
r.normalize!
|
23
|
+
r
|
24
|
+
end
|
25
|
+
|
26
|
+
# Monkeypatch of [Ruby’s StdLib implementation](http://ruby-doc.org/stdlib/libdoc/uri/rdoc/URI/Generic.html#method-i-normalize-21).
|
27
|
+
# In addition to the default implementation, this implementation ensures that
|
28
|
+
#
|
29
|
+
# 1. _no_ unreserved characters are pct-encoded, and
|
30
|
+
# 2. all non-unreserved characters _are_ pct-encoded.
|
31
|
+
#
|
32
|
+
# Check [RFC3986](http://tools.ietf.org/html/rfc3986) syntax:
|
33
|
+
#
|
34
|
+
# abempty = *( "/" *(unreserved / pct-encoded / sub-delims / ":" / "@") )
|
35
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
36
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
37
|
+
# @return [self, nil] `self` if the URI was modified, or `nil` of the uri was
|
38
|
+
# already in normal form.
|
39
|
+
def normalize!
|
40
|
+
#unless %r{\A(?:/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*\z}i === self.path
|
41
|
+
# raise TypeError, "Can’t convert String #{self.path.inspect} to Rackful::Path"
|
42
|
+
#end
|
43
|
+
self.uri_generic_normalize!
|
44
|
+
path = '/' + self.segments( Encoding::BINARY ).collect do |segment|
|
45
|
+
segment.gsub(/([^a-zA-Z0-9\-._~]+)/n) {
|
46
|
+
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
47
|
+
}
|
48
|
+
end.join('/')
|
49
|
+
if path == self.path
|
50
|
+
nil
|
51
|
+
else
|
52
|
+
self.path = path
|
53
|
+
self
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# @return [Path] a copy of `self`, with a trailing slash.
|
59
|
+
def slashify
|
60
|
+
r = self.dup
|
61
|
+
r.slashify!
|
62
|
+
r
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Adds a trailing slash to `self` if necessary.
|
67
|
+
# @return [self]
|
68
|
+
def slashify!
|
69
|
+
if '/' != self.path[-1,1]
|
70
|
+
self.path += '/'
|
71
|
+
self
|
72
|
+
else
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# @return [Path] a copy of `self` without trailing slashes.
|
79
|
+
def unslashify
|
80
|
+
r = self.dup
|
81
|
+
r.unslashify!
|
82
|
+
r
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Removes trailing slashes from `self`.
|
87
|
+
# @return [self]
|
88
|
+
def unslashify!
|
89
|
+
path = self.path.sub( %r{/+\z}, '' )
|
90
|
+
if path == self.path
|
91
|
+
nil
|
92
|
+
else
|
93
|
+
self.path = path
|
94
|
+
self
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# @return [Array<String>] Unencoded segments
|
100
|
+
def segments( encoding = Encoding::UTF_8 )
|
101
|
+
r = self.path.split(%r{/+}).collect do |s|
|
102
|
+
Rack::Utils.unescape( s, encoding )
|
103
|
+
end
|
104
|
+
r.shift
|
105
|
+
r
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# Turns a relative URI (starting with `/`) into a relative path (starting with `./` or `../`)
|
110
|
+
# @param base_path [Path]
|
111
|
+
# @return [String] a relative URI
|
112
|
+
# @deprecated
|
113
|
+
def relative_deprecated base_path
|
114
|
+
case self
|
115
|
+
when base_path
|
116
|
+
# RFC2396, Section 4.2
|
117
|
+
return ''
|
118
|
+
when %r{(?:\A|/)\.\.?(?:/|\z)}
|
119
|
+
# self has abnormal absolute path,
|
120
|
+
# like "/./", "/../", "/x/../", ...
|
121
|
+
return self.dup
|
122
|
+
end
|
123
|
+
|
124
|
+
src_path = base_path.scan(%r{(?:\A|[^/]+)/})
|
125
|
+
dst_path = self.scan(%r{(?:\A|[^/]+)/?})
|
126
|
+
|
127
|
+
# discard same parts
|
128
|
+
while !dst_path.empty? && dst_path.first == src_path.first
|
129
|
+
src_path.shift
|
130
|
+
dst_path.shift
|
131
|
+
end
|
132
|
+
|
133
|
+
tmp = dst_path.join
|
134
|
+
|
135
|
+
# calculate
|
136
|
+
if src_path.empty?
|
137
|
+
if tmp.empty?
|
138
|
+
return './'
|
139
|
+
elsif dst_path.first.include?(':') # (see RFC2396 Section 5)
|
140
|
+
return './' + tmp
|
141
|
+
else
|
142
|
+
return tmp
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
return '../' * src_path.size + tmp
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
end # class URI::Generic
|
data/mkdoc.sh
CHANGED
data/rackful.gemspec
CHANGED
@@ -2,14 +2,14 @@ Gem::Specification.new do |s|
|
|
2
2
|
|
3
3
|
# Required properties:
|
4
4
|
s.name = 'rackful'
|
5
|
-
s.version = '0.
|
5
|
+
s.version = '0.2.0'
|
6
6
|
s.summary = "Library for building ReSTful web services with Rack"
|
7
7
|
s.description = <<EOS
|
8
8
|
Rackful provides a minimal interface for developing ReSTful web services with
|
9
|
-
Rack and Ruby. Instead of writing HTTP method handlers, you
|
9
|
+
Rack and Ruby. Instead of writing HTTP method handlers, you’ll implement
|
10
10
|
resource objects, which expose their state at URLs.
|
11
11
|
|
12
|
-
This version is not backward compatible with v0.
|
12
|
+
This version is not backward compatible with v0.1.x.
|
13
13
|
EOS
|
14
14
|
s.files = Dir[ '{*.md,example/*.ru,lib/**/*.rb}' ] +
|
15
15
|
%w( rackful.gemspec mkdoc.sh )
|
@@ -18,8 +18,9 @@ EOS
|
|
18
18
|
s.author = 'Pieter van Beek'
|
19
19
|
s.email = 'rackful@djinnit.com'
|
20
20
|
s.license = 'Apache License 2.0'
|
21
|
-
s.homepage = 'http://
|
21
|
+
s.homepage = 'http://github.com/pieterb/Rackful'
|
22
22
|
|
23
|
-
s.add_runtime_dependency 'rack',
|
23
|
+
s.add_runtime_dependency 'rack', '~> 1.5'
|
24
|
+
s.add_runtime_dependency 'nokogiri', '~> 1.6'
|
24
25
|
|
25
26
|
end
|
metadata
CHANGED
@@ -1,87 +1,95 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: rackful
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 0.1.4
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
6
5
|
platform: ruby
|
7
|
-
authors:
|
6
|
+
authors:
|
8
7
|
- Pieter van Beek
|
9
|
-
autorequire:
|
8
|
+
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- !ruby/object:Gem::Dependency
|
11
|
+
date: 2014-02-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
16
14
|
name: rack
|
15
|
+
version_requirements: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
requirement: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '1.5'
|
25
|
+
prerelease: false
|
26
|
+
type: :runtime
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '1.6'
|
17
39
|
prerelease: false
|
18
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
-
none: false
|
20
|
-
requirements:
|
21
|
-
- - ">="
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: "1.4"
|
24
40
|
type: :runtime
|
25
|
-
version_requirements: *id001
|
26
41
|
description: |
|
27
42
|
Rackful provides a minimal interface for developing ReSTful web services with
|
28
|
-
Rack and Ruby. Instead of writing HTTP method handlers, you
|
43
|
+
Rack and Ruby. Instead of writing HTTP method handlers, you’ll implement
|
29
44
|
resource objects, which expose their state at URLs.
|
30
|
-
|
31
|
-
This version is not backward compatible with v0.0.x.
|
32
45
|
|
46
|
+
This version is not backward compatible with v0.1.x.
|
33
47
|
email: rackful@djinnit.com
|
34
48
|
executables: []
|
35
|
-
|
36
49
|
extensions: []
|
37
|
-
|
38
50
|
extra_rdoc_files: []
|
39
|
-
|
40
|
-
|
51
|
+
files:
|
52
|
+
- CHANGES.md
|
53
|
+
- LICENSE.md
|
41
54
|
- RACKFUL.md
|
42
55
|
- README.md
|
43
|
-
- LICENSE.md
|
44
|
-
- CHANGES.md
|
45
56
|
- example/config.ru
|
57
|
+
- lib/rackful.rb
|
58
|
+
- lib/rackful/httpstatus.rb
|
59
|
+
- lib/rackful/middleware.rb
|
60
|
+
- lib/rackful/middleware/headerspoofing.rb
|
61
|
+
- lib/rackful/middleware/methodoverride.rb
|
62
|
+
- lib/rackful/parser.rb
|
63
|
+
- lib/rackful/request.rb
|
46
64
|
- lib/rackful/resource.rb
|
47
|
-
- lib/rackful/path.rb
|
48
|
-
- lib/rackful/http_status.rb
|
49
65
|
- lib/rackful/serializer.rb
|
50
66
|
- lib/rackful/server.rb
|
51
|
-
- lib/rackful/
|
52
|
-
- lib/rackful/middleware.rb
|
53
|
-
- lib/rackful/middleware/method_spoofing.rb
|
54
|
-
- lib/rackful/middleware/header_spoofing.rb
|
55
|
-
- lib/rackful/middleware/relative_location.rb
|
56
|
-
- lib/rackful.rb
|
57
|
-
- rackful.gemspec
|
67
|
+
- lib/rackful/uri.rb
|
58
68
|
- mkdoc.sh
|
59
|
-
|
60
|
-
|
69
|
+
- rackful.gemspec
|
70
|
+
homepage: http://github.com/pieterb/Rackful
|
71
|
+
licenses:
|
61
72
|
- Apache License 2.0
|
62
|
-
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
63
75
|
rdoc_options: []
|
64
|
-
|
65
|
-
require_paths:
|
76
|
+
require_paths:
|
66
77
|
- lib
|
67
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
version: "0"
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
79
88
|
requirements: []
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
specification_version: 3
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.2.0
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
85
93
|
summary: Library for building ReSTful web services with Rack
|
86
94
|
test_files: []
|
87
|
-
|
95
|
+
has_rdoc:
|