riak-client 0.8.2 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,13 +1,22 @@
1
- # A sample Gemfile
2
1
  source :gemcutter
3
2
 
4
- gem 'activesupport', '~>2.3.5' #'~>3.0.0'
5
3
  gem 'i18n'
6
-
4
+ gem 'builder'
7
5
  gem 'rspec', "~>2.0.0"
8
6
  gem 'fakeweb', ">=1.2"
9
7
  gem 'rack', '>=1.0'
10
- gem 'curb', '>=0.6'
11
- gem 'yajl-ruby'
12
8
  gem 'rake'
13
9
  gem 'bundler'
10
+ gem 'excon', "~>0.3.4"
11
+
12
+ if defined? JRUBY_VERSION
13
+ gem 'json'
14
+ gem 'jruby-openssl'
15
+ else
16
+ gem 'curb', '>=0.6'
17
+ gem 'yajl-ruby'
18
+ end
19
+
20
+ group :integration do
21
+ gem 'activesupport', '~>3.0'
22
+ end
data/Rakefile CHANGED
@@ -6,32 +6,85 @@ gemspec = Gem::Specification.new do |gem|
6
6
  gem.summary = %Q{riak-client is a rich client for Riak, the distributed database by Basho.}
7
7
  gem.description = %Q{riak-client is a rich client for Riak, the distributed database by Basho. It supports the full HTTP interface including storage operations, bucket configuration, link-walking and map-reduce.}
8
8
  gem.version = File.read('../VERSION').strip
9
- gem.email = "seancribbs@gmail.com"
9
+ gem.email = "sean@basho.com"
10
10
  gem.homepage = "http://seancribbs.github.com/ripple"
11
11
  gem.authors = ["Sean Cribbs"]
12
12
  gem.add_development_dependency "rspec", "~>2.0.0"
13
13
  gem.add_development_dependency "fakeweb", ">=1.2"
14
14
  gem.add_development_dependency "rack", ">=1.0"
15
15
  gem.add_development_dependency "curb", ">=0.6"
16
- gem.add_dependency "activesupport", ">= 2.3.5"
17
- gem.add_dependency "i18n", "~>0.4.0"
18
- gem.requirements << "`gem install curb` for better HTTP performance"
16
+ gem.add_development_dependency "excon", "~>0.3.4"
17
+ gem.add_dependency "i18n", ">=0.4.0"
18
+ gem.add_dependency "builder", "~>2.1.2"
19
19
 
