seomoz-riak-client 1.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +27 -0
- data/Guardfile +14 -0
- data/Rakefile +76 -0
- data/erl_src/riak_kv_test_backend.beam +0 -0
- data/erl_src/riak_kv_test_backend.erl +174 -0
- data/erl_src/riak_search_test_backend.beam +0 -0
- data/erl_src/riak_search_test_backend.erl +175 -0
- data/lib/active_support/cache/riak_store.rb +2 -0
- data/lib/riak.rb +21 -0
- data/lib/riak/bucket.rb +215 -0
- data/lib/riak/cache_store.rb +84 -0
- data/lib/riak/client.rb +415 -0
- data/lib/riak/client/beefcake/messages.rb +147 -0
- data/lib/riak/client/beefcake/object_methods.rb +92 -0
- data/lib/riak/client/beefcake_protobuffs_backend.rb +176 -0
- data/lib/riak/client/excon_backend.rb +65 -0
- data/lib/riak/client/http_backend.rb +203 -0
- data/lib/riak/client/http_backend/configuration.rb +46 -0
- data/lib/riak/client/http_backend/key_streamer.rb +43 -0
- data/lib/riak/client/http_backend/object_methods.rb +94 -0
- data/lib/riak/client/http_backend/request_headers.rb +34 -0
- data/lib/riak/client/http_backend/transport_methods.rb +218 -0
- data/lib/riak/client/net_http_backend.rb +79 -0
- data/lib/riak/client/protobuffs_backend.rb +97 -0
- data/lib/riak/client/pump.rb +30 -0
- data/lib/riak/client/search.rb +94 -0
- data/lib/riak/core_ext.rb +6 -0
- data/lib/riak/core_ext/blank.rb +53 -0
- data/lib/riak/core_ext/extract_options.rb +7 -0
- data/lib/riak/core_ext/json.rb +15 -0
- data/lib/riak/core_ext/slice.rb +18 -0
- data/lib/riak/core_ext/stringify_keys.rb +10 -0
- data/lib/riak/core_ext/symbolize_keys.rb +10 -0
- data/lib/riak/core_ext/to_param.rb +31 -0
- data/lib/riak/encoding.rb +6 -0
- data/lib/riak/failed_request.rb +81 -0
- data/lib/riak/i18n.rb +3 -0
- data/lib/riak/json.rb +28 -0
- data/lib/riak/link.rb +85 -0
- data/lib/riak/locale/en.yml +48 -0
- data/lib/riak/map_reduce.rb +206 -0
- data/lib/riak/map_reduce/filter_builder.rb +94 -0
- data/lib/riak/map_reduce/phase.rb +98 -0
- data/lib/riak/map_reduce_error.rb +7 -0
- data/lib/riak/robject.rb +290 -0
- data/lib/riak/search.rb +3 -0
- data/lib/riak/serializers.rb +74 -0
- data/lib/riak/stamp.rb +77 -0
- data/lib/riak/test_server.rb +252 -0
- data/lib/riak/util/escape.rb +45 -0
- data/lib/riak/util/fiber1.8.rb +48 -0
- data/lib/riak/util/headers.rb +53 -0
- data/lib/riak/util/multipart.rb +52 -0
- data/lib/riak/util/multipart/stream_parser.rb +62 -0
- data/lib/riak/util/tcp_socket_extensions.rb +58 -0
- data/lib/riak/util/translation.rb +19 -0
- data/lib/riak/walk_spec.rb +105 -0
- data/riak-client.gemspec +55 -0
- data/seomoz-riak-client.gemspec +55 -0
- data/spec/fixtures/cat.jpg +0 -0
- data/spec/fixtures/multipart-blank.txt +7 -0
- data/spec/fixtures/multipart-mapreduce.txt +10 -0
- data/spec/fixtures/multipart-with-body.txt +16 -0
- data/spec/fixtures/server.cert.crt +15 -0
- data/spec/fixtures/server.cert.key +15 -0
- data/spec/fixtures/test.pem +1 -0
- data/spec/integration/riak/cache_store_spec.rb +154 -0
- data/spec/integration/riak/http_backends_spec.rb +58 -0
- data/spec/integration/riak/protobuffs_backends_spec.rb +32 -0
- data/spec/integration/riak/test_server_spec.rb +161 -0
- data/spec/riak/beefcake_protobuffs_backend_spec.rb +59 -0
- data/spec/riak/bucket_spec.rb +205 -0
- data/spec/riak/client_spec.rb +517 -0
- data/spec/riak/core_ext/to_param_spec.rb +15 -0
- data/spec/riak/escape_spec.rb +69 -0
- data/spec/riak/excon_backend_spec.rb +64 -0
- data/spec/riak/headers_spec.rb +38 -0
- data/spec/riak/http_backend/configuration_spec.rb +51 -0
- data/spec/riak/http_backend/object_methods_spec.rb +217 -0
- data/spec/riak/http_backend/transport_methods_spec.rb +117 -0
- data/spec/riak/http_backend_spec.rb +269 -0
- data/spec/riak/link_spec.rb +71 -0
- data/spec/riak/map_reduce/filter_builder_spec.rb +32 -0
- data/spec/riak/map_reduce/phase_spec.rb +136 -0
- data/spec/riak/map_reduce_spec.rb +310 -0
- data/spec/riak/multipart_spec.rb +23 -0
- data/spec/riak/net_http_backend_spec.rb +16 -0
- data/spec/riak/robject_spec.rb +427 -0
- data/spec/riak/search_spec.rb +178 -0
- data/spec/riak/serializers_spec.rb +93 -0
- data/spec/riak/stamp_spec.rb +54 -0
- data/spec/riak/stream_parser_spec.rb +53 -0
- data/spec/riak/walk_spec_spec.rb +195 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/drb_mock_server.rb +39 -0
- data/spec/support/http_backend_implementation_examples.rb +266 -0
- data/spec/support/integration_setup.rb +10 -0
- data/spec/support/mock_server.rb +81 -0
- data/spec/support/mocks.rb +4 -0
- data/spec/support/test_server.yml.example +2 -0
- data/spec/support/unified_backend_examples.rb +255 -0
- metadata +271 -0
@@ -0,0 +1,203 @@
|
|
1
|
+
|
2
|
+
require 'riak/util/escape'
|
3
|
+
require 'riak/util/translation'
|
4
|
+
require 'riak/util/multipart'
|
5
|
+
require 'riak/util/multipart/stream_parser'
|
6
|
+
require 'riak/json'
|
7
|
+
require 'riak/client'
|
8
|
+
require 'riak/bucket'
|
9
|
+
require 'riak/robject'
|
10
|
+
require 'riak/client/http_backend/transport_methods'
|
11
|
+
require 'riak/client/http_backend/object_methods'
|
12
|
+
require 'riak/client/http_backend/configuration'
|
13
|
+
require 'riak/client/http_backend/key_streamer'
|
14
|
+
|
15
|
+
module Riak
|
16
|
+
class Client
|
17
|
+
# The parent class for all backends that connect to Riak via
|
18
|
+
# HTTP. This class implements all of the universal backend API
|
19
|
+
# methods on behalf of subclasses, which need only implement the
|
20
|
+
# {TransportMethods#perform} method for library-specific
|
21
|
+
# semantics.
|
22
|
+
class HTTPBackend
|
23
|
+
include Util::Escape
|
24
|
+
include Util::Translation
|
25
|
+
|
26
|
+
include TransportMethods
|
27
|
+
include ObjectMethods
|
28
|
+
include Configuration
|
29
|
+
|
30
|
+
# The Riak::Client that uses this backend
|
31
|
+
attr_reader :client
|
32
|
+
|
33
|
+
# Create an HTTPBackend for the Riak::Client.
|
34
|
+
# @param [Client] client the client
|
35
|
+
def initialize(client)
|
36
|
+
raise ArgumentError, t("client_type", :client => client) unless Client === client
|
37
|
+
@client = client
|
38
|
+
end
|
39
|
+
|
40
|
+
# Pings the server
|
41
|
+
# @return [true,false] whether the server is available
|
42
|
+
def ping
|
43
|
+
get(200, riak_kv_wm_ping, {}, {})
|
44
|
+
true
|
45
|
+
rescue
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
# Fetches an object by bucket/key
|
50
|
+
# @param [Bucket, String] bucket the bucket where the object is
|
51
|
+
# stored
|
52
|
+
# @param [String] key the key of the object
|
53
|
+
# @param [Fixnum, String, Symbol] r the read quorum for the
|
54
|
+
# request - how many nodes should concur on the read
|
55
|
+
# @return [RObject] the fetched object
|
56
|
+
def fetch_object(bucket, key, r=nil)
|
57
|
+
bucket = Bucket.new(client, bucket) if String === bucket
|
58
|
+
options = r ? {:r => r} : {}
|
59
|
+
response = get([200,300],riak_kv_wm_raw, escape(bucket.name), escape(key), options, {})
|
60
|
+
load_object(RObject.new(bucket, key), response)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Reloads the data for a given RObject, a special case of {#fetch_object}.
|
64
|
+
def reload_object(robject, r = nil)
|
65
|
+
options = r ? {:r => r} : {}
|
66
|
+
response = get([200,300,304], riak_kv_wm_raw, escape(robject.bucket.name), escape(robject.key), options, reload_headers(robject))
|
67
|
+
if response[:code].to_i == 304
|
68
|
+
robject
|
69
|
+
else
|
70
|
+
load_object(robject, response)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Stores an object
|
75
|
+
# @param [RObject] robject the object to store
|
76
|
+
# @param [true,false] returnbody (false) whether to update the object
|
77
|
+
# after write with the new value
|
78
|
+
# @param [Fixnum, String, Symbol] w the write quorum
|
79
|
+
# @param [Fixnum, String, Symbol] dw the durable write quorum
|
80
|
+
def store_object(robject, returnbody=false, w=nil, dw=nil)
|
81
|
+
query = {}.tap do |q|
|
82
|
+
q[:returnbody] = returnbody unless returnbody.nil?
|
83
|
+
q[:w] = w unless w.nil?
|
84
|
+
q[:dw] = dw unless dw.nil?
|
85
|
+
end
|
86
|
+
method, codes, path = if robject.key.present?
|
87
|
+
[:put, [200,204,300], "#{escape(robject.bucket.name)}/#{escape(robject.key)}"]
|
88
|
+
else
|
89
|
+
[:post, 201, escape(robject.bucket.name)]
|
90
|
+
end
|
91
|
+
response = send(method, codes, riak_kv_wm_raw, path, query, robject.raw_data, store_headers(robject))
|
92
|
+
load_object(robject, response) if returnbody
|
93
|
+
end
|
94
|
+
|
95
|
+
# Deletes an object
|
96
|
+
# @param [Bucket, String] bucket the bucket where the object
|
97
|
+
# lives
|
98
|
+
# @param [String] key the key where the object lives
|
99
|
+
# @param [Fixnum, String, Symbol] rw the read/write quorum for
|
100
|
+
# the request
|
101
|
+
def delete_object(bucket, key, rw=nil)
|
102
|
+
bucket = bucket.name if Bucket === bucket
|
103
|
+
options = rw ? {:rw => rw} : {}
|
104
|
+
delete([204, 404], riak_kv_wm_raw, escape(bucket), escape(key), options, {})
|
105
|
+
end
|
106
|
+
|
107
|
+
# Fetches bucket properties
|
108
|
+
# @param [Bucket, String] bucket the bucket properties to fetch
|
109
|
+
# @return [Hash] bucket properties
|
110
|
+
def get_bucket_props(bucket)
|
111
|
+
bucket = bucket.name if Bucket === bucket
|
112
|
+
response = get(200, riak_kv_wm_raw, escape(bucket), {:keys => false, :props => true}, {})
|
113
|
+
JSON.parse(response[:body])['props']
|
114
|
+
end
|
115
|
+
|
116
|
+
# Sets bucket properties
|
117
|
+
# @param [Bucket, String] bucket the bucket to set properties on
|
118
|
+
# @param [Hash] properties the properties to set
|
119
|
+
def set_bucket_props(bucket, props)
|
120
|
+
bucket = bucket.name if Bucket === bucket
|
121
|
+
body = {'props' => props}.to_json
|
122
|
+
put(204, riak_kv_wm_raw, escape(bucket), body, {"Content-Type" => "application/json"})
|
123
|
+
end
|
124
|
+
|
125
|
+
# List keys in a bucket
|
126
|
+
# @param [Bucket, String] bucket the bucket to fetch the keys
|
127
|
+
# for
|
128
|
+
# @yield [Array<String>] a list of keys from the current
|
129
|
+
# streamed chunk
|
130
|
+
# @return [Array<String>] the list of keys, if no block was given
|
131
|
+
def list_keys(bucket, &block)
|
132
|
+
bucket = bucket.name if Bucket === bucket
|
133
|
+
if block_given?
|
134
|
+
get(200, riak_kv_wm_raw, escape(bucket), {:props => false, :keys => 'stream'}, {}, &KeyStreamer.new(block))
|
135
|
+
else
|
136
|
+
response = get(200, riak_kv_wm_raw, escape(bucket), {:props => false, :keys => true}, {})
|
137
|
+
obj = JSON.parse(response[:body])
|
138
|
+
obj && obj['keys'].map {|k| unescape(k) }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Lists known buckets
|
143
|
+
# @return [Array<String>] the list of buckets
|
144
|
+
def list_buckets
|
145
|
+
response = get(200, riak_kv_wm_raw, {:buckets => true}, {})
|
146
|
+
JSON.parse(response[:body])['buckets']
|
147
|
+
end
|
148
|
+
|
149
|
+
# Performs a MapReduce query.
|
150
|
+
# @param [MapReduce] mr the query to perform
|
151
|
+
# @yield [Fixnum, Object] the phase number and single result
|
152
|
+
# from the phase
|
153
|
+
# @return [Array<Object>] the list of results, if no block was
|
154
|
+
# given
|
155
|
+
def mapred(mr)
|
156
|
+
if block_given?
|
157
|
+
parser = Riak::Util::Multipart::StreamParser.new do |response|
|
158
|
+
result = JSON.parse(response[:body])
|
159
|
+
yield result['phase'], result['data']
|
160
|
+
end
|
161
|
+
post(200, riak_kv_wm_mapred, {:chunked => true}, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"}, &parser)
|
162
|
+
nil
|
163
|
+
else
|
164
|
+
response = post(200, riak_kv_wm_mapred, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"})
|
165
|
+
begin
|
166
|
+
JSON.parse(response[:body])
|
167
|
+
rescue
|
168
|
+
response
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Gets health statistics
|
174
|
+
# @return [Hash] information about the server, including stats
|
175
|
+
def stats
|
176
|
+
response = get(200, riak_kv_wm_stats, {}, {})
|
177
|
+
JSON.parse(response[:body])
|
178
|
+
end
|
179
|
+
|
180
|
+
# Performs a link-walking query
|
181
|
+
# @param [RObject] robject the object to start at
|
182
|
+
# @param [Array<WalkSpec>] walk_specs a list of walk
|
183
|
+
# specifications to process
|
184
|
+
# @return [Array<Array<RObject>>] a list of the matched objects,
|
185
|
+
# grouped by phase
|
186
|
+
def link_walk(robject, walk_specs)
|
187
|
+
response = get(200, riak_kv_wm_link_walker, escape(robject.bucket.name), escape(robject.key), walk_specs.join("/"))
|
188
|
+
if boundary = Util::Multipart.extract_boundary(response[:headers]['content-type'].first)
|
189
|
+
Util::Multipart.parse(response[:body], boundary).map do |group|
|
190
|
+
group.map do |obj|
|
191
|
+
if obj[:headers] && obj[:body] && obj[:headers]['location']
|
192
|
+
bucket = $1 if obj[:headers]['location'].first =~ %r{/.*/(.*)/.*$}
|
193
|
+
load_object(RObject.new(client.bucket(bucket), nil), obj)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
else
|
198
|
+
[]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
require 'riak/failed_request'
|
3
|
+
require 'riak/client/http_backend'
|
4
|
+
require 'riak/link'
|
5
|
+
|
6
|
+
module Riak
|
7
|
+
class Client
|
8
|
+
class HTTPBackend
|
9
|
+
# Riak 0.14 provides a root URL that enumerates all of the
|
10
|
+
# HTTP endpoints and their paths. This module adds methods to
|
11
|
+
# auto-discover those endpoints via the root URL.
|
12
|
+
module Configuration
|
13
|
+
private
|
14
|
+
def server_config
|
15
|
+
@server_config ||= {}.tap do |hash|
|
16
|
+
begin
|
17
|
+
response = get(200, "/", {}, {})
|
18
|
+
Link.parse(response[:headers]['link'].first).each {|l| hash[l.tag.intern] = l.url }
|
19
|
+
rescue Riak::FailedRequest
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def riak_kv_wm_raw
|
25
|
+
server_config[:riak_kv_wm_raw] || client.prefix
|
26
|
+
end
|
27
|
+
|
28
|
+
def riak_kv_wm_link_walker
|
29
|
+
server_config[:riak_kv_wm_link_walker] || client.prefix
|
30
|
+
end
|
31
|
+
|
32
|
+
def riak_kv_wm_mapred
|
33
|
+
server_config[:riak_kv_wm_mapred] || client.mapred
|
34
|
+
end
|
35
|
+
|
36
|
+
def riak_kv_wm_ping
|
37
|
+
server_config[:riak_kv_wm_ping] || "/ping"
|
38
|
+
end
|
39
|
+
|
40
|
+
def riak_kv_wm_stats
|
41
|
+
server_config[:riak_kv_wm_stats] || "/stats"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'riak/util/escape'
|
2
|
+
require 'riak/json'
|
3
|
+
|
4
|
+
module Riak
|
5
|
+
class Client
|
6
|
+
class HTTPBackend
|
7
|
+
# @private
|
8
|
+
class KeyStreamer
|
9
|
+
include Util::Escape
|
10
|
+
|
11
|
+
def initialize(block)
|
12
|
+
@buffer = ""
|
13
|
+
@block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def accept(chunk)
|
17
|
+
@buffer << chunk
|
18
|
+
consume
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_proc
|
22
|
+
method(:accept).to_proc
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def consume
|
27
|
+
while @buffer =~ /\}\{/
|
28
|
+
stream($~.pre_match + '}')
|
29
|
+
@buffer = '{' + $~.post_match
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def stream(str)
|
34
|
+
obj = JSON.parse(str) rescue nil
|
35
|
+
if obj && obj['keys']
|
36
|
+
@block.call obj['keys'].map(&method(:unescape))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'set'
|
3
|
+
require 'time'
|
4
|
+
require 'riak/client/http_backend'
|
5
|
+
require 'riak/robject'
|
6
|
+
require 'riak/link'
|
7
|
+
require 'riak/util/multipart'
|
8
|
+
|
9
|
+
module Riak
|
10
|
+
class Client
|
11
|
+
class HTTPBackend
|
12
|
+
# Methods for assisting with the handling of {RObject}s present
|
13
|
+
# in HTTP requests and responses.
|
14
|
+
module ObjectMethods
|
15
|
+
# HTTP header hash that will be sent along when reloading the object
|
16
|
+
# @return [Hash] hash of HTTP headers
|
17
|
+
def reload_headers(robject)
|
18
|
+
{}.tap do |h|
|
19
|
+
h['If-None-Match'] = robject.etag if robject.etag.present?
|
20
|
+
h['If-Modified-Since'] = robject.last_modified.httpdate if robject.last_modified.present?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# HTTP header hash that will be sent along when storing the object
|
25
|
+
# @return [Hash] hash of HTTP Headers
|
26
|
+
def store_headers(robject)
|
27
|
+
{}.tap do |hash|
|
28
|
+
hash["Content-Type"] = robject.content_type
|
29
|
+
hash["X-Riak-Vclock"] = robject.vclock if robject.vclock
|
30
|
+
if robject.prevent_stale_writes && robject.etag.present?
|
31
|
+
hash["If-Match"] = robject.etag
|
32
|
+
elsif robject.prevent_stale_writes
|
33
|
+
hash["If-None-Match"] = "*"
|
34
|
+
end
|
35
|
+
unless robject.links.blank?
|
36
|
+
hash["Link"] = robject.links.reject {|l| l.rel == "up" }.map(&:to_s).join(", ")
|
37
|
+
end
|
38
|
+
unless robject.meta.blank?
|
39
|
+
robject.meta.each do |k,v|
|
40
|
+
hash["X-Riak-Meta-#{k}"] = v.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Load object data from an HTTP response
|
47
|
+
# @param [Hash] response a response from {Riak::Client::HTTPBackend}
|
48
|
+
def load_object(robject, response)
|
49
|
+
extract_header(robject, response, "location", :key) {|v| URI.unescape(v.match(%r{.*/(.*?)(\?.*)?$})[1]) }
|
50
|
+
extract_header(robject, response, "content-type", :content_type)
|
51
|
+
extract_header(robject, response, "x-riak-vclock", :vclock)
|
52
|
+
extract_header(robject, response, "link", :links) {|v| Set.new(Link.parse(v)) }
|
53
|
+
extract_header(robject, response, "etag", :etag)
|
54
|
+
extract_header(robject, response, "last-modified", :last_modified) {|v| Time.httpdate(v) }
|
55
|
+
robject.meta = response[:headers].inject({}) do |h,(k,v)|
|
56
|
+
if k =~ /x-riak-meta-(.*)/
|
57
|
+
h[$1] = v
|
58
|
+
end
|
59
|
+
h
|
60
|
+
end
|
61
|
+
robject.conflict = (response[:code] && response[:code].to_i == 300 && robject.content_type =~ /multipart\/mixed/)
|
62
|
+
robject.siblings = robject.conflict? ? extract_siblings(robject, response[:body]) : nil
|
63
|
+
robject.raw_data = response[:body] if response[:body].present? && !robject.conflict?
|
64
|
+
|
65
|
+
robject.conflict? ? robject.attempt_conflict_resolution : robject
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def extract_header(robject, response, name, attribute=nil, &block)
|
70
|
+
extract_if_present(robject, response[:headers], name, attribute) do |value|
|
71
|
+
block ? block.call(value[0]) : value[0]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def extract_if_present(robject, hash, key, attribute=nil)
|
76
|
+
if hash[key].present?
|
77
|
+
attribute ||= key
|
78
|
+
value = block_given? ? yield(hash[key]) : hash[key]
|
79
|
+
robject.send("#{attribute}=", value)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def extract_siblings(robject, data)
|
84
|
+
Util::Multipart.parse(data, Util::Multipart.extract_boundary(robject.content_type)).map do |part|
|
85
|
+
RObject.new(robject.bucket, robject.key) do |sibling|
|
86
|
+
load_object(sibling, part)
|
87
|
+
sibling.vclock = robject.vclock
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require 'riak/util/headers'
|
3
|
+
|
4
|
+
module Riak
|
5
|
+
class Client
|
6
|
+
class HTTPBackend
|
7
|
+
# @private
|
8
|
+
class RequestHeaders < Riak::Util::Headers
|
9
|
+
alias each each_capitalized
|
10
|
+
|
11
|
+
def initialize(hash)
|
12
|
+
initialize_http_header(hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_a
|
16
|
+
[].tap do |arr|
|
17
|
+
each_capitalized do |k,v|
|
18
|
+
arr << "#{k}: #{v}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
{}.tap do |hash|
|
25
|
+
each_capitalized do |k,v|
|
26
|
+
hash[k] ||= []
|
27
|
+
hash[k] << v
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
|
2
|
+
require 'base64'
|
3
|
+
require 'uri'
|
4
|
+
require 'riak/client'
|
5
|
+
require 'riak/util/headers'
|
6
|
+
|
7
|
+
module Riak
|
8
|
+
class Client
|
9
|
+
class HTTPBackend
|
10
|
+
# Methods related to performing HTTP requests in a consistent
|
11
|
+
# fashion across multiple client libraries. HTTP/1.1 verbs are
|
12
|
+
# presented as methods.
|
13
|
+
module TransportMethods
|
14
|
+
# Performs a HEAD request to the specified resource on the Riak server.
|
15
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
16
|
+
# @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
|
17
|
+
# @overload head(expect, *resource)
|
18
|
+
# @overload head(expect, *resource, headers)
|
19
|
+
# Send the request with custom headers
|
20
|
+
# @param [Hash] headers custom headers to send with the request
|
21
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
22
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
23
|
+
def head(expect, *resource)
|
24
|
+
headers = default_headers.merge(resource.extract_options!)
|
25
|
+
verify_path!(resource)
|
26
|
+
perform(:head, path(*resource), headers, expect)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Performs a GET request to the specified resource on the Riak server.
|
30
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
31
|
+
# @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
|
32
|
+
# @overload get(expect, *resource)
|
33
|
+
# @overload get(expect, *resource, headers)
|
34
|
+
# Send the request with custom headers
|
35
|
+
# @param [Hash] headers custom headers to send with the request
|
36
|
+
# @overload get(expect, *resource, headers={})
|
37
|
+
# Stream the response body through the supplied block
|
38
|
+
# @param [Hash] headers custom headers to send with the request
|
39
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
40
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
41
|
+
# @return [Hash] response data, containing :headers, :body, and :code keys
|
42
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
43
|
+
def get(expect, *resource, &block)
|
44
|
+
headers = default_headers.merge(resource.extract_options!)
|
45
|
+
verify_path!(resource)
|
46
|
+
perform(:get, path(*resource), headers, expect, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Performs a PUT request to the specified resource on the Riak server.
|
50
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
51
|
+
# @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
|
52
|
+
# @param [String] body the request body to send to the server
|
53
|
+
# @overload put(expect, *resource, body)
|
54
|
+
# @overload put(expect, *resource, body, headers)
|
55
|
+
# Send the request with custom headers
|
56
|
+
# @param [Hash] headers custom headers to send with the request
|
57
|
+
# @overload put(expect, *resource, body, headers={})
|
58
|
+
# Stream the response body through the supplied block
|
59
|
+
# @param [Hash] headers custom headers to send with the request
|
60
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
61
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
62
|
+
# @return [Hash] response data, containing :headers, :code, and :body keys
|
63
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
64
|
+
def put(expect, *resource, &block)
|
65
|
+
headers = default_headers.merge(resource.extract_options!)
|
66
|
+
uri, data = verify_path_and_body!(resource)
|
67
|
+
perform(:put, path(*uri), headers, expect, data, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Performs a POST request to the specified resource on the Riak server.
|
71
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
72
|
+
# @param [String, Array<String>] resource a relative path or array of path segments that will be joined to the root URI
|
73
|
+
# @param [String] body the request body to send to the server
|
74
|
+
# @overload post(expect, *resource, body)
|
75
|
+
# @overload post(expect, *resource, body, headers)
|
76
|
+
# Send the request with custom headers
|
77
|
+
# @param [Hash] headers custom headers to send with the request
|
78
|
+
# @overload post(expect, *resource, body, headers={})
|
79
|
+
# Stream the response body through the supplied block
|
80
|
+
# @param [Hash] headers custom headers to send with the request
|
81
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
82
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
83
|
+
# @return [Hash] response data, containing :headers, :code and :body keys
|
84
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
85
|
+
def post(expect, *resource, &block)
|
86
|
+
headers = default_headers.merge(resource.extract_options!)
|
87
|
+
uri, data = verify_path_and_body!(resource)
|
88
|
+
perform(:post, path(*uri), headers, expect, data, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Performs a DELETE request to the specified resource on the Riak server.
|
92
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
93
|
+
# @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
|
94
|
+
# @overload delete(expect, *resource)
|
95
|
+
# @overload delete(expect, *resource, headers)
|
96
|
+
# Send the request with custom headers
|
97
|
+
# @param [Hash] headers custom headers to send with the request
|
98
|
+
# @overload delete(expect, *resource, headers={})
|
99
|
+
# Stream the response body through the supplied block
|
100
|
+
# @param [Hash] headers custom headers to send with the request
|
101
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
102
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
103
|
+
# @return [Hash] response data, containing :headers, :code and :body keys
|
104
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
105
|
+
def delete(expect, *resource, &block)
|
106
|
+
headers = default_headers.merge(resource.extract_options!)
|
107
|
+
verify_path!(resource)
|
108
|
+
perform(:delete, path(*resource), headers, expect, &block)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Executes requests according to the underlying HTTP client library semantics.
|
112
|
+
# @abstract Subclasses must implement this internal method to perform HTTP requests
|
113
|
+
# according to the API of their HTTP libraries.
|
114
|
+
# @param [Symbol] method one of :head, :get, :post, :put, :delete
|
115
|
+
# @param [URI] uri the HTTP URI to request
|
116
|
+
# @param [Hash] headers headers to send along with the request
|
117
|
+
# @param [Fixnum, Array] expect the expected response code(s)
|
118
|
+
# @param [String, #read] body the PUT or POST request body
|
119
|
+
# @return [Hash] response data, containing :headers, :code and :body keys. Only :headers and :code should be present when the body is streamed or the method is :head.
|
120
|
+
# @yield [chunk] if the method is not :head, successive chunks of the response body will be yielded as strings
|
121
|
+
# @raise [NotImplementedError] if a subclass does not implement this method
|
122
|
+
def perform(method, uri, headers, expect, body=nil)
|
123
|
+
raise NotImplementedError
|
124
|
+
end
|
125
|
+
|
126
|
+
# Default header hash sent with every request, based on settings in the client
|
127
|
+
# @return [Hash] headers that will be merged with user-specified headers on every request
|
128
|
+
def default_headers
|
129
|
+
{
|
130
|
+
"Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5",
|
131
|
+
"X-Riak-ClientId" => client_id
|
132
|
+
}.merge(basic_auth_header)
|
133
|
+
end
|
134
|
+
|
135
|
+
def client_id
|
136
|
+
value = @client.client_id
|
137
|
+
case value
|
138
|
+
when Integer
|
139
|
+
b64encode(value)
|
140
|
+
when String
|
141
|
+
value
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def basic_auth_header
|
146
|
+
@client.basic_auth ? {"Authorization" => "Basic #{Base64::encode64(@client.basic_auth)}"} : {}
|
147
|
+
end
|
148
|
+
|
149
|
+
# @return [URI] The calculated root URI for the Riak HTTP endpoint
|
150
|
+
def root_uri
|
151
|
+
protocol = client.ssl_enabled? ? "https" : "http"
|
152
|
+
URI.parse("#{protocol}://#{client.host}:#{client.http_port}")
|
153
|
+
end
|
154
|
+
|
155
|
+
# Calculates an absolute URI from a relative path specification
|
156
|
+
# @param [Array<String,Hash>] segments a relative path or sequence of path segments and optional query params Hash that will be joined to the root URI
|
157
|
+
# @return [URI] an absolute URI for the resource
|
158
|
+
def path(*segments)
|
159
|
+
query = segments.extract_options!.to_param
|
160
|
+
root_uri.merge(segments.join("/").gsub(/\/+/, "/").sub(/^\//, '')).tap do |uri|
|
161
|
+
uri.query = query if query.present?
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Verifies that both a resource path and body are present in the arguments
|
166
|
+
# @param [Array] args the arguments to verify
|
167
|
+
# @raise [ArgumentError] if the body or resource is missing, or if the body is not a String
|
168
|
+
def verify_path_and_body!(args)
|
169
|
+
body = args.pop
|
170
|
+
begin
|
171
|
+
verify_path!(args)
|
172
|
+
rescue ArgumentError
|
173
|
+
raise ArgumentError, t("path_and_body_required")
|
174
|
+
end
|
175
|
+
|
176
|
+
raise ArgumentError, t("request_body_type") unless String === body || body.respond_to?(:read)
|
177
|
+
[args, body]
|
178
|
+
end
|
179
|
+
|
180
|
+
# Verifies that the specified resource is valid
|
181
|
+
# @param [String, Array] resource the resource specification
|
182
|
+
# @raise [ArgumentError] if the resource path is too short
|
183
|
+
def verify_path!(resource)
|
184
|
+
resource = Array(resource).flatten
|
185
|
+
raise ArgumentError, t("resource_path_short") unless resource.length > 1 || resource.include?(@client.mapred)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Checks the expected response codes against the actual response code. Use internally when
|
189
|
+
# implementing {#perform}.
|
190
|
+
# @param [String, Fixnum, Array<String,Fixnum>] expected the expected response code(s)
|
191
|
+
# @param [String, Fixnum] actual the received response code
|
192
|
+
# @return [Boolean] whether the actual response code is acceptable given the expectations
|
193
|
+
def valid_response?(expected, actual)
|
194
|
+
Array(expected).map(&:to_i).include?(actual.to_i)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Checks whether a combination of the HTTP method, response code, and block should
|
198
|
+
# result in returning the :body in the response hash. Use internally when implementing {#perform}.
|
199
|
+
# @param [Symbol] method the HTTP method
|
200
|
+
# @param [String, Fixnum] code the received response code
|
201
|
+
# @param [Boolean] has_block whether a streaming block was passed to {#perform}. Pass block_given? to this parameter.
|
202
|
+
# @return [Boolean] whether to return the body in the response hash
|
203
|
+
def return_body?(method, code, has_block)
|
204
|
+
method != :head && !valid_response?([204,205,304], code) && !has_block
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
def response_headers
|
209
|
+
Thread.current[:response_headers] ||= Riak::Util::Headers.new
|
210
|
+
end
|
211
|
+
|
212
|
+
def b64encode(n)
|
213
|
+
Base64.encode64([n].pack("N")).chomp
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|