-
Notifications
You must be signed in to change notification settings - Fork 33
/
service.rb
163 lines (140 loc) · 4.99 KB
/
service.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
module CloudKit
# A CloudKit Service is Rack middleware providing a REST/HTTP 1.1 interface to
# a Store. Its primary purpose is to initialize and adapt a Store for use in a
# Rack middleware stack.
#
# ==Examples
#
# A rackup file exposing _items_ and _things_ as REST collections:
# require 'cloudkit'
# expose :items, :things
#
# The same as above, adding OpenID and OAuth/Discovery:
# require 'cloudkit'
# contain :items, :things
#
# An explicit setup, without using the Rack::Builder shortcuts:
# require 'cloudkit'
# use Rack::Session::Pool
# use CloudKit::OAuthFilter
# use CloudKit::OpenIDFilter
# use CloudKit::Service, :collections => [:items, :things]
# run lambda{|env| [200, {'Content-Type' => 'text/html'}, ['Hello']]}
#
# For more examples, including the use of different storage implementations,
# see the Table of Contents in the examples directory.
class Service
include ResponseHelpers
@@lock = Mutex.new
attr_reader :store
def initialize(app, options)
@app = app
@collections = options[:collections]
end
def call(env)
@@lock.synchronize do
@store = Store.new(:collections => @collections)
end unless @store
request = Request.new(env)
unless bypass?(request)
return auth_config_error if (request.using_auth? && auth_missing?(request))
return not_implemented unless @store.implements?(request.request_method)
send(request.request_method.downcase, request) #rescue internal_server_error.to_rack
else
@app.call(env)
end
end
protected
def get(request)
response = @store.get(
request.uri,
{}.filter_merge!(
:remote_user => request.current_user,
:offset => request['offset'],
:limit => request['limit'],
:search => request['search']))
inject_link_headers(request, response)
response.to_rack
end
def post(request)
if tunnel_methods.include?(request['_method'].try(:upcase))
return send(request['_method'].downcase, request)
end
response = @store.post(
request.uri,
{:json => request.json}.filter_merge!(
:remote_user => request.current_user))
update_location_header(request, response)
response.to_rack
end
def put(request)
response = @store.put(
request.uri,
{:json => request.json}.filter_merge!(
:remote_user => request.current_user,
:etag => request.if_match))
update_location_header(request, response)
response.to_rack
end
def delete(request)
@store.delete(
request.uri,
{}.filter_merge!(
:remote_user => request.current_user,
:etag => request.if_match)).to_rack
end
def head(request)
response = @store.head(
request.uri,
{}.filter_merge!(
:remote_user => request.current_user,
:offset => request['offset'],
:limit => request['limit']))
inject_link_headers(request, response)
response.to_rack
end
def options(request)
@store.options(request.uri).to_rack
end
def inject_link_headers(request, response)
response['Link'] = versions_link_header(request) if request.uri.resource_uri?
response['Link'] = resolved_link_header(request) if request.uri.resource_collection_uri?
response['Link'] = index_link_header(request) if request.uri.resolved_resource_collection_uri?
response['Link'] = resolved_link_header(request) if request.uri.version_collection_uri?
response['Link'] = index_link_header(request) if request.uri.resolved_version_collection_uri?
end
def versions_link_header(request)
base_url = "#{request.domain_root}#{request.path_info}"
"<#{base_url}/versions>; rel=\"http://joncrosby.me/cloudkit/1.0/rel/versions\""
end
def resolved_link_header(request)
base_url = "#{request.domain_root}#{request.path_info}"
"<#{base_url}/_resolved>; rel=\"http://joncrosby.me/cloudkit/1.0/rel/resolved\""
end
def index_link_header(request)
index_path = request.path_info.sub(/\/_resolved(\/)*$/, '')
base_url = "#{request.domain_root}#{index_path}"
"<#{base_url}>; rel=\"index\""
end
def update_location_header(request, response)
return unless response['Location']
response['Location'] = "#{request.domain_root}#{response['Location']}"
end
def auth_missing?(request)
request.current_user == nil
end
def tunnel_methods
['PUT', 'DELETE', 'OPTIONS', 'HEAD', 'TRACE']
end
def not_implemented
json_error_response(501, 'not implemented').to_rack
end
def auth_config_error
json_error_response(500, 'server auth misconfigured').to_rack
end
def bypass?(request)
collection = @collections.detect{|type| request.path_info.match("/#{type.to_s}")}
!collection && !request.uri.meta_uri?
end
end
end