20
- files = FileList["**/*"]
21
- files.exclude /\.DS_Store/
22
- files.exclude /\#/
23
- files.exclude /~/
24
- files.exclude /\.swp/
25
- files.exclude '**/._*'
26
- files.exclude '**/*.orig'
27
- files.exclude '**/*.rej'
28
- files.exclude /^pkg/
29
- files.exclude 'riak-client.gemspec'
30
- files.exclude 'spec/support/test_server.yml'
20
+ gem.files = %W{
21
+ erl_src/riak_kv_test_backend.beam
22
+ erl_src/riak_kv_test_backend.erl
23
+ Gemfile
24
+ lib/active_support/cache/riak_store.rb
25
+ lib/riak/bucket.rb
26
+ lib/riak/cache_store.rb
27
+ lib/riak/client/curb_backend.rb
28
+ lib/riak/client/excon_backend.rb
29
+ lib/riak/client/http_backend.rb
30
+ lib/riak/client/net_http_backend.rb
31
+ lib/riak/client.rb
32
+ lib/riak/core_ext/blank.rb
33
+ lib/riak/core_ext/extract_options.rb
34
+ lib/riak/core_ext/slice.rb
35
+ lib/riak/core_ext/stringify_keys.rb
36
+ lib/riak/core_ext/symbolize_keys.rb
37
+ lib/riak/core_ext/to_param.rb
38
+ lib/riak/core_ext.rb
39
+ lib/riak/failed_request.rb
40
+ lib/riak/i18n.rb
41
+ lib/riak/invalid_response.rb
42
+ lib/riak/link.rb
43
+ lib/riak/locale
44
+ lib/riak/locale/en.yml
45
+ lib/riak/map_reduce.rb
46
+ lib/riak/map_reduce_error.rb
47
+ lib/riak/robject.rb
48
+ lib/riak/search.rb
49
+ lib/riak/test_server.rb
50
+ lib/riak/util/escape.rb
51
+ lib/riak/util/fiber1.8.rb
52
+ lib/riak/util/headers.rb
53
+ lib/riak/util/multipart.rb
54
+ lib/riak/util/tcp_socket_extensions.rb
55
+ lib/riak/util/translation.rb
56
+ lib/riak/walk_spec.rb
57
+ lib/riak.rb
58
+ Rakefile
59
+ riak-client.gemspec
60
+ spec/fixtures/cat.jpg
61
+ spec/fixtures/multipart-blank.txt
62
+ spec/fixtures/multipart-with-body.txt
63
+ spec/integration/riak/cache_store_spec.rb
64
+ spec/integration/riak/test_server_spec.rb
65
+ spec/riak/bucket_spec.rb
66
+ spec/riak/client_spec.rb
67
+ spec/riak/curb_backend_spec.rb
68
+ spec/riak/escape_spec.rb
69
+ spec/riak/excon_backend_spec.rb
70
+ spec/riak/headers_spec.rb
71
+ spec/riak/http_backend_spec.rb
72
+ spec/riak/link_spec.rb
73
+ spec/riak/map_reduce_spec.rb
74
+ spec/riak/multipart_spec.rb
75
+ spec/riak/net_http_backend_spec.rb
76
+ spec/riak/object_spec.rb
77
+ spec/riak/search_spec.rb
78
+ spec/riak/walk_spec_spec.rb
79
+ spec/spec_helper.rb
80
+ spec/support/drb_mock_server.rb
81
+ spec/support/http_backend_implementation_examples.rb
82
+ spec/support/mock_server.rb
83
+ spec/support/mocks.rb
84
+ spec/support/test_server.yml.example
85
+ }
31
86
 
32
- gem.files = files.to_a
33
-
34
- gem.test_files = FileList["spec/**/*.rb"].to_a
87
+ gem.test_files = gem.files.grep(/_spec\.rb$/)
35
88
  end
36
89
 
37
90
  # Gem packaging tasks
data/lib/riak.rb CHANGED
@@ -13,16 +13,25 @@
13
13
  # limitations under the License.
14
14
  $KCODE = "UTF8" if RUBY_VERSION < "1.9"
15
15
 
16
- require 'active_support/all'
17
- require 'active_support/json'
18
- require 'active_support/version'
19
16
  require 'base64'
20
17
  require 'uri'
21
18
  require 'cgi'
19
+ require 'set'
22
20
  require 'net/http'
23
21
  require 'yaml'
24
22
  require 'riak/i18n'
25
23
 
24
+ # Load JSON
25
+ unless defined? JSON
26
+ begin
27
+ require 'yajl/json_gem'
28
+ rescue LoadError
29
+ require 'json'
30
+ end
31
+ end
32
+
33
+ require 'riak/core_ext'
34
+
26
35
  # The Riak module contains all aspects of the HTTP client interface
27
36
  # to Riak.
28
37
  module Riak
@@ -35,9 +44,7 @@ module Riak
35
44
  autoload :MapReduce, "riak/map_reduce"
36
45
 
37
46
  # Cache store - only supports Rails 3 style
38
- if ActiveSupport::VERSION::STRING >= "3.0.0"
39
- autoload :CacheStore, "riak/cache_store"
40
- end
47
+ autoload :CacheStore, "riak/cache_store"
41
48
 
42
49
  # Exceptions
43
50
  autoload :FailedRequest, "riak/failed_request"
data/lib/riak/bucket.rb CHANGED
@@ -41,10 +41,11 @@ module Riak
41
41
  # @return [Bucket] self
42
42
  # @see Client#bucket
43
43
  def load(response={})
44
- unless response.try(:[], :headers).try(:[],'content-type').try(:first) =~ /json$/
44
+ content_type = response[:headers]['content-type'].first rescue ""
45
+ unless content_type =~ /json$/
45
46
  raise Riak::InvalidResponse.new({"content-type" => ["application/json"]}, response[:headers], t("loading_bucket", :name => name))
46
47
  end
