baidubce-sdk 0.9.0

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