aliyun-oss-ruby-sdk 0.4.1

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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +95 -0
  3. data/README.md +423 -0
  4. data/examples/aliyun/oss/bucket.rb +144 -0
  5. data/examples/aliyun/oss/callback.rb +61 -0
  6. data/examples/aliyun/oss/object.rb +182 -0
  7. data/examples/aliyun/oss/resumable_download.rb +42 -0
  8. data/examples/aliyun/oss/resumable_upload.rb +49 -0
  9. data/examples/aliyun/oss/streaming.rb +124 -0
  10. data/examples/aliyun/oss/using_sts.rb +48 -0
  11. data/examples/aliyun/sts/assume_role.rb +59 -0
  12. data/lib/aliyun_sdk/common.rb +6 -0
  13. data/lib/aliyun_sdk/common/exception.rb +18 -0
  14. data/lib/aliyun_sdk/common/logging.rb +46 -0
  15. data/lib/aliyun_sdk/common/struct.rb +56 -0
  16. data/lib/aliyun_sdk/oss.rb +16 -0
  17. data/lib/aliyun_sdk/oss/bucket.rb +661 -0
  18. data/lib/aliyun_sdk/oss/client.rb +106 -0
  19. data/lib/aliyun_sdk/oss/config.rb +39 -0
  20. data/lib/aliyun_sdk/oss/download.rb +255 -0
  21. data/lib/aliyun_sdk/oss/exception.rb +108 -0
  22. data/lib/aliyun_sdk/oss/http.rb +338 -0
  23. data/lib/aliyun_sdk/oss/iterator.rb +92 -0
  24. data/lib/aliyun_sdk/oss/multipart.rb +74 -0
  25. data/lib/aliyun_sdk/oss/object.rb +15 -0
  26. data/lib/aliyun_sdk/oss/protocol.rb +1499 -0
  27. data/lib/aliyun_sdk/oss/struct.rb +208 -0
  28. data/lib/aliyun_sdk/oss/upload.rb +238 -0
  29. data/lib/aliyun_sdk/oss/util.rb +89 -0
  30. data/lib/aliyun_sdk/sts.rb +9 -0
  31. data/lib/aliyun_sdk/sts/client.rb +38 -0
  32. data/lib/aliyun_sdk/sts/config.rb +22 -0
  33. data/lib/aliyun_sdk/sts/exception.rb +53 -0
  34. data/lib/aliyun_sdk/sts/protocol.rb +130 -0
  35. data/lib/aliyun_sdk/sts/struct.rb +64 -0
  36. data/lib/aliyun_sdk/sts/util.rb +48 -0
  37. data/lib/aliyun_sdk/version.rb +7 -0
  38. data/spec/aliyun/oss/bucket_spec.rb +597 -0
  39. data/spec/aliyun/oss/client/bucket_spec.rb +554 -0
  40. data/spec/aliyun/oss/client/client_spec.rb +297 -0
  41. data/spec/aliyun/oss/client/resumable_download_spec.rb +220 -0
  42. data/spec/aliyun/oss/client/resumable_upload_spec.rb +413 -0
  43. data/spec/aliyun/oss/http_spec.rb +83 -0
  44. data/spec/aliyun/oss/multipart_spec.rb +686 -0
  45. data/spec/aliyun/oss/object_spec.rb +785 -0
  46. data/spec/aliyun/oss/service_spec.rb +142 -0
  47. data/spec/aliyun/oss/util_spec.rb +50 -0
  48. data/spec/aliyun/sts/client_spec.rb +150 -0
  49. data/spec/aliyun/sts/util_spec.rb +39 -0
  50. data/tests/config.rb +31 -0
  51. data/tests/test_content_encoding.rb +54 -0
  52. data/tests/test_content_type.rb +95 -0
  53. data/tests/test_custom_headers.rb +70 -0
  54. data/tests/test_encoding.rb +77 -0
  55. data/tests/test_large_file.rb +66 -0
  56. data/tests/test_multipart.rb +97 -0
  57. data/tests/test_object_acl.rb +49 -0
  58. data/tests/test_object_key.rb +68 -0
  59. data/tests/test_object_url.rb +69 -0
  60. data/tests/test_resumable.rb +40 -0
  61. metadata +240 -0
