aliyun-sdk 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +30 -0
- data/examples/aliyun/oss/callback.rb +61 -0
- data/lib/aliyun/oss/bucket.rb +33 -1
- data/lib/aliyun/oss/exception.rb +3 -0
- data/lib/aliyun/oss/http.rb +1 -1
- data/lib/aliyun/oss/protocol.rb +88 -66
- data/lib/aliyun/oss/struct.rb +58 -0
- data/lib/aliyun/oss/upload.rb +2 -1
- data/lib/aliyun/version.rb +1 -1
- data/spec/aliyun/oss/client/bucket_spec.rb +47 -0
- data/spec/aliyun/oss/client/client_spec.rb +1 -1
- data/spec/aliyun/oss/client/resumable_upload_spec.rb +96 -0
- data/spec/aliyun/oss/object_spec.rb +20 -0
- data/tests/test_content_type.rb +1 -1
- data/tests/test_encoding.rb +1 -1
- data/tests/test_object_key.rb +1 -1
- data/tests/test_resumable.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30d7a2978cd6ef7cdc1ed369febb51a24ca84012
|
4
|
+
data.tar.gz: a8e6b8125b4f64ed182b2a190b01eed1db5dac8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9db93f799259a7066bd948cbab927bc38c831f6dea8a1bd155665f85132df0e0520af9a2766d2d33b4dff41f240dfdfe0296b135fb80393f026a0634f41e358
|
7
|
+
data.tar.gz: e4d5e3a83e3950bfb73601a4fcc7e3c0ce6910c6cf1141d7a46277e381f38d34d0254f880e615c5297343340132c3da88a14024dc7c41a62c16324a772bdb356
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -185,6 +185,35 @@ Object的common prefix,包含在`list_objects`的结果中。
|
|
185
185
|
Common prefix让用户不需要遍历所有的object(可能数量巨大)而找出前缀,
|
186
186
|
在模拟目录结构时非常有用。
|
187
187
|
|
188
|
+
## 上传回调
|
189
|
+
|
190
|
+
在`put_object`和`resumable_upload`时可以指定一个`Callback`,这样在文件
|
191
|
+
成功上传到OSS之后,OSS会向用户提供的服务器地址发起一个HTTP POST请求,
|
192
|
+
以通知用户相应的事件发生了。用户可以在收到这个通知之后进行相应的动作,
|
193
|
+
例如更新数据库、统计行为等。更多有关上传回调的内容请参考[OSS上传回调][oss-callback]。
|
194
|
+
|
195
|
+
下面的例子将演示如何使用上传回调:
|
196
|
+
|
197
|
+
callback = Aliyun::OSS::Callback.new(
|
198
|
+
url: 'http://10.101.168.94:1234/callback',
|
199
|
+
query: {user: 'put_object'},
|
200
|
+
body: 'bucket=${bucket}&object=${object}'
|
201
|
+
)
|
202
|
+
|
203
|
+
begin
|
204
|
+
bucket.put_object('files/hello', callback: callback)
|
205
|
+
rescue Aliyun::OSS::CallbackError => e
|
206
|
+
puts "Callback failed: #{e.message}"
|
207
|
+
end
|
208
|
+
|
209
|
+
**注意**
|
210
|
+
|
211
|
+
1. callback的url**不能**包含query string,而应该在`:query`参数中指定
|
212
|
+
2. 可能出现文件上传成功,但是执行回调失败的情况,此时client会抛出
|
213
|
+
`CallbackError`,用户如果要忽略此错误,需要显式接住这个异常。
|
214
|
+
3. 详细的例子可以参考[callback.rb](examples/aliyun/oss/callback.rb)
|
215
|
+
4. 接受回调的server可以参考[callback_server.rb](rails/aliyun_oss_callback_server.rb)
|
216
|
+
|
188
217
|
## 断点上传/下载
|
189
218
|
|
190
219
|
OSS支持大文件的存储,用户如果上传/下载大文件(Object)的时候中断了(网络
|
@@ -386,3 +415,4 @@ SDK采用rspec进行测试,如果要对SDK进行修改,请确保没有break
|
|
386
415
|
[custom-domain]: https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/oss_cname.html
|
387
416
|
[aliyun-sts]: https://help.aliyun.com/document_detail/ram/intro/concepts.html
|
388
417
|
[sdk-api]: http://www.rubydoc.info/gems/aliyun-sdk/
|
418
|
+
[oss-callback]: https://help.aliyun.com/document_detail/oss/user_guide/upload_object/upload_callback.html
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__))
|
4
|
+
require 'yaml'
|
5
|
+
require 'json'
|
6
|
+
require 'aliyun/oss'
|
7
|
+
|
8
|
+
##
|
9
|
+
# 用户在上传文件时可以指定“上传回调”,这样在文件上传成功后OSS会向用户
|
10
|
+
# 提供的服务器地址发起一个HTTP POST请求,相当于一个通知机制。用户可以
|
11
|
+
# 在收到回调的时候做相应的动作。
|
12
|
+
# 1. 如何接受OSS的回调可以参考代码目录下的
|
13
|
+
# rails/aliyun_oss_callback_server.rb
|
14
|
+
# 2. 只有put_object和resumable_upload支持上传回调
|
15
|
+
|
16
|
+
# 初始化OSS client
|
17
|
+
Aliyun::Common::Logging.set_log_level(Logger::DEBUG)
|
18
|
+
conf_file = '~/.oss.yml'
|
19
|
+
conf = YAML.load(File.read(File.expand_path(conf_file)))
|
20
|
+
bucket = Aliyun::OSS::Client.new(
|
21
|
+
:endpoint => conf['endpoint'],
|
22
|
+
:cname => conf['cname'],
|
23
|
+
:access_key_id => conf['access_key_id'],
|
24
|
+
:access_key_secret => conf['access_key_secret']).get_bucket(conf['bucket'])
|
25
|
+
|
26
|
+
# 辅助打印函数
|
27
|
+
def demo(msg)
|
28
|
+
puts "######### #{msg} ########"
|
29
|
+
puts
|
30
|
+
yield
|
31
|
+
puts "-------------------------"
|
32
|
+
puts
|
33
|
+
end
|
34
|
+
|
35
|
+
demo "put object with callback" do
|
36
|
+
callback = Aliyun::OSS::Callback.new(
|
37
|
+
url: 'http://10.101.168.94:1234/callback',
|
38
|
+
query: {user: 'put_object'},
|
39
|
+
body: 'bucket=${bucket}&object=${object}'
|
40
|
+
)
|
41
|
+
|
42
|
+
begin
|
43
|
+
bucket.put_object('files/hello', callback: callback)
|
44
|
+
rescue Aliyun::OSS::CallbackError => e
|
45
|
+
puts "Callback failed: #{e.message}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
demo "resumable upload with callback" do
|
50
|
+
callback = Aliyun::OSS::Callback.new(
|
51
|
+
url: 'http://10.101.168.94:1234/callback',
|
52
|
+
query: {user: 'resumable_upload'},
|
53
|
+
body: 'bucket=${bucket}&object=${object}'
|
54
|
+
)
|
55
|
+
|
56
|
+
begin
|
57
|
+
bucket.resumable_upload('files/world', '/tmp/x', callback: callback)
|
58
|
+
rescue Aliyun::OSS::CallbackError => e
|
59
|
+
puts "Callback failed: #{e.message}"
|
60
|
+
end
|
61
|
+
end
|
data/lib/aliyun/oss/bucket.rb
CHANGED
@@ -178,6 +178,9 @@ module Aliyun
|
|
178
178
|
# @option opts [Hash] :metas 设置object的meta,这是一些用户自定
|
179
179
|
# 义的属性,它们会和object一起存储,在{#get_object}的时候会
|
180
180
|
# 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' }
|
181
|
+
# @option opts [Callback] :callback 指定操作成功后OSS的
|
182
|
+
# 上传回调,上传成功后OSS会向用户的应用服务器发一个HTTP POST请
|
183
|
+
# 求,`:callback`参数指定这个请求的相关参数
|
181
184
|
# @yield [HTTP::StreamWriter] 如果调用的时候传递了block,则写入
|
182
185
|
# 到object的数据由block指定
|
183
186
|
# @example 流式上传数据
|
@@ -188,7 +191,20 @@ module Aliyun
|
|
188
191
|
# @example 指定Content-Type和metas
|
189
192
|
# put_object('x', :file => '/tmp/x', :content_type => 'text/html',
|
190
193
|
# :metas => {'year' => '2015', 'people' => 'mary'})
|
191
|
-
# @
|
194
|
+
# @example 指定Callback
|
195
|
+
# callback = Aliyun::OSS::Callback.new(
|
196
|
+
# url: 'http://10.101.168.94:1234/callback',
|
197
|
+
# query: {user: 'put_object'},
|
198
|
+
# body: 'bucket=${bucket}&object=${object}'
|
199
|
+
# )
|
200
|
+
#
|
201
|
+
# bucket.put_object('files/hello', callback: callback)
|
202
|
+
# @raise [CallbackError] 如果文件上传成功而Callback调用失败,抛
|
203
|
+
# 出此错误
|
204
|
+
# @note 如果opts中指定了`:file`,则block会被忽略
|
205
|
+
# @note 如果指定了`:callback`,则可能文件上传成功,但是callback
|
206
|
+
# 执行失败,此时会抛出{OSS::CallbackError},用户可以选择接住这
|
207
|
+
# 个异常,以忽略Callback调用错误
|
192
208
|
def put_object(key, opts = {}, &block)
|
193
209
|
args = opts.dup
|
194
210
|
|
@@ -420,15 +436,31 @@ module Aliyun
|
|
420
436
|
# 果设置为true,则在上传的过程中不会写checkpoint文件,这意味着
|
421
437
|
# 上传失败后不能断点续传,而只能重新上传整个文件。如果这个值为
|
422
438
|
# true,则:cpt_file会被忽略。
|
439
|
+
# @option opts [Callback] :callback 指定文件上传成功后OSS的
|
440
|
+
# 上传回调,上传成功后OSS会向用户的应用服务器发一个HTTP POST请
|
441
|
+
# 求,`:callback`参数指定这个请求的相关参数
|
423
442
|
# @yield [Float] 如果调用的时候传递了block,则会将上传进度交由
|
424
443
|
# block处理,进度值是一个0-1之间的小数
|
425
444
|
# @raise [CheckpointBrokenError] 如果cpt文件被损坏,则抛出此错误
|
426
445
|
# @raise [FileInconsistentError] 如果指定的文件与cpt中记录的不一
|
427
446
|
# 致,则抛出此错误
|
447
|
+
# @raise [CallbackError] 如果文件上传成功而Callback调用失败,抛
|
448
|
+
# 出此错误
|
428
449
|
# @example
|
429
450
|
# bucket.resumable_upload('my-object', '/tmp/x') do |p|
|
430
451
|
# puts "Progress: #{(p * 100).round(2)} %"
|
431
452
|
# end
|
453
|
+
# @example 指定Callback
|
454
|
+
# callback = Aliyun::OSS::Callback.new(
|
455
|
+
# url: 'http://10.101.168.94:1234/callback',
|
456
|
+
# query: {user: 'put_object'},
|
457
|
+
# body: 'bucket=${bucket}&object=${object}'
|
458
|
+
# )
|
459
|
+
#
|
460
|
+
# bucket.resumable_upload('files/hello', '/tmp/x', callback: callback)
|
461
|
+
# @note 如果指定了`:callback`,则可能文件上传成功,但是callback
|
462
|
+
# 执行失败,此时会抛出{OSS::CallbackError},用户可以选择接住这
|
463
|
+
# 个异常,以忽略Callback调用错误
|
432
464
|
def resumable_upload(key, file, opts = {}, &block)
|
433
465
|
args = opts.dup
|
434
466
|
|
data/lib/aliyun/oss/exception.rb
CHANGED
data/lib/aliyun/oss/http.rb
CHANGED
data/lib/aliyun/oss/protocol.rb
CHANGED
@@ -14,6 +14,7 @@ module Aliyun
|
|
14
14
|
class Protocol
|
15
15
|
|
16
16
|
STREAM_CHUNK_SIZE = 16 * 1024
|
17
|
+
CALLBACK_HEADER = 'x-oss-callback'
|
17
18
|
|
18
19
|
include Common::Logging
|
19
20
|
|
@@ -49,8 +50,8 @@ module Aliyun
|
|
49
50
|
'max-keys' => opts[:limit]
|
50
51
|
}.reject { |_, v| v.nil? }
|
51
52
|
|
52
|
-
|
53
|
-
doc = parse_xml(body)
|
53
|
+
r = @http.get( {}, {:query => params})
|
54
|
+
doc = parse_xml(r.body)
|
54
55
|
|
55
56
|
buckets = doc.css("Buckets Bucket").map do |node|
|
56
57
|
Bucket.new(
|
@@ -135,9 +136,9 @@ module Aliyun
|
|
135
136
|
logger.info("Begin get bucket acl, name: #{name}")
|
136
137
|
|
137
138
|
sub_res = {'acl' => nil}
|
138
|
-
|
139
|
+
r = @http.get({:bucket => name, :sub_res => sub_res})
|
139
140
|
|
140
|
-
doc = parse_xml(body)
|
141
|
+
doc = parse_xml(r.body)
|
141
142
|
acl = get_node_text(doc.at_css("AccessControlList"), 'Grant')
|
142
143
|
logger.info("Done get bucket acl")
|
143
144
|
|
@@ -182,9 +183,9 @@ module Aliyun
|
|
182
183
|
logger.info("Begin get bucket logging, name: #{name}")
|
183
184
|
|
184
185
|
sub_res = {'logging' => nil}
|
185
|
-
|
186
|
+
r = @http.get({:bucket => name, :sub_res => sub_res})
|
186
187
|
|
187
|
-
doc = parse_xml(body)
|
188
|
+
doc = parse_xml(r.body)
|
188
189
|
opts = {:enable => false}
|
189
190
|
|
190
191
|
logging_node = doc.at_css("LoggingEnabled")
|
@@ -249,10 +250,10 @@ module Aliyun
|
|
249
250
|
logger.info("Begin get bucket website, name: #{name}")
|
250
251
|
|
251
252
|
sub_res = {'website' => nil}
|
252
|
-
|
253
|
+
r = @http.get({:bucket => name, :sub_res => sub_res})
|
253
254
|
|
254
255
|
opts = {:enable => true}
|
255
|
-
doc = parse_xml(body)
|
256
|
+
doc = parse_xml(r.body)
|
256
257
|
opts.update(
|
257
258
|
:index => get_node_text(doc.at_css('IndexDocument'), 'Suffix'),
|
258
259
|
:error => get_node_text(doc.at_css('ErrorDocument'), 'Key')
|
@@ -307,9 +308,9 @@ module Aliyun
|
|
307
308
|
logger.info("Begin get bucket referer, name: #{name}")
|
308
309
|
|
309
310
|
sub_res = {'referer' => nil}
|
310
|
-
|
311
|
+
r = @http.get({:bucket => name, :sub_res => sub_res})
|
311
312
|
|
312
|
-
doc = parse_xml(body)
|
313
|
+
doc = parse_xml(r.body)
|
313
314
|
opts = {
|
314
315
|
:allow_empty =>
|
315
316
|
get_node_text(doc.root, 'AllowEmptyReferer', &:to_bool),
|
@@ -370,9 +371,9 @@ module Aliyun
|
|
370
371
|
logger.info("Begin get bucket lifecycle, name: #{name}")
|
371
372
|
|
372
373
|
sub_res = {'lifecycle' => nil}
|
373
|
-
|
374
|
+
r = @http.get({:bucket => name, :sub_res => sub_res})
|
374
375
|
|
375
|
-
doc = parse_xml(body)
|
376
|
+
doc = parse_xml(r.body)
|
376
377
|
rules = doc.css("Rule").map do |n|
|
377
378
|
days = n.at_css("Expiration Days")
|
378
379
|
date = n.at_css("Expiration Date")
|
@@ -443,9 +444,9 @@ module Aliyun
|
|
443
444
|
logger.info("Begin get bucket cors, bucket: #{name}")
|
444
445
|
|
445
446
|
sub_res = {'cors' => nil}
|
446
|
-
|
447
|
+
r = @http.get({:bucket => name, :sub_res => sub_res})
|
447
448
|
|
448
|
-
doc = parse_xml(body)
|
449
|
+
doc = parse_xml(r.body)
|
449
450
|
rules = []
|
450
451
|
|
451
452
|
doc.css("CORSRule").map do |n|
|
@@ -505,6 +506,8 @@ module Aliyun
|
|
505
506
|
# @option opts [Hash<Symbol, String>] :metas key-value pairs
|
506
507
|
# that serve as the object meta which will be stored together
|
507
508
|
# with the object
|
509
|
+
# @option opts [Callback] :callback the HTTP callback performed
|
510
|
+
# by OSS after `put_object` succeeds
|
508
511
|
# @yield [HTTP::StreamWriter] a stream writer is
|
509
512
|
# yielded to the caller to which it can write chunks of data
|
510
513
|
# streamingly
|
@@ -516,13 +519,23 @@ module Aliyun
|
|
516
519
|
"#{object_name}, options: #{opts}")
|
517
520
|
|
518
521
|
headers = {'Content-Type' => opts[:content_type]}
|
522
|
+
if opts.key?(:callback)
|
523
|
+
headers[CALLBACK_HEADER] = opts[:callback].serialize
|
524
|
+
end
|
525
|
+
|
519
526
|
(opts[:metas] || {})
|
520
527
|
.each { |k, v| headers["x-oss-meta-#{k.to_s}"] = v.to_s }
|
521
528
|
|
522
|
-
@http.put(
|
529
|
+
r = @http.put(
|
523
530
|
{:bucket => bucket_name, :object => object_name},
|
524
531
|
{:headers => headers, :body => HTTP::StreamPayload.new(&block)})
|
525
532
|
|
533
|
+
if r.code == 203
|
534
|
+
e = CallbackError.new(r)
|
535
|
+
logger.error(e.to_s)
|
536
|
+
raise e
|
537
|
+
end
|
538
|
+
|
526
539
|
logger.debug('Done put object')
|
527
540
|
end
|
528
541
|
|
@@ -557,13 +570,13 @@ module Aliyun
|
|
557
570
|
(opts[:metas] || {})
|
558
571
|
.each { |k, v| headers["x-oss-meta-#{k.to_s}"] = v.to_s }
|
559
572
|
|
560
|
-
|
561
|
-
|
562
|
-
|
573
|
+
r = @http.post(
|
574
|
+
{:bucket => bucket_name, :object => object_name, :sub_res => sub_res},
|
575
|
+
{:headers => headers, :body => HTTP::StreamPayload.new(&block)})
|
563
576
|
|
564
577
|
logger.debug('Done append object')
|
565
578
|
|
566
|
-
wrap(
|
579
|
+
wrap(r.headers[:x_oss_next_append_position], &:to_i) || -1
|
567
580
|
end
|
568
581
|
|
569
582
|
# List objects in a bucket.
|
@@ -614,12 +627,10 @@ module Aliyun
|
|
614
627
|
'encoding-type' => opts[:encoding]
|
615
628
|
}.reject { |_, v| v.nil? }
|
616
629
|
|
617
|
-
|
618
|
-
|
619
|
-
doc = parse_xml(body)
|
630
|
+
r = @http.get({:bucket => bucket_name}, {:query => params})
|
620
631
|
|
632
|
+
doc = parse_xml(r.body)
|
621
633
|
encoding = get_node_text(doc.root, 'EncodingType')
|
622
|
-
|
623
634
|
objects = doc.css("Contents").map do |node|
|
624
635
|
Object.new(
|
625
636
|
:key => get_node_text(node, "Key") { |x| decode_key(x, encoding) },
|
@@ -728,12 +739,13 @@ module Aliyun
|
|
728
739
|
rewrites[:expires].httpdate if rewrites.key?(:expires)
|
729
740
|
end
|
730
741
|
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
742
|
+
r = @http.get(
|
743
|
+
{:bucket => bucket_name, :object => object_name,
|
744
|
+
:sub_res => sub_res},
|
745
|
+
{:headers => headers}
|
746
|
+
) { |chunk| yield chunk if block_given? }
|
736
747
|
|
748
|
+
h = r.headers
|
737
749
|
metas = {}
|
738
750
|
meta_prefix = 'x_oss_meta_'
|
739
751
|
h.select { |k, _| k.to_s.start_with?(meta_prefix) }
|
@@ -772,10 +784,11 @@ module Aliyun
|
|
772
784
|
headers = {}
|
773
785
|
headers.merge!(get_conditions(opts[:condition])) if opts[:condition]
|
774
786
|
|
775
|
-
|
776
|
-
|
777
|
-
|
787
|
+
r = @http.head(
|
788
|
+
{:bucket => bucket_name, :object => object_name},
|
789
|
+
{:headers => headers})
|
778
790
|
|
791
|
+
h = r.headers
|
779
792
|
metas = {}
|
780
793
|
meta_prefix = 'x_oss_meta_'
|
781
794
|
h.select { |k, _| k.to_s.start_with?(meta_prefix) }
|
@@ -839,11 +852,11 @@ module Aliyun
|
|
839
852
|
|
840
853
|
headers.merge!(get_copy_conditions(opts[:condition])) if opts[:condition]
|
841
854
|
|
842
|
-
|
855
|
+
r = @http.put(
|
843
856
|
{:bucket => bucket_name, :object => dst_object_name},
|
844
857
|
{:headers => headers})
|
845
858
|
|
846
|
-
doc = parse_xml(body)
|
859
|
+
doc = parse_xml(r.body)
|
847
860
|
copy_result = {
|
848
861
|
:last_modified => get_node_text(
|
849
862
|
doc.root, 'LastModified') { |x| Time.parse(x) },
|
@@ -897,13 +910,13 @@ module Aliyun
|
|
897
910
|
query = {}
|
898
911
|
query['encoding-type'] = opts[:encoding] if opts[:encoding]
|
899
912
|
|
900
|
-
|
913
|
+
r = @http.post(
|
901
914
|
{:bucket => bucket_name, :sub_res => sub_res},
|
902
915
|
{:query => query, :body => body})
|
903
916
|
|
904
917
|
deleted = []
|
905
918
|
unless opts[:quiet]
|
906
|
-
doc = parse_xml(body)
|
919
|
+
doc = parse_xml(r.body)
|
907
920
|
encoding = get_node_text(doc.root, 'EncodingType')
|
908
921
|
doc.css("Deleted").map do |n|
|
909
922
|
deleted << get_node_text(n, 'Key') { |x| decode_key(x, encoding) }
|
@@ -942,10 +955,10 @@ module Aliyun
|
|
942
955
|
"object: #{object_name}")
|
943
956
|
|
944
957
|
sub_res = {'acl' => nil}
|
945
|
-
|
946
|
-
|
958
|
+
r = @http.get(
|
959
|
+
{bucket: bucket_name, object: object_name, sub_res: sub_res})
|
947
960
|
|
948
|
-
doc = parse_xml(body)
|
961
|
+
doc = parse_xml(r.body)
|
949
962
|
acl = get_node_text(doc.at_css("AccessControlList"), 'Grant')
|
950
963
|
|
951
964
|
logger.debug("Done get object acl")
|
@@ -974,18 +987,18 @@ module Aliyun
|
|
974
987
|
'Access-Control-Request-Headers' => headers.join(',')
|
975
988
|
}
|
976
989
|
|
977
|
-
|
978
|
-
|
979
|
-
|
990
|
+
r = @http.options(
|
991
|
+
{:bucket => bucket_name, :object => object_name},
|
992
|
+
{:headers => h})
|
980
993
|
|
981
994
|
logger.debug("Done get object cors")
|
982
995
|
|
983
996
|
CORSRule.new(
|
984
|
-
:allowed_origins =>
|
985
|
-
:allowed_methods =>
|
986
|
-
:allowed_headers =>
|
987
|
-
:expose_headers =>
|
988
|
-
:max_age_seconds =>
|
997
|
+
:allowed_origins => r.headers[:access_control_allow_origin],
|
998
|
+
:allowed_methods => r.headers[:access_control_allow_methods],
|
999
|
+
:allowed_headers => r.headers[:access_control_allow_headers],
|
1000
|
+
:expose_headers => r.headers[:access_control_expose_headers],
|
1001
|
+
:max_age_seconds => r.headers[:access_control_max_age]
|
989
1002
|
)
|
990
1003
|
end
|
991
1004
|
|
@@ -1014,12 +1027,12 @@ module Aliyun
|
|
1014
1027
|
(opts[:metas] || {})
|
1015
1028
|
.each { |k, v| headers["x-oss-meta-#{k.to_s}"] = v.to_s }
|
1016
1029
|
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1030
|
+
r = @http.post(
|
1031
|
+
{:bucket => bucket_name, :object => object_name,
|
1032
|
+
:sub_res => sub_res},
|
1033
|
+
{:headers => headers})
|
1021
1034
|
|
1022
|
-
doc = parse_xml(body)
|
1035
|
+
doc = parse_xml(r.body)
|
1023
1036
|
txn_id = get_node_text(doc.root, 'UploadId')
|
1024
1037
|
|
1025
1038
|
logger.info("Done initiate multipart upload: #{txn_id}.")
|
@@ -1040,13 +1053,13 @@ module Aliyun
|
|
1040
1053
|
"#{object_name}, txn id: #{txn_id}, part No: #{part_no}")
|
1041
1054
|
|
1042
1055
|
sub_res = {'partNumber' => part_no, 'uploadId' => txn_id}
|
1043
|
-
|
1056
|
+
r = @http.put(
|
1044
1057
|
{:bucket => bucket_name, :object => object_name, :sub_res => sub_res},
|
1045
1058
|
{:body => HTTP::StreamPayload.new(&block)})
|
1046
1059
|
|
1047
1060
|
logger.debug("Done upload part")
|
1048
1061
|
|
1049
|
-
Multipart::Part.new(:number => part_no, :etag => headers[:etag])
|
1062
|
+
Multipart::Part.new(:number => part_no, :etag => r.headers[:etag])
|
1050
1063
|
end
|
1051
1064
|
|
1052
1065
|
# Upload a part in a multipart uploading transaction by copying
|
@@ -1084,26 +1097,31 @@ module Aliyun
|
|
1084
1097
|
|
1085
1098
|
sub_res = {'partNumber' => part_no, 'uploadId' => txn_id}
|
1086
1099
|
|
1087
|
-
|
1100
|
+
r = @http.put(
|
1088
1101
|
{:bucket => bucket_name, :object => object_name, :sub_res => sub_res},
|
1089
1102
|
{:headers => headers})
|
1090
1103
|
|
1091
1104
|
logger.debug("Done upload part by copy: #{source_object}.")
|
1092
1105
|
|
1093
|
-
Multipart::Part.new(:number => part_no, :etag => headers[:etag])
|
1106
|
+
Multipart::Part.new(:number => part_no, :etag => r.headers[:etag])
|
1094
1107
|
end
|
1095
1108
|
|
1096
1109
|
# Complete a multipart uploading transaction
|
1097
1110
|
# @param bucket_name [String] the bucket name
|
1098
1111
|
# @param object_name [String] the object name
|
1099
1112
|
# @param txn_id [String] the upload id
|
1100
|
-
# @param parts [Array<Multipart::Part>] all the
|
1101
|
-
#
|
1102
|
-
|
1113
|
+
# @param parts [Array<Multipart::Part>] all the parts in this
|
1114
|
+
# transaction
|
1115
|
+
# @param callback [Callback] the HTTP callback performed by OSS
|
1116
|
+
# after this operation succeeds
|
1117
|
+
def complete_multipart_upload(
|
1118
|
+
bucket_name, object_name, txn_id, parts, callback = nil)
|
1103
1119
|
logger.debug("Begin complete multipart upload, "\
|
1104
1120
|
"txn id: #{txn_id}, parts: #{parts.map(&:to_s)}")
|
1105
1121
|
|
1106
1122
|
sub_res = {'uploadId' => txn_id}
|
1123
|
+
headers = {}
|
1124
|
+
headers[CALLBACK_HEADER] = callback.serialize if callback
|
1107
1125
|
|
1108
1126
|
body = Nokogiri::XML::Builder.new do |xml|
|
1109
1127
|
xml.CompleteMultipartUpload {
|
@@ -1116,9 +1134,15 @@ module Aliyun
|
|
1116
1134
|
}
|
1117
1135
|
end.to_xml
|
1118
1136
|
|
1119
|
-
@http.post(
|
1137
|
+
r = @http.post(
|
1120
1138
|
{:bucket => bucket_name, :object => object_name, :sub_res => sub_res},
|
1121
|
-
{:body => body})
|
1139
|
+
{:headers => headers, :body => body})
|
1140
|
+
|
1141
|
+
if r.code == 203
|
1142
|
+
e = CallbackError.new(r)
|
1143
|
+
logger.error(e.to_s)
|
1144
|
+
raise e
|
1145
|
+
end
|
1122
1146
|
|
1123
1147
|
logger.debug("Done complete multipart upload: #{txn_id}.")
|
1124
1148
|
end
|
@@ -1188,14 +1212,12 @@ module Aliyun
|
|
1188
1212
|
'encoding-type' => opts[:encoding]
|
1189
1213
|
}.reject { |_, v| v.nil? }
|
1190
1214
|
|
1191
|
-
|
1215
|
+
r = @http.get(
|
1192
1216
|
{:bucket => bucket_name, :sub_res => sub_res},
|
1193
1217
|
{:query => params})
|
1194
1218
|
|
1195
|
-
doc = parse_xml(body)
|
1196
|
-
|
1219
|
+
doc = parse_xml(r.body)
|
1197
1220
|
encoding = get_node_text(doc.root, 'EncodingType')
|
1198
|
-
|
1199
1221
|
txns = doc.css("Upload").map do |node|
|
1200
1222
|
Multipart::Transaction.new(
|
1201
1223
|
:id => get_node_text(node, "UploadId"),
|
@@ -1259,11 +1281,11 @@ module Aliyun
|
|
1259
1281
|
'encoding-type' => opts[:encoding]
|
1260
1282
|
}.reject { |_, v| v.nil? }
|
1261
1283
|
|
1262
|
-
|
1284
|
+
r = @http.get(
|
1263
1285
|
{:bucket => bucket_name, :object => object_name, :sub_res => sub_res},
|
1264
1286
|
{:query => params})
|
1265
1287
|
|
1266
|
-
doc = parse_xml(body)
|
1288
|
+
doc = parse_xml(r.body)
|
1267
1289
|
parts = doc.css("Part").map do |node|
|
1268
1290
|
Multipart::Part.new(
|
1269
1291
|
:number => get_node_text(node, 'PartNumber', &:to_i),
|
data/lib/aliyun/oss/struct.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
+
require 'base64'
|
4
|
+
require 'json'
|
5
|
+
require 'uri'
|
6
|
+
|
3
7
|
module Aliyun
|
4
8
|
module OSS
|
5
9
|
|
@@ -146,5 +150,59 @@ module Aliyun
|
|
146
150
|
|
147
151
|
end # CORSRule
|
148
152
|
|
153
|
+
##
|
154
|
+
# Callback represents a HTTP call made by OSS to user's
|
155
|
+
# application server after an event happens, such as an object is
|
156
|
+
# successfully uploaded to OSS. See: {https://help.aliyun.com/document_detail/oss/api-reference/object/Callback.html}
|
157
|
+
# Attributes:
|
158
|
+
# * url [String] the URL *WITHOUT* the query string
|
159
|
+
# * query [Hash] the query to generate query string
|
160
|
+
# * body [String] the body of the request
|
161
|
+
# * content_type [String] the Content-Type of the request
|
162
|
+
# * host [String] the Host in HTTP header for this request
|
163
|
+
class Callback < Common::Struct::Base
|
164
|
+
|
165
|
+
attrs :url, :query, :body, :content_type, :host
|
166
|
+
|
167
|
+
include Common::Logging
|
168
|
+
|
169
|
+
def serialize
|
170
|
+
query_string = (query || {}).map { |k, v|
|
171
|
+
[CGI.escape(k.to_s), CGI.escape(v.to_s)].join('=') }.join('&')
|
172
|
+
|
173
|
+
cb = {
|
174
|
+
'callbackUrl' => "#{normalize_url(url)}?#{query_string}",
|
175
|
+
'callbackBody' => body,
|
176
|
+
'callbackBodyType' => content_type || default_content_type
|
177
|
+
}
|
178
|
+
cb['callbackHost'] = host if host
|
179
|
+
|
180
|
+
logger.debug("Callback json: #{cb}")
|
181
|
+
|
182
|
+
Base64.strict_encode64(cb.to_json)
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
def normalize_url(url)
|
187
|
+
uri = URI.parse(url)
|
188
|
+
uri = URI.parse("http://#{url}") unless uri.scheme
|
189
|
+
|
190
|
+
if uri.scheme != 'http' and uri.scheme != 'https'
|
191
|
+
fail ClientError, "Only HTTP and HTTPS endpoint are accepted."
|
192
|
+
end
|
193
|
+
|
194
|
+
unless uri.query.nil?
|
195
|
+
fail ClientError, "Query parameters should not appear in URL."
|
196
|
+
end
|
197
|
+
|
198
|
+
uri.to_s
|
199
|
+
end
|
200
|
+
|
201
|
+
def default_content_type
|
202
|
+
"application/x-www-form-urlencoded"
|
203
|
+
end
|
204
|
+
|
205
|
+
end # Callback
|
206
|
+
|
149
207
|
end # OSS
|
150
208
|
end # Aliyun
|
data/lib/aliyun/oss/upload.rb
CHANGED
@@ -116,7 +116,8 @@ module Aliyun
|
|
116
116
|
parts = sync_get_all_parts.map{ |p|
|
117
117
|
Part.new(:number => p[:number], :etag => p[:etag])
|
118
118
|
}
|
119
|
-
@protocol.complete_multipart_upload(
|
119
|
+
@protocol.complete_multipart_upload(
|
120
|
+
bucket, object, id, parts, @options[:callback])
|
120
121
|
|
121
122
|
File.delete(@cpt_file) unless options[:disable_cpt]
|
122
123
|
|
data/lib/aliyun/version.rb
CHANGED
@@ -104,6 +104,16 @@ module Aliyun
|
|
104
104
|
end.to_xml
|
105
105
|
end
|
106
106
|
|
107
|
+
def mock_error(code, message)
|
108
|
+
Nokogiri::XML::Builder.new do |xml|
|
109
|
+
xml.Error {
|
110
|
+
xml.Code code
|
111
|
+
xml.Message message
|
112
|
+
xml.RequestId '0000'
|
113
|
+
}
|
114
|
+
end.to_xml
|
115
|
+
end
|
116
|
+
|
107
117
|
def err(msg, reqid = '0000')
|
108
118
|
"#{msg} RequestId: #{reqid}"
|
109
119
|
end
|
@@ -229,6 +239,43 @@ module Aliyun
|
|
229
239
|
.with(:body => content, :query => {})
|
230
240
|
end
|
231
241
|
|
242
|
+
it "should put object with callback" do
|
243
|
+
key = 'ruby'
|
244
|
+
stub_request(:put, object_url(key))
|
245
|
+
|
246
|
+
callback = Callback.new(
|
247
|
+
url: 'http://app.server.com/callback',
|
248
|
+
query: {'id' => 1, 'name' => '杭州'},
|
249
|
+
body: 'hello world',
|
250
|
+
host: 'server.com'
|
251
|
+
)
|
252
|
+
@bucket.put_object(key, callback: callback)
|
253
|
+
|
254
|
+
expect(WebMock).to have_requested(:put, object_url(key))
|
255
|
+
.with { |req| req.headers.key?('X-Oss-Callback') }
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should raise CallbackError when callback failed" do
|
259
|
+
key = 'ruby'
|
260
|
+
code = 'CallbackFailed'
|
261
|
+
message = 'Error status: 502.'
|
262
|
+
stub_request(:put, object_url(key))
|
263
|
+
.to_return(:status => 203, :body => mock_error(code, message))
|
264
|
+
|
265
|
+
callback = Callback.new(
|
266
|
+
url: 'http://app.server.com/callback',
|
267
|
+
query: {'id' => 1, 'name' => '杭州'},
|
268
|
+
body: 'hello world',
|
269
|
+
host: 'server.com'
|
270
|
+
)
|
271
|
+
expect {
|
272
|
+
@bucket.put_object(key, callback: callback)
|
273
|
+
}.to raise_error(CallbackError, err(message))
|
274
|
+
|
275
|
+
expect(WebMock).to have_requested(:put, object_url(key))
|
276
|
+
.with { |req| req.headers.key?('X-Oss-Callback') }
|
277
|
+
end
|
278
|
+
|
232
279
|
it "should get object to file" do
|
233
280
|
key = 'ruby'
|
234
281
|
# 100 KB
|
@@ -54,7 +54,7 @@ module Aliyun
|
|
54
54
|
|
55
55
|
expect(WebMock)
|
56
56
|
.to have_requested(:get, "#{bucket}.#{endpoint}/#{object}")
|
57
|
-
.with{ |req|
|
57
|
+
.with{ |req| req.headers.key?('X-Oss-Security-Token') }
|
58
58
|
end
|
59
59
|
|
60
60
|
it "should construct different client" do
|
@@ -66,6 +66,10 @@ module Aliyun
|
|
66
66
|
end.to_xml
|
67
67
|
end
|
68
68
|
|
69
|
+
def err(msg, reqid = '0000')
|
70
|
+
"#{msg} RequestId: #{reqid}"
|
71
|
+
end
|
72
|
+
|
69
73
|
it "should upload file when all goes well" do
|
70
74
|
stub_request(:post, /#{object_url}\?uploads.*/)
|
71
75
|
.to_return(:body => mock_txn_id('upload_id'))
|
@@ -99,6 +103,98 @@ module Aliyun
|
|
99
103
|
expect(prg.size).to eq(10)
|
100
104
|
end
|
101
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
|
+
prg = []
|
119
|
+
@bucket.resumable_upload(
|
120
|
+
@object_key, @file,
|
121
|
+
:part_size => 10, :callback => callback) { |p| prg << p }
|
122
|
+
|
123
|
+
expect(WebMock).to have_requested(
|
124
|
+
:post, /#{object_url}\?uploads.*/).times(1)
|
125
|
+
|
126
|
+
part_numbers = Set.new([])
|
127
|
+
upload_ids = Set.new([])
|
128
|
+
|
129
|
+
expect(WebMock).to have_requested(
|
130
|
+
:put, /#{object_url}\?partNumber.*/).with{ |req|
|
131
|
+
query = parse_query_from_uri(req.uri)
|
132
|
+
part_numbers << query['partNumber']
|
133
|
+
upload_ids << query['uploadId']
|
134
|
+
}.times(10)
|
135
|
+
|
136
|
+
expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s })
|
137
|
+
expect(upload_ids.to_a).to match_array(['upload_id'])
|
138
|
+
|
139
|
+
expect(WebMock)
|
140
|
+
.to have_requested(
|
141
|
+
:post, /#{object_url}\?uploadId.*/)
|
142
|
+
.with { |req| req.headers.key?('X-Oss-Callback') }
|
143
|
+
.times(1)
|
144
|
+
|
145
|
+
expect(File.exist?("#{@file}.cpt")).to be false
|
146
|
+
expect(prg.size).to eq(10)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should raise CallbackError when callback failed" do
|
150
|
+
stub_request(:post, /#{object_url}\?uploads.*/)
|
151
|
+
.to_return(:body => mock_txn_id('upload_id'))
|
152
|
+
stub_request(:put, /#{object_url}\?partNumber.*/)
|
153
|
+
|
154
|
+
code = 'CallbackFailed'
|
155
|
+
message = 'Error status: 502.'
|
156
|
+
stub_request(:post, /#{object_url}\?uploadId.*/)
|
157
|
+
.to_return(:status => 203, :body => mock_error(code, message))
|
158
|
+
|
159
|
+
callback = Callback.new(
|
160
|
+
url: 'http://app.server.com/callback',
|
161
|
+
query: {'id' => 1, 'name' => '杭州'},
|
162
|
+
body: 'hello world',
|
163
|
+
host: 'server.com'
|
164
|
+
)
|
165
|
+
prg = []
|
166
|
+
expect {
|
167
|
+
@bucket.resumable_upload(
|
168
|
+
@object_key, @file,
|
169
|
+
:part_size => 10, :callback => callback) { |p| prg << p }
|
170
|
+
}.to raise_error(CallbackError, err(message))
|
171
|
+
|
172
|
+
expect(WebMock).to have_requested(
|
173
|
+
:post, /#{object_url}\?uploads.*/).times(1)
|
174
|
+
|
175
|
+
part_numbers = Set.new([])
|
176
|
+
upload_ids = Set.new([])
|
177
|
+
|
178
|
+
expect(WebMock).to have_requested(
|
179
|
+
:put, /#{object_url}\?partNumber.*/).with{ |req|
|
180
|
+
query = parse_query_from_uri(req.uri)
|
181
|
+
part_numbers << query['partNumber']
|
182
|
+
upload_ids << query['uploadId']
|
183
|
+
}.times(10)
|
184
|
+
|
185
|
+
expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s })
|
186
|
+
expect(upload_ids.to_a).to match_array(['upload_id'])
|
187
|
+
|
188
|
+
expect(WebMock)
|
189
|
+
.to have_requested(
|
190
|
+
:post, /#{object_url}\?uploadId.*/)
|
191
|
+
.with { |req| req.headers.key?('X-Oss-Callback') }
|
192
|
+
.times(1)
|
193
|
+
|
194
|
+
expect(File.exist?("#{@file}.cpt")).to be true
|
195
|
+
expect(prg.size).to eq(10)
|
196
|
+
end
|
197
|
+
|
102
198
|
it "should restart when begin txn fails" do
|
103
199
|
code = 'Timeout'
|
104
200
|
message = 'Request timeout.'
|
@@ -737,6 +737,26 @@ module Aliyun
|
|
737
737
|
end
|
738
738
|
end # cors
|
739
739
|
|
740
|
+
context "callback" do
|
741
|
+
it "should encode callback" do
|
742
|
+
callback = Callback.new(
|
743
|
+
url: 'http://app.server.com/callback',
|
744
|
+
query: {'id' => 1, 'name' => '杭州'},
|
745
|
+
body: 'hello world',
|
746
|
+
host: 'server.com'
|
747
|
+
)
|
748
|
+
|
749
|
+
encoded = "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly9hcHAuc2VydmVyLmNvbS9jYWxsYmFjaz9pZD0xJm5hbWU9JUU2JTlEJUFEJUU1JUI3JTlFIiwiY2FsbGJhY2tCb2R5IjoiaGVsbG8gd29ybGQiLCJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkIiwiY2FsbGJhY2tIb3N0Ijoic2VydmVyLmNvbSJ9"
|
750
|
+
expect(callback.serialize).to eq(encoded)
|
751
|
+
end
|
752
|
+
|
753
|
+
it "should not accept url with query string" do
|
754
|
+
expect {
|
755
|
+
Callback.new(url: 'http://app.server.com/callback?id=1').serialize
|
756
|
+
}.to raise_error(ClientError, "Query parameters should not appear in URL.")
|
757
|
+
end
|
758
|
+
|
759
|
+
end
|
740
760
|
end # Object
|
741
761
|
|
742
762
|
end # OSS
|
data/tests/test_content_type.rb
CHANGED
@@ -5,7 +5,7 @@ require 'aliyun/oss'
|
|
5
5
|
|
6
6
|
class TestContentType < Minitest::Test
|
7
7
|
def setup
|
8
|
-
Aliyun::
|
8
|
+
Aliyun::Common::Logging.set_log_level(Logger::DEBUG)
|
9
9
|
conf_file = '~/.oss.yml'
|
10
10
|
conf = YAML.load(File.read(File.expand_path(conf_file)))
|
11
11
|
client = Aliyun::OSS::Client.new(
|
data/tests/test_encoding.rb
CHANGED
@@ -6,7 +6,7 @@ require 'aliyun/oss'
|
|
6
6
|
|
7
7
|
class TestEncoding < Minitest::Test
|
8
8
|
def setup
|
9
|
-
Aliyun::
|
9
|
+
Aliyun::Common::Logging.set_log_level(Logger::DEBUG)
|
10
10
|
conf_file = '~/.oss.yml'
|
11
11
|
conf = YAML.load(File.read(File.expand_path(conf_file)))
|
12
12
|
client = Aliyun::OSS::Client.new(
|
data/tests/test_object_key.rb
CHANGED
@@ -6,7 +6,7 @@ require 'aliyun/oss'
|
|
6
6
|
|
7
7
|
class TestObjectKey < Minitest::Test
|
8
8
|
def setup
|
9
|
-
Aliyun::
|
9
|
+
Aliyun::Common::Logging.set_log_level(Logger::DEBUG)
|
10
10
|
conf_file = '~/.oss.yml'
|
11
11
|
conf = YAML.load(File.read(File.expand_path(conf_file)))
|
12
12
|
client = Aliyun::OSS::Client.new(
|
data/tests/test_resumable.rb
CHANGED
@@ -5,7 +5,7 @@ require 'aliyun/oss'
|
|
5
5
|
|
6
6
|
class TestResumable < Minitest::Test
|
7
7
|
def setup
|
8
|
-
Aliyun::
|
8
|
+
Aliyun::Common::Logging.set_log_level(Logger::DEBUG)
|
9
9
|
conf_file = '~/.oss.yml'
|
10
10
|
conf = YAML.load(File.read(File.expand_path(conf_file)))
|
11
11
|
client = Aliyun::OSS::Client.new(
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aliyun-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tianlong Wu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0.10'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '5.8'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '5.8'
|
111
125
|
description: A Ruby program to facilitate accessing Aliyun Object Storage Service
|
112
126
|
email:
|
113
127
|
- rockuw.@gmail.com
|
@@ -120,6 +134,7 @@ files:
|
|
120
134
|
- CHANGELOG.md
|
121
135
|
- README.md
|
122
136
|
- examples/aliyun/oss/bucket.rb
|
137
|
+
- examples/aliyun/oss/callback.rb
|
123
138
|
- examples/aliyun/oss/object.rb
|
124
139
|
- examples/aliyun/oss/resumable_download.rb
|
125
140
|
- examples/aliyun/oss/resumable_upload.rb
|