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,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