riak-client 0.8.3 → 0.9.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -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