cloudkit-jruby 0.11.2

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.
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>