47
- payload = ActiveSupport::JSON.decode(response[:body])
48
+ payload = JSON.parse(response[:body])
48
49
  @keys = payload['keys'].map {|k| CGI.unescape(k) } if payload['keys']
49
50
  @props = payload['props'] if payload['props']
50
51
  self
@@ -61,7 +62,8 @@ module Riak
61
62
  def keys(options={})
62
63
  if block_given?
63
64
  @client.http.get(200, @client.prefix, escape(name), {:props => false, :keys => 'stream'}, {}) do |chunk|
64
- obj = ActiveSupport::JSON.decode(chunk) rescue {}
65
+ obj = JSON.parse(chunk) rescue nil
66
+ next unless obj and obj['keys']
65
67
  yield obj['keys'].map {|k| CGI.unescape(k) } if obj['keys']
66
68
  end
67
69
  elsif @keys.nil? || options[:reload]
@@ -97,13 +99,14 @@ module Riak
97
99
  @client.http.put(204, @client.prefix, escape(name), body, {"Content-Type" => "application/json"})
98
100
  @props.merge!(properties)
99
101
  end
102
+ alias properties= props=
100
103
 
101
104
  # @return [Hash] Internal Riak bucket properties.
102
105
  # @see #props=
103
106
  def props
104
107
  @props
105
108
  end
106
- alias_attribute :properties, :props
109
+ alias properties props
107
110
 
108
111
  # Retrieve an object from within the bucket.
109
112
  # @param [String] key the key of the object to retrieve
@@ -12,6 +12,12 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  require 'riak'
15
+ require 'active_support/version'
16
+ if ActiveSupport::VERSION::STRING < "3.0.0"
17
+ raise LoadError, "ActiveSupport 3.0.0 or greater is required to use Riak::CacheStore."
18
+ else
19
+ require 'active_support/cache'
20
+ end
15
21
 
16
22
  module Riak
17
23
  # An ActiveSupport::Cache::Store implementation that uses Riak.
@@ -71,7 +77,7 @@ module Riak
71
77
  begin
72
78
  bucket.get(key).data
73
79
  rescue Riak::FailedRequest => fr
74
- raise fr unless fr.code == 404
80
+ raise fr unless fr.code.to_i == 404
75
81
  nil
76
82
  end
77
83
  end
data/lib/riak/client.rb CHANGED
@@ -24,6 +24,7 @@ module Riak
24
24
  autoload :HTTPBackend, "riak/client/http_backend"
25
25
  autoload :NetHTTPBackend, "riak/client/net_http_backend"
26
26
  autoload :CurbBackend, "riak/client/curb_backend"
27
+ autoload :ExconBackend, "riak/client/excon_backend"
27
28
 
28
29
  # When using integer client IDs, the exclusive upper-bound of valid values.
29
30
  MAX_CLIENT_ID = 4294967296
@@ -49,6 +50,9 @@ module Riak
49
50
  # @return [String] The URL path to the luwak HTTP endpoint
50
51
  attr_accessor :luwak
51
52
 
53
+ # @return [Symbol] The HTTP backend/client to use
54
+ attr_accessor :http_backend
55
+
52
56
  # Creates a client connection to Riak
53
57
  # @param [Hash] options configuration options for the client
54
58
  # @option options [String] :host ('127.0.0.1') The host or IP address for the Riak endpoint
@@ -56,15 +60,19 @@ module Riak
56
60
  # @option options [String] :prefix ('/riak/') The URL path prefix to the main HTTP endpoint
57
61
  # @option options [String] :mapred ('/mapred') The path to the map-reduce HTTP endpoint
58
62
  # @option options [Fixnum, String] :client_id (rand(MAX_CLIENT_ID)) The internal client ID used by Riak to route responses
63
+ # @option options [String, Symbol] :http_backend (:NetHTTP) which HTTP backend to use
59
64
  # @raise [ArgumentError] raised if any options are invalid
60
65
  def initialize(options={})
