cloudkit 0.11.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,10 @@
1
+ 0.11.1
2
+ - Added a block option for configuring OpenID bypassed routes (Devlin Daley)
3
+ - Added write locks for Tokyo Tyrant Tables
4
+ - Added Tokyo Tyrant Table example
5
+ - Fixed POST method tunneling bug (Saimon Moore)
6
+ - Fixed escaping of nested JSON Objects and Arrays
7
+
1
8
  0.11.0
2
9
  - Added Tokyo Cabinet storage
3
10
  - Added MemoryTable development-time storage
data/cloudkit.gemspec CHANGED
@@ -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.11.0"
6
- s.date = "2008-03-09"
5
+ s.version = "0.11.1"
6
+ s.date = "2008-03-24"
7
7
  s.summary = "An Open Web JSON Appliance."
8
8
  s.description = "An Open Web JSON Appliance."
9
9
  s.authors = ["Jon Crosby"]
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
29
29
  examples/3.ru
30
30
  examples/4.ru
31
31
  examples/5.ru
32
+ examples/6.ru
32
33
  examples/TOC
33
34
  lib/cloudkit.rb
34
35
  lib/cloudkit/constants.rb
data/doc/curl.html CHANGED
@@ -38,13 +38,13 @@ If you haven't already installed the gem:
38
38
  </p>
39
39
 
40
40
  <p>
41
- If you already have the gem, make sure you're running the latest version (0.11.0):
41
+ If you already have the gem, make sure you're running the latest version (0.11.1):
42
42
  <div class="code">
43
43
  $ gem list cloudkit<br/>
44
- cloudkit (0.9.1) &lt;-- need to upgrade<br/>
44
+ cloudkit (0.10.0) &lt;-- need to upgrade<br/>
45
45
  $ gem update cloudkit<br/>
46
46
  $ gem list cloudkit<br/>
47
- cloudkit (0.11.0, 0.9.1) &lt;-- 0.11.0 is now in the list
47
+ cloudkit (0.11.1, 0.10.0) &lt;-- 0.11.1 is now in the list
48
48
  </div>
49
49
  </p>
50
50
 
data/doc/index.html CHANGED
@@ -24,7 +24,7 @@
24
24
  </div>
25
25
  <div class="meta">
26
26
  <p class="wrapper">
27
- Version 0.11 released with Tokyo Cabinet support. Install with <em>gem install cloudkit</em>.
27
+ Version 0.11.1 released with Tokyo Cabinet support. Install with <em>gem install cloudkit</em>.
28
28
  </p>
29
29
  </div>
30
30
  <div class="wrapper intro-row">
