cloudkit 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +2 -0
- data/COPYING +20 -0
- data/README +55 -0
- data/Rakefile +35 -0
- data/TODO +22 -0
- data/cloudkit.gemspec +82 -0
- data/doc/curl.html +329 -0
- data/doc/images/example-code.gif +0 -0
- data/doc/images/json-title.gif +0 -0
- data/doc/images/oauth-discovery-logo.gif +0 -0
- data/doc/images/openid-logo.gif +0 -0
- data/doc/index.html +87 -0
- data/doc/main.css +151 -0
- data/doc/rest-api.html +358 -0
- data/examples/1.ru +3 -0
- data/examples/2.ru +3 -0
- data/examples/3.ru +6 -0
- data/examples/4.ru +5 -0
- data/examples/5.ru +10 -0
- data/examples/6.ru +10 -0
- data/examples/TOC +17 -0
- data/lib/cloudkit.rb +74 -0
- data/lib/cloudkit/flash_session.rb +22 -0
- data/lib/cloudkit/oauth_filter.rb +273 -0
- data/lib/cloudkit/oauth_store.rb +56 -0
- data/lib/cloudkit/openid_filter.rb +198 -0
- data/lib/cloudkit/openid_store.rb +101 -0
- data/lib/cloudkit/rack/builder.rb +120 -0
- data/lib/cloudkit/rack/router.rb +20 -0
- data/lib/cloudkit/request.rb +159 -0
- data/lib/cloudkit/service.rb +135 -0
- data/lib/cloudkit/store.rb +459 -0
- data/lib/cloudkit/store/adapter.rb +9 -0
- data/lib/cloudkit/store/extraction_view.rb +57 -0
- data/lib/cloudkit/store/response.rb +51 -0
- data/lib/cloudkit/store/response_helpers.rb +72 -0
- data/lib/cloudkit/store/sql_adapter.rb +36 -0
- data/lib/cloudkit/templates/authorize_request_token.erb +19 -0
- data/lib/cloudkit/templates/oauth_descriptor.erb +43 -0
- data/lib/cloudkit/templates/oauth_meta.erb +8 -0
- data/lib/cloudkit/templates/openid_login.erb +31 -0
- data/lib/cloudkit/templates/request_authorization.erb +23 -0
- data/lib/cloudkit/templates/request_token_denied.erb +18 -0
- data/lib/cloudkit/user_store.rb +44 -0
- data/lib/cloudkit/util.rb +60 -0
- data/test/ext_test.rb +57 -0
- data/test/flash_session_test.rb +22 -0
- data/test/helper.rb +50 -0
- data/test/oauth_filter_test.rb +331 -0
- data/test/oauth_store_test.rb +12 -0
- data/test/openid_filter_test.rb +54 -0
- data/test/openid_store_test.rb +12 -0
- data/test/rack_builder_test.rb +41 -0
- data/test/request_test.rb +197 -0
- data/test/service_test.rb +718 -0
- data/test/store_test.rb +99 -0
- data/test/user_store_test.rb +12 -0
- data/test/util_test.rb +13 -0
- metadata +190 -0
data/CHANGES
ADDED
data/COPYING
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Jon Crosby http://joncrosby.me
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
'Software'), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
=CloudKit
|
2
|
+
|
3
|
+
CloudKit is an Open Web JSON Appliance.
|
4
|
+
|
5
|
+
More specifically, CloudKit provides RESTful JSON storage with optional OpenID and OAuth support, including OAuth Discovery. Stored entities are versioned. Services manage their own storage and do not require schema updates when models change.
|
6
|
+
|
7
|
+
CloudKit is Rack middleware and as such can be used on its own or alongside other Rack-based applications or middleware components like Rails, Merb or Sinatra.
|
8
|
+
|
9
|
+
===Examples
|
10
|
+
|
11
|
+
In a rackup file called config.ru:
|
12
|
+
|
13
|
+
require 'cloudkit'
|
14
|
+
expose :notes, :todos
|
15
|
+
|
16
|
+
The above creates a versioned HTTP service using JSON to represent two types of resource collections -- Notes and ToDos.
|
17
|
+
|
18
|
+
In a different rackup file:
|
19
|
+
|
20
|
+
require 'cloudkit'
|
21
|
+
contain :notes, :todos
|
22
|
+
|
23
|
+
The aboves provides the same API as example 1 with added authentication and authorization via OpenID and OAuth, respectively.
|
24
|
+
|
25
|
+
An explicit version of example 2, minus the default developer landing page:
|
26
|
+
|
27
|
+
require 'cloudkit'
|
28
|
+
use Rack::Pool::Session
|
29
|
+
use CloudKit::OAuthFilter
|
30
|
+
use CloudKit::OpenIDFilter
|
31
|
+
use CloudKit::Service, :collections => [:notes, :todos]
|
32
|
+
run lambda {|env| [200, {}, ['HELLO']]}
|
33
|
+
|
34
|
+
See the examples directory for more.
|
35
|
+
|
36
|
+
Copyright (c) 2008 Jon Crosby http://joncrosby.me
|
37
|
+
|
38
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
39
|
+
a copy of this software and associated documentation files (the
|
40
|
+
'Software'), to deal in the Software without restriction, including
|
41
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
42
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
43
|
+
permit persons to whom the Software is furnished to do so, subject to
|
44
|
+
the following conditions:
|
45
|
+
|
46
|
+
The above copyright notice and this permission notice shall be
|
47
|
+
included in all copies or substantial portions of the Software.
|
48
|
+
|
49
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
50
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
51
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
52
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
53
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
54
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
55
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
|
3
|
+
CLEAN.include 'doc/api'
|
4
|
+
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
desc 'Run specs'
|
8
|
+
task :test => FileList['test/*_test.rb'] do |t|
|
9
|
+
suite = t.prerequisites.map{|f| "-r#{f.chomp('.rb')}"}.join(' ')
|
10
|
+
sh "ruby -Ilib:test #{suite} -e ''", :verbose => false
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Generate rdoc'
|
14
|
+
task :rdoc do
|
15
|
+
rm_rf 'doc/api'
|
16
|
+
sh((<<-SH).gsub(/[\s\n]+/, ' ').strip)
|
17
|
+
hanna
|
18
|
+
--inline-source
|
19
|
+
--line-numbers
|
20
|
+
--include=lib/cloudkit.rb
|
21
|
+
--include=lib/cloudkit/*.rb
|
22
|
+
--include=lib/cloudkit/*/*.rb
|
23
|
+
--exclude=Rakefile
|
24
|
+
--exclude=TODO
|
25
|
+
--exclude=cloudkit.gemspec
|
26
|
+
--exclude=templates/*
|
27
|
+
--exclude=examples/*
|
28
|
+
--exclude=test/*
|
29
|
+
--exclude=doc/index.html
|
30
|
+
--exclude=doc/curl.html
|
31
|
+
--exclude=doc/rest-api.html
|
32
|
+
--exclude=doc/main.css
|
33
|
+
--op=doc/api
|
34
|
+
SH
|
35
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
1.0
|
2
|
+
- jquery.cloudkit
|
3
|
+
titanium/gears
|
4
|
+
- openid sreg
|
5
|
+
- titanium middleware
|
6
|
+
version/upgrade middleware
|
7
|
+
rake automation
|
8
|
+
- oauth token management
|
9
|
+
- oauth consumer registration
|
10
|
+
- acls i.e. allowed methods per user per uri
|
11
|
+
- complete user data export
|
12
|
+
- method filtering on collections
|
13
|
+
- js functions as observers (validation, mapping, etc.)
|
14
|
+
- custom templates for openid / oauth
|
15
|
+
|
16
|
+
Backlog
|
17
|
+
- expect header/100-continue
|
18
|
+
- deployable gem + admin app
|
19
|
+
- cappuccino adapter
|
20
|
+
- sproutcore adapter
|
21
|
+
- tokyocabinet
|
22
|
+
- ldap adapter for UserStore
|
data/cloudkit.gemspec
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
s.name = "cloudkit"
|
5
|
+
s.version = "0.9.0"
|
6
|
+
s.date = "2008-12-22"
|
7
|
+
s.summary = "An Open Web JSON Appliance."
|
8
|
+
s.description = "An Open Web JSON Appliance."
|
9
|
+
s.authors = ["Jon Crosby"]
|
10
|
+
s.email = "jon@joncrosby.me"
|
11
|
+
s.homepage = "http://getcloudkit.com"
|
12
|
+
s.files = %w[
|
13
|
+
CHANGES
|
14
|
+
cloudkit.gemspec
|
15
|
+
COPYING
|
16
|
+
README
|
17
|
+
Rakefile
|
18
|
+
TODO
|
19
|
+
doc/curl.html
|
20
|
+
doc/images/example-code.gif
|
21
|
+
doc/images/json-title.gif
|
22
|
+
doc/images/oauth-discovery-logo.gif
|
23
|
+
doc/images/openid-logo.gif
|
24
|
+
doc/index.html
|
25
|
+
doc/main.css
|
26
|
+
doc/rest-api.html
|
27
|
+
examples/1.ru
|
28
|
+
examples/2.ru
|
29
|
+
examples/3.ru
|
30
|
+
examples/4.ru
|
31
|
+
examples/5.ru
|
32
|
+
examples/6.ru
|
33
|
+
examples/TOC
|
34
|
+
lib/cloudkit.rb
|
35
|
+
lib/cloudkit/flash_session.rb
|
36
|
+
lib/cloudkit/oauth_filter.rb
|
37
|
+
lib/cloudkit/oauth_store.rb
|
38
|
+
lib/cloudkit/openid_filter.rb
|
39
|
+
lib/cloudkit/openid_store.rb
|
40
|
+
lib/cloudkit/rack/builder.rb
|
41
|
+
lib/cloudkit/rack/router.rb
|
42
|
+
lib/cloudkit/request.rb
|
43
|
+
lib/cloudkit/service.rb
|
44
|
+
lib/cloudkit/store.rb
|
45
|
+
lib/cloudkit/store/adapter.rb
|
46
|
+
lib/cloudkit/store/extraction_view.rb
|
47
|
+
lib/cloudkit/store/response.rb
|
48
|
+
lib/cloudkit/store/response_helpers.rb
|
49
|
+
lib/cloudkit/store/sql_adapter.rb
|
50
|
+
lib/cloudkit/templates/authorize_request_token.erb
|
51
|
+
lib/cloudkit/templates/oauth_descriptor.erb
|
52
|
+
lib/cloudkit/templates/oauth_meta.erb
|
53
|
+
lib/cloudkit/templates/openid_login.erb
|
54
|
+
lib/cloudkit/templates/request_authorization.erb
|
55
|
+
lib/cloudkit/templates/request_token_denied.erb
|
56
|
+
lib/cloudkit/user_store.rb
|
57
|
+
lib/cloudkit/util.rb
|
58
|
+
test/ext_test.rb
|
59
|
+
test/flash_session_test.rb
|
60
|
+
test/helper.rb
|
61
|
+
test/oauth_filter_test.rb
|
62
|
+
test/oauth_store_test.rb
|
63
|
+
test/openid_filter_test.rb
|
64
|
+
test/openid_store_test.rb
|
65
|
+
test/rack_builder_test.rb
|
66
|
+
test/request_test.rb
|
67
|
+
test/service_test.rb
|
68
|
+
test/store_test.rb
|
69
|
+
test/user_store_test.rb
|
70
|
+
test/util_test.rb
|
71
|
+
]
|
72
|
+
s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
|
73
|
+
s.rubyforge_project = "cloudkit"
|
74
|
+
s.rubygems_version = "1.1.1"
|
75
|
+
s.add_dependency 'rack', '~> 0.4'
|
76
|
+
s.add_dependency 'rack-config', '>= 0.9'
|
77
|
+
s.add_dependency 'uuid', '= 2.0.1'
|
78
|
+
s.add_dependency 'sequel', '= 2.6.0'
|
79
|
+
s.add_dependency 'oauth', '>= 0.2.7'
|
80
|
+
s.add_dependency 'ruby-openid', '= 2.1.2'
|
81
|
+
s.add_dependency 'json', '= 1.1.3'
|
82
|
+
end
|
data/doc/curl.html
ADDED
@@ -0,0 +1,329 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
4
|
+
<head>
|
5
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
6
|
+
<title>CloudKit via cURL</title>
|
7
|
+
<link rel="stylesheet" href="main.css" type="text/css" charset="utf-8"/>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div class="wrapper">
|
11
|
+
<ul class="nav">
|
12
|
+
<li><a href="rest-api.html" title="REST API">REST API</a></li>
|
13
|
+
<li><a href="curl.html" title="cURL Tutorial">cURL</a></li>
|
14
|
+
<li><a href="api" title="RDoc">RDoc</a></li>
|
15
|
+
<li><a href="http://github.com/jcrosby/cloudkit" title="GitHub Source Repository">Code</a></li>
|
16
|
+
<li><a href="http://blog.joncrosby.me" title="Author's Blog">Blog</a></li>
|
17
|
+
</ul>
|
18
|
+
</div>
|
19
|
+
<div id="header">
|
20
|
+
<div class="wrapper">
|
21
|
+
<h1><a href="index.html">CloudKit</a></h1>
|
22
|
+
<div class="subpage-subtitle">via cURL</div>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
<div class="meta">
|
26
|
+
<p class="wrapper">
|
27
|
+
This is a tour of the CloudKit <a href="http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm">REST</a>/HTTP 1.1 API
|
28
|
+
using <a href="http://curl.haxx.se/docs/manpage.html">curl</a>. For the complete
|
29
|
+
spec, see the <a href="rest-api.html">CloudKit REST API</a>.
|
30
|
+
</p>
|
31
|
+
</div>
|
32
|
+
<div class="wrapper">
|
33
|
+
<p>
|
34
|
+
If you haven't already installed the gem:
|
35
|
+
<div class="code">
|
36
|
+
$ gem install cloudkit
|
37
|
+
</div>
|
38
|
+
</p>
|
39
|
+
|
40
|
+
<p>
|
41
|
+
Create a rackup file named config.ru, containing these two lines of code:
|
42
|
+
<div class="code">
|
43
|
+
require 'cloudkit'<br/>
|
44
|
+
expose :notes
|
45
|
+
</div>
|
46
|
+
</p>
|
47
|
+
|
48
|
+
<p>
|
49
|
+
Run the app using <a href="http://code.macournoyer.com/thin">Thin</a>:
|
50
|
+
<div class="code">
|
51
|
+
$ thin start -R config.ru
|
52
|
+
</div>
|
53
|
+
</p>
|
54
|
+
|
55
|
+
<p>
|
56
|
+
CloudKit is discoverable from top to bottom. Let's see what resource collections
|
57
|
+
we're hosting:
|
58
|
+
<div class="code">
|
59
|
+
$ curl -i http://localhost:3000/cloudkit-meta<br/>
|
60
|
+
HTTP/1.1 200 OK<br/>
|
61
|
+
ETag: "ef2f29b1834ef8c2bf0d8f1abb100177"<br/>
|
62
|
+
Cache-Control: proxy-revalidate<br/>
|
63
|
+
Content-Type: application/json<br/>
|
64
|
+
Content-Length: 20<br/>
|
65
|
+
Connection: keep-alive<br/><br/>
|
66
|
+
|
67
|
+
{"uris":["\/notes"]}
|
68
|
+
</div>
|
69
|
+
</p>
|
70
|
+
|
71
|
+
<p>
|
72
|
+
See what we can do with these note resources:
|
73
|
+
<div class="code">
|
74
|
+
$ curl -i -XOPTIONS http://localhost:3000/notes<br/>
|
75
|
+
HTTP/1.1 200 OK<br/>
|
76
|
+
Content-Length: 0<br/>
|
77
|
+
Allow: GET, HEAD, POST, OPTIONS<br/>
|
78
|
+
Connection: keep-alive<br/>
|
79
|
+
</div>
|
80
|
+
</p>
|
81
|
+
|
82
|
+
<p>
|
83
|
+
List the currently available notes:
|
84
|
+
<div class="code">
|
85
|
+
$ curl -i http://localhost:3000/notes<br/>
|
86
|
+
HTTP/1.1 200 OK<br/>
|
87
|
+
ETag: "df392c5664e6ecd64b83210fb925f6c8"<br/>
|
88
|
+
Cache-Control: proxy-revalidate<br/>
|
89
|
+
Content-Type: application/json<br/>
|
90
|
+
Content-Length: 32<br/>
|
91
|
+
Connection: keep-alive<br/><br/>
|
92
|
+
|
93
|
+
{"uris":[],"total":0,"offset":0}
|
94
|
+
</div>
|
95
|
+
</p>
|
96
|
+
|
97
|
+
<p>
|
98
|
+
The Link header will be explained momentarily.
|
99
|
+
</p>
|
100
|
+
|
101
|
+
<p>
|
102
|
+
Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.5">POST</a>:
|
103
|
+
<div class="code">
|
104
|
+
$ curl -i -XPOST -d'{"title":"projects"}' http://localhost:3000/notes<br/>
|
105
|
+
HTTP/1.1 201 Created<br/>
|
106
|
+
Cache-Control: no-cache<br/>
|
107
|
+
Content-Type: application/json<br/>
|
108
|
+
Content-Length: 159<br/>
|
109
|
+
Connection: keep-alive<br/><br/>
|
110
|
+
|
111
|
+
{<br/>
|
112
|
+
"uri":"\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348",<br/>
|
113
|
+
"ok":true,<br/>
|
114
|
+
"etag":"0dda0de0-b134-012b-a2d8-0017f2c62348",<br/>
|
115
|
+
"last_modified":"Sun, 21 Dec 2008 02:21:52 GMT"<br/>
|
116
|
+
}
|
117
|
+
</div>
|
118
|
+
</p>
|
119
|
+
|
120
|
+
<p>
|
121
|
+
List the currently available notes again:
|
122
|
+
<div class="code">
|
123
|
+
$ curl -i http://localhost:3000/notes<br/>
|
124
|
+
HTTP/1.1 200 OK<br/>
|
125
|
+
Last-Modified: Sun, 21 Dec 2008 02:21:52 GMT<br/>
|
126
|
+
ETag: "dd30142ff023386d2515b41fb88447a5"<br/>
|
127
|
+
Cache-Control: proxy-revalidate<br/>
|
128
|
+
Content-Type: application/json<br/>
|
129
|
+
Content-Length: 79<br/>
|
130
|
+
Connection: keep-alive<br/><br/>
|
131
|
+
|
132
|
+
{<br/>
|
133
|
+
"uris":["\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348"],<br/>
|
134
|
+
"total":1,<br/>
|
135
|
+
"offset":0<br/>
|
136
|
+
}
|
137
|
+
</div>
|
138
|
+
</p>
|
139
|
+
|
140
|
+
<p>
|
141
|
+
Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.6">PUT</a>
|
142
|
+
so that we can specify its location:
|
143
|
+
<div class="code">
|
144
|
+
$ curl -i -XPUT -d'{"title":"reminders"}' http://localhost:3000/notes/abc<br/>
|
145
|
+
HTTP/1.1 201 Created<br/>
|
146
|
+
Cache-Control: no-cache<br/>
|
147
|
+
Content-Type: application/json<br/>
|
148
|
+
Content-Length: 126<br/>
|
149
|
+
Connection: keep-alive<br/><br/>
|
150
|
+
|
151
|
+
{<br/>
|
152
|
+
"uri":"\/notes\/abc",<br/>
|
153
|
+
"ok":true,<br/>
|
154
|
+
"etag":"89487620-b134-012b-a2d8-0017f2c62348",<br/>
|
155
|
+
"last_modified":"Sun, 21 Dec 2008 02:25:19 GMT"<br/>
|
156
|
+
}
|
157
|
+
</div>
|
158
|
+
</p>
|
159
|
+
|
160
|
+
<p>
|
161
|
+
View the new note:
|
162
|
+
<div class="code">
|
163
|
+
$ curl -i http://localhost:3000/notes/abc<br/>
|
164
|
+
HTTP/1.1 200 OK<br/>
|
165
|
+
Last-Modified: Sun, 21 Dec 2008 02:25:19 GMT<br/>
|
166
|
+
ETag: "89487620-b134-012b-a2d8-0017f2c62348"<br/>
|
167
|
+
Link: <http://localhost:3000/notes/abc/versions>; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
|
168
|
+
Cache-Control: proxy-revalidate<br/>
|
169
|
+
Content-Type: application/json<br/>
|
170
|
+
Content-Length: 21<br/>
|
171
|
+
Connection: keep-alive<br/><br/>
|
172
|
+
|
173
|
+
{"title":"reminders"}
|
174
|
+
</div>
|
175
|
+
</p>
|
176
|
+
|
177
|
+
<p>
|
178
|
+
Along with the usual metadata, individual resources also provide discovery
|
179
|
+
information via
|
180
|
+
<a href="http://www.mnot.net/drafts/draft-nottingham-http-link-header-03.txt">Link Headers</a>
|
181
|
+
as shown above. These links allow user agents to find related resources such as
|
182
|
+
the complete history of a document and its
|
183
|
+
<a href="http://tools.ietf.org/html/rfc2616#section-14.19">ETags</a>.
|
184
|
+
The utility of these items will be demonstrated momentarily.
|
185
|
+
</p>
|
186
|
+
|
187
|
+
<p>
|
188
|
+
Attempt a careless update and enjoy the failure:
|
189
|
+
<div class="code">
|
190
|
+
$ curl -i -XPUT -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
|
191
|
+
HTTP/1.1 400 Bad Request<br/>
|
192
|
+
Cache-Control: no-cache<br/>
|
193
|
+
Content-Type: application/json<br/>
|
194
|
+
Content-Length: 25<br/>
|
195
|
+
Connection: keep-alive<br/><br/>
|
196
|
+
|
197
|
+
{"error":"etag required"}
|
198
|
+
</div>
|
199
|
+
</p>
|
200
|
+
|
201
|
+
<p>
|
202
|
+
Succeed in updating by being specific:
|
203
|
+
<div class="code">
|
204
|
+
$ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
|
205
|
+
HTTP/1.1 200 OK<br/>
|
206
|
+
Cache-Control: no-cache<br/>
|
207
|
+
Content-Type: application/json<br/>
|
208
|
+
Content-Length: 126<br/>
|
209
|
+
Connection: keep-alive<br/><br/>
|
210
|
+
|
211
|
+
{<br/>
|
212
|
+
"uri":"\/notes\/abc",<br/>
|
213
|
+
"ok":true,<br/>
|
214
|
+
"etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
|
215
|
+
"last_modified":"Sun, 21 Dec 2008 02:30:56 GMT"<br/>
|
216
|
+
}
|
217
|
+
</div>
|
218
|
+
</p>
|
219
|
+
|
220
|
+
<p>
|
221
|
+
(Note: Your ETag will likely be different so substitute the one that curl
|
222
|
+
provided when you created your own "abc" resource.)
|
223
|
+
</p>
|
224
|
+
|
225
|
+
<p>
|
226
|
+
Watch a secondary, out-of-date client fail at updating by being specific but also being stale:
|
227
|
+
<div class="code">
|
228
|
+
$ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
|
229
|
+
HTTP/1.1 412 Precondition Failed<br/>
|
230
|
+
Cache-Control: no-cache<br/>
|
231
|
+
Content-Type: application/json<br/>
|
232
|
+
Content-Length: 31<br/>
|
233
|
+
Connection: keep-alive<br/><br/>
|
234
|
+
|
235
|
+
{"error":"precondition failed"}
|
236
|
+
</div>
|
237
|
+
</p>
|
238
|
+
|
239
|
+
<p>
|
240
|
+
A <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">412</a> code is
|
241
|
+
returned indicating a precondition for this request failed. Specifically, the
|
242
|
+
ETag was out of date. In this case, our second client can fall back on the
|
243
|
+
resource's history to "catch up" and apply its changes to the most recent
|
244
|
+
version of the resource.
|
245
|
+
</p>
|
246
|
+
|
247
|
+
<p>
|
248
|
+
Get all versions of the document using the URI tagged with rel="versions" in the
|
249
|
+
link header mentioned above, reverse sorted by Last-Modified, feed style:
|
250
|
+
<div class="code">
|
251
|
+
$ curl -i http://localhost:3000/notes/abc/versions<br/>
|
252
|
+
HTTP/1.1 200 OK<br/>
|
253
|
+
Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
|
254
|
+
ETag: "bd9226d0c5f34fd761f6c64a0fe8bcc9"<br/>
|
255
|
+
Cache-Control: proxy-revalidate<br/>
|
256
|
+
Content-Type: application/json<br/>
|
257
|
+
Content-Length: 109<br/>
|
258
|
+
Connection: keep-alive<br/><br/>
|
259
|
+
|
260
|
+
{<br/>
|
261
|
+
"uris":[<br/>
|
262
|
+
"\/notes\/abc",<br/>
|
263
|
+
"\/notes\/abc\/versions\/89487620-b134-012b-a2d8-0017f2c62348"<br/>
|
264
|
+
],<br/>
|
265
|
+
"total":2,<br/>
|
266
|
+
"offset":0<br/>
|
267
|
+
}
|
268
|
+
</div>
|
269
|
+
</p>
|
270
|
+
|
271
|
+
<p>
|
272
|
+
Delete the document:
|
273
|
+
<div class="code">
|
274
|
+
$ curl -i -XDELETE -H'If-Match:522be9f0-b135-012b-a2d8-0017f2c62348' http://localhost:3000/notes/abc<br/>
|
275
|
+
HTTP/1.1 200 OK<br/>
|
276
|
+
Cache-Control: no-cache<br/>
|
277
|
+
Content-Type: application/json<br/>
|
278
|
+
Content-Length: 174<br/>
|
279
|
+
Connection: keep-alive<br/><br/>
|
280
|
+
|
281
|
+
{<br/>
|
282
|
+
"uri":"\/notes\/abc\/versions\/522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
|
283
|
+
"ok":true,<br/>
|
284
|
+
"etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
|
285
|
+
"last_modified":"Sun, 21 Dec 2008 02:30:56 GMT"<br/>
|
286
|
+
}
|
287
|
+
</div>
|
288
|
+
</p>
|
289
|
+
|
290
|
+
<p>
|
291
|
+
Try to GET it again and notice the helpful <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">410</a>:
|
292
|
+
<div class="code">
|
293
|
+
$ curl -i http://localhost:3000/notes/abc<br/>
|
294
|
+
HTTP/1.1 410 Gone<br/>
|
295
|
+
Link: <http://localhost:3000/notes/abc/versions>; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
|
296
|
+
Cache-Control: no-cache<br/>
|
297
|
+
Content-Type: application/json<br/>
|
298
|
+
Content-Length: 37<br/>
|
299
|
+
Connection: keep-alive<br/><br/>
|
300
|
+
|
301
|
+
{"error":"entity previously deleted"}
|
302
|
+
</div>
|
303
|
+
</p>
|
304
|
+
|
305
|
+
<p>
|
306
|
+
Notice the history is preserved:
|
307
|
+
<div class="code">
|
308
|
+
$ curl -i http://localhost:3000/notes/abc/versions<br/>
|
309
|
+
HTTP/1.1 200 OK<br/>
|
310
|
+
Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
|
311
|
+
ETag: "2308ee33e953c9be41221ff7612e5217"<br/>
|
312
|
+
Cache-Control: proxy-revalidate<br/>
|
313
|
+
Content-Type: application/json<br/>
|
314
|
+
Content-Length: 157<br/>
|
315
|
+
Connection: keep-alive<br/><br/>
|
316
|
+
|
317
|
+
{<br/>
|
318
|
+
"uris":[<br/>
|
319
|
+
"\/notes\/abc\/versions\/522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
|
320
|
+
"\/notes\/abc\/versions\/89487620-b134-012b-a2d8-0017f2c62348"<br/>
|
321
|
+
],<br/>
|
322
|
+
"total":2,<br/>
|
323
|
+
"offset":0<br/>
|
324
|
+
}
|
325
|
+
</div>
|
326
|
+
</p>
|
327
|
+
</div>
|
328
|
+
</body>
|
329
|
+
</html>
|