baidubce-sdk 0.9.0

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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'baidubce/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "baidubce-sdk"
8
+ spec.version = Baidubce::VERSION
9
+ spec.authors = ["xiaoyong"]
10
+ spec.email = ["xiaoyong@baidu.com"]
11
+
12
+ spec.summary = 'BaiduBce BOS Ruby SDK'
13
+ spec.description = 'The official Ruby sdk used to accessing BaiduBce Object Storage Service'
14
+ spec.homepage = "https://github.com/baidubce/bce-sdk-ruby"
15
+ spec.license = "Apache-2.0"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "lib/baidubce"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.15"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_development_dependency "rest-client", "~> 2.0", ">= 2.0.2"
28
+ spec.add_development_dependency "logger", "~> 1.2", ">= 1.2.8"
29
+ spec.add_development_dependency "mimemagic", "~> 0.3", ">= 0.3.2"
30
+ spec.required_ruby_version = ">= 2.0.0"
31
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "baidubce/bce"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+ # Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4
+ # except in compliance with the License. You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the
9
+ # License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
10
+ # either express or implied. See the License for the specific language governing permissions
11
+ # and limitations under the License.
12
+
13
+ =begin
14
+ Provides access to the BCE credentials used for accessing BCE services: BCE access key ID and
15
+ secret access key.
16
+ These credentials are used to securely sign requests to BCE services.
17
+ =end
18
+
19
+ module Baidubce
20
+ module Auth
21
+ class BceCredentials
22
+ attr_accessor :access_key_id, :secret_access_key, :security_token
23
+
24
+ def initialize(access_key_id, secret_access_key, security_token="")
25
+ @access_key_id = access_key_id
26
+ @secret_access_key = secret_access_key
27
+ @security_token = security_token
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,76 @@
1
+ # Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4
+ # except in compliance with the License. You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the
9
+ # License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
10
+ # either express or implied. See the License for the specific language governing permissions
11
+ # and limitations under the License.
12
+
13
+ # This module provides authentication functions for bce services.
14
+
15
+ require 'time'
16
+ require 'openssl'
17
+
18
+ require_relative '../utils/utils'
19
+
20
+ module Baidubce
21
+ module Auth
22
+ class BceV1Signer
23
+
24
+ def get_canonical_headers(headers, headers_to_sign = nil)
25
+ default = false
26
+ if headers_to_sign.to_a.empty?
27
+ default = true
28
+ headers_to_sign = ["host", "content-md5", "content-length", "content-type"]
29
+ end
30
+
31
+ ret_arr = []
32
+ headers_arr = []
33
+ headers.each do |key, value|
34
+ next if value.to_s.strip.empty?
35
+ if headers_to_sign.include?(key.downcase) ||
36
+ (default && key.downcase.to_s.start_with?(Http::BCE_PREFIX))
37
+ str = ERB::Util.url_encode(key.downcase) + ":" + ERB::Util.url_encode(value.to_s.strip)
38
+ ret_arr << str
39
+ headers_arr << key.downcase
40
+ end
41
+ end
42
+ ret_arr.sort!
43
+ headers_arr.sort!
44
+ return ret_arr.join("\n"), headers_arr
45
+ end
46
+
47
+ def get_canonical_uri_path(path)
48
+ return '/' if path.to_s.empty?
49
+ encoded_path = Utils.url_encode_except_slash(path)
50
+ return path[0] == '/' ? encoded_path : '/' + encoded_path
51
+ end
52
+
53
+ # Create the authorization.
54
+ def sign(credentials, http_method, path, headers, params,
55
+ timestamp=nil, expiration_in_seconds=1800, headers_to_sign=nil)
56
+
57
+ timestamp = Time.now.to_i if timestamp.nil?
58
+ sign_key_info = sprintf('bce-auth-v1/%s/%s/%d',
59
+ credentials.access_key_id,
60
+ Time.at(timestamp).utc.iso8601,
61
+ expiration_in_seconds)
62
+ sign_key = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'),
63
+ credentials.secret_access_key, sign_key_info)
64
+ canonical_uri = get_canonical_uri_path(path)
65
+ canonical_querystring = Utils.get_canonical_querystring(params, true)
66
+ canonical_headers, headers_to_sign = get_canonical_headers(headers, headers_to_sign)
67
+ canonical_request = [http_method, canonical_uri, canonical_querystring, canonical_headers].join("\n")
68
+ signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'),
69
+ sign_key, canonical_request)
70
+
71
+ headers_str = headers_to_sign.join(';') unless headers_to_sign.nil?
72
+ sign_key_info + '/' + headers_str + '/' + signature
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,52 @@
1
+ # Copyright 2017 Baidu, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4
+ # except in compliance with the License. You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the
9
+ # License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
10
+ # either express or implied. See the License for the specific language governing permissions
11
+ # and limitations under the License.
12
+
13
+ # This module provide base class for BCE service clients.
14
+
15
+ require_relative 'auth/bce_v1_signer'
16
+ require_relative 'auth/bce_credentials'
17
+ require_relative 'http/base_http_client'
18
+ require_relative 'bce_client_configuration'
19
+
20
+ module Baidubce
21
+
22
+ class BceBaseClient
23
+
24
+ include Http
25
+ include Auth
26
+
27
+ def initialize(config, service_id="", region_supported=true)
28
+ @config = config
29
+ @service_id = service_id
30
+ @region_supported = region_supported
31
+ @config.endpoint = compute_endpoint if @config.endpoint.to_s.empty?
32
+ @http_client = BaseHttpClient.new()
33
+ @signer = BceV1Signer.new()
34
+ end
35
+
36
+ def compute_endpoint
37
+ if @region_supported
38
+ return sprintf('%s://%s.%s.%s',
39
+ @config.protocol,
40
+ @service_id,
41
+ @config.region,
42
+ DEFAULT_SERVICE_DOMAIN)
43
+ else
44
+ return sprintf('%s://%s.%s',
45
+ @config.protocol,
46
+ @service_id,
47
+ DEFAULT_SERVICE_DOMAIN)
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,47 @@
1
+ # Copyright 2017 Baidu, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4
+ # except in compliance with the License. You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the
9
+ # License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
10
+ # either express or implied. See the License for the specific language governing permissions
11
+ # and limitations under the License.
12
+
13
+ # This module defines a common configuration class for BCE.
14
+
15
+ require_relative 'retry_policy'
16
+
17
+ module Baidubce
18
+
19
+ DEFAULT_PROTOCOL = "http"
20
+ DEFAULT_REGION = "bj"
21
+ DEFAULT_OPEN_TIMEOUT_IN_MILLIS = 60 * 1000
22
+ DEFAULT_READ_TIMEOUT_IN_MILLIS = 10 * 60 * 1000
23
+ DEFAULT_SEND_BUF_SIZE = 1024 * 1024
24
+ DEFAULT_RECV_BUF_SIZE = 10 * 1024 * 1024
25
+
26
+ class BceClientConfiguration
27
+ attr_accessor :credentials, :endpoint, :protocol, :region, :open_timeout_in_millis,
28
+ :read_timeout_in_millis, :send_buf_size, :recv_buf_size, :retry_policy
29
+
30
+ def initialize(credentials,
31
+ endpoint,
32
+ options={})
33
+
34
+ @credentials = credentials
35
+ @endpoint = endpoint
36
+ @protocol = options['protocol'] || DEFAULT_PROTOCOL
37
+ @region = options['region'] || DEFAULT_REGION
38
+ @open_timeout_in_millis = options['open_timeout_in_millis'] ||
39
+ DEFAULT_OPEN_TIMEOUT_IN_MILLIS
40
+ @read_timeout_in_millis = options['read_timeout_in_millis'] || DEFAULT_READ_TIMEOUT_IN_MILLIS
41
+ @send_buf_size = options['send_buf_size'] || DEFAULT_SEND_BUF_SIZE
42
+ @recv_buf_size = options['recv_buf_size'] || DEFAULT_RECV_BUF_SIZE
43
+ @retry_policy = options['retry_policy'] || BackOffRetryPolicy.new
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,20 @@
1
+ # Copyright 2017 Baidu, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4
+ # except in compliance with the License. You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the
9
+ # License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
10
+ # either express or implied. See the License for the specific language governing permissions
11
+ # and limitations under the License.
12
+
13
+ # This module provide constants for BCE sdk.
14
+
15
+ module Baidubce
16
+
17
+ DEFAULT_SERVICE_DOMAIN = 'baidubce.com'
18
+ DEFAULT_ENCODING = 'UTF-8'
19
+
20
+ end
@@ -0,0 +1,34 @@
1
+ class BceClientException < RuntimeError
2
+ attr_accessor :message
3
+
4
+ def initialize(message)
5
+ @message = message
6
+ end
7
+
8
+ end
9
+
10
+ class BceServerException < RuntimeError
11
+ attr_accessor :status_code
12
+ attr_accessor :message
13
+
14
+ def initialize(status_code, message)
15
+ @status_code = status_code
16
+ @message = message
17
+ end
18
+
19
+ end
20
+
21
+ class BceHttpException < RuntimeError
22
+ attr_accessor :http_code
23
+ attr_accessor :http_headers
24
+ attr_accessor :http_body
25
+ attr_accessor :message
26
+
27
+ def initialize(http_code, http_headers, http_body, message)
28
+ @http_code = http_code
29
+ @http_headers = http_headers
30
+ @http_body = http_body
31
+ @message = message
32
+ end
33
+
34
+ end
@@ -0,0 +1,259 @@
1
+ # Copyright 2017 Baidu, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4
+ # except in compliance with the License. You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the
9
+ # License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
10
+ # either express or implied. See the License for the specific language governing permissions
11
+ # and limitations under the License.
12
+
13
+ # This module provide http request function for bce services.
14
+
15
+ require 'rest-client'
16
+ require 'time'
17
+ require 'objspace'
18
+ require 'fiber'
19
+
20
+ require_relative '../version'
21
+ require_relative '../exception'
22
+ require_relative '../retry_policy'
23
+ require_relative '../utils/utils'
24
+ require_relative '../utils/log'
25
+ require_relative '../bce_constants'
26
+ require_relative 'http_constants'
27
+
28
+ module Baidubce
29
+ module Http
30
+
31
+ class BaseHttpClient
32
+
33
+ include Log
34
+ # Send request to BCE services.
35
+ def send_request(config, signer, http_method, path, params, headers, body, save_path=nil, &block)
36
+ headers[USER_AGENT] = sprintf(
37
+ 'bce-sdk-ruby/%s/%s/%s',
38
+ VERSION,
39
+ RUBY_VERSION,
40
+ RUBY_PLATFORM
41
+ )
42
+
43
+ should_get_new_date = headers.has_key?(BCE_DATE) ? false : true
44
+
45
+ url, headers[HOST] = Utils.parse_url_host(config)
46
+ url += Utils.url_encode_except_slash(path)
47
+ query_str = Utils.get_canonical_querystring(params, false)
48
+ url += "?#{query_str}" unless query_str.to_s.empty?
49
+
50
+ logger.info("url: #{url}, params: #{params}")
51
+ set_content_length_header(headers, body, &block)
52
+ headers[STS_SECURITY_TOKEN] = config.credentials.security_token unless config.credentials.security_token.to_s.empty?
53
+
54
+ retries_attempted = 0
55
+ while true
56
+ headers[BCE_DATE] = Time.now.utc.iso8601 if should_get_new_date
57
+ headers[AUTHORIZATION] = signer.sign(config.credentials, http_method,
58
+ path, headers, params)
59
+
60
+ logger.debug("Request headers: #{headers}")
61
+ args = { method: http_method,
62
+ url: url,
63
+ headers: headers,
64
+ payload: body,
65
+ open_timeout: config.open_timeout_in_millis / 1000.0,
66
+ read_timeout: config.read_timeout_in_millis / 1000.0
67
+ }
68
+ args[:payload] = BufWriter.new(&block) if block_given?
69
+
70
+ begin
71
+ if save_path
72
+ logger.debug("Response save file path: #{save_path}")
73
+ resp_headers = {}
74
+ File.open(save_path, 'w+') { |f|
75
+ block = proc { |response|
76
+ response.read_body { |chunk| f << chunk }
77
+ resp_headers = response.to_hash
78
+ resp_headers.each { |k, v| resp_headers[k]=v[0] }
79
+ raise BceHttpException.new(response.code.to_i,
80
+ resp_headers, '', 'get_object_to_file exception') if response.code.to_i > 300
81
+ }
82
+ block_arg = { block_response: block }
83
+ args.merge! block_arg
84
+ RestClient::Request.new(args).execute
85
+ return '', resp_headers
86
+ }
87
+ else
88
+ resp = RestClient::Request.new(args).execute
89
+ logger.debug("Response code: #{resp.code}")
90
+ logger.debug("Response headers: #{resp.headers.to_s}")
91
+ return resp.body, resp.headers
92
+ end
93
+ rescue BceHttpException, RestClient::ExceptionWithResponse => err
94
+ logger.debug("ExceptionWithResponse: #{err.http_code}, #{err.http_body}, #{err.http_headers}, #{err.message}")
95
+ if config.retry_policy.should_retry(err.http_code, retries_attempted)
96
+ delay_in_millis = config.retry_policy.get_delay_before_next_retry_in_millis(retries_attempted)
97
+ sleep(delay_in_millis / 1000.0)
98
+ else
99
+ request_id = err.http_headers[:x_bce_request_id]
100
+ if err.is_a?(BceHttpException)
101
+ err.http_body = File.read(save_path)
102
+ request_id = err.http_headers['x-bce-request-id']
103
+ end
104
+ msg = err.http_body
105
+ if err.http_body.empty?
106
+ msg = "{\"code\":\"#{err.http_code}\",\"message\":\"#{err.message}\",\"requestId\":\"#{request_id}\"}"
107
+ end
108
+ raise BceServerException.new(err.http_code, msg)
109
+ end
110
+ end
111
+
112
+ retries_attempted += 1
113
+ end
114
+
115
+ end
116
+
117
+ def set_content_length_header(headers, body, &block)
118
+ unless block_given?
119
+ if body.to_s.empty?
120
+ headers[CONTENT_LENGTH] = 0
121
+ elsif body.instance_of?(String)
122
+ body = body.encode('UTF-8') if body.encoding.name != 'UTF-8'
123
+ headers[CONTENT_LENGTH] = body.bytesize
124
+ elsif body.instance_of?(File)
125
+ headers[CONTENT_LENGTH] = body.size()
126
+ else
127
+ headers[CONTENT_LENGTH] = ObjectSpace.memsize_of(body)
128
+ end
129
+ end
130
+ end
131
+
132
+ class BufWriter
133
+
134
+ def initialize()
135
+ @buffer = ""
136
+ @producer = Fiber.new { yield self if block_given? }
137
+ @producer.resume
138
+ end
139
+
140
+ def read(bytes = nil, outbuf = nil)
141
+ ret = ""
142
+ while true
143
+ if bytes
144
+ fail if bytes < 0
145
+ piece = @buffer.slice!(0, bytes)
146
+ if piece
147
+ ret << piece
148
+ bytes -= piece.size
149
+ break if bytes == 0
150
+ end
151
+ else
152
+ ret << @buffer
153
+ @buffer.clear
154
+ end
155
+
156
+ if @producer.alive?
157
+ @producer.resume
158
+ else
159
+ break
160
+ end
161
+ end
162
+
163
+ if outbuf
164
+ outbuf.clear
165
+ outbuf << ret
166
+ end
167
+
168
+ return nil if ret.empty? && !bytes.nil? && bytes > 0
169
+ ret
170
+ end
171
+
172
+ def write(chunk)
173
+ @buffer << chunk.to_s
174
+ Fiber.yield
175
+ self
176
+ end
177
+
178
+ alias << write
179
+
180
+ def closed?
181
+ false
182
+ end
183
+
184
+ def close
185
+ end
186
+
187
+ def inspect
188
+ "@buffer: " + @buffer[0, 32].inspect + "...#{@buffer.size} bytes"
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ module RestClient
196
+ module Payload
197
+ class Base
198
+ def headers
199
+ ({'content-length' => size.to_s} if size) || {}
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ module Net
206
+ class HTTP
207
+ def transport_request(req)
208
+ count = 0
209
+ begin
210
+ begin_transport req
211
+ res = catch(:response) {
212
+ req.exec @socket, @curr_http_version, edit_path(req.path)
213
+ begin
214
+ res = HTTPResponse.read_new(@socket)
215
+ res.decode_content = req.decode_content
216
+ end while res.kind_of?(HTTPContinue)
217
+
218
+ res.uri = req.uri
219
+
220
+ res
221
+ }
222
+ res.reading_body(@socket, req.response_body_permitted?) {
223
+ yield res if block_given?
224
+ }
225
+ rescue Net::OpenTimeout
226
+ raise
227
+ rescue Net::ReadTimeout, IOError, EOFError,
228
+ Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE,
229
+ # avoid a dependency on OpenSSL
230
+ defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : IOError,
231
+ Timeout::Error => exception
232
+ if count == 0 && IDEMPOTENT_METHODS_.include?(req.method)
233
+ count += 1
234
+ @socket.close if @socket and not @socket.closed?
235
+ D "Conn close because of error #{exception}, and retry"
236
+ if req.body_stream
237
+ if req.body_stream.respond_to?(:rewind)
238
+ req.body_stream.rewind
239
+ else
240
+ raise
241
+ end
242
+ end
243
+ retry
244
+ end
245
+ D "Conn close because of error #{exception}"
246
+ @socket.close if @socket and not @socket.closed?
247
+ raise
248
+ end
249
+
250
+ end_transport req, res
251
+ res
252
+ rescue => exception
253
+ D "Conn close because of error #{exception}"
254
+ @socket.close if @socket and not @socket.closed?
255
+ raise exception
256
+ end
257
+ end
258
+ end
259
+