data/examples/6.ru ADDED
@@ -0,0 +1,11 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__)) + '/../lib'
2
+ require 'cloudkit'
3
+ require 'rufus/tokyo/tyrant' # gem install rufus-tokyo
4
+ # start Tokyo Tyrant with a table store...
5
+ # ttserver data.tct
6
+ CloudKit.setup_storage_adapter(Rufus::Tokyo::TyrantTable.new('127.0.0.1', 1978))
7
+ use Rack::Session::Pool
8
+ use CloudKit::OAuthFilter
9
+ use CloudKit::OpenIDFilter
10
+ use CloudKit::Service, :collections => [:notes]
11
+ run lambda{|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']]}
data/examples/TOC CHANGED
@@ -4,7 +4,7 @@ Index of Examples
4
4
  When using the gem version of CloudKit, the first line of each example can be
5
5
  removed.
6
6
 
7
- 1. Expose Notes - Mount a JSON "notes" API. Uses in-memory SQLite.
7
+ 1. Expose Notes - Mount a JSON "notes" API. Uses in-memory store.
8
8
 
9
9
  2. Contain Notes - Same as #1, adding OpenID and OAuth.
10
10
 
@@ -13,3 +13,5 @@ removed.
13
13
  4. Notes with OAuth - Same as #1 using only OAuth.
14
14
 
15
15
  5. Tokyo Notes - Same as #2 with a Tokyo Cabinet Table store.
16
+
17
+ 6. Tyrant Notes - Same as #2 with a Tokyo Tyrant Table store.
data/lib/cloudkit.rb CHANGED
@@ -34,11 +34,11 @@ require 'cloudkit/user_store'
34
34
  include CloudKit::Constants
35
35
 
36
36
  module CloudKit
37
- VERSION = '0.11.0'
37
+ VERSION = '0.11.1'
38
38
 
39
39
  # Sets up the storage adapter. Defaults to development-time
40
40
  # CloudKit::MemoryTable. Also supports Rufus Tokyo Table instances. See the
41
- # examples directory for a demonstration.
41
+ # examples directory for Cabinet and Tyrant Table examples.
42
42
  def self.setup_storage_adapter(adapter_instance=nil)
43
43
  @storage_adapter = adapter_instance || CloudKit::MemoryTable.new
44
44
  end
@@ -19,9 +19,10 @@ module CloudKit
19
19
  @@lock = Mutex.new
20
20
  @@store = nil
21
21
 
22
- def initialize(app, options={})
22
+ def initialize(app, options={}, &bypass_route_callback)
23
23
  @app = app
24
24
  @options = options
25
+ @bypass_route_callback = bypass_route_callback || Proc.new {|url| url == '/'}
25
26
  end
26
27
 
27
28
  def call(env)
@@ -213,12 +214,12 @@ module CloudKit
213
214
  end
214
215
 
215
216
  def allow?(uri)
216
- @options[:allow] && @options[:allow].include?(uri)
217
+ @bypass_route_callback.call(uri) ||
218
+ @options[:allow] && @options[:allow].include?(uri)
217
219
  end
218
220
 
219
221
  def bypass?(request)
220
- root_request?(request) ||
221
- allow?(request.path_info) ||
222
+ allow?(request.path_info) ||
222
223
  valid_auth_key?(request) ||
223
224
  logged_in?(request)
224
225
  end
@@ -17,7 +17,9 @@ module CloudKit
17
17
  # Return the JSON content from the request body
18
18
  def json
19
19
  self.body.rewind
20
- self.body.read
20
+ raw = self.body.read
21
+ # extract the json from the body to avoid tunneled _method param from being parsed as json
22
+ (matches = raw.match(/(\{.*\})/)) ? matches[1] : raw
21
23
  end
22
24
 
23
25
  # Return a CloudKit::URI instance representing the rack request's path info.
@@ -66,7 +66,7 @@ module CloudKit
66
66
 
67
67
  def post(request)
68
68
  if tunnel_methods.include?(request['_method'].try(:upcase))
69
- return send(request['_method'].downcase)
69
+ return send(request['_method'].downcase, request)
70
70
  end
71
71
  @store.post(
72
72
  request.uri,
@@ -59,12 +59,6 @@ module CloudKit
59
59
  q.run(self)
60
60
  end
61
61
 
62
- # Simulate a transaction. This development-mode transaction merely yields
63
- # to its block.
64
- def transaction
65
- yield
66
- end
67
-
68
62
  protected
69
63
 
70
64
  def valid?(record)
@@ -49,7 +49,7 @@ module CloudKit
49
49
  # modify resources that are not current.
50
50
  def update(json, remote_user=nil)
51
51
  raise HistoricalIntegrityViolation unless current?
52
- CloudKit.storage_adapter.transaction do
52
+ transaction do
53
53
  record = CloudKit.storage_adapter[@id]
54
54
  record['uri'] = "#{@uri.string}/versions/#{@etag}"
55
55
  record['archived'] = escape(true)
@@ -65,7 +65,7 @@ module CloudKit
65
65
  # are not current.
66
66
  def delete
67
67
  raise HistoricalIntegrityViolation unless current?
68
- CloudKit.storage_adapter.transaction do
68
+ transaction do
69
69
  original_uri = @uri
70
70
  record = CloudKit.storage_adapter[@id]
71
71
  record['uri'] = "#{@uri.string}/versions/#{@etag}"
@@ -223,6 +223,8 @@ module CloudKit
223
223
  "null"
224
224
  when Fixnum, Bignum, Float
225
225
  value.to_s
226
+ when Array, Hash
227
+ JSON.generate(value) # temporary bug fix prior to JSONQuery support
226
228
  else
227
229
  value
228
230
  end
@@ -252,5 +254,16 @@ module CloudKit
252
254
  def escape_values(hash)
253
255
  hash.inject({}) { |memo, pair| memo.merge({pair[0] => escape(pair[1])}) }
254
256
  end
257
+
258
+ def transaction
259
+ open('.lock', 'w+') do |f|
260
+ f.flock(File::LOCK_EX)
261
+ begin
262
+ yield
263
+ ensure
264
+ f.flock(File::LOCK_UN)
265
+ end
266
+ end
267
+ end
255
268
  end
256
269
  end
@@ -22,6 +22,20 @@ describe "An OpenIDFilter" do
22
22
  response.status.should == 200
23
23
  end
24
24
 
25
+ it "should allow pass through of URIs defined in bypass route callback" do
26
+ openid_app = Rack::Builder.new {
27
+ use Rack::Lint
28
+ use Rack::Session::Pool
29
+ use CloudKit::OpenIDFilter, :allow => ['/foo'] do |url|
30
+ ['/bar'].include? url
31
+ end
32
+ run echo_env(CLOUDKIT_AUTH_KEY)
33
+ }
34
+ request = Rack::MockRequest.new(openid_app)
35
+ response = request.get('/bar')
36
+ response.status.should == 200
37
+ end
38
+
25
39
  it "should redirect to the login page if authorization is required" do
26
40
  response = @request.get('/protected')
27
41
  response.status.should == 302
@@ -61,4 +75,5 @@ describe "An OpenIDFilter" do
61
75
  end
62
76
 
63
77
  end
78
+
64
79
  end
@@ -93,19 +93,38 @@ describe "A Resource" do
93
93
 
94
94
  describe "on create" do
95
95
 
96
- before(:each) do
97
- resource = CloudKit::Resource.create(
96
+ def store_json(hash)
97
+ CloudKit::Resource.create(
98
98
  CloudKit::URI.new('/items/123'),
99
- JSON.generate({:foo => 'bar'}),
99
+ JSON.generate(hash),
100
100
  'http://eric.dolphy.info')
101
- @result = CloudKit.storage_adapter.query { |q|
101
+ CloudKit.storage_adapter.query { |q|
102
102
  q.add_condition 'uri', :eql, '/items/123'
103
103
  }
104
104
  end
105
105
 
106
106
  it "should save the resource" do
107
- @result.size.should == 1
108
- @result.first['json'].should == "{\"foo\":\"bar\"}"
107
+ result = store_json({:foo => 'bar'})
108
+ result.size.should == 1
109
+ result.first['json'].should == "{\"foo\":\"bar\"}"
110
+ end
111
+
112
+ it "should accept nested array values" do
113
+ result = store_json({:foo => [1,2]})
114
+ result.size.should == 1
115
+ result.first['json'].should == '{"foo":[1,2]}'
116
+ end
117
+
118
+ it "should accept nested hash values" do
119
+ result = store_json({:foo => {:bar => 'baz'}})
120
+ result.size.should == 1
121
+ result.first['json'].should == '{"foo":{"bar":"baz"}}'
122
+ end
123
+
124
+ it "should accept recursively nested array/hash values" do
125
+ result = store_json({:foo => [1,{:bar => [2,3]}]})
126
+ result.size.should == 1
127
+ result.first['json'].should == '{"foo":[1,{"bar":[2,3]}]}'
109
128
  end
110
129
 
111
130
  end
data/spec/service_spec.rb CHANGED
@@ -736,6 +736,21 @@ describe "A CloudKit::Service" do
736
736
  new_etag.should_not == etag
737
737
  end
738
738
 
739
+ describe "using POST method tunneling" do
740
+
741
+ it "should behave like a PUT" do
742
+ json = JSON.generate(:this => 'thing')
743
+ response = @request.post(
744
+ '/items/xyz?_method=PUT',
745
+ {:input => json}.merge(VALID_TEST_AUTH))
746
+ response.status.should == 201
747
+ result = @request.get('/items/xyz', VALID_TEST_AUTH)
748
+ result.status.should == 200
749
+ JSON.parse(result.body)['this'].should == 'thing'
750
+ end
751
+
752
+ end
753
+
739
754
  end
740
755
 
741
756
  describe "on DELETE /:collection/:id" do
@@ -848,6 +863,20 @@ describe "A CloudKit::Service" do
848
863
  json['total'].should == 1
849
864
  end
850
865
 
866
+ describe "using POST method tunneling" do
867
+
868
+ it "should behave like a DELETE" do
869
+ response = @request.post(
870
+ '/items/abc?_method=DELETE',
871
+ 'HTTP_IF_MATCH' => @etag,
872
+ CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
873
+ response.status.should == 200
874
+ result = @request.get('/items/abc', VALID_TEST_AUTH)
875
+ result.status.should == 410
876
+ end
877
+
878
+ end
879
+
851
880
  end
852
881
 
853
882
  describe "on OPTIONS /:collection" do
@@ -866,6 +895,16 @@ describe "A CloudKit::Service" do
866
895
  methods.sort.should == ['GET', 'HEAD', 'OPTIONS', 'POST']
867
896
  end
868
897
 
898
+ describe "using POST method tunneling" do
899
+
900
+ it "should behave like an OPTIONS request" do
901
+ response = @request.post('/items?_method=OPTIONS', VALID_TEST_AUTH)
902
+ response['Allow'].should_not be_nil
903
+ methods = response['Allow'].split(', ')
904
+ methods.sort.should == ['GET', 'HEAD', 'OPTIONS', 'POST']
905
+ end
906
+ end
907
+
869
908
  end
870
909
 
871
910
  describe "on OPTIONS /:collection/_resolved" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Crosby
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-03-09 00:00:00 -08:00
12
+ date: 2008-03-24 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -90,6 +90,7 @@ files:
90
90
  - examples/3.ru
91
91
  - examples/4.ru
92
92
  - examples/5.ru
93
+ - examples/6.ru
93
94
  - examples/TOC
94
95
  - lib/cloudkit.rb
95
96
  - lib/cloudkit/constants.rb