61
- options.assert_valid_keys(:host, :port, :prefix, :client_id, :mapred, :luwak)
62
- self.host = options[:host] || "127.0.0.1"
63
- self.port = options[:port] || 8098
64
- self.client_id = options[:client_id] || make_client_id
65
- self.prefix = options[:prefix] || "/riak/"
66
- self.mapred = options[:mapred] || "/mapred"
67
- self.luwak = options[:luwak] || "/luwak"
66
+ unless (options.keys - [:host, :port, :prefix, :client_id, :mapred, :luwak, :http_backend]).empty?
67
+ raise ArgumentError, "invalid options"
68
+ end
69
+ self.host = options[:host] || "127.0.0.1"
70
+ self.port = options[:port] || 8098
71
+ self.client_id = options[:client_id] || make_client_id
72
+ self.prefix = options[:prefix] || "/riak/"
73
+ self.mapred = options[:mapred] || "/mapred"
74
+ self.luwak = options[:luwak] || "/luwak"
75
+ self.http_backend = options[:http_backend] || :NetHTTP
68
76
  raise ArgumentError, t("missing_host_and_port") unless @host && @port
69
77
  end
70
78
 
@@ -101,17 +109,24 @@ module Riak
101
109
  @port = value
102
110
  end
103
111
 
112
+ # Sets the desired HTTP backend
113
+ def http_backend=(value)
114
+ @http = nil
115
+ @http_backend = value
116
+ end
117
+
104
118
  # Automatically detects and returns an appropriate HTTP backend.
105
119
  # The HTTP backend is used internally by the Riak client, but can also
106
120
  # be used to access the server directly.
107
121
  # @return [HTTPBackend] the HTTP backend for this client
108
122
  def http
109
123
  @http ||= begin
110
- require 'curb'
111
- CurbBackend.new(self)
112
- rescue LoadError, NameError
113
- warn t("install_curb")
114
- NetHTTPBackend.new(self)
124
+ klass = self.class.const_get("#{@http_backend}Backend")
125
+ if klass.configured?
126
+ klass.new(self)
127
+ else
128
+ raise t('http_configuration', :backend => @http_backend)
129
+ end
115
130
  end
116
131
  end
117
132
 
@@ -122,7 +137,9 @@ module Riak
122
137
  # @option options [Boolean] :props (true) whether to retreive the bucket properties
123
138
  # @return [Bucket] the requested bucket
124
139
  def bucket(name, options={})
125
- options.assert_valid_keys(:keys, :props)
140
+ unless (options.keys - [:keys, :props]).empty?
141
+ raise ArgumentError, "invalid options"
142
+ end
126
143
  response = http.get(200, prefix, escape(name), {:keys => false}.merge(options), {})
127
144
  Bucket.new(self, name).load(response)
128
145
  end
@@ -185,7 +202,7 @@ module Riak
185
202
  http.delete([204,404], luwak, escape(filename))
186
203
  true
187
204
  end
188
-
205
+
189
206
  # Checks whether a file exists in "Luwak".
190
207
  # @param [String] key the key to check
191
208
  # @return [true, false] whether the key exists in "Luwak"
@@ -22,14 +22,21 @@ end
22
22
  module Riak
23
23
  class Client
24
24
  # An HTTP backend for Riak::Client that uses the 'curb' library/gem.
25
- # If the 'curb' library is present, this backend will be preferred to
26
- # the backend based on Net::HTTP.
27
25
  # Conforms to the Riak::Client::HTTPBackend interface.
28
26
  class CurbBackend < HTTPBackend
27
+ def self.configured?
28
+ begin
29
+ require 'curb'
30
+ true
31
+ rescue LoadError
32
+ false
33
+ end
34
+ end
35
+
29
36
  private
30
37
  def perform(method, uri, headers, expect, data=nil)
31
38
  # Setup
32
- curl.headers = create_request_headers(headers)
39
+ curl.headers = RequestHeaders.new(headers).to_a
33
40
  curl.url = uri.to_s
34
41
  response_headers.initialize_http_header(nil)
35
42
  if block_given?
@@ -53,7 +60,7 @@ module Riak
53
60
  # Hacks around limitations in curb's PUT semantics
54
61
  _headers, curl.headers = curl.headers, {}
55
62
  curl.put_data = data
56
- curl.headers = create_request_headers(curl.headers) + _headers
63
+ curl.headers = RequestHeaders.new(curl.headers).to_a + _headers
57
64
  curl.http("PUT")
