cloudkit-jruby 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGES +47 -0
  2. data/COPYING +20 -0
  3. data/README +84 -0
  4. data/Rakefile +42 -0
  5. data/TODO +21 -0
  6. data/cloudkit.gemspec +89 -0
  7. data/doc/curl.html +388 -0
  8. data/doc/images/example-code.gif +0 -0
  9. data/doc/images/json-title.gif +0 -0
  10. data/doc/images/oauth-discovery-logo.gif +0 -0
  11. data/doc/images/openid-logo.gif +0 -0
  12. data/doc/index.html +90 -0
  13. data/doc/main.css +151 -0
  14. data/doc/rest-api.html +467 -0
  15. data/examples/1.ru +3 -0
  16. data/examples/2.ru +3 -0
  17. data/examples/3.ru +6 -0
  18. data/examples/4.ru +5 -0
  19. data/examples/5.ru +9 -0
  20. data/examples/6.ru +11 -0
  21. data/examples/TOC +17 -0
  22. data/lib/cloudkit.rb +92 -0
  23. data/lib/cloudkit/constants.rb +34 -0
  24. data/lib/cloudkit/exceptions.rb +10 -0
  25. data/lib/cloudkit/flash_session.rb +20 -0
  26. data/lib/cloudkit/oauth_filter.rb +266 -0
  27. data/lib/cloudkit/oauth_store.rb +48 -0
  28. data/lib/cloudkit/openid_filter.rb +236 -0
  29. data/lib/cloudkit/openid_store.rb +100 -0
  30. data/lib/cloudkit/rack/builder.rb +120 -0
  31. data/lib/cloudkit/rack/router.rb +20 -0
  32. data/lib/cloudkit/request.rb +177 -0
  33. data/lib/cloudkit/service.rb +162 -0
  34. data/lib/cloudkit/store.rb +349 -0
  35. data/lib/cloudkit/store/memory_table.rb +99 -0
  36. data/lib/cloudkit/store/resource.rb +269 -0
  37. data/lib/cloudkit/store/response.rb +52 -0
  38. data/lib/cloudkit/store/response_helpers.rb +84 -0
  39. data/lib/cloudkit/templates/authorize_request_token.erb +19 -0
  40. data/lib/cloudkit/templates/oauth_descriptor.erb +43 -0
  41. data/lib/cloudkit/templates/oauth_meta.erb +8 -0
  42. data/lib/cloudkit/templates/openid_login.erb +31 -0
  43. data/lib/cloudkit/templates/request_authorization.erb +23 -0
  44. data/lib/cloudkit/templates/request_token_denied.erb +18 -0
  45. data/lib/cloudkit/uri.rb +88 -0
  46. data/lib/cloudkit/user_store.rb +37 -0
  47. data/lib/cloudkit/util.rb +25 -0
  48. data/spec/ext_spec.rb +76 -0
  49. data/spec/flash_session_spec.rb +20 -0
  50. data/spec/memory_table_spec.rb +86 -0
  51. data/spec/oauth_filter_spec.rb +326 -0
  52. data/spec/oauth_store_spec.rb +10 -0
  53. data/spec/openid_filter_spec.rb +81 -0
  54. data/spec/openid_store_spec.rb +101 -0
  55. data/spec/rack_builder_spec.rb +39 -0
  56. data/spec/request_spec.rb +191 -0
  57. data/spec/resource_spec.rb +310 -0
  58. data/spec/service_spec.rb +1039 -0
  59. data/spec/spec_helper.rb +32 -0
  60. data/spec/store_spec.rb +10 -0
  61. data/spec/uri_spec.rb +93 -0
  62. data/spec/user_store_spec.rb +10 -0
  63. data/spec/util_spec.rb +11 -0
  64. metadata +180 -0
