aliyun-oss-ruby-sdk 0.4.1

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