58
65
  else
59
66
  curl.send("http_#{method}")
@@ -80,20 +87,6 @@ module Riak
80
87
  end
81
88
  end
82
89
  end
83
-
84
- def response_headers
85
- Thread.current[:response_headers] ||= Riak::Util::Headers.new
86
- end
87
-
88
- def create_request_headers(hash)
89
- h = Riak::Util::Headers.new
90
- hash.each {|k,v| h.add_field(k,v) }
91
- [].tap do |arr|
92
- h.each_capitalized do |k,v|
93
- arr << "#{k}: #{v}"
94
- end
95
- end
96
- end
97
90
  end
98
91
  end
99
92
  end
@@ -0,0 +1,60 @@
1
+ # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'riak'
15
+
16
+ module Riak
17
+ class Client
18
+ # An HTTP backend for Riak::Client that uses Wesley Beary's Excon
19
+ # HTTP library. Comforms to the Riak::Client::HTTPBackend
20
+ # interface.
21
+ class ExconBackend < HTTPBackend
22
+ def self.configured?
23
+ begin
24
+ require 'excon'
25
+ Excon::VERSION >= "0.3.4"
26
+ rescue LoadError
27
+ false
28
+ end
29
+ end
30
+
31
+ private
32
+ def perform(method, uri, headers, expect, data=nil, &block)
33
+ params = {
34
+ :method => method.to_s.upcase,
35
+ :headers => RequestHeaders.new(headers).to_hash,
36
+ :path => uri.path
37
+ }
38
+ params[:query] = uri.query if uri.query
39
+ params[:body] = data if [:put,:post].include?(method)
40
+ params[:idempotent] = (method != :post)
41
+
42
+ response = connection.request(params, &block)
43
+ if valid_response?(expect, response.status)
44
+ response_headers.initialize_http_header(response.headers)
45
+ result = {:headers => response_headers.to_hash, :code => response.status}
46
+ if return_body?(method, response.status, block_given?)
47
+ result[:body] = response.body
48
+ end
49
+ result
50
+ else
51
+ raise FailedRequest.new(method, expect, response.status, response.headers, response.body)
52
+ end
53
+ end
54
+
55
+ def connection
56
+ @connection ||= Excon::Connection.new(root_uri.to_s)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -171,7 +171,7 @@ module Riak
171
171
  resource = Array(resource).flatten
172
172
  raise ArgumentError, t("resource_path_short") unless resource.length > 1 || resource.include?(@client.mapred)
173
173
  end
174
-
174
+
175
175
  # Checks the expected response codes against the actual response code. Use internally when
176
176
  # implementing {#perform}.
177
177
  # @param [String, Fixnum, Array<String,Fixnum>] expected the expected response code(s)
@@ -190,7 +190,7 @@ module Riak
190
190
  def return_body?(method, code, has_block)
191
191
  method != :head && !valid_response?([204,205,304], code) && !has_block
192
192
  end
193
-
193
+
194
194
  # Executes requests according to the underlying HTTP client library semantics.
195
195
  # @abstract Subclasses must implement this internal method to perform HTTP requests
196
196
  # according to the API of their HTTP libraries.
@@ -205,6 +205,37 @@ module Riak
205
205
  def perform(method, uri, headers, expect, body=nil)
206
206
  raise NotImplementedError
207
207
  end
208
+
209
+ private
210
+ def response_headers
211
+ Thread.current[:response_headers] ||= Riak::Util::Headers.new
212
+ end
213
+
214
+ # @private
215
+ class RequestHeaders < Riak::Util::Headers
216
+ alias each each_capitalized
217
+
218
+ def initialize(hash)
219
+ initialize_http_header(hash)
220
+ end
221
+
222
+ def to_a
223
+ [].tap do |arr|
224
+ each_capitalized do |k,v|
225
+ arr << "#{k}: #{v}"
226
+ end
227
+ end
228
+ end
229
+
230
+ def to_hash
231
+ {}.tap do |hash|
232
+ each_capitalized do |k,v|
233
+ hash[k] ||= []
234
+ hash[k] << v
235
+ end
236
+ end
237
+ end
238
+ end
208
239
  end
209
240
  end
210
241
  end