data/CHANGES ADDED
@@ -0,0 +1,47 @@
1
+ 0.11.2
2
+ - Added Location header for 201s
3
+ - Fixed JSON response for DELETE operations
4
+ - Updated MD5 dependency for Ruby 1.9 (Andreas Haller)
5
+ - Updated spec setup for RSpec 1.2 (Andreas Haller)
6
+ - Updated gem dependencies for Rack, OpenID, and JSON
7
+ - Other minor fixes
8
+
9
+ 0.11.1
10
+ - Added a block option for configuring OpenID bypassed routes (Devlin Daley)
11
+ - Added write locks for Tokyo Tyrant Tables
12
+ - Added Tokyo Tyrant Table example
13
+ - Fixed POST method tunneling bug (Saimon Moore)
14
+ - Fixed escaping of nested JSON Objects and Arrays
15
+
16
+ 0.11.0
17
+ - Added Tokyo Cabinet storage
18
+ - Added MemoryTable development-time storage
19
+ - Improved router performance
20
+ - Added CloudKit::Resource model
21
+ - Added custom URIs for OpenID bypass
22
+ - Removed internal undocumented ExtractionViews
23
+ - Removed SQL backends
24
+ - Removed Sequel and Rack::Config dependencies
25
+ - Switched test framework to RSpec
26
+
27
+ 0.10.1
28
+ - Updated oauth and sequel gem dependencies
29
+ - Fixed 410 responses for stale PUT operations
30
+ - Fixed MySQL content encoding (Harry Weppner)
31
+ - Various fixes for filter_merge!, rekey!, and excluding (Cameron Walters)
32
+
33
+ 0.10.0
34
+ - Updated for Rack 0.9
35
+ - Added batch URI resolution for resource collections
36
+ - Refactored use of constants
37
+ - Updated documentation
38
+
39
+ 0.9.1
40
+ - Fixed Rack::Lint/rackup errors related to Content-Type headers
41
+ - Patched Rack to support StringIO#string in Rack::Lint::InputWrapper
42
+ - Fixed server_url encoding in OpenIDStore
43
+ - Added sqlite3-ruby dependency in gemspec
44
+ - Updated documentation
45
+
46
+ 0.9.0
47
+ - First public gem release
data/COPYING ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008, 2009 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,84 @@
1
+ =CloudKit
2
+
3
+ CloudKit is an Open Web JSON Appliance.
4
+
5
+ CloudKit provides schema-free, auto-versioned, RESTful JSON storage with optional OpenID and OAuth support, including OAuth Discovery.
6
+
7
+ CloudKit is Rack middleware. It can be used on its own or alongside other Rack-based applications or middleware components like Rails, Merb or Sinatra.
8
+
9
+ ===Install
10
+
11
+ gem install cloudkit
12
+
13
+ ===Examples
14
+
15
+ In a rackup file called config.ru:
16
+
17
+ require 'cloudkit'
18
+ expose :notes, :todos
19
+
20
+ The above creates a versioned HTTP/REST service using JSON to represent two types of resource collections -- Notes and ToDos.
21
+
22
+ In a different rackup file:
23
+
24
+ require 'cloudkit'
25
+ contain :notes, :todos
26
+
27
+ The above provides the same API as example 1 with added authentication and authorization via OpenID and OAuth, respectively.
28
+
29
+ An explicit version of example 2, minus the default developer landing page:
30
+
31
+ require 'cloudkit'
32
+ use Rack::Session::Pool
33
+ use CloudKit::OAuthFilter
34
+ use CloudKit::OpenIDFilter
35
+ use CloudKit::Service, :collections => [:notes, :todos]
36
+ run lambda {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']]}
37
+
38
+ The same as above, using Tokyo Cabinet:
39
+
40
+ require 'cloudkit'
41
+ require 'rufus/tokyo' # gem install rufus-tokyo
42
+ CloudKit.setup_storage_adapter(Rufus::Tokyo::Table.new('cloudkit.tdb'))
43
+ use Rack::Session::Pool
44
+ use CloudKit::OAuthFilter
45
+ use CloudKit::OpenIDFilter
46
+ use CloudKit::Service, :collections => [:notes, :todos]
47
+ run lambda {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']]}
48
+
49
+ See the examples directory for more.
50
+
51
+ ===Online
52
+
53
+ Main Site: http://getcloudkit.com
54
+
55
+ Blog: http://blog.joncrosby.me
56
+
57
+ Google Group: http://groups.google.com/group/cloudkit
58
+
59
+ Source: http://github.com/jcrosby/cloudkit
60
+
61
+ IRC: #cloudkit on freenode
62
+
63
+ ===License
64
+
65
+ Copyright (c) 2008, 2009 Jon Crosby http://joncrosby.me
66
+
67
+ Permission is hereby granted, free of charge, to any person obtaining
68
+ a copy of this software and associated documentation files (the
69
+ 'Software'), to deal in the Software without restriction, including
70
+ without limitation the rights to use, copy, modify, merge, publish,
71
+ distribute, sublicense, and/or sell copies of the Software, and to
72
+ permit persons to whom the Software is furnished to do so, subject to
73
+ the following conditions:
74
+
75
+ The above copyright notice and this permission notice shall be
76
+ included in all copies or substantial portions of the Software.
77
+
78
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
79
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
80
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
81
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
82
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
83
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
84
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,42 @@
1
+ require 'rake/clean'
2
+ require 'spec/rake/spectask'
3
+
4
+ CLEAN.include 'doc/api'
5
+
6
+ task :default => :spec
7
+
8
+ desc "Run all examples (or a specific spec with TASK=xxxx)"
9
+ Spec::Rake::SpecTask.new('spec') do |t|
10
+ t.spec_opts = ["-c"]
11
+ t.spec_files = begin
12
+ if ENV["TASK"]
13
+ ENV["TASK"].split(',').map { |task| "spec/**/#{task}_spec.rb" }
14
+ else
15
+ FileList['spec/**/*_spec.rb']
16
+ end
17
+ end
18
+ end
19
+
20
+ desc 'Generate rdoc'
21
+ task :rdoc do
22
+ rm_rf 'doc/api'
23
+ sh((<<-SH).gsub(/[\s\n]+/, ' ').strip)
24
+ hanna
25
+ --inline-source
26
+ --line-numbers
27
+ --include=lib/cloudkit.rb
28
+ --include=lib/cloudkit/*.rb
29
+ --include=lib/cloudkit/*/*.rb
30
+ --exclude=Rakefile
31
+ --exclude=TODO
32
+ --exclude=cloudkit.gemspec
33
+ --exclude=templates/*
34
+ --exclude=examples/*
35
+ --exclude=spec/*
36
+ --exclude=doc/index.html
37
+ --exclude=doc/curl.html
38
+ --exclude=doc/rest-api.html
39
+ --exclude=doc/main.css
40
+ --op=doc/api
41
+ SH
42
+ end
data/TODO ADDED
@@ -0,0 +1,21 @@
1
+ 1.0
2
+ - openid sreg
3
+ - oauth token management
4
+ - oauth consumer registration
5
+ - jsonquery
6
+ - jsonschema
7
+ - user lookup via a block for integration with existing stores
8
+ - custom templates for openid / oauth
9
+
10
+ Backlog
11
+ - acls i.e. allowed methods per user per uri
12
+ - method filtering on collections
13
+ - js functions as observers (validation, mapping, etc.)
14
+ - complete user data export
15
+ - expect header/100-continue
16
+ - deployable gem + admin app
17
+ - cappuccino adapter
18
+ - sproutcore adapter
19
+ - titanium middleware
20
+ version/upgrade middleware
21
+ rake automation
@@ -0,0 +1,89 @@
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-jruby"
5
+ s.version = "0.11.2"
6
+ s.date = "2008-05-05"
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
+ COPYING
15
+ README
16
+ Rakefile
17
+ TODO
18
+ cloudkit.gemspec
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/constants.rb
36
+ lib/cloudkit/exceptions.rb
37
+ lib/cloudkit/flash_session.rb
38
+ lib/cloudkit/oauth_filter.rb
39
+ lib/cloudkit/oauth_store.rb
40
+ lib/cloudkit/openid_filter.rb
41
+ lib/cloudkit/openid_store.rb
42
+ lib/cloudkit/rack/builder.rb
43
+ lib/cloudkit/rack/router.rb
44
+ lib/cloudkit/request.rb
45
+ lib/cloudkit/service.rb
46
+ lib/cloudkit/store.rb
47
+ lib/cloudkit/store/memory_table.rb
48
+ lib/cloudkit/store/resource.rb
49
+ lib/cloudkit/store/response.rb
50
+ lib/cloudkit/store/response_helpers.rb
51
+ lib/cloudkit/templates/authorize_request_token.erb
52
+ lib/cloudkit/templates/oauth_descriptor.erb
53
+ lib/cloudkit/templates/oauth_meta.erb
54
+ lib/cloudkit/templates/openid_login.erb
55
+ lib/cloudkit/templates/request_authorization.erb
56
+ lib/cloudkit/templates/request_token_denied.erb
57
+ lib/cloudkit/uri.rb
58
+ lib/cloudkit/user_store.rb
59
+ lib/cloudkit/util.rb
60
+ spec/ext_spec.rb
61
+ spec/flash_session_spec.rb
62
+ spec/memory_table_spec.rb
63
+ spec/oauth_filter_spec.rb
64
+ spec/oauth_store_spec.rb
65
+ spec/openid_filter_spec.rb
66
+ spec/openid_store_spec.rb
67
+ spec/rack_builder_spec.rb
68
+ spec/request_spec.rb
69
+ spec/resource_spec.rb
70
+ spec/service_spec.rb
71
+ spec/spec_helper.rb
72
+ spec/store_spec.rb
73
+ spec/uri_spec.rb
74
+ spec/user_store_spec.rb
75
+ spec/util_spec.rb
76
+ ]
77
+ s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec.rb/}
78
+ s.rubyforge_project = "cloudkit"
79
+ s.rubygems_version = "1.1.1"
80
+ s.add_dependency 'rack', '>= 1.0'
81
+ s.add_dependency 'uuid', '= 2.0.1'
82
+ s.add_dependency 'oauth', '~> 0.3'
83
+ s.add_dependency 'ruby-openid', '~> 2.1'
84
+ if Gem::Platform::CURRENT =~ /java/
85
+ s.add_dependency 'json-jruby', '> 0'
86
+ else
87
+ s.add_dependency 'json', '~> 1.1'
88
+ end
89
+ end
@@ -0,0 +1,388 @@
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
+ If you already have the gem, make sure you're running the latest version (0.11.2):
42
+ <div class="code">
43
+ $ gem list cloudkit<br/>
44
+ cloudkit (0.10.0) &lt;-- need to upgrade<br/>
45
+ $ gem update cloudkit<br/>
46
+ $ gem list cloudkit<br/>
47
+ cloudkit (0.11.2, 0.10.0) &lt;-- 0.11.2 is now in the list
48
+ </div>
49
+ </p>
50
+
51
+ <p>
52
+ Create a rackup file named config.ru, containing these two lines of code:
53
+ <div class="code">
54
+ require 'cloudkit'<br/>
55
+ expose :notes
56
+ </div>
57
+ </p>
58
+
59
+ <p>
60
+ Run the app:
61
+ <div class="code">
62
+ $ rackup config.ru
63
+ </div>
64
+ </p>
65
+
66
+ <p>
67
+ CloudKit is discoverable from top to bottom. Let's see what resource collections
68
+ we're hosting:
69
+ <div class="code">
70
+ $ curl -i http://localhost:9292/cloudkit-meta<br/>
71
+ HTTP/1.1 200 OK<br/>
72
+ ETag: "ef2f29b1834ef8c2bf0d8f1abb100177"<br/>
73
+ Cache-Control: proxy-revalidate<br/>
74
+ Content-Type: application/json<br/>
75
+ Content-Length: 20<br/><br/>
76
+
77
+ {"uris":["\/notes"]}
78
+ </div>
79
+ </p>
80
+
81
+ <p>
82
+ See what we can do with these note resources:
83
+ <div class="code">
84
+ $ curl -i -XOPTIONS http://localhost:9292/notes<br/>
85
+ HTTP/1.1 200 OK<br/>
86
+ Content-Length: 0<br/>
87
+ Content-Type: application/json<br/>
88
+ Allow: GET, HEAD, POST, OPTIONS<br/>
89
+ </div>
90
+ </p>
91
+
92
+ <p>
93
+ List the currently available notes:
94
+ <div class="code">
95
+ $ curl -i http://localhost:9292/notes<br/>
96
+ HTTP/1.1 200 OK<br/>
97
+ ETag: "ffc5e6012614d759283199e67f79071b"<br/>
98
+ Link: &lt;http://localhost:9292/notes/_resolved&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/resolved"<br/>
99
+ Cache-Control: proxy-revalidate<br/>
100
+ Content-Type: application/json<br/>
101
+ Content-Length: 32<br/><br/>
102
+
103
+ {"uris":[],"total":0,"offset":0}
104
+ </div>
105
+ </p>
106
+
107
+ <p>
108
+ Along with the usual metadata, many responses also provide discovery
109
+ information via
110
+ <a href="http://www.mnot.net/drafts/draft-nottingham-http-link-header-03.txt">Link Headers</a>
111
+ as shown above. These links allow user agents to find related resources. The
112
+ purpose of the above rel="resolved" Link Header is to offer a complete representation of
113
+ all documents in a collection as an alternative to the simple list of links provided above.
114
+ We'll look at the the result of using the "resolved" URI after we have created a few resources.
115
+ </p>
116
+
117
+ <p>
118
+ Let's move on, creating a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.5">POST</a>:
119
+ <div class="code">
120
+ $ curl -i -XPOST -d'{"title":"projects"}' http://localhost:9292/notes<br/>
121
+ HTTP/1.1 201 Created<br/>
122
+ Cache-Control: no-cache<br/>
123
+ Location: http://localhost:9292/notes/0dda06f0-b134-012b-a2d8-0017f2c62348<br/>
124
+ Content-Type: application/json<br/>
125
+ Content-Length: 159<br/><br/>
126
+
127
+ {<br/>
128
+ "uri":"\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348",<br/>
129
+ "ok":true,<br/>
130
+ "etag":"0dda0de0-b134-012b-a2d8-0017f2c62348",<br/>
131
+ "last_modified":"Sun, 21 Dec 2008 02:21:52 GMT"<br/>
132
+ }
133
+ </div>
134
+ </p>
135
+
136
+ <p>
137
+ Create a different note using <a href="http://tools.ietf.org/html/rfc2616#section-9.6">PUT</a>
138
+ so that we can specify its location:
139
+ <div class="code">
140
+ $ curl -i -XPUT -d'{"title":"reminders"}' http://localhost:9292/notes/abc<br/>
141
+ HTTP/1.1 201 Created<br/>
142
+ Cache-Control: no-cache<br/>
143
+ Location: http://localhost:9292/notes/abc<br/>
144
+ Content-Type: application/json<br/>
145
+ Content-Length: 126<br/><br/>
146
+
147
+ {<br/>
148
+ "uri":"\/notes\/abc",<br/>
149
+ "ok":true,<br/>
150
+ "etag":"89487620-b134-012b-a2d8-0017f2c62348",<br/>
151
+ "last_modified":"Sun, 21 Dec 2008 02:25:19 GMT"<br/>
152
+ }
153
+ </div>
154
+ </p>
155
+
156
+ <p>
157
+ View the new note:
158
+ <div class="code">
159
+ $ curl -i http://localhost:9292/notes/abc<br/>
160
+ HTTP/1.1 200 OK<br/>
161
+ Last-Modified: Sun, 21 Dec 2008 02:25:19 GMT<br/>
162
+ ETag: "89487620-b134-012b-a2d8-0017f2c62348"<br/>
163
+ Link: &lt;http://localhost:9292/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
164
+ Cache-Control: proxy-revalidate<br/>
165
+ Content-Type: application/json<br/>
166
+ Content-Length: 21<br/><br/>
167
+
168
+ {"title":"reminders"}
169
+ </div>
170
+ </p>
171
+
172
+ <p>
173
+ Once again, we see a Link header. This one lists the location of the complete history
174
+ of this particular document. This history contains all versions of the document including
175
+ the most recent. We will see it in action in a moment.
176
+ </p>
177
+
178
+ <p>
179
+ Next, attempt a careless update of our newest resource and enjoy the failure:
180
+ <div class="code">
181
+ $ curl -i -XPUT -d'{"title":"foo"}' http://localhost:9292/notes/abc<br/>
182
+ HTTP/1.1 400 Bad Request<br/>
183
+ Cache-Control: no-cache<br/>
184
+ Content-Type: application/json<br/>
185
+ Content-Length: 25<br/><br/>
186
+
187
+ {"error":"etag required"}
188
+ </div>
189
+ </p>
190
+
191
+ <p>
192
+ Succeed in updating by being specific:
193
+ <div class="code">
194
+ $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:9292/notes/abc<br/>
195
+ HTTP/1.1 200 OK<br/>
196
+ Cache-Control: no-cache<br/>
197
+ Content-Type: application/json<br/>
198
+ Content-Length: 126<br/>
199
+ Connection: keep-alive<br/><br/>
200
+
201
+ {<br/>
202
+ "uri":"\/notes\/abc",<br/>
203
+ "ok":true,<br/>
204
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
205
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT"<br/>
206
+ }
207
+ </div>
208
+ </p>
209
+
210
+ <p>
211
+ (Note: Your <a href="http://tools.ietf.org/html/rfc2616#section-14.19">ETag</a>
212
+ will likely be different so substitute the one that curl provided when you
213
+ created your own "abc" resource.)
214
+ </p>
215
+
216
+ <p>
217
+ Watch a secondary, out-of-date client fail at updating by being specific but also being stale:
218
+ <div class="code">
219
+ $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"bar"}' http://localhost:9292/notes/abc<br/>
220
+ HTTP/1.1 412 Precondition Failed<br/>
221
+ Cache-Control: no-cache<br/>
222
+ Content-Type: application/json<br/>
223
+ Content-Length: 31<br/><br/>
224
+
225
+ {"error":"precondition failed"}
226
+ </div>
227
+ </p>
228
+
229
+ <p>
230
+ A <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">412</a> code is
231
+ returned indicating a precondition for this request failed. Specifically, the
232
+ ETag was out of date. In this case, our second client can fall back on the
233
+ resource's history to "catch up" and apply its changes to the most recent
234
+ version of the resource.
235
+ </p>
236
+
237
+ <p>
238
+ We can list all versions of the document using the URI tagged with rel="versions" in the
239
+ link header mentioned above, reverse sorted by Last-Modified, feed style:
240
+ <div class="code">
241
+ $ curl -i http://localhost:9292/notes/abc/versions<br/>
242
+ HTTP/1.1 200 OK<br/>
243
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
244
+ ETag: "28ecf6899a45d3cdd0ad82bad56991d1"<br/>
245
+ Link: &lt;http://localhost:9292/notes/abc/versions/_resolved&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/resolved"<br/>
246
+ Cache-Control: proxy-revalidate<br/>
247
+ Content-Type: application/json<br/>
248
+ Content-Length: 109<br/><br/>
249
+
250
+ {<br/>
251
+ "uris":[<br/>
252
+ "\/notes\/abc",<br/>
253
+ "\/notes\/abc\/versions\/89487620-b134-012b-a2d8-0017f2c62348"<br/>
254
+ ],<br/>
255
+ "total":2,<br/>
256
+ "offset":0<br/>
257
+ }
258
+ </div>
259
+ </p>
260
+
261
+ <p>
262
+ List all versions again, this time using the "resolved" URI from the Link header.
263
+ This effectively delivers the same information that would be obtained by first listing
264
+ the URIs, then fetching each one of them individually.
265
+ <div class="code">
266
+ $ curl -i http://localhost:9292/notes/abc/versions/_resolved<br/>
267
+ HTTP/1.1 200 OK<br/>
268
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
269
+ ETag: "282819afc09d7735fd6801532c0c7033"<br/>
270
+ Link: &lt;http://localhost:9292/notes/abc/versions&gt;; rel="index"<br/>
271
+ Cache-Control: proxy-revalidate<br/>
272
+ Content-Type: application/json<br/>
273
+ Content-Length: 390<br/><br/>
274
+
275
+ {<br/>
276
+ "documents":[<br/>
277
+ {<br/>
278
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
279
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT",<br/>
280
+ "document":"{\"title\":\"foo\"}",<br/>
281
+ "uri":"\/notes\/abc"<br/>
282
+ },<br/>
283
+ {<br/>
284
+ "etag":"89487620-b134-012b-a2d8-0017f2c62348",<br/>
285
+ "last_modified":"Sun, 21 Dec 2008 02:25:19 GMT",<br/>
286
+ "document":"{\"title\":\"reminders\"}",<br/>
287
+ "uri":"\/notes\/abc\/versions\/89487620-b134-012b-a2d8-0017f2c62348"}<br/>
288
+ ],<br/>
289
+ "total":2,<br/>
290
+ "offset":0<br/>
291
+ }
292
+ </div>
293
+ </p>
294
+
295
+ <p>
296
+ Notice the resolved response includes a Link header pointing back to its index.
297
+ </p>
298
+
299
+ <p>
300
+ We can use this same "resolved" technique on the main "notes" listing:
301
+ <div class="code">
302
+ $ curl -i http://localhost:9292/notes/_resolved<br/>
303
+ HTTP/1.1 200 OK<br/>
304
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
305
+ ETag: "6628242625a7f71cce838a02deb27912"<br/>
306
+ Link: &lt;http://localhost:9292/notes&gt; rel="index"<br/>
307
+ Cache-Control: proxy-revalidate<br/>
308
+ Content-Type: application/json<br/>
309
+ Content-Length: 374<br/><br/>
310
+
311
+ {<br/>
312
+ "documents":[<br/>
313
+ {<br/>
314
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
315
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT",<br/>
316
+ "document":"{\"title\":\"foo\"}",<br/>
317
+ "uri":"\/notes\/abc"<br/>
318
+ },<br/>
319
+ {<br/>
320
+ "etag":"0dda0de0-b134-012b-a2d8-0017f2c62348",<br/>
321
+ "last_modified":"Sun, 21 Dec 2008 02:21:52 GMT",<br/>
322
+ "document":"{\"title\":\"projects\"}",<br/>
323
+ "uri":"\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348"<br/>
324
+ }<br/>
325
+ ],<br/>
326
+ "total":2,<br/>
327
+ "offset":0<br/>
328
+ }
329
+ </div>
330
+ </p>
331
+
332
+ <p>
333
+ Next, let's delete the our most recent document:
334
+ <div class="code">
335
+ $ curl -i -XDELETE -H'If-Match:522be9f0-b135-012b-a2d8-0017f2c62348' http://localhost:9292/notes/abc<br/>
336
+ HTTP/1.1 200 OK<br/>
337
+ Cache-Control: no-cache<br/>
338
+ Content-Type: application/json<br/>
339
+ Content-Length: 174<br/><br/>
340
+
341
+ {<br/>
342
+ "uri":"\/notes\/abc\/versions\/522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
343
+ "ok":true,<br/>
344
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
345
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT"<br/>
346
+ }
347
+ </div>
348
+ </p>
349
+
350
+ <p>
351
+ Try to GET it again and notice the helpful <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">410</a>:
352
+ <div class="code">
353
+ $ curl -i http://localhost:9292/notes/abc<br/>
354
+ HTTP/1.1 410 Gone<br/>
355
+ Link: &lt;http://localhost:9292/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
356
+ Cache-Control: no-cache<br/>
357
+ Content-Type: application/json<br/>
358
+ Content-Length: 37<br/><br/>
359
+
360
+ {"error":"entity previously deleted"}
361
+ </div>
362
+ </p>
363
+
364
+ <p>
365
+ Notice the history is preserved:
366
+ <div class="code">
367
+ $ curl -i http://localhost:9292/notes/abc/versions<br/>
368
+ HTTP/1.1 200 OK<br/>
369
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
370
+ ETag: "2308ee33e953c9be41221ff7612e5217"<br/>
371
+ Link: &lt;http://localhost:9292/notes/abc/versions/_resolved&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/resolved"<br/>
372
+ Cache-Control: proxy-revalidate<br/>
373
+ Content-Type: application/json<br/>
374
+ Content-Length: 157<br/><br/>
375
+
376
+ {<br/>
377
+ "uris":[<br/>
378
+ "\/notes\/abc\/versions\/522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
379
+ "\/notes\/abc\/versions\/89487620-b134-012b-a2d8-0017f2c62348"<br/>
380
+ ],<br/>
381
+ "total":2,<br/>
382
+ "offset":0<br/>
383
+ }
384
+ </div>
385
+ </p>
386
+ </div>
387
+ </body>
388
+ </html>