Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Change Log

### v0.3.2

- Allow setting custom HTTP headers in put/append/resumable_upload
- Allow setting object acl in put/append

### v0.3.1

- Fix frozen string issue in OSSClient/STSClient config
Expand Down
4 changes: 3 additions & 1 deletion examples/aliyun/oss/resumable_download.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ def demo(msg)

demo "Resumable download" do
# 下载一个100M的文件
cpt_file = '/tmp/y.cpt'
File.delete(cpt_file) if File.exist?(cpt_file)
start = Time.now
puts "Start download: resumable => /tmp/y"
bucket.resumable_download(
'resumable', '/tmp/y', :cpt_file => '/tmp/y.cpt') do |progress|
'resumable', '/tmp/y', :cpt_file => cpt_file) do |progress|
puts "Progress: #{(progress * 100).round(2)} %"
end
puts "Download complete. Cost: #{Time.now - start} seconds."
Expand Down
5 changes: 4 additions & 1 deletion examples/aliyun/oss/resumable_upload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ def demo(msg)
(1..1024*1024).each{ |i| f.puts i.to_s.rjust(99, '0') }
end

cpt_file = '/tmp/x.cpt'
File.delete(cpt_file) if File.exist?(cpt_file)

# 上传一个100M的文件
start = Time.now
puts "Start upload: /tmp/x => resumable"
bucket.resumable_upload(
'resumable', '/tmp/x', :cpt_file => '/tmp/x.cpt') do |progress|
'resumable', '/tmp/x', :cpt_file => cpt_file) do |progress|
puts "Progress: #{(progress * 100).round(2)} %"
end
puts "Upload complete. Cost: #{Time.now - start} seconds."
Expand Down
6 changes: 6 additions & 0 deletions lib/aliyun/oss/bucket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ def list_objects(opts = {})
# @option opts [Callback] :callback 指定操作成功后OSS的
# 上传回调,上传成功后OSS会向用户的应用服务器发一个HTTP POST请
# 求,`:callback`参数指定这个请求的相关参数
# @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小
# 写。这里指定的值会覆盖通过`:content_type`和`:metas`设置的值。
# @yield [HTTP::StreamWriter] 如果调用的时候传递了block,则写入
# 到object的数据由block指定
# @example 流式上传数据
Expand Down Expand Up @@ -315,6 +317,8 @@ def object_exists?(key)
# @option opts [Hash] :metas 设置object的meta,这是一些用户自定
# 义的属性,它们会和object一起存储,在{#get_object}的时候会
# 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' }
# @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小
# 写。这里指定的值会覆盖通过`:content_type`和`:metas`设置的值。
# @example 流式上传数据
# pos = append_object('x', 0){ |stream| 100.times { |i| stream << i.to_s } }
# append_object('x', pos){ |stream| stream << get_data }
Expand Down Expand Up @@ -439,6 +443,8 @@ def get_object_cors(key)
# @option opts [Callback] :callback 指定文件上传成功后OSS的
# 上传回调,上传成功后OSS会向用户的应用服务器发一个HTTP POST请
# 求,`:callback`参数指定这个请求的相关参数
# @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小
# 写。这里指定的值会覆盖通过`:content_type`和`:metas`设置的值。
# @yield [Float] 如果调用的时候传递了block,则会将上传进度交由
# block处理,进度值是一个0-1之间的小数
# @raise [CheckpointBrokenError] 如果cpt文件被损坏,则抛出此错误
Expand Down
14 changes: 7 additions & 7 deletions lib/aliyun/oss/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -207,16 +207,16 @@ def do_request(verb, resources = {}, http_options = {}, &block)
sub_res = resources[:sub_res]

headers = http_options[:headers] || {}
headers['User-Agent'] = get_user_agent
headers['Date'] = Time.now.httpdate
headers['Content-Type'] ||= DEFAULT_CONTENT_TYPE
headers['user-agent'] = get_user_agent
headers['date'] = Time.now.httpdate
headers['content-type'] ||= DEFAULT_CONTENT_TYPE
headers[STS_HEADER] = @config.sts_token if @config.sts_token

if body = http_options[:body]
if body.respond_to?(:read)
headers['Transfer-Encoding'] = 'chunked'
headers['transfer-encoding'] = 'chunked'
else
headers['Content-MD5'] = Util.get_content_md5(body)
headers['content-md5'] = Util.get_content_md5(body)
end
end

Expand All @@ -227,7 +227,7 @@ def do_request(verb, resources = {}, http_options = {}, &block)

if @config.access_key_id and @config.access_key_secret
sig = Util.get_signature(@config.access_key_secret, verb, headers, res)
headers['Authorization'] = "OSS #{@config.access_key_id}:#{sig}"
headers['authorization'] = "OSS #{@config.access_key_id}:#{sig}"
end

