cloudkit-jruby 0.11.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +47 -0
- data/COPYING +20 -0
- data/README +84 -0
- data/Rakefile +42 -0
- data/TODO +21 -0
- data/cloudkit.gemspec +89 -0
- data/doc/curl.html +388 -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 +90 -0
- data/doc/main.css +151 -0
- data/doc/rest-api.html +467 -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 +9 -0
- data/examples/6.ru +11 -0
- data/examples/TOC +17 -0
- data/lib/cloudkit.rb +92 -0
- data/lib/cloudkit/constants.rb +34 -0
- data/lib/cloudkit/exceptions.rb +10 -0
- data/lib/cloudkit/flash_session.rb +20 -0
- data/lib/cloudkit/oauth_filter.rb +266 -0
- data/lib/cloudkit/oauth_store.rb +48 -0
- data/lib/cloudkit/openid_filter.rb +236 -0
- data/lib/cloudkit/openid_store.rb +100 -0
- data/lib/cloudkit/rack/builder.rb +120 -0
- data/lib/cloudkit/rack/router.rb +20 -0
- data/lib/cloudkit/request.rb +177 -0
- data/lib/cloudkit/service.rb +162 -0
- data/lib/cloudkit/store.rb +349 -0
- data/lib/cloudkit/store/memory_table.rb +99 -0
- data/lib/cloudkit/store/resource.rb +269 -0
- data/lib/cloudkit/store/response.rb +52 -0
- data/lib/cloudkit/store/response_helpers.rb +84 -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/uri.rb +88 -0
- data/lib/cloudkit/user_store.rb +37 -0
- data/lib/cloudkit/util.rb +25 -0
- data/spec/ext_spec.rb +76 -0
- data/spec/flash_session_spec.rb +20 -0
- data/spec/memory_table_spec.rb +86 -0
- data/spec/oauth_filter_spec.rb +326 -0
- data/spec/oauth_store_spec.rb +10 -0
- data/spec/openid_filter_spec.rb +81 -0
- data/spec/openid_store_spec.rb +101 -0
- data/spec/rack_builder_spec.rb +39 -0
- data/spec/request_spec.rb +191 -0
- data/spec/resource_spec.rb +310 -0
- data/spec/service_spec.rb +1039 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/store_spec.rb +10 -0
- data/spec/uri_spec.rb +93 -0
- data/spec/user_store_spec.rb +10 -0
- data/spec/util_spec.rb +11 -0
- 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.
|
data/Rakefile
ADDED
@@ -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
|
data/cloudkit.gemspec
ADDED
@@ -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
|
data/doc/curl.html
ADDED
@@ -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) <-- need to upgrade<br/>
|
45
|
+
$ gem update cloudkit<br/>
|
46
|
+
$ gem list cloudkit<br/>
|
47
|
+
cloudkit (0.11.2, 0.10.0) <-- 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: <http://localhost:9292/notes/_resolved>; 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: <http://localhost:9292/notes/abc/versions>; 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: <http://localhost:9292/notes/abc/versions/_resolved>; 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: <http://localhost:9292/notes/abc/versions>; 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: <http://localhost:9292/notes> 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: <http://localhost:9292/notes/abc/versions>; 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: <http://localhost:9292/notes/abc/versions/_resolved>; 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>
|