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,413 @@
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 upload" 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 = './file_to_upload'
22
+ # write 100B data
23
+ File.open(@file, 'w') do |f|
24
+ (1..10).each do |i|
25
+ f.puts i.to_s.rjust(9, '0')
26
+ end
27
+ end
28
+ end
29
+
30
+ before :each do
31
+ File.delete("#{@file}.cpt") if File.exist?("#{@file}.cpt")
32
+ end
33
+
34
+ def object_url
35
+ "#{@bucket_name}.#{@endpoint}/#{@object_key}"
36
+ end
37
+
38
+ def parse_query_from_uri(uri)
39
+ query = {}
40
+ str = uri.to_s[uri.to_s.index('?')+1..-1]
41
+ str.split("&").each do |q|
42
+ v = q.split('=')
43
+ query[v.at(0)] = v.at(1)
44
+ end
45
+
46
+ query
47
+ end
48
+
49
+ def mock_txn_id(txn_id)
50
+ Nokogiri::XML::Builder.new do |xml|
51
+ xml.InitiateMultipartUploadResult {
52
+ xml.Bucket @bucket
53
+ xml.Key @object
54
+ xml.UploadId txn_id
55
+ }
56
+ end.to_xml
57
+ end
58
+
59
+ def mock_error(code, message)
60
+ Nokogiri::XML::Builder.new do |xml|
61
+ xml.Error {
62
+ xml.Code code
63
+ xml.Message message
64
+ xml.RequestId '0000'
65
+ }
66
+ end.to_xml
67
+ end
68
+
69
+ def err(msg, reqid = '0000')
70
+ "#{msg} RequestId: #{reqid}"
71
+ end
72
+
73
+ it "should upload file when all goes well" do
74
+ stub_request(:post, /#{object_url}\?uploads.*/)
75
+ .to_return(:body => mock_txn_id('upload_id'))
76
+ stub_request(:put, /#{object_url}\?partNumber.*/)
77
+ stub_request(:post, /#{object_url}\?uploadId.*/)
78
+
79
+ prg = []
80
+ @bucket.resumable_upload(
81
+ @object_key, @file, :part_size => 10) { |p| prg << p }
82
+
83
+ expect(WebMock).to have_requested(
84
+ :post, /#{object_url}\?uploads.*/).times(1)
85
+
86
+ part_numbers = Set.new([])
87
+ upload_ids = Set.new([])
88
+
89
+ expect(WebMock).to have_requested(
90
+ :put, /#{object_url}\?partNumber.*/).with{ |req|
91
+ query = parse_query_from_uri(req.uri)
92
+ part_numbers << query['partNumber']
93
+ upload_ids << query['uploadId']
94
+ }.times(10)
95
+
96
+ expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s })
97
+ expect(upload_ids.to_a).to match_array(['upload_id'])
98
+
99
+ expect(WebMock).to have_requested(
100
+ :post, /#{object_url}\?uploadId.*/).times(1)
101
+
102
+ expect(File.exist?("#{@file}.cpt")).to be false
103
+ expect(prg.size).to eq(10)
104
+ end
105
+
106
+ it "should upload file with callback" do
107
+ stub_request(:post, /#{object_url}\?uploads.*/)
108
+ .to_return(:body => mock_txn_id('upload_id'))
109
+ stub_request(:put, /#{object_url}\?partNumber.*/)
110
+ stub_request(:post, /#{object_url}\?uploadId.*/)
111
+
112
+ callback = Callback.new(
113
+ url: 'http://app.server.com/callback',
114
+ query: {'id' => 1, 'name' => '杭州'},
115
+ body: 'hello world',
116
+ host: 'server.com'
117
+ )
118
+ @bucket.resumable_upload(
119
+ @object_key, @file, part_size: 10, callback: callback)
120
+
121
+ expect(WebMock).to have_requested(
122
+ :post, /#{object_url}\?uploads.*/).times(1)
123
+
124
+ expect(WebMock).to have_requested(
125
+ :put, /#{object_url}\?partNumber.*/)
126
+ .times(10)
127
+ expect(WebMock)
128
+ .to have_requested(
129
+ :post, /#{object_url}\?uploadId.*/)
130
+ .with { |req| req.headers.key?('X-Oss-Callback') }
131
+ .times(1)
132
+
133
+ expect(File.exist?("#{@file}.cpt")).to be false
134
+ end
135
+
136
+ it "should raise CallbackError when callback failed" do
137
+ stub_request(:post, /#{object_url}\?uploads.*/)
138
+ .to_return(:body => mock_txn_id('upload_id'))
139
+ stub_request(:put, /#{object_url}\?partNumber.*/)
140
+
141
+ code = 'CallbackFailed'
142
+ message = 'Error status: 502.'
143
+ stub_request(:post, /#{object_url}\?uploadId.*/)
144
+ .to_return(:status => 203, :body => mock_error(code, message))
145
+
146
+ callback = Callback.new(
147
+ url: 'http://app.server.com/callback',
148
+ query: {'id' => 1, 'name' => '杭州'},
149
+ body: 'hello world',
150
+ host: 'server.com'
151
+ )
152
+ expect {
153
+ @bucket.resumable_upload(
154
+ @object_key, @file, part_size: 10, callback: callback)
155
+ }.to raise_error(CallbackError, err(message))
156
+
157
+ expect(WebMock).to have_requested(
158
+ :post, /#{object_url}\?uploads.*/).times(1)
159
+
160
+ expect(WebMock).to have_requested(
161
+ :put, /#{object_url}\?partNumber.*/)
162
+ .times(10)
163
+
164
+ expect(WebMock)
165
+ .to have_requested(
166
+ :post, /#{object_url}\?uploadId.*/)
167
+ .with { |req| req.headers.key?('X-Oss-Callback') }
168
+ .times(1)
169
+
170
+ expect(File.exist?("#{@file}.cpt")).to be true
171
+ end
172
+
173
+ it "should upload file with custom headers" do
174
+ stub_request(:post, /#{object_url}\?uploads.*/)
175
+ .to_return(:body => mock_txn_id('upload_id'))
176
+ stub_request(:put, /#{object_url}\?partNumber.*/)
177
+ stub_request(:post, /#{object_url}\?uploadId.*/)
178
+
179
+ @bucket.resumable_upload(
180
+ @object_key, @file,
181
+ part_size: 10,
182
+ headers: {'cache-CONTROL' => 'cacheit', 'CONTENT-disposition' => 'oh;yeah'})
183
+
184
+ headers = {}
185
+ expect(WebMock).to have_requested(
186
+ :post, /#{object_url}\?uploads.*/)
187
+ .with { |req| headers = req.headers }.times(1)
188
+
189
+ expect(headers['Cache-Control']).to eq('cacheit')
190
+ expect(headers['Content-Disposition']).to eq('oh;yeah')
191
+ expect(File.exist?("#{@file}.cpt")).to be false
192
+ end
193
+
194
+ it "should restart when begin txn fails" do
195
+ code = 'Timeout'
196
+ message = 'Request timeout.'
197
+
198
+ stub_request(:post, /#{object_url}\?uploads.*/)
199
+ .to_return(:status => 500, :body => mock_error(code, message)).then
200
+ .to_return(:body => mock_txn_id('upload_id'))
201
+ stub_request(:put, /#{object_url}\?partNumber.*/)
202
+ stub_request(:post, /#{object_url}\?uploadId.*/)
203
+
204
+ success = false
205
+ 2.times do
206
+ begin
207
+ @bucket.resumable_upload(@object_key, @file, :part_size => 10)
208
+ success = true
209
+ rescue
210
+ # pass
211
+ end
212
+ end
213
+
214
+ expect(success).to be true
215
+ expect(WebMock).to have_requested(
216
+ :post, /#{object_url}\?uploads.*/).times(2)
217
+ expect(WebMock).to have_requested(
218
+ :put, /#{object_url}\?partNumber.*/).times(10)
219
+ expect(WebMock).to have_requested(
220
+ :post, /#{object_url}\?uploadId.*/).times(1)
221
+ end
222
+
223
+ it "should resume when upload part fails" do
224
+ # begin multipart
225
+ stub_request(:post, /#{object_url}\?uploads.*/)
226
+ .to_return(:body => mock_txn_id('upload_id'))
227
+
228
+ # commit multipart
229
+ stub_request(:post, /#{object_url}\?uploadId.*/)
230
+
231
+ code = 'Timeout'
232
+ message = 'Request timeout.'
233
+ # upload part
234
+ stub_request(:put, /#{object_url}\?partNumber.*/)
235
+ .to_return(:status => 200).times(3).then
236
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
237
+ .to_return(:status => 200).times(6).then
238
+ .to_return(:status => 500, :body => mock_error(code, message)).then
239
+ .to_return(:status => 200)
240
+
241
+ success = false
242
+ 4.times do
243
+ begin
244
+ @bucket.resumable_upload(
245
+ @object_key, @file, part_size: 10, threads: 1)
246
+ success = true
247
+ rescue
248
+ # pass
249
+ end
250
+ end
251
+
252
+ expect(success).to be true
253
+
254
+ expect(WebMock).to have_requested(
255
+ :post, /#{object_url}\?uploads.*/).times(1)
256
+
257
+ part_numbers = Set.new([])
258
+ upload_ids = Set.new([])
259
+
260
+ expect(WebMock).to have_requested(
261
+ :put, /#{object_url}\?partNumber.*/).with{ |req|
262
+ query = parse_query_from_uri(req.uri)
263
+ part_numbers << query['partNumber']
264
+ upload_ids << query['uploadId']
265
+ }.times(13)
266
+
267
+ expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s })
268
+ expect(upload_ids.to_a).to match_array(['upload_id'])
269
+
270
+ expect(WebMock).to have_requested(
271
+ :post, /#{object_url}\?uploadId.*/).times(1)
272
+ end
273
+
274
+ it "should resume when checkpoint fails" do
275
+ # Monkey patch to inject failures
276
+ class ::AliyunSDK::OSS::Multipart::Upload
277
+ alias :old_checkpoint :checkpoint
278
+
279
+ def checkpoint_fails
280
+ @@fail_injections ||= [false, false, true, true, false, true, false]
281
+ @@fail_injections.shift
282
+ end
283
+
284
+ def checkpoint
285
+ t = checkpoint_fails
286
+ if t == true
287
+ raise ClientError.new("fail injection")
288
+ end
289
+
290
+ old_checkpoint
291
+ end
292
+ end
293
+
294
+ stub_request(:post, /#{object_url}\?uploads.*/)
295
+ .to_return(:body => mock_txn_id('upload_id'))
296
+ stub_request(:put, /#{object_url}\?partNumber.*/)
297
+ stub_request(:post, /#{object_url}\?uploadId.*/)
298
+
299
+ success = false
300
+ 4.times do
301
+ begin
302
+ @bucket.resumable_upload(
303
+ @object_key, @file, part_size: 10, threads: 1)
304
+ success = true
305
+ rescue
306
+ # pass
307
+ end
308
+ end
309
+
310
+ expect(success).to be true
311
+
312
+ expect(WebMock).to have_requested(
313
+ :post, /#{object_url}\?uploads.*/).times(1)
314
+
315
+ part_numbers = Set.new([])
316
+
317
+ expect(WebMock).to have_requested(
318
+ :put, /#{object_url}\?partNumber.*/).with{ |req|
319
+ query = parse_query_from_uri(req.uri)
320
+ part_numbers << query['partNumber']
321
+ query['uploadId'] == 'upload_id'
322
+ }.times(13)
323
+
324
+ expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s })
325
+
326
+ expect(WebMock).to have_requested(
327
+ :post, /#{object_url}\?uploadId.*/).times(1)
328
+ end
329
+
330
+ it "should resume when commit txn fails" do
331
+ # begin multipart
332
+ stub_request(:post, /#{object_url}\?uploads.*/)
333
+ .to_return(:body => mock_txn_id('upload_id'))
334
+
335
+ # upload part
336
+ stub_request(:put, /#{object_url}\?partNumber.*/)
337
+
338
+ code = 'Timeout'
339
+ message = 'Request timeout.'
340
+ # commit multipart
341
+ stub_request(:post, /#{object_url}\?uploadId.*/)
342
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
343
+ .to_return(:status => 200)
344
+
345
+ success = false
346
+ 3.times do
347
+ begin
348
+ @bucket.resumable_upload(
349
+ @object_key, @file, part_size: 10, threads: 1)
350
+ success = true
351
+ rescue
352
+ # pass
353
+ end
354
+ end
355
+
356
+ expect(success).to be true
357
+ expect(WebMock).to have_requested(
358
+ :post, /#{object_url}\?uploads.*/).times(1)
359
+ expect(WebMock).to have_requested(
360
+ :put, /#{object_url}\?partNumber.*/).times(10)
361
+ expect(WebMock).to have_requested(
362
+ :post, /#{object_url}\?uploadId.*/).with{ |req|
363
+ query = parse_query_from_uri(req.uri)
364
+ query['uploadId'] == 'upload_id'
365
+ }.times(3)
366
+ end
367
+
368
+ it "should not write checkpoint when specify disable_cpt" do
369
+ # begin multipart
370
+ stub_request(:post, /#{object_url}\?uploads.*/)
371
+ .to_return(:body => mock_txn_id('upload_id'))
372
+
373
+ # upload part
374
+ stub_request(:put, /#{object_url}\?partNumber.*/)
375
+
376
+ code = 'Timeout'
377
+ message = 'Request timeout.'
378
+ # commit multipart
379
+ stub_request(:post, /#{object_url}\?uploadId.*/)
380
+ .to_return(:status => 500, :body => mock_error(code, message)).times(2).then
381
+ .to_return(:status => 200)
382
+
383
+ cpt_file = "#{File.expand_path(@file)}.cpt"
384
+ success = false
385
+ 3.times do
386
+ begin
387
+ @bucket.resumable_upload(
388
+ @object_key, @file, :part_size => 10,
389
+ :cpt_file => cpt_file, :disable_cpt => true)
390
+ success = true
391
+ rescue
392
+ # pass
393
+ end
394
+
395
+ expect(File.exists?(cpt_file)).to be false
396
+ end
397
+
398
+ expect(success).to be true
399
+ expect(WebMock).to have_requested(
400
+ :post, /#{object_url}\?uploads.*/).times(3)
401
+ expect(WebMock).to have_requested(
402
+ :put, /#{object_url}\?partNumber.*/).times(30)
403
+ expect(WebMock).to have_requested(
404
+ :post, /#{object_url}\?uploadId.*/).with{ |req|
405
+ query = parse_query_from_uri(req.uri)
406
+ query['uploadId'] == 'upload_id'
407
+ }.times(3)
408
+ end
409
+
410
+ end # Resumable upload
411
+
412
+ end # OSS
413
+ end # Aliyun
@@ -0,0 +1,83 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+
5
+ module AliyunSDK
6
+ module OSS
7
+
8
+ describe HTTP do
9
+
10
+ context HTTP::StreamWriter do
11
+ it "should read out chunks that are written" do
12
+ s = HTTP::StreamWriter.new do |sr|
13
+ 100.times { sr << "x" }
14
+ end
15
+
16
+ 10.times do
17
+ bytes, outbuf = 10, ''
18
+ s.read(bytes, outbuf)
19
+ expect(outbuf).to eq("x"*10)
20
+ end
21
+
22
+ outbuf = 'xxx'
23
+ r = s.read(10, outbuf)
24
+ expect(outbuf).to eq('')
25
+ expect(r).to be nil
26
+
27
+ r = s.read
28
+ expect(outbuf.empty?).to be true
29
+ expect(r).to eq('')
30
+ end
31
+
32
+ it "should convert chunk to string" do
33
+ s = HTTP::StreamWriter.new do |sr|
34
+ sr << 100 << 200
35
+ end
36
+
37
+ r = s.read
38
+ expect(r).to eq("100200")
39
+ end
40
+
41
+ it "should encode string to bytes" do
42
+ s = HTTP::StreamWriter.new do |sr|
43
+ 100.times { sr << "中" }
44
+ end
45
+
46
+ r = s.read(1)
47
+ expect(r).to eq('中'.force_encoding(Encoding::ASCII_8BIT)[0])
48
+ s.read(2)
49
+ r = s.read(3)
50
+ expect(r.force_encoding(Encoding::UTF_8)).to eq('中')
51
+
52
+ bytes = (100 - 2) * 3
53
+ outbuf = 'zzz'
54
+ r = s.read(bytes, outbuf)
55
+ expect(outbuf.size).to eq(bytes)
56
+ expect(r.size).to eq(bytes)
57
+
58
+ r = s.read
59
+ expect(r).to eq('')
60
+ end
61
+
62
+ it "should read exactly bytes" do
63
+ s = HTTP::StreamWriter.new do |sr|
64
+ 100.times { sr << 'x' * 10 }
65
+ end
66
+
67
+ r = s.read(11)
68
+ expect(r.size).to eq(11)
69
+
70
+ r = s.read(25)
71
+ expect(r.size).to eq(25)
72
+
73
+ r = s.read(900)
74
+ expect(r.size).to eq(900)
75
+
76
+ r = s.read(1000)
77
+ expect(r.size).to eq(64)
78
+ end
79
+ end # StreamWriter
80
+
81
+ end # HTTP
82
+ end # OSS
83
+ end # Aliyun