logger.debug("Send HTTP request, verb: #{verb}, resources: " \
Expand Down Expand Up @@ -295,7 +295,7 @@ module RestClient
module Payload
class Base
def headers
({'Content-Length' => size.to_s} if size) || {}
({'content-length' => size.to_s} if size) || {}
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/aliyun/oss/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module OSS
#
class Object < Common::Struct::Base

attrs :key, :type, :size, :etag, :metas, :last_modified, :content_type
attrs :key, :type, :size, :etag, :metas, :last_modified, :headers

end # Object
end # OSS
Expand Down
71 changes: 51 additions & 20 deletions lib/aliyun/oss/protocol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@ def delete_bucket(name)
# @param bucket_name [String] the bucket name
# @param object_name [String] the object name
# @param opts [Hash] Options
# @option opts [String] :acl specify the object's ACL. See
# {OSS::ACL}
# @option opts [String] :content_type the HTTP Content-Type
# for the file, if not specified client will try to determine
# the type itself and fall back to HTTP::DEFAULT_CONTENT_TYPE
Expand All @@ -508,6 +510,9 @@ def delete_bucket(name)
# with the object
# @option opts [Callback] :callback the HTTP callback performed
# by OSS after `put_object` succeeds
# @option opts [Hash] :headers custom HTTP headers, case
# insensitive. Headers specified here will overwrite `:metas`
# and `:content_type`
# @yield [HTTP::StreamWriter] a stream writer is
# yielded to the caller to which it can write chunks of data
# streamingly
Expand All @@ -518,14 +523,17 @@ def put_object(bucket_name, object_name, opts = {}, &block)
logger.debug("Begin put object, bucket: #{bucket_name}, object: "\
"#{object_name}, options: #{opts}")

headers = {'Content-Type' => opts[:content_type]}
headers = {'content-type' => opts[:content_type]}
headers['x-oss-object-acl'] = opts[:acl] if opts.key?(:acl)
to_lower_case(opts[:metas] || {})
.each { |k, v| headers["x-oss-meta-#{k.to_s}"] = v.to_s }

headers.merge!(to_lower_case(opts[:headers])) if opts.key?(:headers)

if opts.key?(:callback)
headers[CALLBACK_HEADER] = opts[:callback].serialize
end

(opts[:metas] || {})
.each { |k, v| headers["x-oss-meta-#{k.to_s}"] = v.to_s }

r = @http.put(
{:bucket => bucket_name, :object => object_name},
{:headers => headers, :body => HTTP::StreamPayload.new(&block)})
Expand All @@ -546,13 +554,18 @@ def put_object(bucket_name, object_name, opts = {}, &block)
# @param object_name [String] the object name
# @param position [Integer] the position to append
# @param opts [Hash] Options
# @option opts [String] :acl specify the object's ACL. See
# {OSS::ACL}
# @option opts [String] :content_type the HTTP Content-Type
# for the file, if not specified client will try to determine
# the type itself and fall back to HTTP::DEFAULT_CONTENT_TYPE
# if it fails to do so
# @option opts [Hash<Symbol, String>] :metas key-value pairs
# that serve as the object meta which will be stored together
# with the object
# @option opts [Hash] :headers custom HTTP headers, case
# insensitive. Headers specified here will overwrite `:metas`
# and `:content_type`
# @return [Integer] next position to append
# @yield [HTTP::StreamWriter] a stream writer is
# yielded to the caller to which it can write chunks of data
Expand All @@ -566,10 +579,13 @@ def append_object(bucket_name, object_name, position, opts = {}, &block)
"#{object_name}, position: #{position}, options: #{opts}")

sub_res = {'append' => nil, 'position' => position}
headers = {'Content-Type' => opts[:content_type]}
(opts[:metas] || {})
headers = {'content-type' => opts[:content_type]}
headers['x-oss-object-acl'] = opts[:acl] if opts.key?(:acl)
to_lower_case(opts[:metas] || {})
.each { |k, v| headers["x-oss-meta-#{k.to_s}"] = v.to_s }

headers.merge!(to_lower_case(opts[:headers])) if opts.key?(:headers)

r = @http.post(
{:bucket => bucket_name, :object => object_name, :sub_res => sub_res},
{:headers => headers, :body => HTTP::StreamPayload.new(&block)})
Expand Down Expand Up @@ -721,7 +737,7 @@ def get_object(bucket_name, object_name, opts = {}, &block)
rewrites = opts[:rewrite]

headers = {}
headers['Range'] = get_bytes_range(range) if range
headers['range'] = get_bytes_range(range) if range
headers.merge!(get_conditions(conditions)) if conditions

sub_res = {}
Expand Down Expand Up @@ -758,7 +774,7 @@ def get_object(bucket_name, object_name, opts = {}, &block)
:etag => h[:etag],
:metas => metas,
:last_modified => wrap(h[:last_modified]) { |x| Time.parse(x) },
:content_type => h[:content_type])
:headers => h)

logger.debug("Done get object")

