cloudkit 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,9 @@
1
+ 0.10.0
2
+ - Updated for Rack 0.9
3
+ - Added batch URI resolution for resource collections
4
+ - Refactored use of constants
5
+ - Updated documentation
6
+
1
7
  0.9.1
2
8
  - Fixed Rack::Lint/rackup errors related to Content-Type headers
3
9
  - Patched Rack to support StringIO#string in Rack::Lint::InputWrapper
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Jon Crosby http://joncrosby.me
1
+ Copyright (c) 2008, 2009 Jon Crosby http://joncrosby.me
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README CHANGED
@@ -24,7 +24,7 @@ In a different rackup file:
24
24
  require 'cloudkit'
25
25
  contain :notes, :todos
26
26
 
27
- The aboves provides the same API as example 1 with added authentication and authorization via OpenID and OAuth, respectively.
27
+ The above provides the same API as example 1 with added authentication and authorization via OpenID and OAuth, respectively.
28
28
 
29
29
  An explicit version of example 2, minus the default developer landing page:
30
30
 
@@ -33,19 +33,19 @@ An explicit version of example 2, minus the default developer landing page:
33
33
  use CloudKit::OAuthFilter
34
34
  use CloudKit::OpenIDFilter
35
35
  use CloudKit::Service, :collections => [:notes, :todos]
