riak-client 0.8.3 → 0.9.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
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
+ class HTTPBackend
19
+ # Riak 0.14 provides a root URL that enumerates all of the
20
+ # HTTP endpoints and their paths. This module adds methods to
21
+ # auto-discover those endpoints via the root URL.
22
+ module Configuration
23
+ private
24
+ def server_config
25
+ @server_config ||= {}.tap do |hash|
26
+ begin
27
+ response = get(200, "/", {}, {})
28
+ Link.parse(response[:headers]['link'].first).each {|l| hash[l.tag.intern] = l.url }
29
+ rescue Riak::FailedRequest
30
+ end
31
+ end
32
+ end
33
+
34
+ def riak_kv_wm_raw
35
+ server_config[:riak_kv_wm_raw] || client.prefix
36
+ end
37
+
38
+ def riak_kv_wm_link_walker
39
+ server_config[:riak_kv_wm_link_walker] || client.prefix
40
+ end
41
+
42
+ def riak_kv_wm_mapred
43
+ server_config[:riak_kv_wm_mapred] || client.mapred
44
+ end
45
+
46
+ def riak_kv_wm_ping
47
+ server_config[:riak_kv_wm_ping] || "/ping"
48
+ end
49
+
50
+ def riak_kv_wm_stats
51
+ server_config[:riak_kv_wm_stats] || "/stats"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,101 @@
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
+ require 'time'
16
+
17
+ module Riak
18
+ class Client
19
+ class HTTPBackend
20
+ # Methods for assisting with the handling of {RObject}s present
21
+ # in HTTP requests and responses.
22
+ module ObjectMethods
23
+ # HTTP header hash that will be sent along when reloading the object
24
+ # @return [Hash] hash of HTTP headers
25
+ def reload_headers(robject)
26
+ {}.tap do |h|
27
+ h['If-None-Match'] = robject.etag if robject.etag.present?
28
+ h['If-Modified-Since'] = robject.last_modified.httpdate if robject.last_modified.present?
29
+ end
30
+ end
31
+
32
+ # HTTP header hash that will be sent along when storing the object
33
+ # @return [Hash] hash of HTTP Headers
34
+ def store_headers(robject)
35
+ {}.tap do |hash|
36
+ hash["Content-Type"] = robject.content_type
37
+ hash["X-Riak-Vclock"] = robject.vclock if robject.vclock
38
+ if robject.prevent_stale_writes && robject.etag.present?
39
+ hash["If-Match"] = robject.etag
40
+ elsif robject.prevent_stale_writes
41
+ hash["If-None-Match"] = "*"
42
+ end
43
+ unless robject.links.blank?
44
+ hash["Link"] = robject.links.reject {|l| l.rel == "up" }.map(&:to_s).join(", ")
45
+ end
46
+ unless robject.meta.blank?
47
+ robject.meta.each do |k,v|
48
+ hash["X-Riak-Meta-#{k}"] = v.to_s
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # Load object data from an HTTP response
55
+ # @param [Hash] response a response from {Riak::Client::HTTPBackend}
56
+ def load_object(robject, response)
57
+ extract_header(robject, response, "location", :key) {|v| URI.unescape(v.split("/").last) }
58
+ extract_header(robject, response, "content-type", :content_type)
59
+ extract_header(robject, response, "x-riak-vclock", :vclock)
60
+ extract_header(robject, response, "link", :links) {|v| Set.new(Link.parse(v)) }
61
+ extract_header(robject, response, "etag", :etag)
62
+ extract_header(robject, response, "last-modified", :last_modified) {|v| Time.httpdate(v) }
63
+ robject.meta = response[:headers].inject({}) do |h,(k,v)|
64
+ if k =~ /x-riak-meta-(.*)/
65
+ h[$1] = v
66
+ end
67
+ h
68
+ end
69
+ robject.conflict = (response[:code] && response[:code].to_i == 300 && robject.content_type =~ /multipart\/mixed/)
70
+ robject.siblings = robject.conflict? ? extract_siblings(robject, response[:body]) : nil
71
+ robject.raw_data = response[:body] if response[:body].present? && !robject.conflict?
72
+ robject
73
+ end
74
+
75
+ private
76
+ def extract_header(robject, response, name, attribute=nil, &block)
77
+ extract_if_present(robject, response[:headers], name, attribute) do |value|
78
+ block ? block.call(value[0]) : value[0]
79
+ end
80
+ end
81
+
82
+ def extract_if_present(robject, hash, key, attribute=nil)
83
+ if hash[key].present?
84
+ attribute ||= key
85
+ value = block_given? ? yield(hash[key]) : hash[key]
86
+ robject.send("#{attribute}=", value)
87
+ end
88
+ end
89
+
90
+ def extract_siblings(robject, data)
91
+ Util::Multipart.parse(data, Util::Multipart.extract_boundary(robject.content_type)).map do |part|
92
+ RObject.new(robject.bucket, robject.key) do |sibling|
93
+ load_object(sibling, part)
94
+ sibling.vclock = robject.vclock
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,46 @@
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
+ class HTTPBackend
19
+ # @private
20
+ class RequestHeaders < Riak::Util::Headers
21
+ alias each each_capitalized
22
+
23
+ def initialize(hash)
24
+ initialize_http_header(hash)
25
+ end
26
+
27
+ def to_a
28
+ [].tap do |arr|
29
+ each_capitalized do |k,v|
30
+ arr << "#{k}: #{v}"
31
+ end
32
+ end
33
+ end
34
+
35
+ def to_hash
36
+ {}.tap do |hash|
37
+ each_capitalized do |k,v|
38
+ hash[k] ||= []
39
+ hash[k] << v
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,208 @@
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
+ class HTTPBackend
19
+ # Methods related to performing HTTP requests in a consistent
20
+ # fashion across multiple client libraries. HTTP/1.1 verbs are
21
+ # presented as methods.
22
+ module TransportMethods
23
+ # Performs a HEAD request to the specified resource on the Riak server.
24
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
25
+ # @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
26
+ # @overload head(expect, *resource)
27
+ # @overload head(expect, *resource, headers)
28
+ # Send the request with custom headers
29
+ # @param [Hash] headers custom headers to send with the request
30
+ # @return [Hash] response data, containing only the :headers and :code keys
31
+ # @raise [FailedRequest] if the response code doesn't match the expected response
32
+ def head(expect, *resource)
33
+ headers = default_headers.merge(resource.extract_options!)
34
+ verify_path!(resource)
35
+ perform(:head, path(*resource), headers, expect)
36
+ end
37
+
38
+ # Performs a GET request to the specified resource on the Riak server.
39
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
40
+ # @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
41
+ # @overload get(expect, *resource)
42
+ # @overload get(expect, *resource, headers)
43
+ # Send the request with custom headers
44
+ # @param [Hash] headers custom headers to send with the request
45
+ # @overload get(expect, *resource, headers={})
46
+ # Stream the response body through the supplied block
47
+ # @param [Hash] headers custom headers to send with the request
48
+ # @yield [chunk] yields successive chunks of the response body as strings
49
+ # @return [Hash] response data, containing only the :headers and :code keys
50
+ # @return [Hash] response data, containing :headers, :body, and :code keys
51
+ # @raise [FailedRequest] if the response code doesn't match the expected response
52
+ def get(expect, *resource, &block)
53
+ headers = default_headers.merge(resource.extract_options!)
54
+ verify_path!(resource)
55
+ perform(:get, path(*resource), headers, expect, &block)
56
+ end
57
+
58
+ # Performs a PUT request to the specified resource on the Riak server.
59
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
60
+ # @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
61
+ # @param [String] body the request body to send to the server
62
+ # @overload put(expect, *resource, body)
63
+ # @overload put(expect, *resource, body, headers)
64
+ # Send the request with custom headers
65
+ # @param [Hash] headers custom headers to send with the request
66
+ # @overload put(expect, *resource, body, headers={})
67
+ # Stream the response body through the supplied block
68
+ # @param [Hash] headers custom headers to send with the request
69
+ # @yield [chunk] yields successive chunks of the response body as strings
70
+ # @return [Hash] response data, containing only the :headers and :code keys
71
+ # @return [Hash] response data, containing :headers, :code, and :body keys
72
+ # @raise [FailedRequest] if the response code doesn't match the expected response
73
+ def put(expect, *resource, &block)
74
+ headers = default_headers.merge(resource.extract_options!)
75
+ uri, data = verify_path_and_body!(resource)
76
+ perform(:put, path(*uri), headers, expect, data, &block)
77
+ end
78
+
79
+ # Performs a POST request to the specified resource on the Riak server.
80
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
81
+ # @param [String, Array<String>] resource a relative path or array of path segments that will be joined to the root URI
82
+ # @param [String] body the request body to send to the server
83
+ # @overload post(expect, *resource, body)
84
+ # @overload post(expect, *resource, body, headers)
85
+ # Send the request with custom headers
86
+ # @param [Hash] headers custom headers to send with the request
87
+ # @overload post(expect, *resource, body, headers={})
88
+ # Stream the response body through the supplied block
89
+ # @param [Hash] headers custom headers to send with the request
90
+ # @yield [chunk] yields successive chunks of the response body as strings
91
+ # @return [Hash] response data, containing only the :headers and :code keys
92
+ # @return [Hash] response data, containing :headers, :code and :body keys
93
+ # @raise [FailedRequest] if the response code doesn't match the expected response
94
+ def post(expect, *resource, &block)
95
+ headers = default_headers.merge(resource.extract_options!)
96
+ uri, data = verify_path_and_body!(resource)
97
+ perform(:post, path(*uri), headers, expect, data, &block)
98
+ end
99
+
100
+ # Performs a DELETE request to the specified resource on the Riak server.
101
+ # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
102
+ # @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
103
+ # @overload delete(expect, *resource)
104
+ # @overload delete(expect, *resource, headers)
105
+ # Send the request with custom headers
106
+ # @param [Hash] headers custom headers to send with the request
107
+ # @overload delete(expect, *resource, headers={})
108
+ # Stream the response body through the supplied block
109
+ # @param [Hash] headers custom headers to send with the request
110
+ # @yield [chunk] yields successive chunks of the response body as strings
111
+ # @return [Hash] response data, containing only the :headers and :code keys
112
+ # @return [Hash] response data, containing :headers, :code and :body keys
113
+ # @raise [FailedRequest] if the response code doesn't match the expected response
114
+ def delete(expect, *resource, &block)
115
+ headers = default_headers.merge(resource.extract_options!)
116
+ verify_path!(resource)
117
+ perform(:delete, path(*resource), headers, expect, &block)
118
+ end
119
+
120
+ # Executes requests according to the underlying HTTP client library semantics.
121
+ # @abstract Subclasses must implement this internal method to perform HTTP requests
122
+ # according to the API of their HTTP libraries.
123
+ # @param [Symbol] method one of :head, :get, :post, :put, :delete
124
+ # @param [URI] uri the HTTP URI to request
125
+ # @param [Hash] headers headers to send along with the request
126
+ # @param [Fixnum, Array] expect the expected response code(s)
127
+ # @param [String, IO] body the PUT or POST request body
128
+ # @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.
129
+ # @yield [chunk] if the method is not :head, successive chunks of the response body will be yielded as strings
130
+ # @raise [NotImplementedError] if a subclass does not implement this method
131
+ def perform(method, uri, headers, expect, body=nil)
132
+ raise NotImplementedError
133
+ end
134
+
135
+ # Default header hash sent with every request, based on settings in the client
136
+ # @return [Hash] headers that will be merged with user-specified headers on every request
137
+ def default_headers
138
+ {
139
+ "Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5",
140
+ "X-Riak-ClientId" => @client.client_id
141
+ }
142
+ end
143
+
144
+ # @return [URI] The calculated root URI for the Riak HTTP endpoint
145
+ def root_uri
146
+ URI.parse("http://#{client.host}:#{client.port}")
147
+ end
148
+
149
+ # Calculates an absolute URI from a relative path specification
150
+ # @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
151
+ # @return [URI] an absolute URI for the resource
152
+ def path(*segments)
153
+ query = segments.extract_options!.to_param
154
+ root_uri.merge(segments.join("/").gsub(/\/+/, "/").sub(/^\//, '')).tap do |uri|
155
+ uri.query = query if query.present?
156
+ end
157
+ end
158
+
159
+ # Verifies that both a resource path and body are present in the arguments
160
+ # @param [Array] args the arguments to verify
161
+ # @raise [ArgumentError] if the body or resource is missing, or if the body is not a String
162
+ def verify_path_and_body!(args)
163
+ body = args.pop
164
+ begin
165
+ verify_path!(args)
166
+ rescue ArgumentError
167
+ raise ArgumentError, t("path_and_body_required")
168
+ end
169
+
170
+ raise ArgumentError, t("request_body_type") unless String === body || IO === body
171
+ [args, body]
172
+ end
173
+
174
+ # Verifies that the specified resource is valid
175
+ # @param [String, Array] resource the resource specification
176
+ # @raise [ArgumentError] if the resource path is too short
177
+ def verify_path!(resource)
178
+ resource = Array(resource).flatten
179
+ raise ArgumentError, t("resource_path_short") unless resource.length > 1 || resource.include?(@client.mapred)
180
+ end
181
+
182
+ # Checks the expected response codes against the actual response code. Use internally when
183
+ # implementing {#perform}.
184
+ # @param [String, Fixnum, Array<String,Fixnum>] expected the expected response code(s)
185
+ # @param [String, Fixnum] actual the received response code
186
+ # @return [Boolean] whether the actual response code is acceptable given the expectations
187
+ def valid_response?(expected, actual)
188
+ Array(expected).map(&:to_i).include?(actual.to_i)
189
+ end
190
+
191
+ # Checks whether a combination of the HTTP method, response code, and block should
192
+ # result in returning the :body in the response hash. Use internally when implementing {#perform}.
193
+ # @param [Symbol] method the HTTP method
194
+ # @param [String, Fixnum] code the received response code
195
+ # @param [Boolean] has_block whether a streaming block was passed to {#perform}. Pass block_given? to this parameter.
196
+ # @return [Boolean] whether to return the body in the response hash
197
+ def return_body?(method, code, has_block)
198
+ method != :head && !valid_response?([204,205,304], code) && !has_block
199
+ end
200
+
201
+ private
202
+ def response_headers
203
+ Thread.current[:response_headers] ||= Riak::Util::Headers.new
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -11,13 +11,25 @@ unless Object.new.respond_to? :blank?
11
11
  end
12
12
  end
13
13
 
14
+ class FalseClass
15
+ def blank?
16
+ true
17
+ end
18
+ end
19
+
20
+ class TrueClass
21
+ def blank?
22
+ false
23
+ end
24
+ end
25
+
14
26
  class Set
15
27
  alias :blank? :empty?
16
28
  end
17
29
 
18
30
  class String
19
31
  def blank?
20
- not self =~ /[^\s]/
32
+ self !~ /[^\s]/
21
33
  end
22
34
  end
23
35
 
@@ -33,7 +45,7 @@ end
33
45
  unless Object.new.respond_to? :present?
34
46
  class Object
35
47
  def present?
36
- not blank?
48
+ !blank?
37
49
  end
38
50
  end
39
51
  end