@@ -0,0 +1,297 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+ require 'yaml'
5
+ require 'nokogiri'
6
+
7
+ module AliyunSDK
8
+ module OSS
9
+
10
+ describe Client do
11
+
12
+ context "construct" do
13
+ it "should setup endpoint and a/k" do
14
+ endpoint = 'oss-cn-hangzhou.aliyuncs.com'
15
+ client = Client.new(
16
+ :endpoint => endpoint,
17
+ :access_key_id => 'xxx ', :access_key_secret => ' yyy ',
18
+ :sts_token => 'sts-token')
19
+
20
+ config = client.instance_variable_get('@config')
21
+ expect(config.endpoint.to_s).to eq("http://#{endpoint}")
22
+ expect(config.access_key_id).to eq('xxx')
23
+ expect(config.access_key_secret).to eq('yyy')
24
+ expect(config.sts_token).to eq('sts-token')
25
+ end
26
+
27
+ it "should work with CNAME endpoint" do
28
+ endpoint = 'rockuw.com'
29
+ bucket = 'rubysdk-bucket'
30
+ object = 'rubysdk-object'
31
+ client = Client.new(
32
+ access_key_id: 'xxx',
33
+ access_key_secret: 'yyy',
34
+ endpoint: endpoint,
35
+ cname: true)
36
+
37
+ # TODO: ignore queries here
38
+ # bucket operations
39
+ stub_request(:get, endpoint)
40
+ .with(:query => {'encoding-type' => 'url'})
41
+ client.get_bucket(bucket).list_objects.take(1)
42
+ expect(WebMock)
43
+ .to have_requested(:get, endpoint)
44
+ .with(:query => {'encoding-type' => 'url'})
45
+
46
+ # object operations
47
+ stub_request(:get, "#{endpoint}/#{object}")
48
+ client.get_bucket(bucket).get_object(object) {}
49
+ expect(WebMock).to have_requested(:get, "#{endpoint}/#{object}")
50
+ end
51
+
52
+ it "should work with IP endpoint" do
53
+ endpoint = 'http://127.0.0.1:3000'
54
+ bucket = 'rubysdk-bucket'
55
+ object = 'rubysdk-object'
56
+ client = Client.new(
57
+ access_key_id: 'xxx',
58
+ access_key_secret: 'yyy',
59
+ endpoint: endpoint)
60
+
61
+ # TODO: ignore queries here
62
+ # bucket operations
63
+ stub_request(:get, "#{endpoint}/#{bucket}/")
64
+ .with(:query => {'encoding-type' => 'url'})
65
+ client.get_bucket(bucket).list_objects.take(1)
66
+ expect(WebMock)
67
+ .to have_requested(:get, "#{endpoint}/#{bucket}/")
68
+ .with(:query => {'encoding-type' => 'url'})
69
+
70
+ # object operations
71
+ stub_request(:get, "#{endpoint}/#{bucket}/#{object}")
72
+ client.get_bucket(bucket).get_object(object) {}
73
+ expect(WebMock).to have_requested(:get, "#{endpoint}/#{bucket}/#{object}")
74
+ end
75
+
76
+ it "should not set Authorization with anonymous client" do
77
+ endpoint = 'oss-cn-hangzhou.aliyuncs.com'
78
+ bucket = 'rubysdk-bucket'
79
+ object = 'rubysdk-object'
80
+ client = Client.new(:endpoint => endpoint)
81
+
82
+ stub_request(:get, "#{bucket}.#{endpoint}/#{object}")
83
+
84
+ client.get_bucket(bucket).get_object(object) {}
85
+
86
+ expect(WebMock)
87
+ .to have_requested(:get, "#{bucket}.#{endpoint}/#{object}")
88
+ .with{ |req| not req.headers.has_key?('Authorization') }
89
+ end
90
+
91
+ it "should set STS header" do
92
+ endpoint = 'oss-cn-hangzhou.aliyuncs.com'
93
+ bucket = 'rubysdk-bucket'
94
+ object = 'rubysdk-object'
95
+ client = Client.new(
96
+ :endpoint => endpoint,
97
+ :access_key_id => 'xxx', :access_key_secret => 'yyy',
98
+ :sts_token => 'sts-token')
99
+
100
+ stub_request(:get, "#{bucket}.#{endpoint}/#{object}")
101
+
102
+ client.get_bucket(bucket).get_object(object) {}
103
+
104
+ expect(WebMock)
105
+ .to have_requested(:get, "#{bucket}.#{endpoint}/#{object}")
106
+ .with{ |req| req.headers.key?('X-Oss-Security-Token') }
107
+ end
108
+
109
+ it "should construct different client" do
110
+ bucket = 'rubysdk-bucket'
111
+ object = 'rubysdk-object'
112
+ ep1 = 'oss-cn-hangzhou.aliyuncs.com'
113
+ c1 = Client.new(
114
+ :endpoint => ep1,
115
+ :access_key_id => 'xxx', :access_key_secret => 'yyy')
116
+ ep2 = 'oss-cn-beijing.aliyuncs.com'
117
+ c2 = Client.new(
118
+ :endpoint => ep2,
119
+ :access_key_id => 'aaa', :access_key_secret => 'bbb')
120
+
121
+ stub_request(:get, "#{bucket}.#{ep1}/#{object}")
122
+ stub_request(:put, "#{bucket}.#{ep2}/#{object}")
123
+
124
+ c1.get_bucket(bucket).get_object(object) {}
125
+ c2.get_bucket(bucket).put_object(object)
126
+
127
+ expect(WebMock).to have_requested(:get, "#{bucket}.#{ep1}/#{object}")
128
+ expect(WebMock).to have_requested(:put, "#{bucket}.#{ep2}/#{object}")
129
+ end
130
+ end # construct
131
+
132
+ def mock_buckets(buckets, more = {})
133
+ Nokogiri::XML::Builder.new do |xml|
134
+ xml.ListAllMyBucketsResult {
135
+ xml.Owner {
136
+ xml.ID 'owner_id'
137
+ xml.DisplayName 'owner_name'
138
+ }
139
+ xml.Buckets {
140
+ buckets.each do |b|
141
+ xml.Bucket {
142
+ xml.Location b.location
143
+ xml.Name b.name
144
+ xml.CreationDate b.creation_time.to_s
145
+ }
146
+ end
147
+ }
148
+
149
+ unless more.empty?
150
+ xml.Prefix more[:prefix]
151
+ xml.Marker more[:marker]
152
+ xml.MaxKeys more[:limit].to_s
153
+ xml.NextMarker more[:next_marker]
154
+ xml.IsTruncated more[:truncated]
155
+ end
156
+ }
157
+ end.to_xml
158
+ end
159
+
160
+ def mock_location(location)
161
+ Nokogiri::XML::Builder.new do |xml|
162
+ xml.CreateBucketConfiguration {
163
+ xml.LocationConstraint location
164
+ }
165
+ end.to_xml
166
+ end
167
+
168
+ def mock_acl(acl)
169
+ Nokogiri::XML::Builder.new do |xml|
170
+ xml.AccessControlPolicy {
171
+ xml.Owner {
172
+ xml.ID 'owner_id'
173
+ xml.DisplayName 'owner_name'
174
+ }
175
+
176
+ xml.AccessControlList {
177
+ xml.Grant acl
178
+ }
179
+ }
180
+ end.to_xml
181
+ end
182
+
183
+ context "bucket operations" do
184
+ before :all do
185
+ @endpoint = 'oss.aliyuncs.com'
186
+ @client = Client.new(
187
+ :endpoint => @endpoint,
188
+ :access_key_id => 'xxx',
189
+ :access_key_secret => 'yyy')
190
+ @bucket = 'rubysdk-bucket'
191
+ end
192
+
193
+ def bucket_url
194
+ @bucket + "." + @endpoint
195
+ end
196
+
197
+ it "should create bucket" do
198
+ location = 'oss-cn-hangzhou'
199
+
200
+ stub_request(:put, bucket_url).with(:body => mock_location(location))
201
+
202
+ @client.create_bucket(@bucket, :location => 'oss-cn-hangzhou')
203
+
204
+ expect(WebMock).to have_requested(:put, bucket_url)
205
+ .with(:body => mock_location(location), :query => {})
206
+ end
207
+
208
+ it "should delete bucket" do
209
+ stub_request(:delete, bucket_url)
210
+
211
+ @client.delete_bucket(@bucket)
212
+
213
+ expect(WebMock).to have_requested(:delete, bucket_url)
214
+ .with(:body => nil, :query => {})
215
+ end
216
+
217
+ it "should paging list buckets" do
218
+ return_buckets_1 = (1..5).map do |i|
219
+ name = "rubysdk-bucket-#{i.to_s.rjust(3, '0')}"
220
+ Bucket.new(
221
+ :name => name,
222
+ :location => 'oss-cn-hangzhou',
223
+ :creation_time => Time.now)
224
+ end
225
+
226
+ more_1 = {:next_marker => return_buckets_1.last.name, :truncated => true}
227
+
228
+ return_buckets_2 = (6..10).map do |i|
229
+ name = "rubysdk-bucket-#{i.to_s.rjust(3, '0')}"
230
+ Bucket.new(
231
+ :name => name,
232
+ :location => 'oss-cn-hangzhou',
233
+ :creation_time => Time.now)
234
+ end
235
+
236
+ more_2 = {:truncated => false}
237
+
238
+ stub_request(:get, /#{@endpoint}.*/)
239
+ .to_return(:body => mock_buckets(return_buckets_1, more_1)).then
240
+ .to_return(:body => mock_buckets(return_buckets_2, more_2))
241
+
242
+ buckets = @client.list_buckets
243
+
244
+ expect(buckets.map {|b| b.to_s}.join(";"))
245
+ .to eq((return_buckets_1 + return_buckets_2).map {|b| b.to_s}.join(";"))
246
+ expect(WebMock).to have_requested(:get, /#{@endpoint}.*/).times(2)
247
+ end
248
+
249
+ it "should test bucket existence" do
250
+ query = {'acl' => ''}
251
+ return_acl = ACL::PUBLIC_READ
252
+ stub_request(:get, bucket_url)
253
+ .with(:query => query)
254
+ .to_return(:body => mock_acl(return_acl)).then
255
+ .to_return(:status => 404)
256
+
257
+ exist = @client.bucket_exists?(@bucket)
258
+ expect(exist).to be true
259
+
260
+ exist = @client.bucket_exists?(@bucket)
261
+ expect(exist).to be false
262
+
263
+ expect(WebMock).to have_requested(:get, bucket_url)
264
+ .with(:query => query, :body => nil).times(2)
265
+ end
266
+
267
+ it "should not list buckets when endpoint is cname" do
268
+ cname_client = Client.new(
269
+ :endpoint => @endpoint,
270
+ :access_key_id => 'xxx',
271
+ :access_key_secret => 'yyy',
272
+ :cname => true)
273
+
274
+ expect {
275
+ cname_client.list_buckets
276
+ }.to raise_error(ClientError)
277
+ end
278
+
279
+ it "should use HTTPS" do
280
+ stub_request(:put, "https://#{bucket_url}")
281
+
282
+ https_client = Client.new(
283
+ :endpoint => "https://#{@endpoint}",
284
+ :access_key_id => 'xxx',
285
+ :access_key_secret => 'yyy',
286
+ :cname => false)
287
+
288
+ https_client.create_bucket(@bucket)
289
+
290
+ expect(WebMock).to have_requested(:put, "https://#{bucket_url}")
291
+ end
292
+ end # bucket operations
293
+
294
+ end # Client
295
+
296
+ end # OSS
297
+ end # Aliyun
@@ -0,0 +1,220 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+ require 'yaml'
5
+ require 'nokogiri'
6
+
7
+ module AliyunSDK
8
+ module OSS
9
+
10
+ describe "Resumable download" do
11
+
12
+ before :all do
13
+ @endpoint = 'oss-cn-hangzhou.aliyuncs.com'
14
+ @bucket_name = 'rubysdk-bucket'
15
+ @object_key = 'resumable_file'
16
+ @bucket = Client.new(
17
+ :endpoint => @endpoint,
18
+ :access_key_id => 'xxx',
19
+ :access_key_secret => 'yyy').get_bucket(@bucket_name)
20
+
21
+ @file = './download_file'
22
+ end
23
+
24
+ before :each do
25
+ File.delete("#{@file}.cpt") if File.exist?("#{@file}.cpt")
26
+ end
27
+
28
+ def object_url
29
+ "#{@bucket_name}.#{@endpoint}/#{@object_key}"
30
+ end
31
+
32
+ def mock_object(i)
33
+ i.to_s.rjust(9, '0') + "\n"
34
+ end
35
+
36
+ def mock_range(i)
37
+ "bytes=#{(i-1)*10}-#{i*10 - 1}"
38
+ end
39
+
40
+ def mock_error(code, message)
41
+ Nokogiri::XML::Builder.new do |xml|
42
+ xml.Error {
43
+ xml.Code code
44
+ xml.Message message
45
+ xml.RequestId '0000'
46
+ }
47
+ end.to_xml
48
+ end
49
+
50
+ it "should download file when all goes well" do
51
+ return_headers = {
52
+ 'x-oss-object-type' => 'Normal',
53
+ 'ETag' => 'xxxyyyzzz',
54
+ 'Content-Length' => 100,
55
+ 'Last-Modified' => Time.now.rfc822
56
+ }
57
+
58
+ # get object meta
59
+ stub_request(:head, object_url).to_return(:headers => return_headers)
60
+
61
+ # get object by range
62
+ stub_request(:get, object_url)
63
+ .to_return((1..10).map{ |i| {:body => mock_object(i)} })
64
+
65
+ prg = []
66
+ @bucket.resumable_download(
67
+ @object_key, @file, :part_size => 10) { |p| prg << p }
68
+
69
+ ranges = []
70
+ expect(WebMock).to have_requested(:get, object_url).with{ |req|
71
+ ranges << req.headers['Range']
72
+ }.times(10)
73
+
74
+ expect(ranges).to match_array((1..10).map{ |i| mock_range(i) })
75
+ expect(File.exist?("#{@file}.cpt")).to be false
76
+ expect(Dir.glob("#{@file}.part.*").empty?).to be true
77
+
78
+ expect(File.read(@file).lines)
79
+ .to match_array((1..10).map{ |i| mock_object(i) })
80
+ expect(prg.size).to eq(10)
81
+ end
82
+
83
+ it "should resume when download part fails" do
84
+ return_headers = {
85
+ 'x-oss-object-type' => 'Normal',
86
+ 'ETag' => 'xxxyyyzzz',
87
+ 'Content-Length' => 100,
88
+ 'Last-Modified' => Time.now.rfc822
89
+ }
90
+
91
+ # get object meta
92
+ stub_request(:head, object_url).to_return(:headers => return_headers)
93
+
94
+ code = 'Timeout'
95
+ message = 'Request timeout.'
96
+ # upload part
97
+ stub_request(:get, object_url)
98
+ .to_return((1..3).map{ |i| {:body => mock_object(i)} }).then
99
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
100
+ .to_return((4..9).map{ |i| {:body => mock_object(i)} }).then
101
+ .to_return(:status => 500, :body => mock_error(code, message)).then
102
+ .to_return((10..10).map{ |i| {:body => mock_object(i)} })
103
+
104
+ success = false
105
+ 4.times do
106
+ begin
107
+ @bucket.resumable_download(
108
+ @object_key, @file, :part_size => 10, :threads => 1)
109
+ success = true
110
+ rescue
111
+ # pass
112
+ end
113
+ end
114
+
115
+ expect(success).to be true
116
+ ranges = []
117
+ expect(WebMock).to have_requested(:get, object_url).with{ |req|
118
+ ranges << req.headers['Range']
119
+ }.times(13)
120
+
121
+ expect(ranges.uniq).to match_array((1..10).map{ |i| mock_range(i) })
122
+ expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join)
123
+ end
124
+
125
+ it "should resume when checkpoint fails" do
126
+ # Monkey patch to inject failures
127
+ class ::AliyunSDK::OSS::Multipart::Download
128
+ alias :old_checkpoint :checkpoint
129
+
130
+ def checkpoint_fails
131
+ @@fail_injections ||= [false, false, true, true, false, true, false]
132
+ @@fail_injections.shift
133
+ end
134
+
135
+ def checkpoint
136
+ t = checkpoint_fails
137
+ if t == true
138
+ raise ClientError.new("fail injection")
139
+ end
140
+
141
+ old_checkpoint
142
+ end
143
+ end
144
+
145
+ return_headers = {
146
+ 'x-oss-object-type' => 'Normal',
147
+ 'ETag' => 'xxxyyyzzz',
148
+ 'Content-Length' => 100,
149
+ 'Last-Modified' => Time.now.rfc822
150
+ }
151
+
152
+ # get object meta
153
+ stub_request(:head, object_url).to_return(:headers => return_headers)
154
+
155
+ # get object by range
156
+ returns = [1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10]
157
+ stub_request(:get, object_url)
158
+ .to_return(returns.map{ |i| {:body => mock_object(i)} })
159
+
160
+ success = false
161
+ 4.times do
162
+ begin
163
+ @bucket.resumable_download(
164
+ @object_key, @file, :part_size => 10, :threads => 1)
165
+ success = true
166
+ rescue
167
+ # pass
168
+ end
169
+ end
170
+
171
+ expect(success).to be true
172
+ expect(WebMock).to have_requested(:get, object_url).times(13)
173
+ expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join)
174
+ end
175
+
176
+ it "should not resume when specify disable_cpt" do
177
+ return_headers = {
178
+ 'x-oss-object-type' => 'Normal',
179
+ 'ETag' => 'xxxyyyzzz',
180
+ 'Content-Length' => 100,
181
+ 'Last-Modified' => Time.now.rfc822
182
+ }
183
+
184
+ # get object meta
185
+ stub_request(:head, object_url).to_return(:headers => return_headers)
186
+
187
+ code = 'Timeout'
188
+ message = 'Request timeout.'
189
+ # upload part
190
+ stub_request(:get, object_url)
191
+ .to_return((1..3).map{ |i| {:body => mock_object(i)} }).then
192
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
193
+ .to_return((1..9).map{ |i| {:body => mock_object(i)} }).then
194
+ .to_return(:status => 500, :body => mock_error(code, message)).then
195
+ .to_return((1..10).map{ |i| {:body => mock_object(i)} })
196
+
197
+ cpt_file = "#{File.expand_path(@file)}.cpt"
198
+ success = false
199
+ 4.times do
200
+ begin
201
+ @bucket.resumable_download(
202
+ @object_key, @file, :part_size => 10,
203
+ :cpt_file => cpt_file, :disable_cpt => true, :threads => 1)
204
+ success = true
205
+ rescue
206
+ # pass
207
+ end
208
+
209
+ expect(File.exists?(cpt_file)).to be false
210
+ end
211
+
212
+ expect(success).to be true
213
+ expect(WebMock).to have_requested(:get, object_url).times(25)
214
+ expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join)
215
+ expect(Dir.glob("#{@file}.part.*").empty?).to be true
216
+ end
217
+
218
+ end # Resumable upload
219
+ end # OSS
220
+ end # Aliyun