Expand Down Expand Up @@ -801,7 +817,7 @@ def get_object_meta(bucket_name, object_name, opts = {})
:etag => h[:etag],
:metas => metas,
:last_modified => wrap(h[:last_modified]) { |x| Time.parse(x) },
:content_type => h[:content_type])
:headers => h)

logger.debug("Done get object meta")

Expand Down Expand Up @@ -840,7 +856,7 @@ def copy_object(bucket_name, src_object_name, dst_object_name, opts = {})
headers = {
'x-oss-copy-source' =>
@http.get_resource_path(bucket_name, src_object_name),
'Content-Type' => opts[:content_type]
'content-type' => opts[:content_type]
}
(opts[:metas] || {})
.each { |k, v| headers["x-oss-meta-#{k.to_s}"] = v.to_s }
Expand Down Expand Up @@ -982,9 +998,9 @@ def get_object_cors(bucket_name, object_name, origin, method, headers = [])
"headers: #{headers.join(',')}")

h = {
'Origin' => origin,
'Access-Control-Request-Method' => method,
'Access-Control-Request-Headers' => headers.join(',')
'origin' => origin,
'access-control-request-method' => method,
'access-control-request-headers' => headers.join(',')
}

r = @http.options(
Expand Down Expand Up @@ -1017,16 +1033,21 @@ def get_object_cors(bucket_name, object_name, origin, method, headers = [])
# @option opts [Hash<Symbol, String>] :metas key-value pairs
# that serve as the object meta which will be stored together
# with the object
# @option opts [Hash] :headers custom HTTP headers, case
# insensitive. Headers specified here will overwrite `:metas`
# and `:content_type`
# @return [String] the upload id
def initiate_multipart_upload(bucket_name, object_name, opts = {})
logger.info("Begin initiate multipart upload, bucket: "\
"#{bucket_name}, object: #{object_name}, options: #{opts}")

sub_res = {'uploads' => nil}
headers = {'Content-Type' => opts[:content_type]}
(opts[:metas] || {})
headers = {'content-type' => opts[:content_type]}
to_lower_case(opts[:metas] || {})
.each { |k, v| headers["x-oss-meta-#{k.to_s}"] = v.to_s }

headers.merge!(to_lower_case(opts[:headers])) if opts.key?(:headers)

r = @http.post(
{:bucket => bucket_name, :object => object_name,
:sub_res => sub_res},
Expand Down Expand Up @@ -1092,7 +1113,7 @@ def upload_part_by_copy(
'x-oss-copy-source' =>
@http.get_resource_path(bucket_name, source_object)
}
headers['Range'] = get_bytes_range(range) if range
headers['range'] = get_bytes_range(range) if range
headers.merge!(get_copy_conditions(conditions)) if conditions

sub_res = {'partNumber' => part_no, 'uploadId' => txn_id}
Expand Down Expand Up @@ -1392,14 +1413,14 @@ def wrap(x, &block)
# @return [Hash] conditions for HTTP headers
def get_conditions(conditions)
{
:if_modified_since => 'If-Modified-Since',
:if_unmodified_since => 'If-Unmodified-Since',
:if_modified_since => 'if-modified-since',
:if_unmodified_since => 'if-unmodified-since',
}.reduce({}) { |h, (k, v)|
conditions.key?(k)? h.merge(v => conditions[k].httpdate) : h
}.merge(
{
:if_match_etag => 'If-Match',
:if_unmatch_etag => 'If-None-Match'
:if_match_etag => 'if-match',
:if_unmatch_etag => 'if-none-match'
}.reduce({}) { |h, (k, v)|
conditions.key?(k)? h.merge(v => conditions[k]) : h
}
Expand Down Expand Up @@ -1445,6 +1466,16 @@ def update_if_exists(hash, kv)
kv.each { |k, v| hash[k] = v.call(hash[k]) if hash.key?(k) }
end

# Convert hash keys to lower case Non-Recursively
# @param hash [Hash] the hash to be converted
# @return [Hash] hash with lower case keys
def to_lower_case(hash)
hash.reduce({}) do |result, (k, v)|
result[k.to_s.downcase] = v
result
end
end

end # Protocol
end # OSS
end # Aliyun
6 changes: 3 additions & 3 deletions lib/aliyun/oss/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class << self
def get_signature(key, verb, headers, resources)
logger.debug("Sign, headers: #{headers}, resources: #{resources}")

content_md5 = headers['Content-MD5'] || ""
content_type = headers['Content-Type'] || ""
date = headers['Date']
content_md5 = headers['content-md5'] || ""
content_type = headers['content-type'] || ""
date = headers['date']

cano_headers = headers.select { |k, v| k.start_with?(HEADER_PREFIX) }
.map { |k, v| [k.downcase.strip, v.strip] }
Expand Down
2 changes: 1 addition & 1 deletion lib/aliyun/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

module Aliyun

VERSION = "0.3.1"
VERSION = "0.3.2"

end # Aliyun
Loading