36
- run lambda {|env| [200, {'Content-Type' => 'text/html'}, ['HELLO']]}
36
+ run lambda {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']]}
37
37
 
38
38
  The same as above, using MySQL:
39
39
 
40
40
  require 'cloudkit'
41
- use Rack::Config { |env|
42
- env['cloudkit.storage.uri'] = 'mysql://user:pass@localhost/cloudkit_example'
43
- }
41
+ use Rack::Config do |env|
42
+ env[CLOUDKIT_STORAGE_URI] = 'mysql://user:pass@localhost/cloudkit_example'
43
+ end
44
44
  use Rack::Pool::Session
45
45
  use CloudKit::OAuthFilter
46
46
  use CloudKit::OpenIDFilter
47
47
  use CloudKit::Service, :collections => [:notes, :todos]
48
- run lambda {|env| [200, {'Content-Type' => 'text/html'}, ['HELLO']]}
48
+ run lambda {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']]}
49
49
 
50
50
  See the examples directory for more.
51
51
 
@@ -61,7 +61,7 @@ Source: http://github.com/jcrosby/cloudkit
61
61
 
62
62
  ===License
63
63
 
64
- Copyright (c) 2008 Jon Crosby http://joncrosby.me
64
+ Copyright (c) 2008, 2009 Jon Crosby http://joncrosby.me
65
65
 
66
66
  Permission is hereby granted, free of charge, to any person obtaining
67
67
  a copy of this software and associated documentation files (the
data/TODO CHANGED
@@ -2,9 +2,6 @@
2
2
  - jquery.cloudkit
3
3
  titanium/gears
4
4
  - openid sreg
5
- - titanium middleware
6
- version/upgrade middleware
7
- rake automation
8
5
  - oauth token management
9
6
  - oauth consumer registration
10
7
  - acls i.e. allowed methods per user per uri
@@ -14,9 +11,13 @@
14
11
  - custom templates for openid / oauth
15
12
 
16
13
  Backlog
14
+ - batch updates (post, put, delete)
17
15
  - expect header/100-continue
18
16
  - deployable gem + admin app
19
17
  - cappuccino adapter
20
18
  - sproutcore adapter
21
19
  - tokyocabinet
22
20
  - ldap adapter for UserStore
21
+ - titanium middleware
22
+ version/upgrade middleware
23
+ rake automation
@@ -2,8 +2,8 @@ Gem::Specification.new do |s|
2
2
  s.specification_version = 2 if s.respond_to? :specification_version=
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
  s.name = "cloudkit"
5
- s.version = "0.9.1"
6
- s.date = "2008-12-31"
5
+ s.version = "0.10.0"
6
+ s.date = "2008-01-09"
7
7
  s.summary = "An Open Web JSON Appliance."
8
8
  s.description = "An Open Web JSON Appliance."
9
9
  s.authors = ["Jon Crosby"]
@@ -32,13 +32,13 @@ Gem::Specification.new do |s|
32
32
  examples/6.ru
33
33
  examples/TOC
34
34
  lib/cloudkit.rb
35
+ lib/cloudkit/constants.rb
35
36
  lib/cloudkit/flash_session.rb
36
37
  lib/cloudkit/oauth_filter.rb
37
38
  lib/cloudkit/oauth_store.rb
38
39
  lib/cloudkit/openid_filter.rb
39
40
  lib/cloudkit/openid_store.rb
40
41
  lib/cloudkit/rack/builder.rb
41
- lib/cloudkit/rack/lint.rb
42
42
  lib/cloudkit/rack/router.rb
43
43
  lib/cloudkit/request.rb
44
44
  lib/cloudkit/service.rb
@@ -73,7 +73,7 @@ Gem::Specification.new do |s|
73
73
  s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
74
74
  s.rubyforge_project = "cloudkit"
75
75
  s.rubygems_version = "1.1.1"
76
- s.add_dependency 'rack', '~> 0.4'
76
+ s.add_dependency 'rack', '~> 0.9'
77
77
  s.add_dependency 'rack-config', '>= 0.9'
78
78
  s.add_dependency 'uuid', '= 2.0.1'
79
79
  s.add_dependency 'sequel', '= 2.6.0'
@@ -37,6 +37,17 @@ If you haven't already installed the gem:
37
37
  </div>
38
38
  </p>
39
39
 
40
+ <p>
41
+ If you already have the gem, make sure you're running the latest version (0.10.0):
42
+ <div class="code">
43
+ $ gem list cloudkit<br/>
44
+ cloudkit (0.9.1) &lt;-- need to upgrade<br/>
45
+ $ gem update cloudkit<br/>
46
+ $ gem list cloudkit<br/>
47
+ cloudkit (0.10.0, 0.9.1) &lt;-- 0.10.0 is now in the list
48
+ </div>
49
+ </p>
50
+
40
51
  <p>
41
52
  Create a rackup file named config.ru, containing these two lines of code:
42
53
  <div class="code">
@@ -46,9 +57,9 @@ Create a rackup file named config.ru, containing these two lines of code:
46
57
  </p>
47
58
 
48
59
  <p>
49
- Run the app using <a href="http://code.macournoyer.com/thin">Thin</a>:
60
+ Run the app:
50
61
  <div class="code">
51
- $ thin start -R config.ru
62
+ $ rackup config.ru
52
63
  </div>
53
64
  </p>
54
65
 
@@ -56,13 +67,12 @@ Run the app using <a href="http://code.macournoyer.com/thin">Thin</a>:
56
67
  CloudKit is discoverable from top to bottom. Let's see what resource collections
57
68
  we're hosting:
58
69
  <div class="code">
59
- $ curl -i http://localhost:3000/cloudkit-meta<br/>
70
+ $ curl -i http://localhost:9292/cloudkit-meta<br/>
60
71
  HTTP/1.1 200 OK<br/>
61
72
  ETag: "ef2f29b1834ef8c2bf0d8f1abb100177"<br/>
62
73
  Cache-Control: proxy-revalidate<br/>
63
74
  Content-Type: application/json<br/>
64
- Content-Length: 20<br/>
65
- Connection: keep-alive<br/><br/>
75
+ Content-Length: 20<br/><br/>
66
76
 
67
77
  {"uris":["\/notes"]}
68
78
  </div>
@@ -71,39 +81,47 @@ we're hosting:
71
81
  <p>
72
82
  See what we can do with these note resources:
73
83
  <div class="code">
74
- $ curl -i -XOPTIONS http://localhost:3000/notes<br/>
84
+ $ curl -i -XOPTIONS http://localhost:9292/notes<br/>
75
85
  HTTP/1.1 200 OK<br/>
76
86
  Content-Length: 0<br/>
77
87
  Content-Type: application/json<br/>
78
88
  Allow: GET, HEAD, POST, OPTIONS<br/>
79
- Connection: keep-alive<br/>
80
89
  </div>
81
90
  </p>
82
91
 
83
92
  <p>
84
93
  List the currently available notes:
85
94
  <div class="code">
86
- $ curl -i http://localhost:3000/notes<br/>
95
+ $ curl -i http://localhost:9292/notes<br/>
87
96
  HTTP/1.1 200 OK<br/>
88
- ETag: "df392c5664e6ecd64b83210fb925f6c8"<br/>
97
+ ETag: "ffc5e6012614d759283199e67f79071b"<br/>
98
+ Link: &lt;http://localhost:9292/notes/_resolved&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/resolved"<br/>
89
99
  Cache-Control: proxy-revalidate<br/>
90
100
  Content-Type: application/json<br/>
91
- Content-Length: 32<br/>
92
- Connection: keep-alive<br/><br/>
101
+ Content-Length: 32<br/><br/>
93
102
 
94
103
  {"uris":[],"total":0,"offset":0}
95
104
  </div>
96
105
  </p>
97
106
 
98
107
  <p>
99
- Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.5">POST</a>:
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>:
100
119
  <div class="code">
101
- $ curl -i -XPOST -d'{"title":"projects"}' http://localhost:3000/notes<br/>
120
+ $ curl -i -XPOST -d'{"title":"projects"}' http://localhost:9292/notes<br/>
102
121
  HTTP/1.1 201 Created<br/>
103
122
  Cache-Control: no-cache<br/>
104
123
  Content-Type: application/json<br/>
105
- Content-Length: 159<br/>
106
- Connection: keep-alive<br/><br/>
124
+ Content-Length: 159<br/><br/>
107
125
 
108
126
  {<br/>
109
127
  "uri":"\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348",<br/>
@@ -115,35 +133,14 @@ Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.5">POS
115
133
  </p>
116
134
 
117
135
  <p>
118
- List the currently available notes again:
119
- <div class="code">
120
- $ curl -i http://localhost:3000/notes<br/>
121
- HTTP/1.1 200 OK<br/>
122
- Last-Modified: Sun, 21 Dec 2008 02:21:52 GMT<br/>
123
- ETag: "dd30142ff023386d2515b41fb88447a5"<br/>
124
- Cache-Control: proxy-revalidate<br/>
125
- Content-Type: application/json<br/>
126
- Content-Length: 79<br/>
127
- Connection: keep-alive<br/><br/>
128
-
129
- {<br/>
130
- "uris":["\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348"],<br/>
131
- "total":1,<br/>
132
- "offset":0<br/>
133
- }
134
- </div>
135
- </p>
136
-
137
- <p>
138
- Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.6">PUT</a>
136
+ Create a different note using <a href="http://tools.ietf.org/html/rfc2616#section-9.6">PUT</a>
139
137
  so that we can specify its location:
140
138
  <div class="code">
141
- $ curl -i -XPUT -d'{"title":"reminders"}' http://localhost:3000/notes/abc<br/>
139
+ $ curl -i -XPUT -d'{"title":"reminders"}' http://localhost:9292/notes/abc<br/>
142
140
  HTTP/1.1 201 Created<br/>
143
141
  Cache-Control: no-cache<br/>
144
142
  Content-Type: application/json<br/>
145
- Content-Length: 126<br/>
146
- Connection: keep-alive<br/><br/>
143
+ Content-Length: 126<br/><br/>
147
144
 
148
145
  {<br/>
149
146
  "uri":"\/notes\/abc",<br/>
@@ -157,37 +154,33 @@ so that we can specify its location:
157
154
  <p>
158
155
  View the new note:
159
156
  <div class="code">
160
- $ curl -i http://localhost:3000/notes/abc<br/>
157
+ $ curl -i http://localhost:9292/notes/abc<br/>
161
158
  HTTP/1.1 200 OK<br/>
162
159
  Last-Modified: Sun, 21 Dec 2008 02:25:19 GMT<br/>
163
160
  ETag: "89487620-b134-012b-a2d8-0017f2c62348"<br/>
164
- Link: &lt;http://localhost:3000/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
161
+ Link: &lt;http://localhost:9292/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
165
162
  Cache-Control: proxy-revalidate<br/>
166
163
  Content-Type: application/json<br/>
167
- Content-Length: 21<br/>
168
- Connection: keep-alive<br/><br/>
164
+ Content-Length: 21<br/><br/>
169
165
 
170
166
  {"title":"reminders"}
171
167
  </div>
172
168
  </p>
173
169
 
174
170
  <p>
175
- Along with the usual metadata, individual resources also provide discovery
176
- information via
177
- <a href="http://www.mnot.net/drafts/draft-nottingham-http-link-header-03.txt">Link Headers</a>
178
- as shown above. These links allow user agents to find related resources such as
179
- the complete history of a document.
171
+ Once again, we see a Link header. This one lists the location of the complete history
172
+ of this particular document. This history contains all versions of the document including
173
+ the most recent. We will see it in action in a moment.
180
174
  </p>
181
175
 
182
176
  <p>
183
- Attempt a careless update and enjoy the failure:
177
+ Next, attempt a careless update of our newest resource and enjoy the failure:
184
178
  <div class="code">
185
- $ curl -i -XPUT -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
179
+ $ curl -i -XPUT -d'{"title":"foo"}' http://localhost:9292/notes/abc<br/>
186
180
  HTTP/1.1 400 Bad Request<br/>
187
181
  Cache-Control: no-cache<br/>
188
182
  Content-Type: application/json<br/>
189
- Content-Length: 25<br/>
190
- Connection: keep-alive<br/><br/>
183
+ Content-Length: 25<br/><br/>
191
184
 
192
185
  {"error":"etag required"}
193
186
  </div>
@@ -196,7 +189,7 @@ Attempt a careless update and enjoy the failure:
196
189
  <p>
197
190
  Succeed in updating by being specific:
198
191
  <div class="code">
199
- $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
192
+ $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:9292/notes/abc<br/>
200
193
  HTTP/1.1 200 OK<br/>
201
194
  Cache-Control: no-cache<br/>
202
195
  Content-Type: application/json<br/>
@@ -221,12 +214,11 @@ created your own "abc" resource.)
221
214
  <p>
222
215
  Watch a secondary, out-of-date client fail at updating by being specific but also being stale:
223
216
  <div class="code">
224
- $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
217
+ $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"bar"}' http://localhost:9292/notes/abc<br/>
225
218
  HTTP/1.1 412 Precondition Failed<br/>
226
219
  Cache-Control: no-cache<br/>
227
220
  Content-Type: application/json<br/>
228
- Content-Length: 31<br/>
229
- Connection: keep-alive<br/><br/>
221
+ Content-Length: 31<br/><br/>
230
222
 
231
223
  {"error":"precondition failed"}
232
224
  </div>
@@ -241,17 +233,17 @@ version of the resource.
241
233
  </p>
242
234
 
243
235
  <p>
244
- Get all versions of the document using the URI tagged with rel="versions" in the
236
+ We can list all versions of the document using the URI tagged with rel="versions" in the
245
237
  link header mentioned above, reverse sorted by Last-Modified, feed style:
246
238
  <div class="code">
247
- $ curl -i http://localhost:3000/notes/abc/versions<br/>
239
+ $ curl -i http://localhost:9292/notes/abc/versions<br/>
248
240
  HTTP/1.1 200 OK<br/>
249
241
  Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
250
- ETag: "bd9226d0c5f34fd761f6c64a0fe8bcc9"<br/>
242
+ ETag: "28ecf6899a45d3cdd0ad82bad56991d1"<br/>
243
+ Link: &lt;http://localhost:9292/notes/abc/versions/_resolved&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/resolved"<br/>
251
244
  Cache-Control: proxy-revalidate<br/>
252
245
  Content-Type: application/json<br/>
253
- Content-Length: 109<br/>
254
- Connection: keep-alive<br/><br/>
246
+ Content-Length: 109<br/><br/>
255
247
 
256
248
  {<br/>
257
249
  "uris":[<br/>
@@ -265,14 +257,84 @@ link header mentioned above, reverse sorted by Last-Modified, feed style:
265
257
  </p>
266
258
 
267
259
  <p>
268
- Delete the document:
260
+ List all versions again, this time using the "resolved" URI from the Link header.
261
+ This effectively delivers the same information that would be obtained by first listing
262
+ the URIs, then fetching each one of them individually.
263
+ <div class="code">
264
+ $ curl -i http://localhost:9292/notes/abc/versions/_resolved<br/>
265
+ HTTP/1.1 200 OK<br/>
266
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
267
+ ETag: "282819afc09d7735fd6801532c0c7033"<br/>
268
+ Link: &lt;http://localhost:9292/notes/abc/versions&gt;; rel="index"<br/>
269
+ Cache-Control: proxy-revalidate<br/>
270
+ Content-Type: application/json<br/>
271
+ Content-Length: 390<br/><br/>
272
+
273
+ {<br/>
274
+ "documents":[<br/>
275
+ {<br/>
276
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
277
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT",<br/>
278
+ "document":"{\"title\":\"foo\"}",<br/>
279
+ "uri":"\/notes\/abc"<br/>
280
+ },<br/>
281
+ {<br/>
282
+ "etag":"89487620-b134-012b-a2d8-0017f2c62348",<br/>
283
+ "last_modified":"Sun, 21 Dec 2008 02:25:19 GMT",<br/>
284
+ "document":"{\"title\":\"reminders\"}",<br/>
285
+ "uri":"\/notes\/abc\/versions\/89487620-b134-012b-a2d8-0017f2c62348"}<br/>
286
+ ],<br/>
287
+ "total":2,<br/>
288
+ "offset":0<br/>
289
+ }
290
+ </div>
291
+ </p>
292
+
293
+ <p>
294
+ Notice the resolved response includes a Link header pointing back to its index.
295
+ </p>
296
+
297
+ <p>
298
+ We can use this same "resolved" technique on the main "notes" listing:
269
299
  <div class="code">
270
- $ curl -i -XDELETE -H'If-Match:522be9f0-b135-012b-a2d8-0017f2c62348' http://localhost:3000/notes/abc<br/>
300
+ $ curl -i http://localhost:9292/notes/_resolved<br/>
301
+ HTTP/1.1 200 OK<br/>
302
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
303
+ ETag: "6628242625a7f71cce838a02deb27912"<br/>
304
+ Link: &lt;http://localhost:9292/notes&gt; rel="index"<br/>
305
+ Cache-Control: proxy-revalidate<br/>
306
+ Content-Type: application/json<br/>
307
+ Content-Length: 374<br/><br/>
308
+
309
+ {<br/>
310
+ "documents":[<br/>
311
+ {<br/>
312
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
313
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT",<br/>
314
+ "document":"{\"title\":\"foo\"}",<br/>
315
+ "uri":"\/notes\/abc"<br/>
316
+ },<br/>
317
+ {<br/>
318
+ "etag":"0dda0de0-b134-012b-a2d8-0017f2c62348",<br/>
319
+ "last_modified":"Sun, 21 Dec 2008 02:21:52 GMT",<br/>
320
+ "document":"{\"title\":\"projects\"}",<br/>
321
+ "uri":"\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348"<br/>
322
+ }<br/>
323
+ ],<br/>
324
+ "total":2,<br/>
325
+ "offset":0<br/>
326
+ }
327
+ </div>
328
+ </p>
329
+
330
+ <p>
331
+ Next, let's delete the our most recent document:
332
+ <div class="code">
333
+ $ curl -i -XDELETE -H'If-Match:522be9f0-b135-012b-a2d8-0017f2c62348' http://localhost:9292/notes/abc<br/>
271
334
  HTTP/1.1 200 OK<br/>
272
335
  Cache-Control: no-cache<br/>
273
336
  Content-Type: application/json<br/>
274
- Content-Length: 174<br/>
275
- Connection: keep-alive<br/><br/>
337
+ Content-Length: 174<br/><br/>
276
338
 
277
339
  {<br/>
278
340
  "uri":"\/notes\/abc\/versions\/522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
@@ -286,13 +348,12 @@ Delete the document:
286
348
  <p>
287
349
  Try to GET it again and notice the helpful <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">410</a>:
288
350
  <div class="code">
289
- $ curl -i http://localhost:3000/notes/abc<br/>
351
+ $ curl -i http://localhost:9292/notes/abc<br/>
290
352
  HTTP/1.1 410 Gone<br/>
291
- Link: &lt;http://localhost:3000/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
353
+ Link: &lt;http://localhost:9292/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
292
354
  Cache-Control: no-cache<br/>
293
355
  Content-Type: application/json<br/>
294
- Content-Length: 37<br/>
295
- Connection: keep-alive<br/><br/>
356
+ Content-Length: 37<br/><br/>
296
357
 
297
358
  {"error":"entity previously deleted"}
298
359
  </div>
@@ -301,14 +362,14 @@ Try to GET it again and notice the helpful <a href="http://tools.ietf.org/html/r
301
362
  <p>
302
363
  Notice the history is preserved:
303
364
  <div class="code">
304
- $ curl -i http://localhost:3000/notes/abc/versions<br/>
365
+ $ curl -i http://localhost:9292/notes/abc/versions<br/>
305
366
  HTTP/1.1 200 OK<br/>
306
367
  Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
307
368
  ETag: "2308ee33e953c9be41221ff7612e5217"<br/>
369
+ Link: &lt;http://localhost:9292/notes/abc/versions/_resolved&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/resolved"<br/>
308
370
  Cache-Control: proxy-revalidate<br/>
309
371
  Content-Type: application/json<br/>
310
- Content-Length: 157<br/>
311
- Connection: keep-alive<br/><br/>
372
+ Content-Length: 157<br/><br/>
312
373
 
313
374
  {<br/>
314
375
  "uris":[<br/>