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