pax_global_header00006660000000000000000000000064137163714700014523gustar00rootroot0000000000000052 comment=1d573e726f2538bd661cc554962b2eec849a74d7 aliyun-aliyun-oss-ruby-sdk-1d573e7/000077500000000000000000000000001371637147000172245ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/.gitignore000066400000000000000000000002761371637147000212210ustar00rootroot00000000000000/.bundle/ vendor /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ .pryrc .versions.conf aliyun_sdk.log download_file file_to_upload lib/aliyun/crcx.bundle .idea aliyun-aliyun-oss-ruby-sdk-1d573e7/.rspec000066400000000000000000000000361371637147000203400ustar00rootroot00000000000000--color --require spec_helper aliyun-aliyun-oss-ruby-sdk-1d573e7/.travis.yml000066400000000000000000000144471371637147000213470ustar00rootroot00000000000000language: ruby cache: bundler rvm: - 2.0 - 2.1 - 2.2 - 2.3 - 2.4 - 2.5 env: global: - secure: cC3aeKKrNLGuK60zEPKNPmRlE3m/VlzQLAXjD8nYjhbPVL+zjaylY87vq657gSFmDDGY3ISF0aR3rjFpSHeoVr6CiVZDnQIgoaC2FD1oDxkzy6/8tEQBJ0YORLzfCO63NJTKltVMvSsPWck4xE+qIsQLIpDjia9fHc2InLtkn9pTnuhMpHxjBIat6TgFPSiIC9YXXjCveudTy2o+9Is2nCmkdsr7lqEkifLIyXmGzyUp99hmQy0OZ2lF06OMH/RwWSDRBZryqMT+ju1qsv+/LkDRFwaqKfxqDdewhxj6eZdA7Q1YyB7ChT+uFRNaHkrWwPCC+JcZnHq8kt8qykUfN+Saq/txZ65qpQFp0rDUG929iaUUnfreearniqe9+u+7LnrFeseVLa6dJD+fcsTcCQRgfUvuaMARLq3EwqUCvAXPWHFTnY6ynqerTEwlJbxpCXtYktXaO0KugOI+iF4APs/r2iroAM/zkf3AjH78UcSAIBn1AzxQy7+Ah6G+nS69/nvbtPGiiSAmJYgKfBcD1ctDTJXzen78MGz7ImTC64k+2J4kuu8CSi/j8wbdIRHrIIszBUEPKQrGp9RDx08MRffinhnInq1uGEBFvip2Q1qk3tuGXJT+2m/OgbK7KlVmlzRz9M+NGav6ixQpW4qPvu2mf2bdmMeAd/0sg4IIIRc= - secure: iMjPztZoWaRxrgI/buZZj8SliuOYGEcOyO++xskr0O6x8+3qkT7JRqozwu/PK3Au77Py9+LTpZqNrjy3ZZx/KBzYUX4+g+k5YcjiUAnlX3/CEdj5Apti6Qb0CwtOgHY7V+y205vZeh0x83RuPUY1ZZ0kOS2C7Frd0S7rMWSTDbTnVxpUn46f1Pgnjin5ohtlUKiOuP4txEWiJgJhVJQkgDo749GanX7Wg9KOd4PnUdjXVVVWsYt1x9LXTUXyCpq8jwnOFFw0NKKUVyjVv+6b75KokTU/3QCr2AMnaPRTbkn89244ZLM1mr0Per0LJmTowpBGKF2K2PWMuJ8uDW3U/GgsG2OqwarGFKsE2JZsezctZRAhQGVDtLcG/SLY90THtskgIG4nXykfin49RfCyEPAKXk+VJ1QtevogX4Bw5qOvoCen3Uww5LBQYS3cjjTJDmTiJyqqOblMCZibRjwf8+Lvw20VRkrYqYPqFr/VxRddWVjeK/ktjJS/cnAZyoCOl6PRP9Rrdf7MmdJGYT9RhfTC/nGpPqGv7CUM7n/wnmRx3tmE0ohHW8phwRTBlXT412EzMZS6Heo5tSlTFkTWHXoYz840gqok/APS6Y8h7idXmav4PbgJO7PVfiIGl8koGT2yM1XEmmjX7/T2vJprdO6zr5xRXu4tx2CHnA/yQCI= - secure: a4zvMhM/Ml19tWAM+HJxOqr/T7KdNJX4bfdSAv37oXwYzsaBYXhUYKzyGfRum1DDFPpdC4X5uRLnC8eXRxKCLJfDVmMX9atui20v6QWLTXiyy1jajdnTeyCRuuVfclwAT1JMja8J+qaaB7eZoTJgT3dVKuoRpueCodSpLL70hK0gWxtdFnBRqgDZR/4oxfqEryZbIEjiC2zxOxBJSWrqpIJdO4kR39fsc+ThMbpM8Ii7yyDYZmBbHF2AMiB96pLCOr3uEk0l8smg/6iu0BcgXY6A+0bKzI5nmM0Nm261+7nLok5VLAThxnvzio6ybeaMp5/VhYowA/RyeyUTcnNZqhyc8dbssOBaZpeJVdVJiNA/lCaR/jHbx7i25H2BhKvboEypW9aaCAclWbkMLg9p03CQuXHProViNDL6jvu+rQJVpqNDDozKxCyxIbf6yOS71qQFDeXoJpurokgyV/FCN4bbPkBkJnQdNaF5Zeyoc5fX8yipv0fL8SrFZi2ZGSaRKn6WChZwl1eUi0xAeqbXdJsGaf5P0VxUSc7I/bIR6IlXlSu0SpA2KqQvpc4QMNhzaHbhoquNJ9SK4FwCjhdFhGrfRNsePH6tpK9Po2PQoFBqT1WEFZogA5QcdC/3axlfpsXPL8RRHw+YqgplVpR43N2f0ug6rTq2hL8/YM6Dn7Y= - secure: Bh0qS85guNrnHwGTAqD0TY0xWJ5VSMO9Qh7//oBxT+eqWNmp7jmH7etDGQ6EzlnbXE7MUOBLarlnzH8uONhpz7RsU2R+OaQ4Z1i0npAS7t9sivEK8mHlUpX5iMhbnPvi70LQf8Xh/JEtOis/aYS/J86DM6S4tkFaSgbKAFFpyaxCHconHCLcRTXLRl8PgK+k8VAhlUBIbI2u7v1uYhjefy3eHf/I0ZnUBvM5VOtjflddOH607fbo65SzaQiUrxg7Ed2oy/Vdhgksq4HPSLjS5afsF7EifW2eVq/EjdXeyF8K3XTdMiV9oYj7KkLXVL21jA5LWCiM5rvkG+Zz/ATHZodOgBw/Wdim/Tbni+E3cLkhN3LEVUqx/W4/PKK3bWiIuaf37T7UuZC2+tEpRt1UHCc83w9uX+4XPUjGkH3N5rXrTAhz82z455nSl6lVi8zvIkXECP05/HJFU83ottc7GyWX2a7rpuLVe3iq8D6BCGRpjI3nKsfnCAjbs5UR/ED8/xie1/RylqpBbgQc3yLB/3m9LVV5mZxBYLQGKS4yfYpwJN7sqFZn9dFGqhLTGFfOVyPPmSnjjL7x1R5WgfNHEtwfGFXZN97kt4xd8TkR0UwW4opqePsqX1+JQQODJixxm3CfZVE4P8mQaZWHz7OMGTpnrTSZPqLZPnwte/6pAUk= - secure: kZBO0KL9ZHTOwDsJPFt/iF0B0JgaDOfO2pX9xwbhhnS8muXAbqt85TM9d5RxLgzKthxgdcxUPQbOewvrkdlrGe+g5rLhDjTXJzX8z6BB583/iXzkXci4PIT0lXmi5XcGVepIez3lhM1g4iutpxeKklYu/h7z9J2LMlNRiAqU0P5tsSIwFYbUazutP1ZrHyL8IC8yOYyJ9QR/EvFmr3T31mndios0MKfGofnQiuBnChWRZEPxa1aqF2bv+NEbCDnaavPq7zbZ3X2EY7OfbloqRM93cTg0Jmudfn6KoROSyWEMOMHHPEWCVYidjRQrcCSjcDiAe9NBZ4i8QcnLFGGy4FBKW4OLR08iI4F0UfQ0ugz3XQFVSneySiGxNAt6RwwFei/vJSyfMJRIcaIQbpxeKZ574HXpRDfV/kk4YByyYR4JtK4JI1h+K+sPsZDjXvC8ufYmMKZOithbULymIIBa2VJj+ukcoCmFPQbnYLkX2vZGBxtwF+RJGdZAcC2OYRhXBloPirNFB+XCQ4mXdX/gmaITwOOk5RIpVPVsHR+xcxWlLUsk9ngIFXM7TfIOVwsMJ8N1QYEickKZ9sBAGv2vS5miw+JPyLqY6Gq3KnQL99gx8+Lqd3NTrVPGLoqv7b1My/TSkWGqpopnj3X1HdV3BsfF/ArxlClv3SE3feB8LaI= - secure: v/mne8fMeGLw67LxVmKyychnGHuA38OGarsB/oUs1ehTIgj7Uiom6Z2YGVbWsoVxGzGuRlN+pcgaE/oHoRA5D7m+aO0oyCDs2O3MeGfcFk1vg+LMwZ2wi3GDKJ5P4kRGuB6YIdQ1jONZWvu55I5KoDOODHNpWHUxx1BGGS8bFCWLA4/u+4DkORrFtQ27weIN5y7Tw0Pm73pJlHl82Pc33s3RKI0bErOqdCD9amltrmYm5/2Bnsy/b5TMdZMTjQE9j//aTB0b5Dy70R7i9fXnC3bNSB/MK0FEHiR+3azb4V0qdiQhrdYfWnR0/VRRfgab12SJLcMtTbMREgvpv+tWQdZuyW2Jp2fGdQIUMIaoG/FdFx+vOmNfSUEK3RuGiCWHm3el8DdFQMHTS9fT2Qf4kO2QRP5/z2rqKEm0J3MmL9NPyGhD4+d32v8HBBfqEGiElCBFfPNQqH6uA2ooJn/hVLFE4Bx4HlOdReHpExOZlF4VnrLf/cH1ugpGDrLPmDKwXdtjvnOJsy+0tEfE8RjOA87eLNAu0s3KWifgMYodNn5hDRRg7R3+d13v7hZSu5Ub7FMTu5VpsLyZiB1JCrVR725TczDGuR3BvZftpQxqA9Wbr/KFzTtz2UBgSEoxzoEKYiNSLr/aj2zY94jZr1cxp0Hw2kVjx7dbmQAuKuIDtbU= - secure: qnz4m9DmbH+Ar/BxQgmnsclxoUTY5+BAgZw9yfpPkh+n1iJc/ppOfvETNpbelty0k4Vf+0Cq4xojJszEs56SwCCrNByHuKT8D8aCJMwlNNuUt6pcgF4JNZCexyQZIYiyniwiS91BQvsMYfCq2qF3328xdl2VUz9g6TMBq8BCde/wRQFRn9HABQuIwE4J+RKLMvwP2+aHoAA04/Bpk/iq6wv8xhMEU/cAI+/QSuFQCHUvK/FzjFO85cXAmR+ColQPxIh/OBdpD1ZtBQPD9kFjEC5zahIX9Vr83weoOlUoYAdYTfdLjgEbhE4812s3L/meVK/yW3ceW6qIkqS8C6ZbcvqzbfRcNal+co6AVUMs+fkH6KOuj6dU8LUGouD0KorApwWpfLv2DbmAmxZl3zitKtb5IeBfdwLfWGjhMDp2H3LJ0ck1Y/556OamfLLHX25zzFz6F/fWCZWH0+6ADKz1HM7ogfn4nQCPk8+hVrkjZgLCeX3PcfBeFHG1eHIp69XE3P5L8fvF5gGXU8wVbSie8gOrU2VwP4Tb3mZODTh3Nx9ZBru8K6lzBN1c20SVZnKpjrKK7uKTFMwjkh3ypVDw6Djm2yjIwctCnsQ3YlAVr4maHa+5N0YMuyH+k/a7xFFy5TRXaA3wxf9Pou26dKjOmNxbFinRH3uLvtKPl9l90fw= - secure: FffdE4JDaJoBpbL4WvZNY8uwTVZ7wKIeDx2EUhIguFTa2NGXNLVJ7TamOhHpIjtiu8FjvlC5EZvYmct5nB7DHPwAxM7937p0gW2Wb4w58dZqU1+Ak86lo2P0oaI9oqMwfR+h9HUN4O4UrfSeuHVajSLEn8EuQEhAbD3HhYCiMLfCHZ8z4xTd8Nt+EH3y52AAGAYtR4OzJuhA9DjlPPhpOPg2A7wd0UStHfWRpehhJ/qqMTU5tEuqP4YcOYuLAGtHHPVYxnDge32FqGO4aUbJQf54D8nShrLh7XsAEDuvQV6yVOHI8ITopgnVJyZt6A8yYTbji+m4VP8EUgp7E4QC5enZXqlsNVMRWpaz82BJHy+mCeIYQ0Efqe055FfSir367fbp/Qrne/WnWYeOF/P1PU+NicZVCHVxtO6uSYAxaErdDnGwzQyyZc9IMwS70G7fzUGJI7IOB12zq7lZEIdGWhk3qUPknTKtZ6ig4qyPA8G4KMlcSkb9tjfKYayQ4RrmIY5JUyh1fTPGVmS4hqxA8pHzAK+9RUlwVmDnw7eMTxmb9u+AscVPEOQ3EVRumcJNOfJvNK2+qQbi5/K77/cbXR7Au7xuLiPA65RTL0QDdSONEELn1E9GnIKBaF6XvcmTyNLeI1fnW4FF73BgNQKNWEfswEeoA8rxlCdJnD1XDXg= - secure: VZuRhCu8kvJrMsTPZVJZVPbeJs6AJK8RVBvbNAhYGhiAO4wHUQ5ZcpO6IZVJ8c+pVL0WzGRogZg95sO7Ul1zpjxIWdla6XVqhU8YY+5RdcOUZK14qhM4XE5qnUSGAHwpY3jyRdFUOVbA6N/jus+AplQRsbbYNOZTMYKpnduyPqLaUfxeH6BvdPUiuSSG0CW+RZDsrbCTsQsVBk2QI6T5Y1yIKWl60zXYBelyb9X7RdCKg0i6xYMav18puHatozvJkOpLTXeOGxbZbclFwagH+GhJ7DFIs/FbLqDymS/+eLdzU8NSIqe9mUnRKul0jVWiruMR4a12f/ANCEuSvOcqeO1GuDAHyykmbWEdnKRlhZ3xmVhuIpKnRIlKenf2/WImsew1w07mnmLjjKVoxY+VghC06jSfvWDTqdRiJY1pSuUomuAmGRY0Ap6KehFgPJYAhI61US26hHolr6aVGdxB5ggipf/9UMWLWmn74YT4Ao6Bim+PqRVSRCX3v30t1TTrPvCpoa43PfVWLonDuJWSGD5aLaOWfZCtnemZYcR81Q/HE3MCGtSSLgQcAGRcHxxJ4wzG/GivP6c5bIHkjzTud4GJDQthuguErXkIxK6QyxXzpNzm4Va1ZXhuMkb29KgR9LIHeHXyJ8blGZC2Onmq1qGLtiv259kFsuxOVO9TxLA= script: bundle exec rake smart_test before_install: gem install bundler -v 1.10 aliyun-aliyun-oss-ruby-sdk-1d573e7/.yardopts000066400000000000000000000000441371637147000210700ustar00rootroot00000000000000--private --protected - CHANGELOG.mdaliyun-aliyun-oss-ruby-sdk-1d573e7/CHANGELOG.md000066400000000000000000000047551371637147000210500ustar00rootroot00000000000000## Change Log ### v0.8.0 / 2020-08-17 - add bucket encryption - add bucket versioning - add env parameter to set default log level ### v0.7.3 / 2020-06-28 - add variable control log output path ### v0.7.2 / 2020-06-05 - add env parameter to descide whether output log file ### v0.7.1 / 2019-11-16 - add the validity check of bucket name - add parameters argument for buclet.object_url api - fix http.get_request_url function bug - fix warning constant ::Fixnum is deprecated - support rest-client 2.1.0 ### v0.7.0 / 2018-06-05 - deps: upgrade nokogiri to > 1.6 and ruby version >= 2.0 ### v0.6.0 / 2017-07-23 - deps: upgrade rest-client to 2.x ### v0.5.0 / 2016-11-08 - feat: add crc check for uploading(enabled by default) and downloading(disabled by default) - bug: fix file open mode for multipart ### v0.4.1 / 2016-07-19 - Support signature object url with STS ### v0.4.0 / 2016-05-19 - Enable copy objects of different buckets(but in the same region) ### v0.3.7 - Remove monkey patch for Hash ### v0.3.6 - Fix Zlib::Inflate in ruby-1.9.x - Add function test(tests/) in travis CI - Add Gem version badge - Support IP endpoint ### v0.3.5 - Fix the issue that StreamWriter will read more bytes than wanted ### v0.3.4 - Fix handling gzip/deflate response - Change the default accept-encoding to 'identity' - Allow setting custom HTTP headers in get_object ### v0.3.3 - Fix object key problem in batch_delete ### 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 ### v0.3.0 - Add support for OSS Callback ### v0.2.0 - Add aliyun/sts - OSS::Client support STS ### v0.1.8 - Fix StreamWriter string encoding problem - Add ruby version and os version in User-Agent - Some comments & examples refine ### v0.1.7 - Fix StreamWriter#inspect bug - Fix wrong in README ### v0.1.6 - Required ruby version >= 1.9.3 due to 1.9.2 has String encoding compatibility problems - Add travis & overalls - Open source to github.com ### v0.1.5 - Add open_timeout and read_timeout config - Fix a concurrency bug in resumable_upload/download ### v0.1.4 - Fix object key encoding problem - Fix Content-Type problem - Add list_uploads & abort_uploads methods - Make resumable_upload/download faster by multi-threading - Enable log rotate ### v0.1.3 - Include request id in exception message ### v0.1.2 - Fix yard doc unresolved link ### v0.1.1 - Add README.md CHANGELOG.md in gem aliyun-aliyun-oss-ruby-sdk-1d573e7/Gemfile000066400000000000000000000002421371637147000205150ustar00rootroot00000000000000source 'https://rubygems.org' gemspec gem 'coveralls', require: false # for 1.9.x compatibility gem 'term-ansicolor', '~> 1.3.2' gem 'addressable', '~> 2.3.6' aliyun-aliyun-oss-ruby-sdk-1d573e7/LICENSE000066400000000000000000000021051371637147000202270ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) ali-sdk and other contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. aliyun-aliyun-oss-ruby-sdk-1d573e7/README-CN.md000066400000000000000000000376541371637147000210200ustar00rootroot00000000000000# Aliyun OSS SDK for Ruby [![Gem Version](https://badge.fury.io/rb/aliyun-sdk.svg)](https://badge.fury.io/rb/aliyun-sdk) [![Build Status](https://travis-ci.org/aliyun/aliyun-oss-ruby-sdk.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-oss-ruby-sdk?branch=master) [![Coverage Status](https://coveralls.io/repos/aliyun/aliyun-oss-ruby-sdk/badge.svg?branch=master&service=github)](https://coveralls.io/github/aliyun/aliyun-oss-ruby-sdk?branch=master) ## [README of English](https://github.com/aliyun/aliyun-oss-ruby-sdk/blob/master/README.md) ## 关于 Aliyun OSS SDK for Ruby是用于方便访问阿里云OSS(Object Storage Service)RESTful API的Ruby客户端程序。 了解OSS的的更多信息请访问[OSS官网](http://www.aliyun.com/product/oss)。 ## 运行环境 - Ruby版本 >= 2.0, Ruby1.9请使用 v0.5.0. - 支持Ruby运行环境的Windows/Linux/OS X 安装Ruby请参考:https://www.ruby-lang.org/zh_cn/downloads/ ## 快速开始 ### 开通OSS账号 登录官网:http://www.aliyun.com/product/oss ,点击“立即开通”。按照提示 开通OSS服务。开通服务之后请在“管理控制台”中查看您的AccessKeyId和 AccessKeySecret,在使用Aliyun OSS SDK时需要提供您的这两个信息。 ### 安装Aliyun OSS SDK for Ruby gem install aliyun-sdk 并在你的程序中或者`irb`命令下包含: require 'aliyun/oss' **注意:** 1. SDK依赖的一些gem是本地扩展的形式,因此安装完Ruby之后还需要安装 ruby-dev以支持编译本地扩展的gem 2. SDK依赖的处理XML的gem(nokogiri)要求环境中包含zlib库 以Ubuntu为例,安装上述依赖的方法: sudo apt-get install ruby-dev sudo apt-get install zlib1g-dev 其他系统类似。 ### 创建Client client = Aliyun::OSS::Client.new( :endpoint => 'endpoint', :access_key_id => 'access_key_id', :access_key_secret => 'access_key_secret') 其中`endpoint`是OSS服务的地址,根据节点区域不同,这个地址可能不一样,例如 杭州节点的地址是:`http://oss-cn-hangzhou.aliyuncs.com`,其他节点的地址见: [节点列表][region-list] `access_key_id`和`access_key_secret`是您的服务凭证,在官网的“管理控制 台”上面可以查看。**请妥善保管您的AccessKeySecret,泄露之后可能影响您的 数据安全** #### 使用用户绑定的域名作为endpoint OSS支持自定义域名绑定,允许用户将自己的域名指向阿里云OSS的服务地址 (CNAME),这样用户迁移到OSS上时应用内资源的路径可以不用修改。绑定的域 名指向OSS的一个bucket。绑定域名的操作只能在OSS控制台进行。更多关于自定 义域名绑定的内容请到官网了解:[OSS自定义域名绑定][custom-domain] 用户绑定了域名后,使用SDK时指定的endpoint可以使用标准的OSS服务地址,也 可以使用用户绑定的域名: client = Aliyun::OSS::Client.new( :endpoint => 'http://img.my-domain.com', :access_key_id => 'access_key_id', :access_key_secret => 'access_key_secret', :cname => true) 有以下几点需要注意: 1. 在Client初始化时必须指定:cname为true 2. 自定义域名绑定了OSS的一个bucket,所以用这种方式创建的client不能进行 list_buckets操作 3. 在{Aliyun::OSS::Client#get_bucket}时仍需要指定bucket名字,并且要与 域名所绑定的bucket名字相同 #### 使用STS创建Client OSS支持用户使用STS进行访问,更多有关STS的内容,请参考 [阿里云STS][aliyun-sts]。 在使用STS之前需要先向STS申请一个临时token, aliyun-sdk中包含了STS的SDK,使用时只需要`require 'aliyun/sts'`即可: require 'aliyun/sts' sts = Aliyun::STS::Client.new( access_key_id: 'access_key_id', access_key_secret: 'access_key_secret') token = sts.assume_role('role-arn', 'my-app') client = Aliyun::OSS::Client.new( :endpoint => 'http://oss-cn-hangzhou.aliyuncs.com', :access_key_id => token.access_key_id, :access_key_secret => token.access_key_secret, :sts_token => token.security_token) 注意使用STS时必须指定`:sts_token`参数。用户还可以通过`STS::Client`申请 带Policy的token,细节请参考[API文档][sdk-api]。 ### 列出当前所有的Bucket buckets = client.list_buckets buckets.each{ |b| puts b.name } `list_buckets`返回的是一个迭代器,用户依次获取每个Bucket的信息。Bucket 对象的结构请查看API文档中的{Aliyun::OSS::Bucket} ### 创建一个Bucket bucket = client.create_bucket('my-bucket') ### 列出Bucket中所有的Object bucket = client.get_bucket('my-bucket') objects = bucket.list_objects objects.each{ |o| puts o.key } `list_objects`返回的是一个迭代器,用户依次获取每个Object的信息。Object 对象的结构请查看API文档中的{Aliyun::OSS::Object} ### 在Bucket中创建一个Object bucket.put_object(object_key){ |stream| stream << 'hello world' } 用户也可以通过上传本地文件创建一个Object: bucket.put_object(object_key, :file => local_file) ### 从Bucket中下载一个Object bucket.get_object(object_key){ |content| puts content } 用户也可以将Object下载到本地文件中: bucket.get_object(object_key, :file => local_file) ### 拷贝Object bucket.copy_object(from_key, to_key) ### 判断一个Object是否存在 bucket.object_exists?(object_key) 更多Bucket的操作请参考API文档中的{Aliyun::OSS::Bucket} ## 模拟目录结构 OSS是Object存储服务,本身不支持目录结构,所有的object都是“平”的。但是 用户可以通过设置object的key为"foo/bar/file"这样的形式来模拟目录结构。 假设现在有以下Objects: foo/x foo/bar/f1 foo/bar/dir/file foo/hello/file 列出"foo/"目录下的所有文件就是以"foo/"为prefix进行`list_objects`,但 是这样也会把"foo/bar/"下的所有object也列出来。为此需要用到delimiter参 数,其含义是从prefix往后遇到第一个delimiter时停止,这中间的key作为 Object的common prefix,包含在`list_objects`的结果中。 objs = bucket.list_objects(:prefix => 'foo/', :delimiter => '/') objs.each do |i| if i.is_a?(Aliyun::OSS::Object) # a object puts "object: #{i.key}" else puts "common prefix: #{i}" end end # output object: foo/x common prefix: foo/bar/ common prefix: foo/hello/ Common prefix让用户不需要遍历所有的object(可能数量巨大)而找出前缀, 在模拟目录结构时非常有用。 ## 上传回调 在`put_object`和`resumable_upload`时可以指定一个`Callback`,这样在文件 成功上传到OSS之后,OSS会向用户提供的服务器地址发起一个HTTP POST请求, 以通知用户相应的事件发生了。用户可以在收到这个通知之后进行相应的动作, 例如更新数据库、统计行为等。更多有关上传回调的内容请参考[OSS上传回调][oss-callback]。 下面的例子将演示如何使用上传回调: callback = Aliyun::OSS::Callback.new( url: 'http://10.101.168.94:1234/callback', query: {user: 'put_object'}, body: 'bucket=${bucket}&object=${object}' ) begin bucket.put_object('files/hello', callback: callback) rescue Aliyun::OSS::CallbackError => e puts "Callback failed: #{e.message}" end **注意** 1. callback的url**不能**包含query string,而应该在`:query`参数中指定 2. 可能出现文件上传成功,但是执行回调失败的情况,此时client会抛出 `CallbackError`,用户如果要忽略此错误,需要显式接住这个异常。 3. 详细的例子可以参考[callback.rb](examples/aliyun/oss/callback.rb) 4. 接受回调的server可以参考[callback_server.rb](rails/aliyun_oss_callback_server.rb) ## 断点上传/下载 OSS支持大文件的存储,用户如果上传/下载大文件(Object)的时候中断了(网络 闪断、程序崩溃、机器断电等),重新上传/下载是件很费资源的事情。OSS支持 Multipart的功能,可以在上传/下载时将大文件进行分片传输。Aliyun OSS SDK 基于此提供了断点上传/下载的功能。如果发生中断,可以从上次中断的地方继 续进行上传/下载。对于文件大小超过100MB的文件,都建议采用断点上传/下载 的方式进行。 ### 断点上传 bucket.resumable_upload(object_key, local_file, :cpt_file => cpt_file) 其中`:cpt_file`指定保存上传中间状态的checkpoint文件所在的位置,如果用户 没有指定,SDK将为用户在`local_file`所在的目录生成一个 `local_file.cpt`。上传中断后,只需要提供相同的cpt文件,上传将会从 中断的点继续上传。所以典型的上传代码是: retry_times = 5 retry_times.times do begin bucket.resumable_upload(object_key, local_file) rescue => e logger.error(e.message) end end 注意: 1. SDK会将上传的中间状态信息记录在cpt文件中,所以要确保用户对cpt文 件有写权限 2. cpt文件记录了上传的中间状态信息并自带了校验,用户不能去编辑它,如 果cpt文件损坏则上传无法继续。整个上传完成后cpt文件会被删除。 ### 断点下载 bucket.resumable_download(object_key, local_file, :cpt_file => cpt_file) 其中`:cpt_file`指定保存下载中间状态的checkpoint文件所在的位置,如果用户 没有指定,SDK将为用户在`local_file`所在的目录生成一个 `local_file.cpt`。下载中断后,只需要提供相同的cpt文件,下载将会从 中断的点继续下载。所以典型的下载代码是: retry_times = 5 retry_times.times do begin bucket.resumable_download(object_key, local_file) rescue => e logger.error(e.message) end end 注意: 1. 在下载过程中,对于下载完成的每个分片,会在`local_file`所在的目录生 成一个`local_file.part.N`的临时文件。整个下载完成后这些文件会被删除。 用户不能去编辑或删除part文件,否则下载不能继续。 2. SDK会将下载的中间状态信息记录在cpt文件中,所以要确保用户对cpt文 件有写权限 3. cpt文件记录了下载的中间状态信息并自带了校验,用户不能去编辑它,如 果cpt文件损坏则下载无法继续。整个下载完成后cpt文件会被删除。 ## 可追加的文件 阿里云OSS中的Object分为两种类型:Normal和Appendable。 - 对于Normal Object,每次上传都是作为一个整体,如果一个Object已存在, 两次上传相同key的Object将会覆盖原有的Object - 对于Appendable Object,第一次通过`append_object`创建它,后续的 `append_object`不会覆盖原有的内容,而是在Object末尾追加内容 - 不能向Normal Object追加内容 - 不能拷贝一个Appendable Object ### 创建一个Appendable Object bucket.append_object(object_key, 0){ |stream| stream << "hello world" } 第二个参数是追加的位置,对一个Object第一次追加时,这个参数为0。后续的 追加这个参数要求是追加前Object的长度。 当然,也可以从文件中读取追加的内容: bucket.append_object(object_key, 0, :file => local_file) ### 向Object追加内容 pos = bucket.get_object(object_key).size next_pos = bucket.append_object(object_key, pos, :file => local_file) 程序第一次追加时,可以通过{Aliyun::OSS::Bucket#get_object}获取文件的长度, 后续追加时,可以根据{Aliyun::OSS::Bucket#append_object}返回的下次追加长度。 注意:如果并发地`append_object`,`next_pos`并不总是对的。 ## Object meta信息 在上传Object时,除了Object内容,OSS还允许用户为Object设置一些"meta信息 ",这些meta信息是一个个的Key-Value对,用于标识Object特有的属性信息。这 些meta信息会跟Object一起存储,并在`get_object`和`get_object`时返 回给用户。 bucket.put_object(object_key, :file => local_file, :metas => { 'key1' => 'value1', 'key2' => 'value2'}) obj = bucket.get_object(object_key, :file => localfile) puts obj.metas 关于meta信息有以下几点需要注意: 1. meta信息的key和value都只能是简单的ASCII非换行字符,并且总的大小不能超过8KB。 2. Copy object时默认将拷贝源object的meta信息,如果用户不希望这么做,需要 显式地将`:meta_directive`设置成{Aliyun::OSS::MetaDirective::REPLACE} ## 权限控制 OSS允许用户对Bucket和Object分别设置访问权限,方便用户控制自己的资源可 以被如何访问。对于Bucket,有三种访问权限: - public-read-write 允许匿名用户向该Bucket中创建/获取/删除Object - public-read 允许匿名用户获取该Bucket中的Object - private 不允许匿名访问,所有的访问都要经过签名 创建Bucket时,默认是private权限。之后用户可以通过`bucket.acl=`来设置 Bucket的权限。 bucket.acl = Aliyun::OSS::ACL::PUBLIC_READ puts bucket.acl # public-read 对于Object,有四种访问权限: - default 继承所属的Bucket的访问权限,即与所属Bucket的权限值一样 - public-read-write 允许匿名用户读写该Object - public-read 允许匿名用户读该Object - private 不允许匿名访问,所有的访问都要经过签名 创建Object时,默认为default权限。之后用户可以通过 `bucket.set_object_acl`来设置Object的权限。 acl = bucket.get_object_acl(object_key) puts acl # default bucket.set_object_acl(object_key, Aliyun::OSS::ACL::PUBLIC_READ) acl = bucket.get_object_acl(object_key) puts acl # public-read 需要注意的是: 1. 如果设置了Object的权限,则访问该Object时进行权限认证时会优先判断 Object的权限,而Bucket的权限设置会被忽略。 2. 允许匿名访问时(设置了public-read或者public-read-write权限),用户 可以直接通过浏览器访问,例如: http://bucket-name.oss-cn-hangzhou.aliyuncs.com/object.jpg 3. 访问具有public权限的Bucket/Object时,也可以通过创建匿名的Client来进行: # 不填access_key_id和access_key_secret,将创建匿名Client,只能访问 # 具有public权限的Bucket/Object client = Client.new(:endpoint => 'oss-cn-hangzhou.aliyuncs.com') bucket = client.get_bucket('public-bucket') obj = bucket.get_object('public-object', :file => local_file) ## 运行examples SDK的examples/目录下有一些展示SDK功能的示例程序,用户稍加配置就可以直 接运行。examples需要的权限信息和bucket信息从用户`HOME`目录下的配置文件 `~/.oss.yml`中读取,其中应该包含以下字段(**注意冒号后面需要有一个空格**): endpoint: oss-cn-hangzhou.aliyuncs.com cname: false access_key_id: access_key_secret: bucket: 用户需要创建(如果不存在)或者修改其中的内容,然后运行: ruby examples/aliyun/oss/bucket.rb ## 运行测试 ```bash bundle exec rake spec export RUBY_SDK_OSS_ENDPOINT=endpoint export RUBY_SDK_OSS_ID=AccessKeyId export RUBY_SDK_OSS_KEY=AccessKeySecret export RUBY_SDK_OSS_BUCKET=bucket-name bundle exec rake test ``` ## 更多 更多文档请查看: - 阿里云官网文档:http://help.aliyun.com/product/8314910_oss.html - OSS官网SDK文档:https://help.aliyun.com/document_detail/oss/sdk/ruby-sdk/install.html [region-list]: https://help.aliyun.com/document_detail/oss/user_guide/endpoint_region.html [custom-domain]: https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/oss_cname.html [aliyun-sts]: https://help.aliyun.com/document_detail/ram/intro/concepts.html [sdk-api]: http://www.rubydoc.info/gems/aliyun-sdk/ [oss-callback]: https://help.aliyun.com/document_detail/oss/user_guide/upload_object/upload_callback.html aliyun-aliyun-oss-ruby-sdk-1d573e7/README.md000066400000000000000000000451001371637147000205030ustar00rootroot00000000000000# Alibaba Cloud OSS SDK for Ruby [![Gem Version](https://badge.fury.io/rb/aliyun-sdk.svg)](https://badge.fury.io/rb/aliyun-sdk) [![Build Status](https://travis-ci.org/aliyun/aliyun-oss-ruby-sdk.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-oss-ruby-sdk?branch=master) [![Coverage Status](https://coveralls.io/repos/aliyun/aliyun-oss-ruby-sdk/badge.svg?branch=master&service=github)](https://coveralls.io/github/aliyun/aliyun-oss-ruby-sdk?branch=master) ## [README of Chinese](https://github.com/aliyun/aliyun-oss-ruby-sdk/blob/master/README-CN.md) ## About Alibaba Cloud OSS SDK for Ruby is a Ruby client program for convenient access to Alibaba Cloud OSS (Object Storage Service) RESTful APIs. For more information about OSS, visit [the OSS official website]( http://www.aliyun.com/product/oss). ## Run environment - Ruby ***2.0*** or above. For Ruby 1.9, please use v0.5.0. - *Windows*, *Linux* or *OS X* system that supports Ruby. To learn how to install Ruby, refer to: [ruby-lang](https://www.ruby-lang.org/en/documentation/installation/). ## Quick start ### Activate an OSS account Log onto [the official website](http://www.aliyun.com/product/oss) and click *Activate Now*. Follow the prompts to activate OSS. After the service is activated, go to *Console* to view your `AccessKeyId` and `AccessKeySecret`. These two information items are required when you use Alibaba Cloud OSS SDK. ### Install Alibaba Cloud OSS SDK for Ruby gem install aliyun-sdk Include the following in your project or 'irb' command: require 'aliyun/oss' **Note:** - Some gems on which the SDK depends are local extensions, and you need to install ruby-dev to compile locally extended gems after you install Ruby. - The environment for running the SDK-dependent gem (nokogiri) for processing XML must have the zlib library. The following method is used to install the preceding dependencies taking *Ubuntu* as an example: sudo apt-get install ruby-dev sudo apt-get install zlib1g-dev The practices for other systems are similar. ### Create a client client = Aliyun::OSS::Client.new( :endpoint => 'endpoint', :access_key_id => 'access_key_id', :access_key_secret => 'access_key_secret') In specific, the `endpoint` is the OSS service address. The address may vary based on different regions for the node. For example, the address for a Hangzhou node is: `http://oss-cn-hangzhou.aliyuncs.com`. For addresses for other nodes, see: [Node List][region-list]. The `access_key_id` and `access_key_secret` are credentials for your service. You can view them in `Console` on the official website. **Please keep your AccessKeySecret safe. Disclosing the AccessKeySecret may compromise your data security**. #### Use a bound domain as the endpoint OSS supports binding a custom domain name and allows you to direct your domain name to the OSS service address (CNAME) of Alibaba Cloud. In this way, you don't need to change the resource path in your app when migrating your data to the OSS. The bound domain name points to a bucket in the OSS. The domain name binding operation can only be carried out in the OSS console. For more information about binding a custom domain name, visit the official website: [Binding Custom Domain Names in OSS][custom-domain]. After you have bound a custom domain name, you can use the standard OSS service address as the specified endpoint of the OSS, or use the bound domain name: client = Aliyun::OSS::Client.new( :endpoint => 'http://img.my-domain.com', :access_key_id => 'access_key_id', :access_key_secret => 'access_key_secret', :cname => true) **Note:** - You must set the `cname` to ***true*** when initializing the client. - The custom domain name is bound to a bucket of the OSS, so the client created in this method does not support List_buckets operations. - You still need to specify the bucket name during the {Aliyun::OSS::Client#get_bucket} operation and the bucket name should be the same as that bound to the domain name. #### Create a client using STS OSS supports access via STS. For more information about STS, refer to [Alibaba Cloud STS][aliyun-sts]. Before using STS, you must apply for a temporary token from the STS. Alibaba Cloud Ruby SDK contains the STS SDK, and you only need to `require 'aliyun/sts'` for usage: require 'aliyun/sts' sts = Aliyun::STS::Client.new( access_key_id: 'access_key_id', access_key_secret: 'access_key_secret') token = sts.assume_role('role-arn', 'my-app') client = Aliyun::OSS::Client.new( :endpoint => 'http://oss-cn-hangzhou.aliyuncs.com', :access_key_id => token.access_key_id, :access_key_secret => token.access_key_secret, :sts_token => token.security_token) **Note:** the `:sts_token` parameter must be specified for using STS. You can also apply for a token with a policy through `STS::Client`, for details, refer to [API Documentation][sdk-api]. ### List all the current buckets buckets = client.list_buckets buckets.each{ |b| puts b.name } The `list_buckets` command returns an iterator for you to get the information of each bucket in order. Bucket For the object structure, see {Aliyun::OSS::Bucket} in the API documentation. ### Create a bucket bucket = client.create_bucket('my-bucket') ### List all the objects in a bucket bucket = client.get_bucket('my-bucket') objects = bucket.list_objects objects.each{ |o| puts o.key } The `list_objects` command returns an iterator for you to get the information of each object in order. Object For the object structure, see {Aliyun::OSS::Object} in the API documentation. ### Create an object in the bucket bucket.put_object(object_key){ |stream| stream << 'hello world' } You can also create an object by uploading a local file: bucket.put_object(object_key, :file => local_file) ### Download an object from the bucket bucket.get_object(object_key){ |content| puts content } You can also download the object to a local file: bucket.get_object(object_key, :file => local_file) ### Copy an object bucket.copy_object(from_key, to_key) ### Identify whether an object exists bucket.object_exists?(object_key) For more operations on buckets, refer to {Aliyun::OSS::Bucket} in the API documentation. ## Simulate the directory structure OSS is a storage service for objects and does not support the directory structure. All objects are flatly structured. But you can simulate the directory structure by setting the object key in the format "foo/bar/file". Suppose there are several objects as follows: foo/x foo/bar/f1 foo/bar/dir/file foo/hello/file Listing all the objects under the "foo/" directory means to perform the *list_objects* operation with "foo/" as the prefix. But this method will also list all the objects under "foo/bar/". That's why we need the delimiter parameter. This parameter means to stop processing at the first delimiter after the prefix. The key during the process acts as the common prefix of objects, objects with the prefix will be included in the *list_objects* result. objs = bucket.list_objects(:prefix => 'foo/', :delimiter => '/') objs.each do |i| if i.is_a?(Aliyun::OSS::Object) # a object puts "object: #{i.key}" else puts "common prefix: #{i}" end end # output object: foo/x common prefix: foo/bar/ common prefix: foo/hello/ Common prefixes free you from traversing all the objects (the number of objects may be huge) to determine the prefix, and is quite helpful for simulating the directory structure. ## Upload callback You can specify a *callback* for `put_object` and `resumable_upload` so that after the file is successfully uploaded to the OSS, the OSS will initiate an *HTTP POST* request to the server address you provided to notify you that the corresponding event has occurred. You can perform desired actions after receiving the notification, such as updating the database and making statistics. For more details about upload callback, refer to [OSS Upload Callback][oss-callback]. The example below demonstrates how to use the upload callback: callback = Aliyun::OSS::Callback.new( url: 'http://10.101.168.94:1234/callback', query: {user: 'put_object'}, body: 'bucket=${bucket}&object=${object}' ) begin bucket.put_object('files/hello', callback: callback) rescue Aliyun::OSS::CallbackError => e puts "Callback failed: #{e.message}" end **Note:** - The callback URL **must not** contain the query string which must be specified in the `:query` parameter. - In the event that the file is successfully uploaded but callback execution fails, the client will throw `CallbackError`. To ignore the error, you need to explicitly catch the exception. - For detailed examples, refer to [callback.rb](examples/aliyun/oss/callback.rb). - For servers that support callback, refer to [callback_server.rb](rails/aliyun_oss_callback_server.rb). ## Resumable upload/download OSS supports the storage of large objects. If the upload/download task of a large object is interrupted (due to network transient disconnections, program crashes, or machine power-off), the re-upload/re-download is taxing on system resources. The OSS supports multipart upload/download to divide a large object into multiple parts for upload/download. Alibaba Cloud OSS SDK provides the resumable upload/download feature based on this principle. If an interruption occurs, you can resume the upload/download task beginning with the interrupted part. ***Resumable upload/download is recommended for objects larger than 100MB***. ### Resumable upload bucket.resumable_upload(object_key, local_file, :cpt_file => cpt_file) In specific, `:cpt_file` specifies the location of the checkpoint object which stores the intermediate state of the upload. If no object is specified, the SDK will generate a `local_file.cpt` in the directory of the `local_file`. After the upload interruption, you only need to provide the same cpt object for the upload task to resume from the interrupted part. The typical upload code is: retry_times = 5 retry_times.times do begin bucket.resumable_upload(object_key, local_file) rescue => e logger.error(e.message) end end **Notes:** - The SDK records the upload intermediate states in the cpt object. Therefore, ensure that you have write permission on the cpt object. - The cpt object records the intermediate state information of the upload and has a self-checking function. You cannot edit the object. Upload will fail if the cpt object is corrupted. When the upload is completed, the checkpoint file will be deleted. ### Resumable download bucket.resumable_download(object_key, local_file, :cpt_file => cpt_file) In specific, `:cpt_file` specifies the location of the checkpoint object which stores the intermediate state of the download. If no object is specified, the SDK will generate a `local_file.cpt` in the directory of the `local_file`. After the download interruption, you only need to provide the same cpt object for the download task to resume from the interrupted part. The typical download code is: retry_times = 5 retry_times.times do begin bucket.resumable_download(object_key, local_file) rescue => e logger.error(e.message) end end **Notes:** - During the download process, a temporary object of `local_file.part.N` will be generated in the directory of the `local_file` for each part downloaded. When the download is completed, the objects will be deleted. You cannot edit or delete the part objects, otherwise the download will not proceed. - The SDK records the download intermediate states in the cpt object; therefore, ensure that you have write permission on the cpt object. - The cpt object records the intermediate state information of the download and has a self-checking function. You cannot edit the object. Download will fail if the cpt object is corrupted. When the download is completed, the `checkpoint` object will be deleted. # Appendable object Objects in Alibaba Cloud OSS can be divided into two types: Normal and Appendable. - A normal object functions as a whole for every upload. If an object already exists, the later uploaded object will overwrite the previous object with the same key. - An appendable object is created through `append_object` for the first time. The later uploaded object through `append_object` will not overwrite the previous one, but will append content to the end of the object. - You cannot append content to a normal object. - You cannot copy an appendable object. ### Create an appendable object bucket.append_object(object_key, 0){ |stream| stream << "hello world" } The second parameter indicates the position to append the content. This parameter is ***0*** for the first append to the object. In later append operations, the value of this parameter is the length of the object before the append. Of course, you can also read the appended content from the object: bucket.append_object(object_key, 0, :file => local_file) ### Append content to the object pos = bucket.get_object(object_key).size next_pos = bucket.append_object(object_key, pos, :file => local_file) During the first append, you can use {Aliyun::OSS::Bucket#get_object} to get the object length. For later append operations, you can refer to the response of {Aliyun::OSS::Bucket#append_object} to determine the length value for next append. ***Note:*** Concurrent `append_object` and `next_pos` operations do not always produce correct results. ## Object meta information Besides the object content, the OSS also allows you to set some *meta information* for the object during object uploading. The meta information is a key-value pair to identify the specific attributes of the object. The meta information will be stored together with the object and returned to users in `get_object` and `get_object_meta` operations. bucket.put_object(object_key, :file => local_file, :metas => { 'key1' => 'value1', 'key2' => 'value2'}) obj = bucket.get_object(object_key, :file => localfile) puts obj.metas **Note:** - The key and value of the meta information can only be simple ASCII non-newline characters and the total size must not exceed ***8KB***. - In the copy object operation, the meta information of the source object will be copied by default. If you don't want this, explicitly set the `:meta_directive` to {Aliyun::OSS::MetaDirective::REPLACE}. ## Permission control OSS allows you to set access permissions for buckets and objects respectively, so that you can conveniently control external access to your resources. A bucket is enabled with three types of access permissions: - public-read-write: Anonymous users are allowed to create/retrieve/delete objects in the bucket. - public-read: Anonymous users are allowed to retrieve objects in the bucket. - private: Anonymous users are not allowed to access the bucket. Signature is required for all accesses. When a bucket is created, the private permission applies by default. You can use 'bucket.acl=' to set the ACL of the bucket. bucket.acl = Aliyun::OSS::ACL::PUBLIC_READ puts bucket.acl # public-read An object is enabled with four types of access permissions: - default: The object inherits the access permissions of the bucket it belongs to, that is, the access permission of the object is the same as that of the bucket where the object is stored. - public-read-write: Anonymous users are allowed to read/write the object. - public-read: Anonymous users are allowed to read the object. - private: Anonymous users are not allowed to access the object. Signature is required for all accesses. When an object is created, the default permission applies by default. You can use 'bucket.set_object_acl' to configure the ACL of the object. acl = bucket.get_object_acl(object_key) puts acl # default bucket.set_object_acl(object_key, Aliyun::OSS::ACL::PUBLIC_READ) acl = bucket.get_object_acl(object_key) puts acl # public-read **Notes:** - If an object is configured with an ACL policy, the object ACL takes priority during permission authentication when the object is accessed. The bucket ACL will be ignored. - If anonymous access is allowed (public-read or public-read-write is configured for the object), you can directly access the object using a browser. For example, http://bucket-name.oss-cn-hangzhou.aliyuncs.com/object.jpg - A bucket or an object with the public permission can be accessed by an anonymous client which is created with the following code: # If access_key_id and access_key_secret are not specified, an anonymous client will be created. The client can only access # the buckets and objects with the public permission. client = Client.new(:endpoint => 'oss-cn-hangzhou.aliyuncs.com') bucket = client.get_bucket('public-bucket') obj = bucket.get_object('public-object', :file => local_file) ## Run examples Some example projects are provided in the examples/ directory of the SDK to demonstrate the SDK features. You can run the examples after some configuration. The permission information and the bucket information required by the examples are available in the `~/.oss.yml` configuration file under the *HOME* directory. The information should include the following fields (**Note the space after the colon**): endpoint: oss-cn-hangzhou.aliyuncs.com cname: false access_key_id: access_key_secret: bucket: You need to create (if not in existence) or modify the content and run the example project: ruby examples/aliyun/oss/bucket.rb ## Run test ```bash bundle exec rake spec export RUBY_SDK_OSS_ENDPOINT=endpoint export RUBY_SDK_OSS_ID=AccessKeyId export RUBY_SDK_OSS_KEY=AccessKeySecret export RUBY_SDK_OSS_BUCKET=bucket-name bundle exec rake test ``` ## License - MIT ## More For more documentation, see: - Alibaba Cloud OSS Ruby SDK [documentation](https://help.aliyun.com/document_detail/oss/sdk/ruby-sdk/install.html). - Alibaba Cloud OSS [documentation](http://help.aliyun.com/product/8314910_oss.html). [region-list]: https://help.aliyun.com/document_detail/oss/user_guide/endpoint_region.html [custom-domain]: https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/oss_cname.html [aliyun-sts]: https://help.aliyun.com/document_detail/ram/intro/concepts.html [sdk-api]: http://www.rubydoc.info/gems/aliyun-sdk/ [oss-callback]: https://help.aliyun.com/document_detail/oss/user_guide/upload_object/upload_callback.html aliyun-aliyun-oss-ruby-sdk-1d573e7/Rakefile000077500000000000000000000032731371637147000207010ustar00rootroot00000000000000#!/usr/bin/env rake require 'bundler' require "bundler/gem_tasks" require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do Bundler.setup(:default, :test) end task :example do FileList['examples/**/*.rb'].each do |f| puts "==== Run example: #{f} ====" ruby f end end require 'rake/testtask' begin require 'rake/extensiontask' rescue LoadError abort <<-error rake-compile is missing; Rugged depends on rake-compiler to build the C wrapping code. Install it by running `gem i rake-compiler` error end gemspec = Gem::Specification::load( File.expand_path('../aliyun-sdk.gemspec', __FILE__)) Gem::PackageTask.new(gemspec) do |pkg| end Rake::ExtensionTask.new('crcx', gemspec) do |ext| ext.name = 'crcx' ext.ext_dir = 'ext/crcx' ext.lib_dir = 'lib/aliyun' end Rake::TestTask.new do |t| t.pattern = "tests/**/test_*.rb" end task :default => [:compile, :spec] task :smart_test do # run spec test Rake::Task[:spec].invoke if ENV.keys.include?('RUBY_SDK_OSS_KEY') begin env_upload_crc_enable = ENV['RUBY_SDK_OSS_UPLOAD_CRC_ENABLE'] env_download_crc_enable = ENV['RUBY_SDK_OSS_DOWNLOAD_CRC_ENABLE'] # run test with crc ENV['RUBY_SDK_OSS_UPLOAD_CRC_ENABLE'] = 'true' ENV['RUBY_SDK_OSS_DOWNLOAD_CRC_ENABLE'] = 'true' Rake::Task[:test].invoke # run test without crc ENV['RUBY_SDK_OSS_UPLOAD_CRC_ENABLE'] = 'false' ENV['RUBY_SDK_OSS_DOWNLOAD_CRC_ENABLE'] = 'false' Rake::Task[:test].invoke ensure ENV['RUBY_SDK_OSS_UPLOAD_CRC_ENABLE'] = env_upload_crc_enable ENV['RUBY_SDK_OSS_DOWNLOAD_CRC_ENABLE'] = env_download_crc_enable end end end Rake::Task[:smart_test].prerequisites << :compile aliyun-aliyun-oss-ruby-sdk-1d573e7/aliyun-sdk.gemspec000066400000000000000000000030171371637147000226520ustar00rootroot00000000000000# -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'aliyun/version' Gem::Specification.new do |spec| spec.name = 'aliyun-sdk' spec.version = Aliyun::VERSION spec.authors = ['Tianlong Wu'] spec.email = ['rockuw.@gmail.com'] spec.summary = 'Aliyun OSS SDK for Ruby' spec.description = 'A Ruby program to facilitate accessing Aliyun Object Storage Service' spec.homepage = 'https://github.com/aliyun/aliyun-oss-ruby-sdk' spec.files = Dir.glob("lib/**/*.rb") + Dir.glob("examples/**/*.rb") + Dir.glob("ext/**/*.{rb,c,h}") spec.test_files = Dir.glob("spec/**/*_spec.rb") + Dir.glob("tests/**/*.rb") spec.extra_rdoc_files = ['README.md', 'CHANGELOG.md'] spec.bindir = 'lib/aliyun' spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.require_paths = ['lib'] spec.license = 'MIT' spec.extensions = ['ext/crcx/extconf.rb'] spec.add_dependency 'nokogiri', '~> 1.6' spec.add_dependency 'rest-client', '~> 2.0' spec.add_development_dependency 'bundler', '~> 1.10' spec.add_development_dependency 'rake', '~> 10.4' spec.add_development_dependency 'rake-compiler', '~> 0.9.0' spec.add_development_dependency 'rspec', '~> 3.3' spec.add_development_dependency 'webmock', '~> 3.0' spec.add_development_dependency 'simplecov', '~> 0.10.0' spec.add_development_dependency 'minitest', '~> 5.8' spec.required_ruby_version = '>= 2.0' end aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/000077500000000000000000000000001371637147000210425ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/000077500000000000000000000000001371637147000223435ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/oss/000077500000000000000000000000001371637147000231475ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/oss/bucket.rb000066400000000000000000000106711371637147000247560ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) require 'yaml' require 'aliyun/oss' # 初始化OSS client Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) client = Aliyun::OSS::Client.new( :endpoint => conf['endpoint'], :cname => conf['cname'], :access_key_id => conf['access_key_id'], :access_key_secret => conf['access_key_secret']) bucket = client.get_bucket(conf['bucket']) # 辅助打印函数 def demo(msg) puts "######### #{msg} ########" puts yield puts "-------------------------" puts end # 列出当前所有的bucket demo "List all buckets" do buckets = client.list_buckets buckets.each{ |b| puts "Bucket: #{b.name}"} end # 创建bucket,如果同名的bucket已经存在,则创建会失败 demo "Create bucket" do begin bucket_name = 't-foo-bar' client.create_bucket(bucket_name, :location => 'oss-cn-hangzhou') puts "Create bucket success: #{bucket_name}" rescue => e puts "Create bucket failed: #{bucket_name}, #{e.message}" end end # 向bucket中添加5个空的object: # foo/obj1, foo/bar/obj1, foo/bar/obj2, foo/xxx/obj1 demo "Put objects before list" do bucket.put_object('foo/obj1') bucket.put_object('foo/bar/obj1') bucket.put_object('foo/bar/obj2') bucket.put_object('foo/xxx/obj1') bucket.put_object('中国の') end # list bucket下所有objects demo "List first 10 objects" do objects = bucket.list_objects objects.take(10).each do |o| puts "Object: #{o.key}, type: #{o.type}, size: #{o.size}" end end # list bucket下所有前缀为foo/bar/的object demo "List first 10 objects with prefix 'foo/bar/'" do objects = bucket.list_objects(:prefix => 'foo/bar/') objects.take(10).each do |o| puts "Object: #{o.key}, type: #{o.type}, size: #{o.size}" end end # 获取object的common prefix,common prefix是指bucket下所有object(也可 # 以指定特定的前缀)的公共前缀,这在object数量巨多的时候很有用,例如有 # 如下的object: # /foo/bar/obj1 # /foo/bar/obj2 # ... # /foo/bar/obj9999999 # /foo/xx/ # 指定foo/为prefix,/为delimiter,则返回的common prefix为 # /foo/bar/, /foo/xxx/ # 这可以表示/foo/目录下的子目录。如果没有common prefix,你可能要遍历所 # 有的object来找公共的前缀 demo "List first 10 objects/common prefixes" do objects = bucket.list_objects(:prefix => 'foo/', :delimiter => '/') objects.take(10).each do |o| if o.is_a?(Aliyun::OSS::Object) puts "Object: #{o.key}, type: #{o.type}, size: #{o.size}" else puts "Common prefix: #{o}" end end end # 获取/设置Bucket属性: ACL, Logging, Referer, Website, LifeCycle, CORS demo "Get/Set bucket properties: ACL/Logging/Referer/Website/Lifecycle/CORS" do puts "Bucket acl before: #{bucket.acl}" bucket.acl = Aliyun::OSS::ACL::PUBLIC_READ puts "Bucket acl now: #{bucket.acl}" puts puts "Bucket logging before: #{bucket.logging.to_s}" bucket.logging = Aliyun::OSS::BucketLogging.new( :enable => true, :target_bucket => conf['bucket'], :target_prefix => 'foo/') puts "Bucket logging now: #{bucket.logging.to_s}" puts puts "Bucket referer before: #{bucket.referer.to_s}" bucket.referer = Aliyun::OSS::BucketReferer.new( :allow_empty => true, :whitelist => ['baidu.com', 'aliyun.com']) puts "Bucket referer now: #{bucket.referer.to_s}" puts puts "Bucket website before: #{bucket.website.to_s}" bucket.website = Aliyun::OSS::BucketWebsite.new( :enable => true, :index => 'default.html', :error => 'error.html') puts "Bucket website now: #{bucket.website.to_s}" puts puts "Bucket lifecycle before: #{bucket.lifecycle.map(&:to_s)}" bucket.lifecycle = [ Aliyun::OSS::LifeCycleRule.new( :id => 'rule1', :enable => true, :prefix => 'foo/', :expiry => 1), Aliyun::OSS::LifeCycleRule.new( :id => 'rule2', :enable => false, :prefix => 'bar/', :expiry => Date.new(2016, 1, 1)) ] puts "Bucket lifecycle now: #{bucket.lifecycle.map(&:to_s)}" puts puts "Bucket cors before: #{bucket.cors.map(&:to_s)}" bucket.cors = [ Aliyun::OSS::CORSRule.new( :allowed_origins => ['aliyun.com', 'http://www.taobao.com'], :allowed_methods => ['PUT', 'POST', 'GET'], :allowed_headers => ['Authorization'], :expose_headers => ['x-oss-test'], :max_age_seconds => 100) ] puts "Bucket cors now: #{bucket.cors.map(&:to_s)}" puts end aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/oss/callback.rb000066400000000000000000000034351371637147000252350ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) require 'yaml' require 'json' require 'aliyun/oss' ## # 用户在上传文件时可以指定“上传回调”,这样在文件上传成功后OSS会向用户 # 提供的服务器地址发起一个HTTP POST请求,相当于一个通知机制。用户可以 # 在收到回调的时候做相应的动作。 # 1. 如何接受OSS的回调可以参考代码目录下的 # rails/aliyun_oss_callback_server.rb # 2. 只有put_object和resumable_upload支持上传回调 # 初始化OSS client Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) bucket = Aliyun::OSS::Client.new( :endpoint => conf['endpoint'], :cname => conf['cname'], :access_key_id => conf['access_key_id'], :access_key_secret => conf['access_key_secret']).get_bucket(conf['bucket']) # 辅助打印函数 def demo(msg) puts "######### #{msg} ########" puts yield puts "-------------------------" puts end demo "put object with callback" do callback = Aliyun::OSS::Callback.new( url: 'http://10.101.168.94:1234/callback', query: {user: 'put_object'}, body: 'bucket=${bucket}&object=${object}' ) begin bucket.put_object('files/hello', callback: callback) rescue Aliyun::OSS::CallbackError => e puts "Callback failed: #{e.message}" end end demo "resumable upload with callback" do callback = Aliyun::OSS::Callback.new( url: 'http://10.101.168.94:1234/callback', query: {user: 'resumable_upload'}, body: 'bucket=${bucket}&object=${object}' ) begin bucket.resumable_upload('files/world', '/tmp/x', callback: callback) rescue Aliyun::OSS::CallbackError => e puts "Callback failed: #{e.message}" end end aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/oss/object.rb000066400000000000000000000114671371637147000247530ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) require 'yaml' require 'aliyun/oss' # 初始化OSS client Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) bucket = Aliyun::OSS::Client.new( :endpoint => conf['endpoint'], :cname => conf['cname'], :access_key_id => conf['access_key_id'], :access_key_secret => conf['access_key_secret']).get_bucket(conf['bucket']) # 辅助打印函数 def demo(msg) puts "######### #{msg} ########" puts yield puts "-------------------------" puts end # 上传一个object # 流式上传请参考:examples/streaming.rb demo "Put object from input" do bucket.put_object('files/hello') do |content| content << 'hello world.' end puts "Put object: files/hello" end # 上传一个文件 # 断点续传请参考:examples/resumable_upload.rb demo "Put object from local file" do File.open('/tmp/x', 'w'){ |f| f.write("hello world\n") } bucket.put_object('files/world', :file => '/tmp/x') puts "Put object: files/world" end # 创建一个Appendable object demo "Create appendable object" do size = bucket.get_object('files/appendable').size rescue 0 bucket.append_object('files/appendable', size) do |content| content << 'hello appendable.' end puts "Append object: files/appendable" end # 向files/appendable中追加内容 # 首先要获取object当前的长度 demo "Append to object" do size = bucket.get_object('files/appendable').size bucket.append_object('files/appendable', size) do |content| content << 'again appendable.' end puts "Append object: files/appendable" end # 使用错误的position进行追加会失败 demo "Append with wrong pos" do begin bucket.append_object('files/appendable', 0) do |content| content << 'again appendable.' end rescue => e puts "Append failed: #{e.message}" end end # 向一个normal object中追加内容会失败 demo "Append to normal object(fail)" do begin bucket.append_object('files/hello', 0) do |content| content << 'hello appendable.' end rescue => e puts "Append object failed: #{e.message}" end end # 拷贝一个object demo "Copy object" do bucket.copy_object('files/hello', 'files/copy') puts "Copy object files/hello => files/copy" end # 拷贝一个appendable object会失败 demo "Copy appendable object(fail)" do begin bucket.copy_object('files/appendable', 'files/copy') rescue => e puts "Copy object failed: #{e.message}" end end # 下载一个object:流式处理 # 流式下载请参考:examples/streaming.rb demo "Get object: handle content" do total_size = 0 bucket.get_object('files/hello') do |chunk| total_size += chunk.size end puts "Total size: #{total_size}" end # 下载一个object:下载到文件中 demo "Get object to local file" do bucket.get_object('files/hello', :file => '/tmp/hello') puts "Get object: files/hello => /tmp/hello" end # 删除一个object demo "Delete object" do bucket.delete_object('files/world') puts "Delete object: files/world" end # 删除一个不存在的object返回OK # 这意味着delete_object是幂等的,在删除失败的时候可以不断重试,直到成 # 功,成功意味着object已经不存在 demo "Delete a non-existent object(OK)" do bucket.delete_object('non-existent-object') puts "Delete object: non-existent-object" end # 设置Object metas demo "Put objec with metas" do bucket.put_object( 'files/hello', :metas => {'year' => '2015', 'people' => 'mary'} ) do |content| content << 'hello world.' end o = bucket.get_object('files/hello', :file => '/tmp/x') puts "Object metas: #{o.metas}" end # 修改Object metas demo "Update object metas" do bucket.update_object_metas( 'files/hello', {'year' => '2016', 'people' => 'jack'}) o = bucket.get_object('files/hello') puts "Meta changed: #{o.metas}" end # 设置Object的ACL demo "Set object ACL" do puts "Object acl before: #{bucket.get_object_acl('files/hello')}" bucket.set_object_acl('files/hello', Aliyun::OSS::ACL::PUBLIC_READ) puts "Object acl now: #{bucket.get_object_acl('files/hello')}" end # 指定条件get_object demo "Get object with conditions" do o = bucket.get_object('files/hello') begin o = bucket.get_object( 'files/hello', :condition => {:if_match_etag => o.etag + 'x'}) rescue Aliyun::OSS::ServerError => e puts "Get object failed: #{e.message}" end begin o = bucket.get_object( 'files/hello', :condition => {:if_unmodified_since => o.last_modified - 60}) rescue Aliyun::OSS::ServerError => e puts "Get object failed: #{e.message}" end o = bucket.get_object( 'files/hello', :condition => {:if_match_etag => o.etag, :if_unmodified_since => Time.now}) puts "Get object: #{o.to_s}" end aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/oss/resumable_download.rb000066400000000000000000000022731371637147000273460ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) require 'yaml' require 'aliyun/oss' # 初始化OSS Bucket Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) bucket = Aliyun::OSS::Client.new( :endpoint => conf['endpoint'], :cname => conf['cname'], :access_key_id => conf['access_key_id'], :access_key_secret => conf['access_key_secret']).get_bucket(conf['bucket']) # 辅助打印函数 def demo(msg) puts "######### #{msg} ########" puts yield puts "-------------------------" puts end 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 => cpt_file) do |progress| puts "Progress: #{(progress * 100).round(2)} %" end puts "Download complete. Cost: #{Time.now - start} seconds." # 测试方法: # 1. ruby examples/resumable_download.rb # 2. 过几秒后用Ctrl-C中断下载 # 3. ruby examples/resumable_download.rb恢复下载 end aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/oss/resumable_upload.rb000066400000000000000000000025371371637147000270260ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) require 'yaml' require 'aliyun/oss' # 初始化OSS Bucket Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) bucket = Aliyun::OSS::Client.new( :endpoint => conf['endpoint'], :cname => conf['cname'], :access_key_id => conf['access_key_id'], :access_key_secret => conf['access_key_secret']).get_bucket(conf['bucket']) # 辅助打印函数 def demo(msg) puts "######### #{msg} ########" puts yield puts "-------------------------" puts end demo "Resumable upload" do puts "Generate file: /tmp/x, size: 100MB" # 生成一个100M的文件 File.open('/tmp/x', 'w') do |f| (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 => cpt_file) do |progress| puts "Progress: #{(progress * 100).round(2)} %" end puts "Upload complete. Cost: #{Time.now - start} seconds." # 测试方法: # 1. ruby examples/resumable_upload.rb # 2. 过几秒后用Ctrl-C中断上传 # 3. ruby examples/resumable_upload.rb恢复上传 end aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/oss/streaming.rb000066400000000000000000000076221371637147000254740ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) require 'yaml' require 'aliyun/oss' ## # 一般来说用户在上传object和下载object时只需要指定文件名就可以满足需要: # - 在上传的时候client会从指定的文件中读取数据上传到OSS # - 在下载的时候client会把从OSS下载的数据写入到指定的文件中 # # 在某些情况下用户可能会需要流式地上传和下载: # - 用户要写入到object中的数据不能立即得到全部,而是从网络中流式获取, # 然后再一段一段地写入到OSS中 # - 用户要写入到object的数据是经过运算得出,每次得到一部分,用户不希望 # 保留所有的数据然后一次性写入到OSS # - 用户下载的object很大,用户不希望一次性把它们下载到内存中,而是希望 # 获取一部分就处理一部分;用户也不希望把它先下载到文件中,然后再从文 # 件中读出来处理,这样会让数据经历不必要的拷贝 # # 当然,对于流式上传的需求,我们可以使用OSS的appendable object来满足。 # 但是即使是normal object,利用sdk的streaming功能,也可以实现流式上传 # 和下载。 # 初始化OSS client Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) bucket = Aliyun::OSS::Client.new( :endpoint => conf['endpoint'], :cname => conf['cname'], :access_key_id => conf['access_key_id'], :access_key_secret => conf['access_key_secret']).get_bucket(conf['bucket']) # 辅助打印函数 def demo(msg) puts "######### #{msg} ########" puts yield puts "-------------------------" puts end # 例子1: 归并排序 # 有两个文件sort.1, sort.2,它们分别存了一些从小到大排列的整数,每个整 # 数1行,现在要将它们做归并排序的结果上传到OSS中,命名为sort.all local_1, local_2 = 'sort.1', 'sort.2' result_object = 'sort.all' File.open(File.expand_path(local_1), 'w') do |f| [1001, 2005, 2007, 2011, 2013, 2015].each do |i| f.puts(i.to_s) end end File.open(File.expand_path(local_2), 'w') do |f| [2009, 2010, 2012, 2017, 2020, 9999].each do |i| f.puts(i.to_s) end end demo "Streaming upload" do bucket.put_object(result_object) do |content| f1 = File.open(File.expand_path(local_1)) f2 = File.open(File.expand_path(local_2)) v1, v2 = f1.readline, f2.readline until f1.eof? or f2.eof? if v1.to_i < v2.to_i content << v1 v1 = f1.readline else content << v2 v2 = f2.readline end end [v1, v2].sort.each{|i| content << i} content << f1.readline until f1.eof? content << f2.readline until f2.eof? end puts "Put object: #{result_object}" # 将文件下载下来查看 bucket.get_object(result_object, :file => result_object) puts "Get object: #{result_object}" puts "Content: #{File.read(result_object)}" end # 例子2: 下载进度条 # 下载一个大文件(10M),在下载的过程中打印下载进度 large_file = 'large_file' demo "Streaming download" do puts "Begin put object: #{large_file}" # 利用streaming上传 bucket.put_object(large_file) do |stream| 10.times { stream << "x" * (1024 * 1024) } end # 查看object大小 object_size = bucket.get_object(large_file).size puts "Put object: #{large_file}, size: #{object_size}" # 流式下载文件,仅打印进度,不保存文件 def to_percentile(v) "#{(v * 100.0).round(2)} %" end puts "Begin download: #{large_file}" last_got, got = 0, 0 bucket.get_object(large_file) do |chunk| got += chunk.size # 仅在下载进度大于10%的时候打印 if (got - last_got).to_f / object_size > 0.1 puts "Progress: #{to_percentile(got.to_f / object_size)}" last_got = got end end puts "Get object: #{large_file}, size: #{object_size}" end aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/oss/using_sts.rb000066400000000000000000000023771371637147000255230ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) require 'yaml' require 'aliyun/sts' require 'aliyun/oss' # 初始化OSS client Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.sts.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) # 辅助打印函数 def demo(msg) puts "######### #{msg} ########" puts yield puts "-------------------------" puts end demo "Using STS" do sts = Aliyun::STS::Client.new( :access_key_id => conf['access_key_id'], :access_key_secret => conf['access_key_secret']) token = sts.assume_role( 'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-1') client = Aliyun::OSS::Client.new( :endpoint => 'http://oss-cn-hangzhou.aliyuncs.com', :sts_token => token.security_token, :access_key_id => token.access_key_id, :access_key_secret => token.access_key_secret) unless client.bucket_exists?('bucket-for-sts-test') client.create_bucket('bucket-for-sts-test') end bucket = client.get_bucket('bucket-for-sts-test') bucket.put_object('hello') { |s| s << 'hello' } bucket.put_object('world') { |s| s << 'world' } bucket.list_objects.take(10).each do |obj| puts "Object: #{obj.key}, size: #{obj.size}" end end aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/sts/000077500000000000000000000000001371637147000231545ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/examples/aliyun/sts/assume_role.rb000066400000000000000000000032561371637147000260250ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) require 'yaml' require 'aliyun/sts' Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.sts.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) client = Aliyun::STS::Client.new( :access_key_id => conf['access_key_id'], :access_key_secret => conf['access_key_secret']) # 辅助打印函数 def demo(msg) puts "######### #{msg} ########" puts yield puts "-------------------------" puts end token = client.assume_role( 'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-1') demo "Assume role" do begin token = client.assume_role( 'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-1') puts "Credentials for session: #{token.session_name}" puts "access key id: #{token.access_key_id}" puts "access key secret: #{token.access_key_secret}" puts "security token: #{token.security_token}" puts "expiration at: #{token.expiration}" rescue => e puts "AssumeRole failed: #{e.message}" end end demo "Assume role with policy" do begin policy = Aliyun::STS::Policy.new policy.allow( ['oss:Get*', 'oss:PutObject'], ['acs:oss:*:*:my-bucket', 'acs:oss:*:*:my-bucket/*']) token = client.assume_role( 'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-2', policy, 900) puts "Credentials for session: #{token.session_name}" puts "access key id: #{token.access_key_id}" puts "access key secret: #{token.access_key_secret}" puts "security token: #{token.security_token}" puts "expiration at: #{token.expiration}" rescue => e puts "AssumeRole failed: #{e.message}" end end aliyun-aliyun-oss-ruby-sdk-1d573e7/ext/000077500000000000000000000000001371637147000200245ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/ext/crcx/000077500000000000000000000000001371637147000207635ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/ext/crcx/crc64_ecma.c000066400000000000000000000207001371637147000230340ustar00rootroot00000000000000/* crc64.c -- compute CRC-64 * Copyright (C) 2013 Mark Adler * Version 1.4 16 Dec 2013 Mark Adler */ /* This software is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Mark Adler madler@alumni.caltech.edu */ /* Compute CRC-64 in the manner of xz, using the ECMA-182 polynomial, bit-reversed, with one's complement pre and post processing. Provide a means to combine separately computed CRC-64's. */ /* Version history: 1.0 13 Dec 2013 First version 1.1 13 Dec 2013 Fix comments in test code 1.2 14 Dec 2013 Determine endianess at run time 1.3 15 Dec 2013 Add eight-byte processing for big endian as well Make use of the pthread library optional 1.4 16 Dec 2013 Make once variable volatile for limited thread protection */ #include #include #include /* 64-bit CRC polynomial with these coefficients, but reversed: 64, 62, 57, 55, 54, 53, 52, 47, 46, 45, 40, 39, 38, 37, 35, 33, 32, 31, 29, 27, 24, 23, 22, 21, 19, 17, 13, 12, 10, 9, 7, 4, 1, 0 */ #define POLY UINT64_C(0xc96c5795d7870f42) /* Tables for CRC calculation -- filled in by initialization functions that are called once. These could be replaced by constant tables generated in the same way. There are two tables, one for each endianess. Since these are static, i.e. local, one should be compiled out of existence if the compiler can evaluate the endianess check in crc64() at compile time. */ static uint64_t crc64_little_table[8][256]; static uint64_t crc64_big_table[8][256]; /* Fill in the CRC-64 constants table. */ static void crc64_init(uint64_t table[][256]) { unsigned n, k; uint64_t crc; /* generate CRC-64's for all single byte sequences */ for (n = 0; n < 256; n++) { crc = n; for (k = 0; k < 8; k++) crc = crc & 1 ? POLY ^ (crc >> 1) : crc >> 1; table[0][n] = crc; } /* generate CRC-64's for those followed by 1 to 7 zeros */ for (n = 0; n < 256; n++) { crc = table[0][n]; for (k = 1; k < 8; k++) { crc = table[0][crc & 0xff] ^ (crc >> 8); table[k][n] = crc; } } } /* This function is called once to initialize the CRC-64 table for use on a little-endian architecture. */ static void crc64_little_init(void) { crc64_init(crc64_little_table); } /* Reverse the bytes in a 64-bit word. */ static inline uint64_t rev8(uint64_t a) { uint64_t m; m = UINT64_C(0xff00ff00ff00ff); a = ((a >> 8) & m) | (a & m) << 8; m = UINT64_C(0xffff0000ffff); a = ((a >> 16) & m) | (a & m) << 16; return a >> 32 | a << 32; } /* This function is called once to initialize the CRC-64 table for use on a big-endian architecture. */ static void crc64_big_init(void) { unsigned k, n; crc64_init(crc64_big_table); for (k = 0; k < 8; k++) for (n = 0; n < 256; n++) crc64_big_table[k][n] = rev8(crc64_big_table[k][n]); } /* init table once */ void crc64_init_once(void) { crc64_little_init(); crc64_big_init(); } /* Calculate a CRC-64 eight bytes at a time on a little-endian architecture. */ static inline uint64_t crc64_little(uint64_t crc, void *buf, size_t len) { unsigned char *next = (unsigned char *) buf; crc = ~crc; while (len && ((uintptr_t)next & 7) != 0) { crc = crc64_little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8); len--; } while (len >= 8) { crc ^= *(uint64_t *)next; crc = crc64_little_table[7][crc & 0xff] ^ crc64_little_table[6][(crc >> 8) & 0xff] ^ crc64_little_table[5][(crc >> 16) & 0xff] ^ crc64_little_table[4][(crc >> 24) & 0xff] ^ crc64_little_table[3][(crc >> 32) & 0xff] ^ crc64_little_table[2][(crc >> 40) & 0xff] ^ crc64_little_table[1][(crc >> 48) & 0xff] ^ crc64_little_table[0][crc >> 56]; next += 8; len -= 8; } while (len) { crc = crc64_little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8); len--; } return ~crc; } /* Calculate a CRC-64 eight bytes at a time on a big-endian architecture. */ static inline uint64_t crc64_big(uint64_t crc, void *buf, size_t len) { unsigned char *next = (unsigned char *) buf; crc = ~rev8(crc); while (len && ((uintptr_t)next & 7) != 0) { crc = crc64_big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8); len--; } while (len >= 8) { crc ^= *(uint64_t *)next; crc = crc64_big_table[0][crc & 0xff] ^ crc64_big_table[1][(crc >> 8) & 0xff] ^ crc64_big_table[2][(crc >> 16) & 0xff] ^ crc64_big_table[3][(crc >> 24) & 0xff] ^ crc64_big_table[4][(crc >> 32) & 0xff] ^ crc64_big_table[5][(crc >> 40) & 0xff] ^ crc64_big_table[6][(crc >> 48) & 0xff] ^ crc64_big_table[7][crc >> 56]; next += 8; len -= 8; } while (len) { crc = crc64_big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8); len--; } return ~rev8(crc); } /* Return the CRC-64 of buf[0..len-1] with initial crc, processing eight bytes at a time. This selects one of two routines depending on the endianess of the architecture. A good optimizing compiler will determine the endianess at compile time if it can, and get rid of the unused code and table. If the endianess can be changed at run time, then this code will handle that as well, initializing and using two tables, if called upon to do so. */ uint64_t crc64(uint64_t crc, void *buf, size_t len) { uint64_t n = 1; return *(char *)&n ? crc64_little(crc, buf, len) : crc64_big(crc, buf, len); } #define GF2_DIM 64 /* dimension of GF(2) vectors (length of CRC) */ static uint64_t gf2_matrix_times(uint64_t *mat, uint64_t vec) { uint64_t sum; sum = 0; while (vec) { if (vec & 1) sum ^= *mat; vec >>= 1; mat++; } return sum; } static void gf2_matrix_square(uint64_t *square, uint64_t *mat) { unsigned n; for (n = 0; n < GF2_DIM; n++) square[n] = gf2_matrix_times(mat, mat[n]); } /* Return the CRC-64 of two sequential blocks, where crc1 is the CRC-64 of the first block, crc2 is the CRC-64 of the second block, and len2 is the length of the second block. */ uint64_t crc64_combine(uint64_t crc1, uint64_t crc2, uintmax_t len2) { unsigned n; uint64_t row; uint64_t even[GF2_DIM]; /* even-power-of-two zeros operator */ uint64_t odd[GF2_DIM]; /* odd-power-of-two zeros operator */ /* degenerate case */ if (len2 == 0) return crc1; /* put operator for one zero bit in odd */ odd[0] = POLY; /* CRC-64 polynomial */ row = 1; for (n = 1; n < GF2_DIM; n++) { odd[n] = row; row <<= 1; } /* put operator for two zero bits in even */ gf2_matrix_square(even, odd); /* put operator for four zero bits in odd */ gf2_matrix_square(odd, even); /* apply len2 zeros to crc1 (first square will put the operator for one zero byte, eight zero bits, in even) */ do { /* apply zeros operator for this bit of len2 */ gf2_matrix_square(even, odd); if (len2 & 1) crc1 = gf2_matrix_times(even, crc1); len2 >>= 1; /* if no more bits set, then done */ if (len2 == 0) break; /* another iteration of the loop with odd and even swapped */ gf2_matrix_square(odd, even); if (len2 & 1) crc1 = gf2_matrix_times(odd, crc1); len2 >>= 1; /* if no more bits set, then done */ } while (len2 != 0); /* return combined crc */ crc1 ^= crc2; return crc1; } aliyun-aliyun-oss-ruby-sdk-1d573e7/ext/crcx/crcx.c000066400000000000000000000022071371637147000220670ustar00rootroot00000000000000#include "crcx.h" void Init_crcx(){ VALUE mAliyun = Qnil; VALUE CrcX = Qnil; crc64_init_once(); mAliyun = rb_define_module("Aliyun"); CrcX = rb_define_module_under(mAliyun, "CrcX"); rb_define_module_function(CrcX, "crc64", crc64_wrapper, 3); rb_define_module_function(CrcX, "crc64_combine", crc64_combine_wrapper, 3); } void check_num_type(VALUE crc_value) { if (T_BIGNUM == TYPE(crc_value)) { Check_Type(crc_value, T_BIGNUM); } else { Check_Type(crc_value, T_FIXNUM); } return ; } VALUE crc64_wrapper(VALUE self, VALUE init_crc, VALUE buffer, VALUE size) { uint64_t crc_value = 0; check_num_type(init_crc); check_num_type(size); Check_Type(buffer, T_STRING); crc_value = crc64(NUM2ULL(init_crc), (void *)RSTRING_PTR(buffer), NUM2ULL(size)); return ULL2NUM(crc_value); } VALUE crc64_combine_wrapper(VALUE self, VALUE crc1, VALUE crc2, VALUE len2) { uint64_t crc_value = 0; check_num_type(crc1); check_num_type(crc2); check_num_type(len2); crc_value = crc64_combine(NUM2ULL(crc1), NUM2ULL(crc2), NUM2ULL(len2)); return ULL2NUM(crc_value); } aliyun-aliyun-oss-ruby-sdk-1d573e7/ext/crcx/crcx.h000066400000000000000000000005031371637147000220710ustar00rootroot00000000000000#include uint64_t crc64(uint64_t crc, void *buf, size_t len); uint64_t crc64_combine(uint64_t crc1, uint64_t crc2, uintmax_t len2); void crc64_init_once(void); VALUE crc64_wrapper(VALUE self, VALUE init_crc, VALUE buffer, VALUE size); VALUE crc64_combine_wrapper(VALUE self, VALUE crc1, VALUE crc2, VALUE len2); aliyun-aliyun-oss-ruby-sdk-1d573e7/ext/crcx/extconf.rb000066400000000000000000000000571371637147000227600ustar00rootroot00000000000000require 'mkmf' create_makefile('aliyun/crcx') aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/000077500000000000000000000000001371637147000177725ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/000077500000000000000000000000001371637147000212735ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/common.rb000066400000000000000000000002351371637147000231100ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require_relative 'version' require_relative 'common/struct' require_relative 'common/logging' require_relative 'common/exception' aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/common/000077500000000000000000000000001371637147000225635ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/common/exception.rb000066400000000000000000000004111371637147000251020ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module Common ## # Base exception class # class Exception < RuntimeError attr_reader :message def initialize(message) @message = message end end end # Common end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/common/logging.rb000066400000000000000000000026411371637147000245410ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'logger' module Aliyun module Common ## # Logging support # @example # include Logging # logger.info(xxx) module Logging MAX_NUM_LOG = 100 ROTATE_SIZE = 10 * 1024 * 1024 # level = Logger::DEBUG | Logger::INFO | Logger::ERROR | Logger::FATAL def self.set_log_level(level) Logging.logger.level = level end # 设置日志输出的文件 def self.set_log_file(file) @log_file = file end # 获取logger def logger Logging.logger end private def self.logger unless @logger # Environment parameter ALIYUN_OSS_SDK_LOG_PATH used to set output log to a file,do not output log if not set @log_file ||= ENV["ALIYUN_OSS_SDK_LOG_PATH"] @logger = Logger.new( @log_file, MAX_NUM_LOG, ROTATE_SIZE) @logger.level = get_env_log_level || Logger::INFO end @logger end def self.get_env_log_level return unless ENV["ALIYUN_OSS_SDK_LOG_LEVEL"] case ENV["ALIYUN_OSS_SDK_LOG_LEVEL"].upcase when "DEBUG" Logger::DEBUG when "WARN" Logger::WARN when "ERROR" Logger::ERROR when "FATAL" Logger::FATAL when "UNKNOWN" Logger::UNKNOWN end end end # logging end # Common end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/common/struct.rb000066400000000000000000000027531371637147000244430ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module Common # Common structs used. It provides a 'attrs' helper method for # subclass to define its attributes. 'attrs' is based on # attr_reader and provide additional functionalities for classes # that inherits Struct::Base : # * the constuctor is provided to accept options and set the # corresponding attibute automatically # * the #to_s method is rewrite to concatenate the defined # attributes keys and values # @example # class X < Struct::Base # attrs :foo, :bar # end # # x.new(:foo => 'hello', :bar => 'world') # x.foo # == "hello" # x.bar # == "world" # x.to_s # == "foo: hello, bar: world" module Struct class Base module AttrHelper def attrs(*s) define_method(:attrs) {s} attr_reader(*s) end end extend AttrHelper def initialize(opts = {}) extra_keys = opts.keys - attrs unless extra_keys.empty? fail Common::Exception, "Unexpected extra keys: #{extra_keys.join(', ')}" end attrs.each do |attr| instance_variable_set("@#{attr}", opts[attr]) end end def to_s attrs.map do |attr| v = instance_variable_get("@#{attr}") "#{attr.to_s}: #{v}" end.join(", ") end end # Base end # Struct end # Common end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss.rb000066400000000000000000000007331371637147000224270ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require_relative 'common' require_relative 'oss/util' require_relative 'oss/exception' require_relative 'oss/struct' require_relative 'oss/config' require_relative 'oss/http' require_relative 'oss/protocol' require_relative 'oss/multipart' require_relative 'oss/upload' require_relative 'oss/download' require_relative 'oss/iterator' require_relative 'oss/object' require_relative 'oss/bucket' require_relative 'oss/client' require_relative 'crcx' aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/000077500000000000000000000000001371637147000220775ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/bucket.rb000066400000000000000000000724721371637147000237150ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module OSS ## # Bucket是用户的Object相关的操作的client,主要包括三部分功能: # 1. bucket相关:获取/设置bucket的属性(acl, logging, referer, # website, lifecycle, cors) # 2. object相关:上传、下载、追加、拷贝object等 # 3. multipart相关:断点续传、断点续载 class Bucket < Common::Struct::Base attrs :name, :location, :creation_time def initialize(opts = {}, protocol = nil) super(opts) @protocol = protocol end ### Bucket相关的API ### # 获取Bucket的ACL # @return [String] Bucket的{OSS::ACL ACL} def acl @protocol.get_bucket_acl(name) end # 设置Bucket的ACL # @param acl [String] Bucket的{OSS::ACL ACL} def acl=(acl) @protocol.put_bucket_acl(name, acl) end # 获取Bucket的logging配置 # @return [BucketLogging] Bucket的logging配置 def logging @protocol.get_bucket_logging(name) end # 设置Bucket的logging配置 # @param logging [BucketLogging] logging配置 def logging=(logging) if logging.enabled? @protocol.put_bucket_logging(name, logging) else @protocol.delete_bucket_logging(name) end end # 获取Bucket的versioning配置 # @return [BucketVersioning] Bucket的versioning配置 def versioning @protocol.get_bucket_versioning(name) end # 设置Bucket的versioning配置 # @param versioning [BucketVersioning] versioning配置 def versioning=(versioning) @protocol.put_bucket_versioning(name, versioning) end # 获取Bucket的encryption配置 # @return [BucketEncryption] Bucket的encryption配置 def encryption @protocol.get_bucket_encryption(name) end # 设置Bucket的encryption配置 # @param encryption [BucketEncryption] encryption配置 def encryption=(encryption) if encryption.enabled? @protocol.put_bucket_encryption(name, encryption) else @protocol.delete_bucket_encryption(name) end end # 获取Bucket的website配置 # @return [BucketWebsite] Bucket的website配置 def website begin w = @protocol.get_bucket_website(name) rescue ServerError => e raise unless e.http_code == 404 end w || BucketWebsite.new end # 设置Bucket的website配置 # @param website [BucketWebsite] website配置 def website=(website) if website.enabled? @protocol.put_bucket_website(name, website) else @protocol.delete_bucket_website(name) end end # 获取Bucket的Referer配置 # @return [BucketReferer] Bucket的Referer配置 def referer @protocol.get_bucket_referer(name) end # 设置Bucket的Referer配置 # @param referer [BucketReferer] Referer配置 def referer=(referer) @protocol.put_bucket_referer(name, referer) end # 获取Bucket的生命周期配置 # @return [Array] Bucket的生命周期规则,如果 # 当前Bucket未设置lifecycle,则返回[] def lifecycle begin r = @protocol.get_bucket_lifecycle(name) rescue ServerError => e raise unless e.http_code == 404 end r || [] end # 设置Bucket的生命周期配置 # @param rules [Array] 生命 # 周期配置规则 # @see OSS::LifeCycleRule 查看如何设置生命周期规则 # @note 如果rules为空,则会删除这个bucket上的lifecycle配置 def lifecycle=(rules) if rules.empty? @protocol.delete_bucket_lifecycle(name) else @protocol.put_bucket_lifecycle(name, rules) end end # 获取Bucket的跨域资源共享(CORS)的规则 # @return [Array] Bucket的CORS规则,如果当前 # Bucket未设置CORS规则,则返回[] def cors begin r = @protocol.get_bucket_cors(name) rescue ServerError => e raise unless e.http_code == 404 end r || [] end # 设置Bucket的跨域资源共享(CORS)的规则 # @param rules [Array] CORS规则 # @note 如果rules为空,则会删除这个bucket上的CORS配置 def cors=(rules) if rules.empty? @protocol.delete_bucket_cors(name) else @protocol.set_bucket_cors(name, rules) end end ### Object相关的API ### # 列出bucket中的object # @param opts [Hash] 查询选项 # @option opts [String] :prefix 返回的object的前缀,如果设置则只 # 返回那些名字以它为前缀的object # @option opts [String] :marker 如果设置,则只返回名字在它之后 # (字典序,不包含marker)的object # @option opts [String] :delimiter 用于获取公共前缀的分隔符,从 # 前缀后面开始到第一个分隔符出现的位置之前的字符,作为公共前缀。 # @example # 假设我们有如下objects: # /foo/bar/obj1 # /foo/bar/obj2 # ... # /foo/bar/obj9999999 # /foo/xxx/ # 用'foo/'作为前缀, '/'作为分隔符, 则得到的公共前缀是: # '/foo/bar/', '/foo/xxx/'。它们恰好就是目录'/foo/'下的所有子目 # 录。用delimiter获取公共前缀的方法避免了查询当前bucket下的所有 # object(可能数量巨大),是用于模拟目录结构的常用做法。 # @return [Enumerator] 其中Object可能是{OSS::Object},也 # 可能是{String},此时它是一个公共前缀 # @example # all = bucket.list_objects # all.each do |i| # if i.is_a?(Object) # puts "Object: #{i.key}" # else # puts "Common prefix: #{i}" # end # end def list_objects(opts = {}) Iterator::Objects.new( @protocol, name, opts.merge(encoding: KeyEncoding::URL)).to_enum end # 向Bucket中上传一个object # @param key [String] Object的名字 # @param opts [Hash] 上传object时的选项(可选) # @option opts [String] :file 设置所上传的文件 # @option opts [String] :content_type 设置所上传的内容的 # Content-Type,默认是application/octet-stream # @option opts [Hash] :metas 设置object的meta,这是一些用户自定 # 义的属性,它们会和object一起存储,在{#get_object}的时候会 # 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' } # @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 流式上传数据 # put_object('x'){ |stream| 100.times { |i| stream << i.to_s } } # put_object('x'){ |stream| stream << get_data } # @example 上传文件 # put_object('x', :file => '/tmp/x') # @example 指定Content-Type和metas # put_object('x', :file => '/tmp/x', :content_type => 'text/html', # :metas => {'year' => '2015', 'people' => 'mary'}) # @example 指定Callback # callback = Aliyun::OSS::Callback.new( # url: 'http://10.101.168.94:1234/callback', # query: {user: 'put_object'}, # body: 'bucket=${bucket}&object=${object}' # ) # # bucket.put_object('files/hello', callback: callback) # @raise [CallbackError] 如果文件上传成功而Callback调用失败,抛 # 出此错误 # @note 如果opts中指定了`:file`,则block会被忽略 # @note 如果指定了`:callback`,则可能文件上传成功,但是callback # 执行失败,此时会抛出{OSS::CallbackError},用户可以选择接住这 # 个异常,以忽略Callback调用错误 def put_object(key, opts = {}, &block) args = opts.dup file = args[:file] args[:content_type] ||= get_content_type(file) if file args[:content_type] ||= get_content_type(key) if file @protocol.put_object(name, key, args) do |sw| File.open(File.expand_path(file), 'rb') do |f| sw << f.read(Protocol::STREAM_CHUNK_SIZE) until f.eof? end end else @protocol.put_object(name, key, args, &block) end end # 从Bucket中下载一个object # @param key [String] Object的名字 # @param opts [Hash] 下载Object的选项(可选) # @option opts [Array] :range 指定下载object的部分数据, # range应只包含两个数字,表示一个*左开右闭*的bytes range # @option opts [String] :file 指定将下载的object写入到文件中 # @option opts [Hash] :condition 指定下载object需要满足的条件 # * :if_modified_since (Time) 指定如果object的修改时间晚于这个值,则下载 # * :if_unmodified_since (Time) 指定如果object从这个时间后再无修改,则下载 # * :if_match_etag (String) 指定如果object的etag等于这个值,则下载 # * :if_unmatch_etag (String) 指定如果object的etag不等于这个值,则下载 # @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小 # 写。这里指定的值会覆盖通过`:range`和`:condition`设置的值。 # @option opts [Hash] :rewrite 指定下载object时Server端返回的响应头部字段的值 # * :content_type (String) 指定返回的响应中Content-Type的值 # * :content_language (String) 指定返回的响应中Content-Language的值 # * :expires (Time) 指定返回的响应中Expires的值 # * :cache_control (String) 指定返回的响应中Cache-Control的值 # * :content_disposition (String) 指定返回的响应中Content-Disposition的值 # * :content_encoding (String) 指定返回的响应中Content-Encoding的值 # @return [OSS::Object] 返回Object对象 # @yield [String] 如果调用的时候传递了block,则获取到的object的数据交由block处理 # @example 流式下载文件 # get_object('x'){ |chunk| handle_chunk_data(chunk) } # @example 下载到本地文件 # get_object('x', :file => '/tmp/x') # @example 指定检查条件 # get_object('x', :file => '/tmp/x', :condition => {:if_match_etag => 'etag'}) # @example 指定重写响应的header信息 # get_object('x', :file => '/tmp/x', :rewrite => {:content_type => 'text/html'}) # @note 如果opts中指定了`:file`,则block会被忽略 # @note 如果既没有指定`:file`也没有指定block,则只获取Object # meta而不下载Object内容 def get_object(key, opts = {}, &block) obj = nil file = opts[:file] if file File.open(File.expand_path(file), 'wb') do |f| obj = @protocol.get_object(name, key, opts) do |chunk| f.write(chunk) end end elsif block obj = @protocol.get_object(name, key, opts, &block) else obj = @protocol.get_object_meta(name, key, opts) end obj end # 更新Object的metas # @param key [String] Object的名字 # @param metas [Hash] Object的meta # @param conditions [Hash] 指定更新Object meta需要满足的条件, # 同{#get_object} # @return [Hash] 更新后文件的信息 # * :etag [String] 更新后文件的ETag # * :last_modified [Time] 更新后文件的最后修改时间 def update_object_metas(key, metas, conditions = {}) @protocol.copy_object( name, key, key, :meta_directive => MetaDirective::REPLACE, :metas => metas, :condition => conditions) end # 判断一个object是否存在 # @param key [String] Object的名字 # @return [Boolean] 如果Object存在返回true,否则返回false def object_exists?(key) begin get_object(key) return true rescue ServerError => e return false if e.http_code == 404 raise e end false end alias :object_exist? :object_exists? # 向Bucket中的object追加内容。如果object不存在,则创建一个 # Appendable Object。 # @param key [String] Object的名字 # @param opts [Hash] 上传object时的选项(可选) # @option opts [String] :file 指定追加的内容从文件中读取 # @option opts [String] :content_type 设置所上传的内容的 # Content-Type,默认是application/octet-stream # @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 } # @example 上传文件 # append_object('x', 0, :file => '/tmp/x') # @example 指定Content-Type和metas # append_object('x', 0, :file => '/tmp/x', :content_type => 'text/html', # :metas => {'year' => '2015', 'people' => 'mary'}) # @return [Integer] 返回下次append的位置 # @yield [HTTP::StreamWriter] 同 {#put_object} def append_object(key, pos, opts = {}, &block) args = opts.dup file = args[:file] args[:content_type] ||= get_content_type(file) if file args[:content_type] ||= get_content_type(key) if file next_pos = @protocol.append_object(name, key, pos, args) do |sw| File.open(File.expand_path(file), 'rb') do |f| sw << f.read(Protocol::STREAM_CHUNK_SIZE) until f.eof? end end else next_pos = @protocol.append_object(name, key, pos, args, &block) end next_pos end # 将Bucket中的一个object拷贝成另外一个object # @param source [String] 源object名字 # @param dest [String] 目标object名字 # @param opts [Hash] 拷贝object时的选项(可选) # @option opts [String] :src_bucket 源object所属的Bucket,默认与 # 目标文件为同一个Bucket。源Bucket与目标Bucket必须属于同一个Region。 # @option opts [String] :acl 目标文件的acl属性,默认为private # @option opts [String] :meta_directive 指定是否拷贝源object的 # meta信息,默认为{OSS::MetaDirective::COPY}:即拷贝object的时 # 候也拷贝meta信息。 # @option opts [Hash] :metas 设置object的meta,这是一些用户自定 # 义的属性,它们会和object一起存储,在{#get_object}的时候会 # 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' # }。如果:meta_directive为{OSS::MetaDirective::COPY},则:metas # 会被忽略。 # @option opts [Hash] :condition 指定拷贝object需要满足的条件, # 同 {#get_object} # @return [Hash] 目标文件的信息 # * :etag [String] 目标文件的ETag # * :last_modified [Time] 目标文件的最后修改时间 def copy_object(source, dest, opts = {}) args = opts.dup args[:content_type] ||= get_content_type(dest) @protocol.copy_object(name, source, dest, args) end # 删除一个object # @param key [String] Object的名字 def delete_object(key) @protocol.delete_object(name, key) end # 批量删除object # @param keys [Array] Object的名字集合 # @param opts [Hash] 删除object的选项(可选) # @option opts [Boolean] :quiet 指定是否允许Server返回成功删除的 # object,默认为false,即返回删除结果 # @return [Array] 成功删除的object的名字,如果指定 # 了:quiet参数,则返回[] def batch_delete_objects(keys, opts = {}) @protocol.batch_delete_objects( name, keys, opts.merge(encoding: KeyEncoding::URL)) end # 设置object的ACL # @param key [String] Object的名字 # @param acl [String] Object的{OSS::ACL ACL} def set_object_acl(key, acl) @protocol.put_object_acl(name, key, acl) end # 获取object的ACL # @param key [String] Object的名字 # @return [String] object的{OSS::ACL ACL} def get_object_acl(key) @protocol.get_object_acl(name, key) end # 获取object的CORS规则 # @param key [String] Object的名字 # @return [OSS::CORSRule] def get_object_cors(key) @protocol.get_object_cors(name, key) end ## # 断点续传相关的API # # 上传一个本地文件到bucket中的一个object,支持断点续传。指定的文 # 件会被分成多个分片进行上传,只有所有分片都上传成功整个文件才 # 上传成功。 # @param key [String] Object的名字 # @param file [String] 本地文件的路径 # @param opts [Hash] 上传文件的可选项 # @option opts [String] :content_type 设置所上传的内容的 # Content-Type,默认是application/octet-stream # @option opts [Hash] :metas 设置object的meta,这是一些用户自定 # 义的属性,它们会和object一起存储,在{#get_object}的时候会 # 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' } # @option opts [Integer] :part_size 设置分片上传时每个分片的大小, # 默认为10 MB。断点上传最多允许10000个分片,如果文件大于10000个 # 分片的大小,则每个分片的大小会大于10MB。 # @option opts [String] :cpt_file 断点续传的checkpoint文件,如果 # 指定的cpt文件不存在,则会在file所在目录创建一个默认的cpt文件, # 命名方式为:file.cpt,其中file是用户要上传的文件。在上传的过 # 程中会不断更新此文件,成功完成上传后会删除此文件;如果指定的 # cpt文件已存在,则从cpt文件中记录的点继续上传。 # @option opts [Boolean] :disable_cpt 是否禁用checkpoint功能,如 # 果设置为true,则在上传的过程中不会写checkpoint文件,这意味着 # 上传失败后不能断点续传,而只能重新上传整个文件。如果这个值为 # true,则:cpt_file会被忽略。 # @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文件被损坏,则抛出此错误 # @raise [FileInconsistentError] 如果指定的文件与cpt中记录的不一 # 致,则抛出此错误 # @raise [CallbackError] 如果文件上传成功而Callback调用失败,抛 # 出此错误 # @example # bucket.resumable_upload('my-object', '/tmp/x') do |p| # puts "Progress: #{(p * 100).round(2)} %" # end # @example 指定Callback # callback = Aliyun::OSS::Callback.new( # url: 'http://10.101.168.94:1234/callback', # query: {user: 'put_object'}, # body: 'bucket=${bucket}&object=${object}' # ) # # bucket.resumable_upload('files/hello', '/tmp/x', callback: callback) # @note 如果指定了`:callback`,则可能文件上传成功,但是callback # 执行失败,此时会抛出{OSS::CallbackError},用户可以选择接住这 # 个异常,以忽略Callback调用错误 def resumable_upload(key, file, opts = {}, &block) args = opts.dup args[:content_type] ||= get_content_type(file) args[:content_type] ||= get_content_type(key) cpt_file = args[:cpt_file] || get_cpt_file(file) Multipart::Upload.new( @protocol, options: args, progress: block, object: key, bucket: name, creation_time: Time.now, file: File.expand_path(file), cpt_file: cpt_file ).run end # 下载bucket中的一个object到本地文件,支持断点续传。指定的object # 会被分成多个分片进行下载,只有所有的分片都下载成功整个object才 # 下载成功。对于每个下载的分片,会在file所在目录建立一个临时文件 # file.part.N,下载成功后这些part文件会被合并成最后的file然后删 # 除。 # @param key [String] Object的名字 # @param file [String] 本地文件的路径 # @param opts [Hash] 下载文件的可选项 # @option opts [Integer] :part_size 设置分片上传时每个分片的大小, # 默认为10 MB。断点下载最多允许100个分片,如果文件大于100个分片, # 则每个分片的大小会大于10 MB # @option opts [String] :cpt_file 断点续传的checkpoint文件,如果 # 指定的cpt文件不存在,则会在file所在目录创建一个默认的cpt文件, # 命名方式为:file.cpt,其中file是用户要下载的文件名。在下载的过 # 程中会不断更新此文件,成功完成下载后会删除此文件;如果指定的 # cpt文件已存在,则从cpt文件中记录的点继续下载。 # @option opts [Boolean] :disable_cpt 是否禁用checkpoint功能,如 # 果设置为true,则在下载的过程中不会写checkpoint文件,这意味着 # 下载失败后不能断点续传,而只能重新下载整个文件。如果这个值为true, # 则:cpt_file会被忽略。 # @option opts [Hash] :condition 指定下载object需要满足的条件, # 同 {#get_object} # @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小 # 写。这里指定的值会覆盖通过`:condition`设置的值。 # @option opts [Hash] :rewrite 指定下载object时Server端返回的响 # 应头部字段的值,同 {#get_object} # @yield [Float] 如果调用的时候传递了block,则会将下载进度交由 # block处理,进度值是一个0-1之间的小数 # @raise [CheckpointBrokenError] 如果cpt文件被损坏,则抛出此错误 # @raise [ObjectInconsistentError] 如果指定的object的etag与cpt文 # 件中记录的不一致,则抛出错误 # @raise [PartMissingError] 如果已下载的部分(.part文件)找不到, # 则抛出此错误 # @raise [PartInconsistentError] 如果已下载的部分(.part文件)的 # MD5值与cpt文件记录的不一致,则抛出此错误 # @note 已经下载的部分会在file所在的目录创建.part文件,命名方式 # 为file.part.N # @example # bucket.resumable_download('my-object', '/tmp/x') do |p| # puts "Progress: #{(p * 100).round(2)} %" # end def resumable_download(key, file, opts = {}, &block) args = opts.dup args[:content_type] ||= get_content_type(file) args[:content_type] ||= get_content_type(key) cpt_file = args[:cpt_file] || get_cpt_file(file) Multipart::Download.new( @protocol, options: args, progress: block, object: key, bucket: name, creation_time: Time.now, file: File.expand_path(file), cpt_file: cpt_file ).run end # 列出此Bucket中正在进行的multipart上传请求,不包括已经完成或者 # 被取消的。 # @param [Hash] opts 可选项 # @option opts [String] :key_marker object key的标记,根据有没有 # 设置:id_marker,:key_marker的含义不同: # 1. 如果未设置:id_marker,则只返回object key在:key_marker之后 # (字典序,不包含marker)的upload请求 # 2. 如果设置了:id_marker,则返回object key在:key_marker之后 # (字典序,不包含marker)的uplaod请求*和*Object # key与:key_marker相等,*且*upload id在:id_marker之后(字母 # 表顺序排序,不包含marker)的upload请求 # @option opts [String] :id_marker upload id的标记,如 # 果:key_marker没有设置,则此参数会被忽略;否则与:key_marker一起 # 决定返回的结果(见上) # @option opts [String] :prefix 如果指定,则只返回object key中符 # 合指定前缀的upload请求 # @return [Enumerator] 其中每一个元素表 # 示一个upload请求 # @example # key_marker = 1, id_marker = null # # return <2, 0>, <2, 1>, <3, 0> ... # key_marker = 1, id_marker = 5 # # return <1, 6>, <1, 7>, <2, 0>, <3, 0> ... def list_uploads(opts = {}) Iterator::Uploads.new( @protocol, name, opts.merge(encoding: KeyEncoding::URL)).to_enum end # 取消一个multipart上传请求,一般用于清除Bucket下因断点上传而产 # 生的文件碎片。成功取消后属于这个上传请求的分片都会被清除。 # @param [String] upload_id 上传请求的id,可通过{#list_uploads} # 获得 # @param [String] key Object的名字 def abort_upload(upload_id, key) @protocol.abort_multipart_upload(name, key, upload_id) end # 获取Bucket的URL # @return [String] Bucket的URL def bucket_url @protocol.get_request_url(name) end # 获取Object的URL # @param [String] key Object的key # @param [Boolean] sign 是否对URL进行签名,默认为是 # @param [Integer] expiry URL的有效时间,单位为秒,默认为60s # @param [Hash] parameters 附加的query参数,默认为空 # @return [String] 用于直接访问Object的URL def object_url(key, sign = true, expiry = 60, parameters = {}) url = @protocol.get_request_url(name, key).gsub('%2F', '/') query = parameters.dup if sign #header expires = Time.now.to_i + expiry headers = { 'date' => expires.to_s, } #query if @protocol.get_sts_token query['security-token'] = @protocol.get_sts_token end res = { :path => @protocol.get_resource_path(name, key), :sub_res => query, } signature = Util.get_signature(@protocol.get_access_key_secret, 'GET', headers, res) query['Expires'] = expires.to_s query['OSSAccessKeyId'] = @protocol.get_access_key_id query['Signature'] = signature end query_string = query.map { |k, v| v ? [k, CGI.escape(v)].join("=") : k }.join("&") link_char = query_string.empty? ? '' : '?' [url, query_string].join(link_char) end # 获取用户所设置的ACCESS_KEY_ID # @return [String] 用户的ACCESS_KEY_ID def access_key_id @protocol.get_access_key_id end # 用ACCESS_KEY_SECRET对内容进行签名 # @param [String] string_to_sign 要进行签名的内容 # @return [String] 生成的签名 def sign(string_to_sign) @protocol.sign(string_to_sign) end # Get the download crc status # @return true(download crc enable) or false(download crc disable) def download_crc_enable @protocol.download_crc_enable end # Get the upload crc status # @return true(upload crc enable) or false(upload crc disable) def upload_crc_enable @protocol.upload_crc_enable end private # Infer the file's content type using MIME::Types # @param file [String] the file path # @return [String] the infered content type or nil if it fails # to infer the content type def get_content_type(file) t = MIME::Types.of(file) t.first.content_type unless t.empty? end # Get the checkpoint file path for file # @param file [String] the file path # @return [String] the checkpoint file path def get_cpt_file(file) "#{File.expand_path(file)}.cpt" end end # Bucket end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/client.rb000066400000000000000000000103201371637147000236760ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module OSS ## # OSS服务的客户端,用于获取bucket列表,创建/删除bucket。Object相关 # 的操作请使用{OSS::Bucket}。 # @example 创建Client # endpoint = 'oss-cn-hangzhou.aliyuncs.com' # client = Client.new( # :endpoint => endpoint, # :access_key_id => 'access_key_id', # :access_key_secret => 'access_key_secret') # buckets = client.list_buckets # client.create_bucket('my-bucket') # client.delete_bucket('my-bucket') # bucket = client.get_bucket('my-bucket') class Client # 构造OSS client,用于操作buckets。 # @param opts [Hash] 构造Client时的参数选项 # @option opts [String] :endpoint [必填]OSS服务的地址,可以是以 # oss-cn-hangzhou.aliyuncs.com的标准域名,也可以是用户绑定的域名 # @option opts [String] :access_key_id [可选]用户的ACCESS KEY ID, # 如果不填则会尝试匿名访问 # @option opts [String] :access_key_secret [可选]用户的ACCESS # KEY SECRET,如果不填则会尝试匿名访问 # @option opts [Boolean] :cname [可选] 指定endpoint是否是用户绑 # 定的域名 # @option opts [Boolean] :upload_crc_enable [可选]指定上传处理 # 是否开启CRC校验,默认为开启(true) # @option opts [Boolean] :download_crc_enable [可选]指定下载处理 # 是否开启CRC校验,默认为不开启(false) # @option opts [String] :sts_token [可选] 指定STS的 # SecurityToken,如果指定,则使用STS授权访问 # @option opts [Integer] :open_timeout [可选] 指定建立连接的超时 # 时间,默认为10秒 # @option opts [Integer] :read_timeout [可选] 指定等待响应的超时 # 时间,默认为120秒 # @example 标准endpoint # oss-cn-hangzhou.aliyuncs.com # oss-cn-beijing.aliyuncs.com # @example 用户绑定的域名 # my-domain.com # foo.bar.com def initialize(opts) fail ClientError, "Endpoint must be provided" unless opts[:endpoint] @config = Config.new(opts) @protocol = Protocol.new(@config) end # 列出当前所有的bucket # @param opts [Hash] 查询选项 # @option opts [String] :prefix 如果设置,则只返回以它为前缀的bucket # @option opts [String] :marker 如果设置,则只返回名字在它之后 # (字典序,不包含marker)的bucket # @return [Enumerator] Bucket的迭代器 def list_buckets(opts = {}) if @config.cname fail ClientError, "Cannot list buckets for a CNAME endpoint." end Iterator::Buckets.new(@protocol, opts).to_enum end # 创建一个bucket # @param name [String] Bucket名字 # @param opts [Hash] 创建Bucket的属性(可选) # @option opts [:location] [String] 指定bucket所在的区域,默认为oss-cn-hangzhou def create_bucket(name, opts = {}) Util.ensure_bucket_name_valid(name) @protocol.create_bucket(name, opts) end # 删除一个bucket # @param name [String] Bucket名字 # @note 如果要删除的Bucket不为空(包含有object),则删除会失败 def delete_bucket(name) Util.ensure_bucket_name_valid(name) @protocol.delete_bucket(name) end # 判断一个bucket是否存在 # @param name [String] Bucket名字 # @return [Boolean] 如果Bucket存在则返回true,否则返回false def bucket_exists?(name) Util.ensure_bucket_name_valid(name) exist = false begin @protocol.get_bucket_acl(name) exist = true rescue ServerError => e raise unless e.http_code == 404 end exist end alias :bucket_exist? :bucket_exists? # 获取一个Bucket对象,用于操作bucket中的objects。 # @param name [String] Bucket名字 # @return [Bucket] Bucket对象 def get_bucket(name) Util.ensure_bucket_name_valid(name) Bucket.new({:name => name}, @protocol) end end # Client end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/config.rb000066400000000000000000000023051371637147000236710ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module OSS ## # A place to store various configurations: credentials, api # timeout, retry mechanism, etc # class Config < Common::Struct::Base attrs :endpoint, :cname, :sts_token, :access_key_id, :access_key_secret, :open_timeout, :read_timeout, :download_crc_enable, :upload_crc_enable def initialize(opts = {}) super(opts) @access_key_id = @access_key_id.strip if @access_key_id @access_key_secret = @access_key_secret.strip if @access_key_secret normalize_endpoint if endpoint @upload_crc_enable = (@upload_crc_enable == 'false' || @upload_crc_enable == false) ? false : true @download_crc_enable = (@download_crc_enable == 'true' || @download_crc_enable == true) ? true : false end private def normalize_endpoint uri = URI.parse(endpoint) uri = URI.parse("http://#{endpoint}") unless uri.scheme if uri.scheme != 'http' and uri.scheme != 'https' fail ClientError, "Only HTTP and HTTPS endpoint are accepted." end @endpoint = uri end end # Config end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/download.rb000066400000000000000000000164431371637147000242430ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module OSS module Multipart ## # A multipart download transaction # class Download < Transaction include Common::Logging PART_SIZE = 10 * 1024 * 1024 READ_SIZE = 16 * 1024 NUM_THREAD = 10 def initialize(protocol, opts) args = opts.dup @protocol = protocol @progress = args.delete(:progress) @file = args.delete(:file) @cpt_file = args.delete(:cpt_file) super(args) @object_meta = {} @num_threads = options[:threads] || NUM_THREAD @all_mutex = Mutex.new @parts = [] @todo_mutex = Mutex.new @todo_parts = [] end # Run the download transaction, which includes 3 stages: # * 1a. initiate(new downlaod) and divide parts # * 1b. rebuild states(resumed download) # * 2. download each unfinished part # * 3. combine the downloaded parts into the final file def run logger.info("Begin download, file: #{@file}, "\ "checkpoint file: #{@cpt_file}, "\ "threads: #{@num_threads}") # Rebuild transaction states from checkpoint file # Or initiate new transaction states rebuild # Divide the target object into parts to download by ranges divide_parts if @parts.empty? # Download each part(object range) @todo_parts = @parts.reject { |p| p[:done] } (1..@num_threads).map { Thread.new { loop { p = sync_get_todo_part break unless p download_part(p) } } }.map(&:join) # Combine the parts into the final file commit logger.info("Done download, file: #{@file}") end # Checkpoint structures: # @example # states = { # :id => 'download_id', # :file => 'file', # :object_meta => { # :etag => 'xxx', # :size => 1024 # }, # :parts => [ # {:number => 1, :range => [0, 100], :md5 => 'xxx', :done => false}, # {:number => 2, :range => [100, 200], :md5 => 'yyy', :done => true} # ], # :md5 => 'states_md5' # } def checkpoint logger.debug("Begin make checkpoint, disable_cpt: "\ "#{options[:disable_cpt] == true}") ensure_object_not_changed parts = sync_get_all_parts states = { :id => id, :file => @file, :object_meta => @object_meta, :parts => parts } # report progress if @progress done = parts.count { |p| p[:done] } @progress.call(done.to_f / parts.size) if done > 0 end write_checkpoint(states, @cpt_file) unless options[:disable_cpt] logger.debug("Done make checkpoint, states: #{states}") end private # Combine the downloaded parts into the final file # @todo avoid copy all part files def commit logger.info("Begin commit transaction, id: #{id}") parts = sync_get_all_parts # concat all part files into the target file File.open(@file, 'wb') do |w| parts.sort{ |x, y| x[:number] <=> y[:number] }.each do |p| File.open(get_part_file(p)) do |r| w.write(r.read(READ_SIZE)) until r.eof? end end end File.delete(@cpt_file) unless options[:disable_cpt] parts.each{ |p| File.delete(get_part_file(p)) } logger.info("Done commit transaction, id: #{id}") end # Rebuild the states of the transaction from checkpoint file def rebuild logger.info("Begin rebuild transaction, checkpoint: #{@cpt_file}") if options[:disable_cpt] || !File.exists?(@cpt_file) initiate else states = load_checkpoint(@cpt_file) states[:parts].select{ |p| p[:done] }.each do |p| part_file = get_part_file(p) unless File.exist?(part_file) fail PartMissingError, "The part file is missing: #{part_file}." end if p[:md5] != get_file_md5(part_file) fail PartInconsistentError, "The part file is changed: #{part_file}." end end @id = states[:id] @object_meta = states[:object_meta] @parts = states[:parts] end logger.info("Done rebuild transaction, states: #{states}") end def initiate logger.info("Begin initiate transaction") @id = generate_download_id obj = @protocol.get_object_meta(bucket, object) @object_meta = { :etag => obj.etag, :size => obj.size } checkpoint logger.info("Done initiate transaction, id: #{id}") end # Download a part def download_part(p) logger.debug("Begin download part: #{p}") part_file = get_part_file(p) File.open(part_file, 'wb') do |w| @protocol.get_object( bucket, object, @options.merge(range: p[:range])) { |chunk| w.write(chunk) } end sync_update_part(p.merge(done: true, md5: get_file_md5(part_file))) checkpoint logger.debug("Done download part: #{p}") end # Devide the object to download into parts to download def divide_parts logger.info("Begin divide parts, object: #{@object}") max_parts = 100 object_size = @object_meta[:size] part_size = [@options[:part_size] || PART_SIZE, object_size / max_parts].max num_parts = (object_size - 1) / part_size + 1 @parts = (1..num_parts).map do |i| { :number => i, :range => [(i - 1) * part_size, [i * part_size, object_size].min], :done => false } end checkpoint logger.info("Done divide parts, parts: #{@parts}") end def sync_get_todo_part @todo_mutex.synchronize { @todo_parts.shift } end def sync_update_part(p) @all_mutex.synchronize { @parts[p[:number] - 1] = p } end def sync_get_all_parts @all_mutex.synchronize { @parts.dup } end # Ensure file not changed during uploading def ensure_object_not_changed obj = @protocol.get_object_meta(bucket, object) unless obj.etag == @object_meta[:etag] fail ObjectInconsistentError, "The object to download is changed: #{object}." end end # Generate a download id def generate_download_id "download_#{bucket}_#{object}_#{Time.now.to_i}" end # Get name for part file def get_part_file(p) "#{@file}.part.#{p[:number]}" end end # Download end # Multipart end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/exception.rb000066400000000000000000000065601371637147000244310ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'nokogiri' module Aliyun module OSS ## # ServerError represents exceptions from the OSS # service. i.e. Client receives a HTTP response whose status is # NOT OK. #message provides the error message and #to_s gives # detailed information probably including the OSS request id. # class ServerError < Common::Exception attr_reader :http_code, :error_code, :message, :request_id def initialize(response) @http_code = response.code @attrs = {'RequestId' => get_request_id(response)} doc = Nokogiri::XML(response.body) do |config| config.options |= Nokogiri::XML::ParseOptions::NOBLANKS end rescue nil if doc and doc.root doc.root.children.each do |n| @attrs[n.name] = n.text end end @error_code = @attrs['Code'] @message = @attrs['Message'] @request_id = @attrs['RequestId'] end def message msg = @attrs['Message'] || "UnknownError[#{http_code}]." "#{msg} RequestId: #{request_id}" end def to_s @attrs.merge({'HTTPCode' => @http_code}).map do |k, v| [k, v].join(": ") end.join(", ") end private def get_request_id(response) r = response.headers[:x_oss_request_id] if response.headers r.to_s end end # ServerError class CallbackError < ServerError end # CallbackError ## # ClientError represents client exceptions caused mostly by # invalid parameters. # class ClientError < Common::Exception end # ClientError ## # CrcInconsistentError will be raised after a upload operation, # when the local crc is inconsistent with the response crc from server. # class CrcInconsistentError < Common::Exception; end ## # FileInconsistentError happens in a resumable upload transaction, # when the file to upload has changed during the uploading # process. Which means the transaction cannot go on. Or user may # have inconsistent data uploaded to OSS. # class FileInconsistentError < ClientError; end ## # ObjectInconsistentError happens in a resumable download transaction, # when the object to download has changed during the downloading # process. Which means the transaction cannot go on. Or user may # have inconsistent data downloaded to OSS. # class ObjectInconsistentError < ClientError; end ## # PartMissingError happens in a resumable download transaction, # when a downloaded part cannot be found as the client tries to # resume download. The process cannot go on until the part is # restored. # class PartMissingError < ClientError; end ## # PartMissingError happens in a resumable download transaction, # when a downloaded part has changed(MD5 mismatch) as the client # tries to resume download. The process cannot go on until the # part is restored. # class PartInconsistentError < ClientError; end ## # CheckpointBrokenError happens in a resumable upload/download # transaction, when the client finds the checkpoint file has # changed(MD5 mismatch) as it tries to resume upload/download. The # process cannot go on until the checkpoint file is restored. # class CheckpointBrokenError < ClientError; end end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/http.rb000066400000000000000000000237441371637147000234150ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'rest-client' require 'resolv' require 'fiber' module Aliyun module OSS ## # HTTP wraps the HTTP functionalities for accessing OSS RESTful # API. It handles the OSS-specific protocol elements, and # rest-client details for the user, which includes: # * automatically generate signature for every request # * parse response headers/body # * raise exceptions and capture the request id # * encapsulates streaming upload/download # @example simple get # headers, body = http.get({:bucket => 'bucket'}) # @example streaming download # http.get({:bucket => 'bucket', :object => 'object'}) do |chunk| # # handle chunk # end # @example streaming upload # def streaming_upload(&block) # http.put({:bucket => 'bucket', :object => 'object'}, # {:body => HTTP::StreamPlayload.new(block)}) # end # # streaming_upload do |stream| # stream << "hello world" # end class HTTP DEFAULT_CONTENT_TYPE = 'application/octet-stream' DEFAULT_ACCEPT_ENCODING = 'identity' STS_HEADER = 'x-oss-security-token' OPEN_TIMEOUT = 10 READ_TIMEOUT = 120 ## # A stream implementation # A stream is any class that responds to :read(bytes, outbuf) # class StreamWriter attr_reader :data_crc def initialize(crc_enable = false, init_crc = 0) @buffer = "" @producer = Fiber.new { yield self if block_given? } @producer.resume @data_crc = init_crc.to_i @crc_enable = crc_enable end def read(bytes = nil, outbuf = nil) ret = "" loop do if bytes fail if bytes < 0 piece = @buffer.slice!(0, bytes) if piece ret << piece bytes -= piece.size break if bytes == 0 end else ret << @buffer @buffer.clear end if @producer.alive? @producer.resume else break end end if outbuf # WARNING: Using outbuf = '' here DOES NOT work! outbuf.clear outbuf << ret end # Conform to IO#read(length[, outbuf]): # At end of file, it returns nil or "" depend on # length. ios.read() and ios.read(nil) returns # "". ios.read(positive-integer) returns nil. return nil if ret.empty? && !bytes.nil? && bytes > 0 @data_crc = Aliyun::OSS::Util.crc(ret, @data_crc) if @crc_enable ret end def write(chunk) @buffer << chunk.to_s.force_encoding(Encoding::ASCII_8BIT) Fiber.yield self end alias << write def closed? false end def close end def inspect "@buffer: " + @buffer[0, 32].inspect + "...#{@buffer.size} bytes" end end include Common::Logging def initialize(config) @config = config end def get_request_url(bucket, object) url = @config.endpoint.dup url.query = nil url.fragment = nil isIP = !!(url.host =~ Resolv::IPv4::Regex) url.host = "#{bucket}." + url.host if bucket && !@config.cname && !isIP url.path = '/' url.path << "#{bucket}/" if bucket && isIP url.path << CGI.escape(object) if object url.to_s end def get_resource_path(bucket, object) res = '/' res << "#{bucket}/" if bucket res << "#{object}" if object res end # Handle Net::HTTPRespoonse def handle_response(r, &block) # read all body on error if r.code.to_i >= 300 r.read_body else # streaming read body on success encoding = r['content-encoding'] if encoding == 'gzip' stream = StreamWriter.new { |s| r.read_body { |chunk| s << chunk } } reader = Zlib::GzipReader.new(stream) yield reader.read(16 * 1024) until reader.eof? elsif encoding == 'deflate' begin stream = Zlib::Inflate.new # 1.9.x doesn't support streaming inflate if RUBY_VERSION < '2.0.0' yield stream.inflate(r.read_body) else r.read_body { |chunk| stream << chunk } stream.finish { |chunk| yield chunk } end rescue Zlib::DataError # No luck with Zlib decompression. Let's try with raw deflate, # like some broken web servers do. stream = Zlib::Inflate.new(-Zlib::MAX_WBITS) # 1.9.x doesn't support streaming inflate if RUBY_VERSION < '2.0.0' yield stream.inflate(r.read_body) else r.read_body { |chunk| stream << chunk } stream.finish { |chunk| yield chunk } end end else r.read_body { |chunk| yield chunk } end end end ## # helper methods # def get(resources = {}, http_options = {}, &block) do_request('GET', resources, http_options, &block) end def put(resources = {}, http_options = {}, &block) do_request('PUT', resources, http_options, &block) end def post(resources = {}, http_options = {}, &block) do_request('POST', resources, http_options, &block) end def delete(resources = {}, http_options = {}, &block) do_request('DELETE', resources, http_options, &block) end def head(resources = {}, http_options = {}, &block) do_request('HEAD', resources, http_options, &block) end def options(resources = {}, http_options = {}, &block) do_request('OPTIONS', resources, http_options, &block) end private # Do HTTP reqeust # @param verb [String] HTTP Verb: GET/PUT/POST/DELETE/HEAD/OPTIONS # @param resources [Hash] OSS related resources # @option resources [String] :bucket the bucket name # @option resources [String] :object the object name # @option resources [Hash] :sub_res sub-resources # @param http_options [Hash] HTTP options # @option http_options [Hash] :headers HTTP headers # @option http_options [Hash] :query HTTP queries # @option http_options [Object] :body HTTP body, may be String # or Stream def do_request(verb, resources = {}, http_options = {}, &block) bucket = resources[:bucket] object = resources[:object] 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['accept-encoding'] ||= DEFAULT_ACCEPT_ENCODING headers[STS_HEADER] = @config.sts_token if @config.sts_token if body = http_options[:body] if body.respond_to?(:read) headers['transfer-encoding'] = 'chunked' else headers['content-md5'] = Util.get_content_md5(body) end end res = { :path => get_resource_path(bucket, object), :sub_res => sub_res, } 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}" end logger.debug("Send HTTP request, verb: #{verb}, resources: " \ "#{resources}, http options: #{http_options}") # From rest-client: # "Due to unfortunate choices in the original API, the params # used to populate the query string are actually taken out of # the headers hash." headers[:params] = (sub_res || {}).merge(http_options[:query] || {}) block_response = ->(r) { handle_response(r, &block) } if block request = RestClient::Request.new( :method => verb, :url => get_request_url(bucket, object), :headers => headers, :payload => http_options[:body], :block_response => block_response, :open_timeout => @config.open_timeout || OPEN_TIMEOUT, :read_timeout => @config.read_timeout || READ_TIMEOUT ) response = request.execute do |resp, &blk| if resp.code >= 300 e = ServerError.new(resp) logger.error(e.to_s) raise e else resp.return!(&blk) end end # If streaming read_body is used, we need to create the # RestClient::Response ourselves unless response.is_a?(RestClient::Response) if response.code.to_i >= 300 body = response.body if RestClient::version < '2.1.0' body = RestClient::Request.decode(response['content-encoding'], response.body) end response = RestClient::Response.create(body, response, request) e = ServerError.new(response) logger.error(e.to_s) raise e end response = RestClient::Response.create(nil, response, request) response.return! end logger.debug("Received HTTP response, code: #{response.code}, headers: " \ "#{response.headers}, body: #{response.body}") response end def get_user_agent "aliyun-sdk-ruby/#{VERSION} ruby-#{RUBY_VERSION}/#{RUBY_PLATFORM}" end end # HTTP end # OSS end # Aliyun # Monkey patch rest-client to exclude the 'Content-Length' header when # 'Transfer-Encoding' is set to 'chuncked'. This may be a problem for # some http servers like tengine. module RestClient module Payload class Base def headers ({'content-length' => size.to_s} if size) || {} end end end end aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/iterator.rb000066400000000000000000000044421371637147000242610ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module OSS ## # Iterator structs that wrap the multiple communications with the # server to provide an iterable result. # module Iterator ## # Iterator base that stores fetched results and fetch more if needed. # class Base def initialize(protocol, opts = {}) @protocol = protocol @results, @more = [], opts end def next loop do # Communicate with the server to get more results fetch_more if @results.empty? # Return the first result r = @results.shift break unless r yield r end end def to_enum self.enum_for(:next) end private def fetch_more return if @more[:truncated] == false fetch(@more) end end # Base ## # Buckets iterator # class Buckets < Base def fetch(more) @results, cont = @protocol.list_buckets(more) @more[:marker] = cont[:next_marker] @more[:truncated] = cont[:truncated] || false end end # Buckets ## # Objects iterator # class Objects < Base def initialize(protocol, bucket_name, opts = {}) super(protocol, opts) @bucket = bucket_name end def fetch(more) @results, cont = @protocol.list_objects(@bucket, more) @results = cont[:common_prefixes] + @results if cont[:common_prefixes] @more[:marker] = cont[:next_marker] @more[:truncated] = cont[:truncated] || false end end # Objects ## # Uploads iterator # class Uploads < Base def initialize(protocol, bucket_name, opts = {}) super(protocol, opts) @bucket = bucket_name end def fetch(more) @results, cont = @protocol.list_multipart_uploads(@bucket, more) @results = cont[:common_prefixes] + @results if cont[:common_prefixes] @more[:id_marker] = cont[:next_id_marker] @more[:key_marker] = cont[:next_key_marker] @more[:truncated] = cont[:truncated] || false end end # Objects end # Iterator end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/multipart.rb000066400000000000000000000031551371637147000244510ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'json' require 'digest/md5' module Aliyun module OSS ## # Multipart upload/download structures # module Multipart ## # A multipart transaction. Provide the basic checkpoint methods. # class Transaction < Common::Struct::Base attrs :id, :object, :bucket, :creation_time, :options def initialize(opts = {}) super(opts) @mutex = Mutex.new end private # Persist transaction states to file def write_checkpoint(states, file) md5= Util.get_content_md5(states.to_json) @mutex.synchronize { File.open(file, 'wb') { |f| f.write(states.merge(md5: md5).to_json) } } end # Load transaction states from file def load_checkpoint(file) states = {} @mutex.synchronize { states = JSON.load(File.read(file)) } states = Util.symbolize(states) md5 = states.delete(:md5) fail CheckpointBrokenError, "Missing MD5 in checkpoint." unless md5 unless md5 == Util.get_content_md5(states.to_json) fail CheckpointBrokenError, "Unmatched checkpoint MD5." end states end def get_file_md5(file) Digest::MD5.file(file).to_s end end # Transaction ## # A part in a multipart uploading transaction # class Part < Common::Struct::Base attrs :number, :etag, :size, :last_modified end # Part end # Multipart end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/object.rb000066400000000000000000000004131371637147000236700ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module OSS ## # Object表示OSS存储的一个对象 # class Object < Common::Struct::Base attrs :key, :type, :size, :etag, :metas, :last_modified, :headers end # Object end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/protocol.rb000066400000000000000000001665411371637147000243020ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'rest-client' require 'nokogiri' require 'time' module Aliyun module OSS ## # Protocol implement the OSS Open API which is low-level. User # should refer to {OSS::Client} for normal use. # class Protocol STREAM_CHUNK_SIZE = 16 * 1024 CALLBACK_HEADER = 'x-oss-callback' include Common::Logging def initialize(config) @config = config @http = HTTP.new(config) end # List all the buckets. # @param opts [Hash] options # @option opts [String] :prefix return only those buckets # prefixed with it if specified # @option opts [String] :marker return buckets after where it # indicates (exclusively). All buckets are sorted by name # alphabetically # @option opts [Integer] :limit return only the first N # buckets if specified # @return [Array, Hash] the returned buckets and a # hash including the next tokens, which includes: # * :prefix [String] the prefix used # * :delimiter [String] the delimiter used # * :marker [String] the marker used # * :limit [Integer] the limit used # * :next_marker [String] marker to continue list buckets # * :truncated [Boolean] whether there are more buckets to # be returned def list_buckets(opts = {}) logger.info("Begin list buckets, options: #{opts}") params = { 'prefix' => opts[:prefix], 'marker' => opts[:marker], 'max-keys' => opts[:limit] }.reject { |_, v| v.nil? } r = @http.get( {}, {:query => params}) doc = parse_xml(r.body) buckets = doc.css("Buckets Bucket").map do |node| Bucket.new( { :name => get_node_text(node, "Name"), :location => get_node_text(node, "Location"), :creation_time => get_node_text(node, "CreationDate") { |t| Time.parse(t) } }, self ) end more = { :prefix => 'Prefix', :limit => 'MaxKeys', :marker => 'Marker', :next_marker => 'NextMarker', :truncated => 'IsTruncated' }.reduce({}) { |h, (k, v)| value = get_node_text(doc.root, v) value.nil?? h : h.merge(k => value) } update_if_exists( more, { :limit => ->(x) { x.to_i }, :truncated => ->(x) { x.to_bool } } ) logger.info("Done list buckets, buckets: #{buckets}, more: #{more}") [buckets, more] end # Create a bucket # @param name [String] the bucket name # @param opts [Hash] options # @option opts [String] :location the region where the bucket # is located # @example # oss-cn-hangzhou def create_bucket(name, opts = {}) logger.info("Begin create bucket, name: #{name}, opts: #{opts}") location = opts[:location] body = nil if location builder = Nokogiri::XML::Builder.new do |xml| xml.CreateBucketConfiguration { xml.LocationConstraint location } end body = builder.to_xml end @http.put({:bucket => name}, {:body => body}) logger.info("Done create bucket") end # Put bucket acl # @param name [String] the bucket name # @param acl [String] the bucket acl # @see OSS::ACL def put_bucket_acl(name, acl) logger.info("Begin put bucket acl, name: #{name}, acl: #{acl}") sub_res = {'acl' => nil} headers = {'x-oss-acl' => acl} @http.put( {:bucket => name, :sub_res => sub_res}, {:headers => headers, :body => nil}) logger.info("Done put bucket acl") end # Get bucket acl # @param name [String] the bucket name # @return [String] the acl of this bucket def get_bucket_acl(name) logger.info("Begin get bucket acl, name: #{name}") sub_res = {'acl' => nil} r = @http.get({:bucket => name, :sub_res => sub_res}) doc = parse_xml(r.body) acl = get_node_text(doc.at_css("AccessControlList"), 'Grant') logger.info("Done get bucket acl") acl end # Put bucket logging settings # @param name [String] the bucket name # @param logging [BucketLogging] logging options def put_bucket_logging(name, logging) logger.info("Begin put bucket logging, "\ "name: #{name}, logging: #{logging}") if logging.enabled? && !logging.target_bucket fail ClientError, "Must specify target bucket when enabling bucket logging." end sub_res = {'logging' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.BucketLoggingStatus { if logging.enabled? xml.LoggingEnabled { xml.TargetBucket logging.target_bucket xml.TargetPrefix logging.target_prefix if logging.target_prefix } end } end.to_xml @http.put( {:bucket => name, :sub_res => sub_res}, {:body => body}) logger.info("Done put bucket logging") end # Get bucket logging settings # @param name [String] the bucket name # @return [BucketLogging] logging options of this bucket def get_bucket_logging(name) logger.info("Begin get bucket logging, name: #{name}") sub_res = {'logging' => nil} r = @http.get({:bucket => name, :sub_res => sub_res}) doc = parse_xml(r.body) opts = {:enable => false} logging_node = doc.at_css("LoggingEnabled") opts.update( :target_bucket => get_node_text(logging_node, 'TargetBucket'), :target_prefix => get_node_text(logging_node, 'TargetPrefix') ) opts[:enable] = true if opts[:target_bucket] logger.info("Done get bucket logging") BucketLogging.new(opts) end # Delete bucket logging settings, a.k.a. disable bucket logging # @param name [String] the bucket name def delete_bucket_logging(name) logger.info("Begin delete bucket logging, name: #{name}") sub_res = {'logging' => nil} @http.delete({:bucket => name, :sub_res => sub_res}) logger.info("Done delete bucket logging") end # Put bucket versioning settings # @param name [String] the bucket name # @param versioning [BucketVersioning] versioning options def put_bucket_versioning(name, versioning) logger.info("Begin put bucket versioning, "\ "name: #{name}, versioning: #{versioning}") sub_res = {'versioning' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.VersioningConfiguration { xml.Status versioning.status } end.to_xml @http.put( {:bucket => name, :sub_res => sub_res}, {:body => body}) logger.info("Done put bucket versioning") end # Get bucket versioning settings # @param name [String] the bucket name # @return [BucketVersioning] versioning options of this bucket def get_bucket_versioning(name) logger.info("Begin get bucket versioning, name: #{name}") sub_res = {'versioning' => nil} r = @http.get({:bucket => name, :sub_res => sub_res}) doc = parse_xml(r.body) versioning_node = doc.at_css("VersioningConfiguration") opts = { :status => get_node_text(versioning_node, 'Status') } logger.info("Done get bucket versioning") BucketVersioning.new(opts) end # Put bucket encryption settings # @param name [String] the bucket name # @param encryption [BucketEncryption] encryption options def put_bucket_encryption(name, encryption) logger.info("Begin put bucket encryption, "\ "name: #{name}, encryption: #{encryption}") sub_res = {'encryption' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.ServerSideEncryptionRule { xml.ApplyServerSideEncryptionByDefault { xml.SSEAlgorithm encryption.sse_algorithm xml.KMSMasterKeyID encryption.kms_master_key_id if encryption.kms_master_key_id } } end.to_xml @http.put( {:bucket => name, :sub_res => sub_res}, {:body => body}) logger.info("Done put bucket encryption") end # Get bucket encryption settings # @param name [String] the bucket name # @return [BucketEncryption] encryption options of this bucket def get_bucket_encryption(name) logger.info("Begin get bucket encryption, name: #{name}") sub_res = {'encryption' => nil} r = @http.get({:bucket => name, :sub_res => sub_res}) doc = parse_xml(r.body) encryption_node = doc.at_css("ApplyServerSideEncryptionByDefault") opts = { :sse_algorithm => get_node_text(encryption_node, 'SSEAlgorithm'), :kms_master_key_id => get_node_text(encryption_node, 'KMSMasterKeyID') } logger.info("Done get bucket encryption") BucketEncryption.new(opts) end # Delete bucket encryption settings, a.k.a. disable bucket encryption # @param name [String] the bucket name def delete_bucket_encryption(name) logger.info("Begin delete bucket encryption, name: #{name}") sub_res = {'encryption' => nil} @http.delete({:bucket => name, :sub_res => sub_res}) logger.info("Done delete bucket encryption") end # Put bucket website settings # @param name [String] the bucket name # @param website [BucketWebsite] the bucket website options def put_bucket_website(name, website) logger.info("Begin put bucket website, "\ "name: #{name}, website: #{website}") unless website.index fail ClientError, "Must specify index to put bucket website." end sub_res = {'website' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.WebsiteConfiguration { xml.IndexDocument { xml.Suffix website.index } if website.error xml.ErrorDocument { xml.Key website.error } end } end.to_xml @http.put( {:bucket => name, :sub_res => sub_res}, {:body => body}) logger.info("Done put bucket website") end # Get bucket website settings # @param name [String] the bucket name # @return [BucketWebsite] the bucket website options def get_bucket_website(name) logger.info("Begin get bucket website, name: #{name}") sub_res = {'website' => nil} r = @http.get({:bucket => name, :sub_res => sub_res}) opts = {:enable => true} doc = parse_xml(r.body) opts.update( :index => get_node_text(doc.at_css('IndexDocument'), 'Suffix'), :error => get_node_text(doc.at_css('ErrorDocument'), 'Key') ) logger.info("Done get bucket website") BucketWebsite.new(opts) end # Delete bucket website settings # @param name [String] the bucket name def delete_bucket_website(name) logger.info("Begin delete bucket website, name: #{name}") sub_res = {'website' => nil} @http.delete({:bucket => name, :sub_res => sub_res}) logger.info("Done delete bucket website") end # Put bucket referer # @param name [String] the bucket name # @param referer [BucketReferer] the bucket referer options def put_bucket_referer(name, referer) logger.info("Begin put bucket referer, "\ "name: #{name}, referer: #{referer}") sub_res = {'referer' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.RefererConfiguration { xml.AllowEmptyReferer referer.allow_empty? xml.RefererList { (referer.whitelist or []).each do |r| xml.Referer r end } } end.to_xml @http.put( {:bucket => name, :sub_res => sub_res}, {:body => body}) logger.info("Done put bucket referer") end # Get bucket referer # @param name [String] the bucket name # @return [BucketReferer] the bucket referer options def get_bucket_referer(name) logger.info("Begin get bucket referer, name: #{name}") sub_res = {'referer' => nil} r = @http.get({:bucket => name, :sub_res => sub_res}) doc = parse_xml(r.body) opts = { :allow_empty => get_node_text(doc.root, 'AllowEmptyReferer', &:to_bool), :whitelist => doc.css("RefererList Referer").map(&:text) } logger.info("Done get bucket referer") BucketReferer.new(opts) end # Put bucket lifecycle settings # @param name [String] the bucket name # @param rules [Array] the # lifecycle rules # @see OSS::LifeCycleRule def put_bucket_lifecycle(name, rules) logger.info("Begin put bucket lifecycle, name: #{name}, rules: "\ "#{rules.map { |r| r.to_s }}") sub_res = {'lifecycle' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.LifecycleConfiguration { rules.each do |r| xml.Rule { xml.ID r.id if r.id xml.Status r.enabled? ? 'Enabled' : 'Disabled' xml.Prefix r.prefix xml.Expiration { if r.expiry.is_a?(Date) xml.Date Time.utc( r.expiry.year, r.expiry.month, r.expiry.day) .iso8601.sub('Z', '.000Z') elsif r.expiry.is_a?(Integer) xml.Days r.expiry else fail ClientError, "Expiry must be a Date or Integer." end } } end } end.to_xml @http.put( {:bucket => name, :sub_res => sub_res}, {:body => body}) logger.info("Done put bucket lifecycle") end # Get bucket lifecycle settings # @param name [String] the bucket name # @return [Array] the # lifecycle rules. See {OSS::LifeCycleRule} def get_bucket_lifecycle(name) logger.info("Begin get bucket lifecycle, name: #{name}") sub_res = {'lifecycle' => nil} r = @http.get({:bucket => name, :sub_res => sub_res}) doc = parse_xml(r.body) rules = doc.css("Rule").map do |n| days = n.at_css("Expiration Days") date = n.at_css("Expiration Date") if (days && date) || (!days && !date) fail ClientError, "We can only have one of Date and Days for expiry." end LifeCycleRule.new( :id => get_node_text(n, 'ID'), :prefix => get_node_text(n, 'Prefix'), :enable => get_node_text(n, 'Status') { |x| x == 'Enabled' }, :expiry => days ? days.text.to_i : Date.parse(date.text) ) end logger.info("Done get bucket lifecycle") rules end # Delete *all* lifecycle rules on the bucket # @note this will delete all lifecycle rules # @param name [String] the bucket name def delete_bucket_lifecycle(name) logger.info("Begin delete bucket lifecycle, name: #{name}") sub_res = {'lifecycle' => nil} @http.delete({:bucket => name, :sub_res => sub_res}) logger.info("Done delete bucket lifecycle") end # Set bucket CORS(Cross-Origin Resource Sharing) rules # @param name [String] the bucket name # @param rules [Array nil} body = Nokogiri::XML::Builder.new do |xml| xml.CORSConfiguration { rules.each do |r| xml.CORSRule { r.allowed_origins.each { |x| xml.AllowedOrigin x } r.allowed_methods.each { |x| xml.AllowedMethod x } r.allowed_headers.each { |x| xml.AllowedHeader x } r.expose_headers.each { |x| xml.ExposeHeader x } xml.MaxAgeSeconds r.max_age_seconds if r.max_age_seconds } end } end.to_xml @http.put( {:bucket => name, :sub_res => sub_res}, {:body => body}) logger.info("Done delete bucket lifecycle") end # Get bucket CORS rules # @param name [String] the bucket name # @return [Array nil} r = @http.get({:bucket => name, :sub_res => sub_res}) doc = parse_xml(r.body) rules = [] doc.css("CORSRule").map do |n| allowed_origins = n.css("AllowedOrigin").map(&:text) allowed_methods = n.css("AllowedMethod").map(&:text) allowed_headers = n.css("AllowedHeader").map(&:text) expose_headers = n.css("ExposeHeader").map(&:text) max_age_seconds = get_node_text(n, 'MaxAgeSeconds', &:to_i) rules << CORSRule.new( :allowed_origins => allowed_origins, :allowed_methods => allowed_methods, :allowed_headers => allowed_headers, :expose_headers => expose_headers, :max_age_seconds => max_age_seconds) end logger.info("Done get bucket cors") rules end # Delete all bucket CORS rules # @note this will delete all CORS rules of this bucket # @param name [String] the bucket name def delete_bucket_cors(name) logger.info("Begin delete bucket cors, bucket: #{name}") sub_res = {'cors' => nil} @http.delete({:bucket => name, :sub_res => sub_res}) logger.info("Done delete bucket cors") end # Delete a bucket # @param name [String] the bucket name # @note it will fails if the bucket is not empty (it contains # objects) def delete_bucket(name) logger.info("Begin delete bucket: #{name}") @http.delete({:bucket => name}) logger.info("Done delete bucket") end # Put an object to the specified bucket, a block is required # to provide the object data. # @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 # if it fails to do so # @option opts [Hash] :metas key-value pairs # that serve as the object meta which will be stored together # 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 # @example # chunk = get_chunk # put_object('bucket', 'object') { |sw| sw.write(chunk) } 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['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 payload = HTTP::StreamWriter.new(@config.upload_crc_enable, opts[:init_crc], &block) r = @http.put( {:bucket => bucket_name, :object => object_name}, {:headers => headers, :body => payload}) if r.code == 203 e = CallbackError.new(r) logger.error(e.to_s) raise e end if @config.upload_crc_enable && !r.headers[:x_oss_hash_crc64ecma].nil? data_crc = payload.data_crc Aliyun::OSS::Util.crc_check(data_crc, r.headers[:x_oss_hash_crc64ecma], 'put') end logger.debug('Done put object') end # Append to an object of a bucket. Create an "Appendable # Object" if the object does not exist. A block is required to # provide the appending data. # @param bucket_name [String] the bucket name # @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] :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 # streamingly # @note # 1. Can not append to a "Normal Object" # 2. The position must equal to the object's size before append # 3. The :content_type is only used when the object is created def append_object(bucket_name, object_name, position, opts = {}, &block) logger.debug("Begin append object, bucket: #{bucket_name}, object: "\ "#{object_name}, position: #{position}, options: #{opts}") sub_res = {'append' => nil, 'position' => position} 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) payload = HTTP::StreamWriter.new( @config.upload_crc_enable && !opts[:init_crc].nil?, opts[:init_crc], &block) r = @http.post( {:bucket => bucket_name, :object => object_name, :sub_res => sub_res}, {:headers => headers, :body => payload}) if @config.upload_crc_enable && !r.headers[:x_oss_hash_crc64ecma].nil? && !opts[:init_crc].nil? data_crc = payload.data_crc Aliyun::OSS::Util.crc_check(data_crc, r.headers[:x_oss_hash_crc64ecma], 'append') end logger.debug('Done append object') wrap(r.headers[:x_oss_next_append_position], &:to_i) || -1 end # List objects in a bucket. # @param bucket_name [String] the bucket name # @param opts [Hash] options # @option opts [String] :prefix return only those buckets # prefixed with it if specified # @option opts [String] :marker return buckets after where it # indicates (exclusively). All buckets are sorted by name # alphabetically # @option opts [Integer] :limit return only the first N # buckets if specified # @option opts [String] :delimiter the delimiter to get common # prefixes of all objects # @option opts [String] :encoding the encoding of object key # in the response body. Only {OSS::KeyEncoding::URL} is # supported now. # @example # Assume we have the following objects: # /foo/bar/obj1 # /foo/bar/obj2 # ... # /foo/bar/obj9999999 # /foo/xxx/ # use 'foo/' as the prefix, '/' as the delimiter, the common # prefixes we get are: '/foo/bar/', '/foo/xxx/'. They are # coincidentally the sub-directories under '/foo/'. Using # delimiter we avoid list all the objects whose number may be # large. # @return [Array, Hash] the returned object and a # hash including the next tokens, which includes: # * :common_prefixes [String] the common prefixes returned # * :prefix [String] the prefix used # * :delimiter [String] the delimiter used # * :marker [String] the marker used # * :limit [Integer] the limit used # * :next_marker [String] marker to continue list objects # * :truncated [Boolean] whether there are more objects to # be returned def list_objects(bucket_name, opts = {}) logger.debug("Begin list object, bucket: #{bucket_name}, options: #{opts}") params = { 'prefix' => opts[:prefix], 'delimiter' => opts[:delimiter], 'marker' => opts[:marker], 'max-keys' => opts[:limit], 'encoding-type' => opts[:encoding] }.reject { |_, v| v.nil? } r = @http.get({:bucket => bucket_name}, {:query => params}) doc = parse_xml(r.body) encoding = get_node_text(doc.root, 'EncodingType') objects = doc.css("Contents").map do |node| Object.new( :key => get_node_text(node, "Key") { |x| decode_key(x, encoding) }, :type => get_node_text(node, "Type"), :size => get_node_text(node, "Size", &:to_i), :etag => get_node_text(node, "ETag"), :last_modified => get_node_text(node, "LastModified") { |x| Time.parse(x) } ) end || [] more = { :prefix => 'Prefix', :delimiter => 'Delimiter', :limit => 'MaxKeys', :marker => 'Marker', :next_marker => 'NextMarker', :truncated => 'IsTruncated', :encoding => 'EncodingType' }.reduce({}) { |h, (k, v)| value = get_node_text(doc.root, v) value.nil?? h : h.merge(k => value) } update_if_exists( more, { :limit => ->(x) { x.to_i }, :truncated => ->(x) { x.to_bool }, :delimiter => ->(x) { decode_key(x, encoding) }, :marker => ->(x) { decode_key(x, encoding) }, :next_marker => ->(x) { decode_key(x, encoding) } } ) common_prefixes = [] doc.css("CommonPrefixes Prefix").map do |node| common_prefixes << decode_key(node.text, encoding) end more[:common_prefixes] = common_prefixes unless common_prefixes.empty? logger.debug("Done list object. objects: #{objects}, more: #{more}") [objects, more] end # Get an object from the bucket. A block is required to handle # the object data chunks. # @note User can get the whole object or only part of it by specify # the bytes range; # @note User can specify conditions to get the object like: # if-modified-since, if-unmodified-since, if-match-etag, # if-unmatch-etag. If the object to get fails to meet the # conditions, it will not be returned; # @note User can indicate the server to rewrite the response headers # such as content-type, content-encoding when get the object # by specify the :rewrite options. The specified headers will # be returned instead of the original property of the object. # @param bucket_name [String] the bucket name # @param object_name [String] the object name # @param opts [Hash] options # @option opts [Array] :range bytes range to get from # the object, in the format: xx-yy # @option opts [Hash] :condition preconditions to get the object # * :if_modified_since (Time) get the object if its modified # time is later than specified # * :if_unmodified_since (Time) get the object if its # unmodified time if earlier than specified # * :if_match_etag (String) get the object if its etag match # specified # * :if_unmatch_etag (String) get the object if its etag # doesn't match specified # @option opts [Hash] :headers custom HTTP headers, case # insensitive. Headers specified here will overwrite `:condition` # and `:range` # @option opts [Hash] :rewrite response headers to rewrite # * :content_type (String) the Content-Type header # * :content_language (String) the Content-Language header # * :expires (Time) the Expires header # * :cache_control (String) the Cache-Control header # * :content_disposition (String) the Content-Disposition header # * :content_encoding (String) the Content-Encoding header # @return [OSS::Object] The object meta # @yield [String] it gives the data chunks of the object to # the block def get_object(bucket_name, object_name, opts = {}, &block) logger.debug("Begin get object, bucket: #{bucket_name}, "\ "object: #{object_name}") range = opts[:range] conditions = opts[:condition] rewrites = opts[:rewrite] headers = {} headers['range'] = get_bytes_range(range) if range headers.merge!(get_conditions(conditions)) if conditions headers.merge!(to_lower_case(opts[:headers])) if opts.key?(:headers) sub_res = {} if rewrites [ :content_type, :content_language, :cache_control, :content_disposition, :content_encoding ].each do |k| key = "response-#{k.to_s.sub('_', '-')}" sub_res[key] = rewrites[k] if rewrites.key?(k) end sub_res["response-expires"] = rewrites[:expires].httpdate if rewrites.key?(:expires) end data_crc = opts[:init_crc].nil? ? 0 : opts[:init_crc] r = @http.get( {:bucket => bucket_name, :object => object_name, :sub_res => sub_res}, {:headers => headers} ) do |chunk| if block_given? # crc enable and no range and oss server support crc data_crc = Aliyun::OSS::Util.crc(chunk, data_crc) if @config.download_crc_enable && range.nil? yield chunk end end if @config.download_crc_enable && range.nil? && !r.headers[:x_oss_hash_crc64ecma].nil? Aliyun::OSS::Util.crc_check(data_crc, r.headers[:x_oss_hash_crc64ecma], 'get') end h = r.headers metas = {} meta_prefix = 'x_oss_meta_' h.select { |k, _| k.to_s.start_with?(meta_prefix) } .each { |k, v| metas[k.to_s.sub(meta_prefix, '')] = v.to_s } obj = Object.new( :key => object_name, :type => h[:x_oss_object_type], :size => wrap(h[:content_length], &:to_i), :etag => h[:etag], :metas => metas, :last_modified => wrap(h[:last_modified]) { |x| Time.parse(x) }, :headers => h) logger.debug("Done get object") obj end # Get the object meta rather than the whole object. # @note User can specify conditions to get the object like: # if-modified-since, if-unmodified-since, if-match-etag, # if-unmatch-etag. If the object to get fails to meet the # conditions, it will not be returned. # # @param bucket_name [String] the bucket name # @param object_name [String] the object name # @param opts [Hash] options # @option opts [Hash] :condition preconditions to get the # object meta. The same as #get_object # @return [OSS::Object] The object meta def get_object_meta(bucket_name, object_name, opts = {}) logger.debug("Begin get object meta, bucket: #{bucket_name}, "\ "object: #{object_name}, options: #{opts}") headers = {} headers.merge!(get_conditions(opts[:condition])) if opts[:condition] r = @http.head( {:bucket => bucket_name, :object => object_name}, {:headers => headers}) h = r.headers metas = {} meta_prefix = 'x_oss_meta_' h.select { |k, _| k.to_s.start_with?(meta_prefix) } .each { |k, v| metas[k.to_s.sub(meta_prefix, '')] = v.to_s } obj = Object.new( :key => object_name, :type => h[:x_oss_object_type], :size => wrap(h[:content_length], &:to_i), :etag => h[:etag], :metas => metas, :last_modified => wrap(h[:last_modified]) { |x| Time.parse(x) }, :headers => h) logger.debug("Done get object meta") obj end # Copy an object in the bucket. The source object and the dest # object may be from different buckets of the same region. # @param bucket_name [String] the bucket name # @param src_object_name [String] the source object name # @param dst_object_name [String] the dest object name # @param opts [Hash] options # @option opts [String] :src_bucket specify the source object's # bucket. It MUST be in the same region as the dest bucket. It # defaults to dest bucket if not specified. # @option opts [String] :acl specify the dest object's # ACL. See {OSS::ACL} # @option opts [String] :meta_directive specify what to do # with the object's meta: copy or replace. See # {OSS::MetaDirective} # @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] :metas key-value pairs # that serve as the object meta which will be stored together # with the object # @option opts [Hash] :condition preconditions to get the # object. See #get_object # @return [Hash] the copy result # * :etag [String] the etag of the dest object # * :last_modified [Time] the last modification time of the # dest object def copy_object(bucket_name, src_object_name, dst_object_name, opts = {}) logger.debug("Begin copy object, bucket: #{bucket_name}, "\ "source object: #{src_object_name}, dest object: "\ "#{dst_object_name}, options: #{opts}") src_bucket = opts[:src_bucket] || bucket_name headers = { 'x-oss-copy-source' => @http.get_resource_path(src_bucket, src_object_name), 'content-type' => opts[:content_type] } (opts[:metas] || {}) .each { |k, v| headers["x-oss-meta-#{k.to_s}"] = v.to_s } { :acl => 'x-oss-object-acl', :meta_directive => 'x-oss-metadata-directive' }.each { |k, v| headers[v] = opts[k] if opts[k] } headers.merge!(get_copy_conditions(opts[:condition])) if opts[:condition] r = @http.put( {:bucket => bucket_name, :object => dst_object_name}, {:headers => headers}) doc = parse_xml(r.body) copy_result = { :last_modified => get_node_text( doc.root, 'LastModified') { |x| Time.parse(x) }, :etag => get_node_text(doc.root, 'ETag') }.reject { |_, v| v.nil? } logger.debug("Done copy object") copy_result end # Delete an object from the bucket # @param bucket_name [String] the bucket name # @param object_name [String] the object name def delete_object(bucket_name, object_name) logger.debug("Begin delete object, bucket: #{bucket_name}, "\ "object: #{object_name}") @http.delete({:bucket => bucket_name, :object => object_name}) logger.debug("Done delete object") end # Batch delete objects # @param bucket_name [String] the bucket name # @param object_names [Enumerator] the object names # @param opts [Hash] options # @option opts [Boolean] :quiet indicates whether the server # should return the delete result of the objects # @option opts [String] :encoding the encoding type for # object key in the response body, only # {OSS::KeyEncoding::URL} is supported now # @return [Array] object names that have been # successfully deleted or empty if :quiet is true def batch_delete_objects(bucket_name, object_names, opts = {}) logger.debug("Begin batch delete object, bucket: #{bucket_name}, "\ "objects: #{object_names}, options: #{opts}") sub_res = {'delete' => nil} # It may have invisible chars in object key which will corrupt # libxml. So we're constructing xml body manually here. body = '' body << '' body << '' << (opts[:quiet]? true : false).to_s << '' object_names.each { |k| body << '' << CGI.escapeHTML(k) << '' } body << '' query = {} query['encoding-type'] = opts[:encoding] if opts[:encoding] r = @http.post( {:bucket => bucket_name, :sub_res => sub_res}, {:query => query, :body => body}) deleted = [] unless opts[:quiet] doc = parse_xml(r.body) encoding = get_node_text(doc.root, 'EncodingType') doc.css("Deleted").map do |n| deleted << get_node_text(n, 'Key') { |x| decode_key(x, encoding) } end end logger.debug("Done delete object") deleted end # Put object acl # @param bucket_name [String] the bucket name # @param object_name [String] the object name # @param acl [String] the object's ACL. See {OSS::ACL} def put_object_acl(bucket_name, object_name, acl) logger.debug("Begin update object acl, bucket: #{bucket_name}, "\ "object: #{object_name}, acl: #{acl}") sub_res = {'acl' => nil} headers = {'x-oss-object-acl' => acl} @http.put( {:bucket => bucket_name, :object => object_name, :sub_res => sub_res}, {:headers => headers}) logger.debug("Done update object acl") end # Get object acl # @param bucket_name [String] the bucket name # @param object_name [String] the object name # [return] the object's acl. See {OSS::ACL} def get_object_acl(bucket_name, object_name) logger.debug("Begin get object acl, bucket: #{bucket_name}, "\ "object: #{object_name}") sub_res = {'acl' => nil} r = @http.get( {bucket: bucket_name, object: object_name, sub_res: sub_res}) doc = parse_xml(r.body) acl = get_node_text(doc.at_css("AccessControlList"), 'Grant') logger.debug("Done get object acl") acl end # Get object CORS rule # @note this is usually used by browser to make a "preflight" # @param bucket_name [String] the bucket name # @param object_name [String] the object name # @param origin [String] the Origin of the reqeust # @param method [String] the method to request access: # Access-Control-Request-Method # @param headers [Array] the headers to request access: # Access-Control-Request-Headers # @return [CORSRule] the CORS rule of the object def get_object_cors(bucket_name, object_name, origin, method, headers = []) logger.debug("Begin get object cors, bucket: #{bucket_name}, object: "\ "#{object_name}, origin: #{origin}, method: #{method}, "\ "headers: #{headers.join(',')}") h = { 'origin' => origin, 'access-control-request-method' => method, 'access-control-request-headers' => headers.join(',') } r = @http.options( {:bucket => bucket_name, :object => object_name}, {:headers => h}) logger.debug("Done get object cors") CORSRule.new( :allowed_origins => r.headers[:access_control_allow_origin], :allowed_methods => r.headers[:access_control_allow_methods], :allowed_headers => r.headers[:access_control_allow_headers], :expose_headers => r.headers[:access_control_expose_headers], :max_age_seconds => r.headers[:access_control_max_age] ) end ## # Multipart uploading # # Initiate a a multipart uploading transaction # @param bucket_name [String] the bucket name # @param object_name [String] the object name # @param opts [Hash] options # @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] :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]} 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}) doc = parse_xml(r.body) txn_id = get_node_text(doc.root, 'UploadId') logger.info("Done initiate multipart upload: #{txn_id}.") txn_id end # Upload a part in a multipart uploading transaction. # @param bucket_name [String] the bucket name # @param object_name [String] the object name # @param txn_id [String] the upload id # @param part_no [Integer] the part number # @yield [HTTP::StreamWriter] a stream writer is # yielded to the caller to which it can write chunks of data # streamingly def upload_part(bucket_name, object_name, txn_id, part_no, &block) logger.debug("Begin upload part, bucket: #{bucket_name}, object: "\ "#{object_name}, txn id: #{txn_id}, part No: #{part_no}") sub_res = {'partNumber' => part_no, 'uploadId' => txn_id} payload = HTTP::StreamWriter.new(@config.upload_crc_enable, &block) r = @http.put( {:bucket => bucket_name, :object => object_name, :sub_res => sub_res}, {:body => payload}) if @config.upload_crc_enable && !r.headers[:x_oss_hash_crc64ecma].nil? data_crc = payload.data_crc Aliyun::OSS::Util.crc_check(data_crc, r.headers[:x_oss_hash_crc64ecma], 'put') end logger.debug("Done upload part") Multipart::Part.new(:number => part_no, :etag => r.headers[:etag]) end # Upload a part in a multipart uploading transaction by copying # from an existent object as the part's content. It may copy # only part of the object by specifying the bytes range to read. # @param bucket_name [String] the bucket name # @param object_name [String] the object name # @param txn_id [String] the upload id # @param part_no [Integer] the part number # @param source_object [String] the source object name to copy from # @param opts [Hash] options # @option opts [String] :src_bucket specify the source object's # bucket. It MUST be in the same region as the dest bucket. It # defaults to dest bucket if not specified. # @option opts [Array] :range the bytes range to # copy, int the format: [begin(inclusive), end(exclusive)] # @option opts [Hash] :condition preconditions to copy the # object. See #get_object def upload_part_by_copy( bucket_name, object_name, txn_id, part_no, source_object, opts = {}) logger.debug("Begin upload part by copy, bucket: #{bucket_name}, "\ "object: #{object_name}, source object: #{source_object}"\ "txn id: #{txn_id}, part No: #{part_no}, options: #{opts}") range = opts[:range] conditions = opts[:condition] if range && (!range.is_a?(Array) || range.size != 2) fail ClientError, "Range must be an array containing 2 Integers." end src_bucket = opts[:src_bucket] || bucket_name headers = { 'x-oss-copy-source' => @http.get_resource_path(src_bucket, source_object) } headers['range'] = get_bytes_range(range) if range headers.merge!(get_copy_conditions(conditions)) if conditions sub_res = {'partNumber' => part_no, 'uploadId' => txn_id} r = @http.put( {:bucket => bucket_name, :object => object_name, :sub_res => sub_res}, {:headers => headers}) logger.debug("Done upload part by copy: #{source_object}.") Multipart::Part.new(:number => part_no, :etag => r.headers[:etag]) end # Complete a multipart uploading transaction # @param bucket_name [String] the bucket name # @param object_name [String] the object name # @param txn_id [String] the upload id # @param parts [Array] all the parts in this # transaction # @param callback [Callback] the HTTP callback performed by OSS # after this operation succeeds def complete_multipart_upload( bucket_name, object_name, txn_id, parts, callback = nil) logger.debug("Begin complete multipart upload, "\ "txn id: #{txn_id}, parts: #{parts.map(&:to_s)}") sub_res = {'uploadId' => txn_id} headers = {} headers[CALLBACK_HEADER] = callback.serialize if callback body = Nokogiri::XML::Builder.new do |xml| xml.CompleteMultipartUpload { parts.each do |p| xml.Part { xml.PartNumber p.number xml.ETag p.etag } end } end.to_xml r = @http.post( {:bucket => bucket_name, :object => object_name, :sub_res => sub_res}, {:headers => headers, :body => body}) if r.code == 203 e = CallbackError.new(r) logger.error(e.to_s) raise e end logger.debug("Done complete multipart upload: #{txn_id}.") end # Abort a multipart uploading transaction # @note All the parts are discarded after abort. For some parts # being uploaded while the abort happens, they may not be # discarded. Call abort_multipart_upload several times for this # situation. # @param bucket_name [String] the bucket name # @param object_name [String] the object name # @param txn_id [String] the upload id def abort_multipart_upload(bucket_name, object_name, txn_id) logger.debug("Begin abort multipart upload, txn id: #{txn_id}") sub_res = {'uploadId' => txn_id} @http.delete( {:bucket => bucket_name, :object => object_name, :sub_res => sub_res}) logger.debug("Done abort multipart: #{txn_id}.") end # Get a list of all the on-going multipart uploading # transactions. That is: thoses started and not aborted. # @param bucket_name [String] the bucket name # @param opts [Hash] options: # @option opts [String] :id_marker return only thoese # transactions with txn id after :id_marker # @option opts [String] :key_marker the object key marker for # a multipart upload transaction. # 1. if +:id_marker+ is not set, return only those # transactions with object key *after* +:key_marker+; # 2. if +:id_marker+ is set, return only thoese transactions # with object key *equals* +:key_marker+ and txn id after # +:id_marker+ # @option opts [String] :prefix the prefix of the object key # for a multipart upload transaction. if set only return # those transactions with the object key prefixed with it # @option opts [String] :encoding the encoding of object key # in the response body. Only {OSS::KeyEncoding::URL} is # supported now. # @return [Array, Hash] # the returned transactions and a hash including next tokens, # which includes: # * :prefix [String] the prefix used # * :limit [Integer] the limit used # * :id_marker [String] the upload id marker used # * :next_id_marker [String] upload id marker to continue list # multipart transactions # * :key_marker [String] the object key marker used # * :next_key_marker [String] object key marker to continue # list multipart transactions # * :truncated [Boolean] whether there are more transactions # to be returned # * :encoding [String] the object key encoding used def list_multipart_uploads(bucket_name, opts = {}) logger.debug("Begin list multipart uploads, "\ "bucket: #{bucket_name}, opts: #{opts}") sub_res = {'uploads' => nil} params = { 'prefix' => opts[:prefix], 'upload-id-marker' => opts[:id_marker], 'key-marker' => opts[:key_marker], 'max-uploads' => opts[:limit], 'encoding-type' => opts[:encoding] }.reject { |_, v| v.nil? } r = @http.get( {:bucket => bucket_name, :sub_res => sub_res}, {:query => params}) doc = parse_xml(r.body) encoding = get_node_text(doc.root, 'EncodingType') txns = doc.css("Upload").map do |node| Multipart::Transaction.new( :id => get_node_text(node, "UploadId"), :object => get_node_text(node, "Key") { |x| decode_key(x, encoding) }, :bucket => bucket_name, :creation_time => get_node_text(node, "Initiated") { |t| Time.parse(t) } ) end || [] more = { :prefix => 'Prefix', :limit => 'MaxUploads', :id_marker => 'UploadIdMarker', :next_id_marker => 'NextUploadIdMarker', :key_marker => 'KeyMarker', :next_key_marker => 'NextKeyMarker', :truncated => 'IsTruncated', :encoding => 'EncodingType' }.reduce({}) { |h, (k, v)| value = get_node_text(doc.root, v) value.nil?? h : h.merge(k => value) } update_if_exists( more, { :limit => ->(x) { x.to_i }, :truncated => ->(x) { x.to_bool }, :key_marker => ->(x) { decode_key(x, encoding) }, :next_key_marker => ->(x) { decode_key(x, encoding) } } ) logger.debug("Done list multipart transactions") [txns, more] end # Get a list of parts that are successfully uploaded in a # transaction. # @param txn_id [String] the upload id # @param opts [Hash] options: # @option opts [Integer] :marker the part number marker after # which to return parts # @option opts [Integer] :limit max number parts to return # @return [Array, Hash] the returned parts and # a hash including next tokens, which includes: # * :marker [Integer] the marker used # * :limit [Integer] the limit used # * :next_marker [Integer] marker to continue list parts # * :truncated [Boolean] whether there are more parts to be # returned def list_parts(bucket_name, object_name, txn_id, opts = {}) logger.debug("Begin list parts, bucket: #{bucket_name}, object: "\ "#{object_name}, txn id: #{txn_id}, options: #{opts}") sub_res = {'uploadId' => txn_id} params = { 'part-number-marker' => opts[:marker], 'max-parts' => opts[:limit], 'encoding-type' => opts[:encoding] }.reject { |_, v| v.nil? } r = @http.get( {:bucket => bucket_name, :object => object_name, :sub_res => sub_res}, {:query => params}) doc = parse_xml(r.body) parts = doc.css("Part").map do |node| Multipart::Part.new( :number => get_node_text(node, 'PartNumber', &:to_i), :etag => get_node_text(node, 'ETag'), :size => get_node_text(node, 'Size', &:to_i), :last_modified => get_node_text(node, 'LastModified') { |x| Time.parse(x) }) end || [] more = { :limit => 'MaxParts', :marker => 'PartNumberMarker', :next_marker => 'NextPartNumberMarker', :truncated => 'IsTruncated', :encoding => 'EncodingType' }.reduce({}) { |h, (k, v)| value = get_node_text(doc.root, v) value.nil?? h : h.merge(k => value) } update_if_exists( more, { :limit => ->(x) { x.to_i }, :truncated => ->(x) { x.to_bool } } ) logger.debug("Done list parts, parts: #{parts}, more: #{more}") [parts, more] end # Get bucket/object url # @param [String] bucket the bucket name # @param [String] object the bucket name # @return [String] url for the bucket/object def get_request_url(bucket, object = nil) @http.get_request_url(bucket, object) end # Get bucket/object resource path # @param [String] bucket the bucket name # @param [String] object the bucket name # @return [String] resource path for the bucket/object def get_resource_path(bucket, object = nil) @http.get_resource_path(bucket, object) end # Get user's access key id # @return [String] the access key id def get_access_key_id @config.access_key_id end # Get user's access key secret # @return [String] the access key secret def get_access_key_secret @config.access_key_secret end # Get user's STS token # @return [String] the STS token def get_sts_token @config.sts_token end # Sign a string using the stored access key secret # @param [String] string_to_sign the string to sign # @return [String] the signature def sign(string_to_sign) Util.sign(@config.access_key_secret, string_to_sign) end # Get the download crc status # @return true(download crc enable) or false(download crc disable) def download_crc_enable @config.download_crc_enable end # Get the upload crc status # @return true(upload crc enable) or false(upload crc disable) def upload_crc_enable @config.upload_crc_enable end private # Parse body content to xml document # @param content [String] the xml content # @return [Nokogiri::XML::Document] the parsed document def parse_xml(content) doc = Nokogiri::XML(content) do |config| config.options |= Nokogiri::XML::ParseOptions::NOBLANKS end doc end # Get the text of a xml node # @param node [Nokogiri::XML::Node] the xml node # @param tag [String] the node tag # @yield [String] the node text is given to the block def get_node_text(node, tag, &block) n = node.at_css(tag) if node value = n.text if n block && value ? yield(value) : value end # Decode object key using encoding. If encoding is nil it # returns the key directly. # @param key [String] the object key # @param encoding [String] the encoding used # @return [String] the decoded key def decode_key(key, encoding) return key unless encoding unless KeyEncoding.include?(encoding) fail ClientError, "Unsupported key encoding: #{encoding}" end if encoding == KeyEncoding::URL return CGI.unescape(key) end end # Transform x if x is not nil # @param x [Object] the object to transform # @yield [Object] the object if given to the block # @return [Object] the transformed object def wrap(x, &block) yield x if x end # Get conditions for HTTP headers # @param conditions [Hash] the conditions # @return [Hash] conditions for HTTP headers def get_conditions(conditions) { :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' }.reduce({}) { |h, (k, v)| conditions.key?(k)? h.merge(v => conditions[k]) : h } ) end # Get copy conditions for HTTP headers # @param conditions [Hash] the conditions # @return [Hash] copy conditions for HTTP headers def get_copy_conditions(conditions) { :if_modified_since => 'x-oss-copy-source-if-modified-since', :if_unmodified_since => 'x-oss-copy-source-if-unmodified-since', }.reduce({}) { |h, (k, v)| conditions.key?(k)? h.merge(v => conditions[k].httpdate) : h }.merge( { :if_match_etag => 'x-oss-copy-source-if-match', :if_unmatch_etag => 'x-oss-copy-source-if-none-match' }.reduce({}) { |h, (k, v)| conditions.key?(k)? h.merge(v => conditions[k]) : h } ) end # Get bytes range # @param range [Array] range # @return [String] bytes range for HTTP headers def get_bytes_range(range) if range && (!range.is_a?(Array) || range.size != 2 || !range.at(0).is_a?(Integer) || !range.at(1).is_a?(Integer)) fail ClientError, "Range must be an array containing 2 Integers." end "bytes=#{range.at(0)}-#{range.at(1) - 1}" end # Update values for keys that exist in hash # @param hash [Hash] the hash to be updated # @param kv [Hash] keys & blocks to updated 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 aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/struct.rb000066400000000000000000000174221371637147000237560ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'base64' require 'json' require 'uri' module Aliyun module OSS ## # Access Control List, it controls how the bucket/object can be # accessed. # * public-read-write: allow access(read&write) anonymously # * public-read: allow read anonymously # * private: access must be signatured # module ACL PUBLIC_READ_WRITE = "public-read-write" PUBLIC_READ = "public-read" PRIVATE = "private" end # ACL ## # A OSS object may carry some metas(String key-value pairs) with # it. MetaDirective specifies what to do with the metas in the # copy process. # * COPY: metas are copied from the source object to the dest # object # * REPLACE: source object's metas are NOT copied, use user # provided metas for the dest object # module MetaDirective COPY = "COPY" REPLACE = "REPLACE" end # MetaDirective ## # The object key may contains unicode charactors which cannot be # encoded in the request/response body(XML). KeyEncoding specifies # the encoding type for the object key. # * url: the object key is url-encoded # @note url-encoding is the only supported KeyEncoding type # module KeyEncoding URL = "url" @@all = [URL] def self.include?(enc) all.include?(enc) end def self.all @@all end end # KeyEncoding ## # Bucket Logging setting. See: {http://help.aliyun.com/document_detail/oss/product-documentation/function/logging.html OSS Bucket logging} # Attributes: # * enable [Boolean] whether to enable bucket logging # * target_bucket [String] the target bucket to store access logs # * target_prefix [String] the target object prefix to store access logs # @example Enable bucket logging # bucket.logging = BucketLogging.new( # :enable => true, :target_bucket => 'log_bucket', :target_prefix => 'my-log') # @example Disable bucket logging # bucket.logging = BucketLogging.new(:enable => false) class BucketLogging < Common::Struct::Base attrs :enable, :target_bucket, :target_prefix def enabled? enable == true end end ## # Bucket Versioning setting. See: {http://help.aliyun.com/document_detail/oss/product-documentation/function/versioning.html OSS Bucket versioning} # Attributes: # * status [String] the bucket versioning status, can be 'Enabled' and 'Suspended' # @example Enable bucket versioning # bucket.versioning = BucketVersioning.new(:status => 'Enabled') # @example Suspend bucket versioning # bucket.versioning = BucketVersioning.new(:status => 'Suspended') class BucketVersioning < Common::Struct::Base attrs :status end ## # Bucket Encryption setting. See: {http://help.aliyun.com/document_detail/oss/product-documentation/function/encryption.html OSS Bucket encryption} # Attributes: # * sse_algorithm [string] Indicates the default server-side encryption method # * kms_master_key_id [string] Indicates the ID of CMK that is currently used. class BucketEncryption < Common::Struct::Base attrs :enable, :sse_algorithm, :kms_master_key_id def enabled? enable == true end end ## # Bucket website setting. See: {http://help.aliyun.com/document_detail/oss/product-documentation/function/host-static-website.html OSS Website hosting} # Attributes: # * enable [Boolean] whether to enable website hosting for the bucket # * index [String] the index object as the index page for the website # * error [String] the error object as the error page for the website class BucketWebsite < Common::Struct::Base attrs :enable, :index, :error def enabled? enable == true end end ## # Bucket referer setting. See: {http://help.aliyun.com/document_detail/oss/product-documentation/function/referer-white-list.html OSS Website hosting} # Attributes: # * allow_empty [Boolean] whether to allow requests with empty "Referer" # * whitelist [Array] the allowed origins for requests class BucketReferer < Common::Struct::Base attrs :allow_empty, :whitelist def allow_empty? allow_empty == true end end ## # LifeCycle rule for bucket. See: {http://help.aliyun.com/document_detail/oss/product-documentation/function/lifecycle.html OSS Bucket LifeCycle} # Attributes: # * id [String] the unique id of a rule # * enabled [Boolean] whether to enable this rule # * prefix [String] the prefix objects to apply this rule # * expiry [Date] or [Integer] the expire time of objects # * if expiry is a Date, it specifies the absolute date to # expire objects # * if expiry is a Integer, it specifies the relative date to # expire objects: how many days after the object's last # modification time to expire the object # @example Specify expiry as Date # LifeCycleRule.new( # :id => 'rule1', # :enabled => true, # :prefix => 'foo/', # :expiry => Date.new(2016, 1, 1)) # @example Specify expiry as days # LifeCycleRule.new( # :id => 'rule1', # :enabled => true, # :prefix => 'foo/', # :expiry => 15) # @note the expiry date is treated as UTC time class LifeCycleRule < Common::Struct::Base attrs :id, :enable, :prefix, :expiry def enabled? enable == true end end # LifeCycleRule ## # CORS rule for bucket. See: {http://help.aliyun.com/document_detail/oss/product-documentation/function/referer-white-list.html OSS CORS} # Attributes: # * allowed_origins [Array] the allowed origins # * allowed_methods [Array] the allowed methods # * allowed_headers [Array] the allowed headers # * expose_headers [Array] the expose headers # * max_age_seconds [Integer] the max age seconds class CORSRule < Common::Struct::Base attrs :allowed_origins, :allowed_methods, :allowed_headers, :expose_headers, :max_age_seconds end # CORSRule ## # Callback represents a HTTP call made by OSS to user's # application server after an event happens, such as an object is # successfully uploaded to OSS. See: {https://help.aliyun.com/document_detail/oss/api-reference/object/Callback.html} # Attributes: # * url [String] the URL *WITHOUT* the query string # * query [Hash] the query to generate query string # * body [String] the body of the request # * content_type [String] the Content-Type of the request # * host [String] the Host in HTTP header for this request class Callback < Common::Struct::Base attrs :url, :query, :body, :content_type, :host include Common::Logging def serialize query_string = (query || {}).map { |k, v| [CGI.escape(k.to_s), CGI.escape(v.to_s)].join('=') }.join('&') cb = { 'callbackUrl' => "#{normalize_url(url)}?#{query_string}", 'callbackBody' => body, 'callbackBodyType' => content_type || default_content_type } cb['callbackHost'] = host if host logger.debug("Callback json: #{cb}") Base64.strict_encode64(cb.to_json) end private def normalize_url(url) uri = URI.parse(url) uri = URI.parse("http://#{url}") unless uri.scheme if uri.scheme != 'http' and uri.scheme != 'https' fail ClientError, "Only HTTP and HTTPS endpoint are accepted." end unless uri.query.nil? fail ClientError, "Query parameters should not appear in URL." end uri.to_s end def default_content_type "application/x-www-form-urlencoded" end end # Callback end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/upload.rb000066400000000000000000000152521371637147000237150ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module OSS module Multipart ## # A multipart upload transaction # class Upload < Transaction include Common::Logging PART_SIZE = 10 * 1024 * 1024 READ_SIZE = 16 * 1024 NUM_THREAD = 10 def initialize(protocol, opts) args = opts.dup @protocol = protocol @progress = args.delete(:progress) @file = args.delete(:file) @cpt_file = args.delete(:cpt_file) super(args) @file_meta = {} @num_threads = options[:threads] || NUM_THREAD @all_mutex = Mutex.new @parts = [] @todo_mutex = Mutex.new @todo_parts = [] end # Run the upload transaction, which includes 3 stages: # * 1a. initiate(new upload) and divide parts # * 1b. rebuild states(resumed upload) # * 2. upload each unfinished part # * 3. commit the multipart upload transaction def run logger.info("Begin upload, file: #{@file}, "\ "checkpoint file: #{@cpt_file}, "\ "threads: #{@num_threads}") # Rebuild transaction states from checkpoint file # Or initiate new transaction states rebuild # Divide the file to upload into parts to upload separately divide_parts if @parts.empty? # Upload each part @todo_parts = @parts.reject { |p| p[:done] } (1..@num_threads).map { Thread.new { loop { p = sync_get_todo_part break unless p upload_part(p) } } }.map(&:join) # Commit the multipart upload transaction commit logger.info("Done upload, file: #{@file}") end # Checkpoint structures: # @example # states = { # :id => 'upload_id', # :file => 'file', # :file_meta => { # :mtime => Time.now, # :md5 => 1024 # }, # :parts => [ # {:number => 1, :range => [0, 100], :done => false}, # {:number => 2, :range => [100, 200], :done => true} # ], # :md5 => 'states_md5' # } def checkpoint logger.debug("Begin make checkpoint, disable_cpt: "\ "#{options[:disable_cpt] == true}") ensure_file_not_changed parts = sync_get_all_parts states = { :id => id, :file => @file, :file_meta => @file_meta, :parts => parts } # report progress if @progress done = parts.count { |p| p[:done] } @progress.call(done.to_f / parts.size) if done > 0 end write_checkpoint(states, @cpt_file) unless options[:disable_cpt] logger.debug("Done make checkpoint, states: #{states}") end private # Commit the transaction when all parts are succefully uploaded # @todo handle undefined behaviors: commit succeeds in server # but return error in client def commit logger.info("Begin commit transaction, id: #{id}") parts = sync_get_all_parts.map{ |p| Part.new(:number => p[:number], :etag => p[:etag]) } @protocol.complete_multipart_upload( bucket, object, id, parts, @options[:callback]) File.delete(@cpt_file) unless options[:disable_cpt] logger.info("Done commit transaction, id: #{id}") end # Rebuild the states of the transaction from checkpoint file def rebuild logger.info("Begin rebuild transaction, checkpoint: #{@cpt_file}") if options[:disable_cpt] || !File.exists?(@cpt_file) initiate else states = load_checkpoint(@cpt_file) if states[:file_md5] != @file_meta[:md5] fail FileInconsistentError.new("The file to upload is changed.") end @id = states[:id] @file_meta = states[:file_meta] @parts = states[:parts] end logger.info("Done rebuild transaction, states: #{states}") end def initiate logger.info("Begin initiate transaction") @id = @protocol.initiate_multipart_upload(bucket, object, options) @file_meta = { :mtime => File.mtime(@file), :md5 => get_file_md5(@file) } checkpoint logger.info("Done initiate transaction, id: #{id}") end # Upload a part def upload_part(p) logger.debug("Begin upload part: #{p}") result = nil File.open(@file) do |f| range = p[:range] pos = range.first f.seek(pos) result = @protocol.upload_part(bucket, object, id, p[:number]) do |sw| while pos < range.at(1) bytes = [READ_SIZE, range.at(1) - pos].min sw << f.read(bytes) pos += bytes end end end sync_update_part(p.merge(done: true, etag: result.etag)) checkpoint logger.debug("Done upload part: #{p}") end # Devide the file into parts to upload def divide_parts logger.info("Begin divide parts, file: #{@file}") max_parts = 10000 file_size = File.size(@file) part_size = [@options[:part_size] || PART_SIZE, file_size / max_parts].max num_parts = (file_size - 1) / part_size + 1 @parts = (1..num_parts).map do |i| { :number => i, :range => [(i-1) * part_size, [i * part_size, file_size].min], :done => false } end checkpoint logger.info("Done divide parts, parts: #{@parts}") end def sync_get_todo_part @todo_mutex.synchronize { @todo_parts.shift } end def sync_update_part(p) @all_mutex.synchronize { @parts[p[:number] - 1] = p } end def sync_get_all_parts @all_mutex.synchronize { @parts.dup } end # Ensure file not changed during uploading def ensure_file_not_changed return if File.mtime(@file) == @file_meta[:mtime] if @file_meta[:md5] != get_file_md5(@file) fail FileInconsistentError, "The file to upload is changed." end end end # Upload end # Multipart end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/oss/util.rb000066400000000000000000000063651371637147000234130ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'time' require 'base64' require 'openssl' require 'digest/md5' module Aliyun module OSS ## # Util functions to help generate formatted Date, signatures, # etc. # module Util # Prefix for OSS specific HTTP headers HEADER_PREFIX = "x-oss-" class << self include Common::Logging # Calculate request signatures 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'] cano_headers = headers.select { |k, v| k.start_with?(HEADER_PREFIX) } .map { |k, v| [k.downcase.strip, v.strip] } .sort.map { |k, v| [k, v].join(":") + "\n" }.join cano_res = resources[:path] || "/" sub_res = (resources[:sub_res] || {}) .sort.map { |k, v| v ? [k, v].join("=") : k }.join("&") cano_res += "?#{sub_res}" unless sub_res.empty? string_to_sign = "#{verb}\n#{content_md5}\n#{content_type}\n#{date}\n" + "#{cano_headers}#{cano_res}" Util.sign(key, string_to_sign) end # Sign a string using HMAC and BASE64 # @param [String] key the secret key # @param [String] string_to_sign the string to sign # @return [String] the signature def sign(key, string_to_sign) logger.debug("String to sign: #{string_to_sign}") Base64.strict_encode64( OpenSSL::HMAC.digest('sha1', key, string_to_sign)) end # Calculate content md5 def get_content_md5(content) Base64.strict_encode64(OpenSSL::Digest::MD5.digest(content)) end # Symbolize keys in Hash, recursively def symbolize(v) if v.is_a?(Hash) result = {} v.each_key { |k| result[k.to_sym] = symbolize(v[k]) } result elsif v.is_a?(Array) result = [] v.each { |x| result << symbolize(x) } result else v end end # Get a crc value of the data def crc(data, init_crc = 0) CrcX::crc64(init_crc, data, data.size) end # Calculate a value of the crc1 combine with crc2. def crc_combine(crc1, crc2, len2) CrcX::crc64_combine(crc1, crc2, len2) end def crc_check(crc_a, crc_b, operation) if crc_a.nil? || crc_b.nil? || crc_a.to_i != crc_b.to_i logger.error("The crc of #{operation} between client and oss is not inconsistent. crc_a=#{crc_a} crc_b=#{crc_b}") fail CrcInconsistentError.new("The crc of #{operation} between client and oss is not inconsistent.") end end def ensure_bucket_name_valid(name) unless (name =~ %r|^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$|) fail ClientError, "The bucket name is invalid." end end end # self end # Util end # OSS end # Aliyun # Monkey patch to support #to_bool class String def to_bool return true if self =~ /^true$/i false end endaliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/sts.rb000066400000000000000000000003541371637147000224330ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require_relative 'common' require_relative 'sts/util' require_relative 'sts/exception' require_relative 'sts/struct' require_relative 'sts/config' require_relative 'sts/protocol' require_relative 'sts/client' aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/sts/000077500000000000000000000000001371637147000221045ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/sts/client.rb000066400000000000000000000021441371637147000237100ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module STS # STS服务的客户端,用于向STS申请临时token。 # @example 创建Client # client = Client.new( # :access_key_id => 'access_key_id', # :access_key_secret => 'access_key_secret') # token = client.assume_role('role:arn', 'app') # # policy = Policy.new # policy.allow(['oss:Get*'], ['acs:oss:*:*:my-bucket/*']) # token = client.assume_role('role:arn', 'app', policy, 60) # puts token.to_s class Client def initialize(opts) @config = Config.new(opts) @protocol = Protocol.new(@config) end # Assume a role # @param role [String] the role arn # @param session [String] the session name # @param policy [STS::Policy] the policy # @param duration [Integer] the duration seconds for the # requested token # @return [STS::Token] the sts token def assume_role(role, session, policy = nil, duration = 3600) @protocol.assume_role(role, session, policy, duration) end end # Client end # STS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/sts/config.rb000066400000000000000000000010461371637147000236770ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module STS # A place to store various configurations: credentials, api # timeout, retry mechanism, etc class Config < Common::Struct::Base attrs :access_key_id, :access_key_secret, :endpoint def initialize(opts = {}) super(opts) @access_key_id = @access_key_id.strip if @access_key_id @access_key_secret = @access_key_secret.strip if @access_key_secret @endpoint = @endpoint.strip if @endpoint end end # Config end # STS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/sts/exception.rb000066400000000000000000000025751371637147000244400ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'nokogiri' module Aliyun module STS # ServerError represents exceptions from the STS # service. i.e. Client receives a HTTP response whose status is # NOT OK. #message provides the error message and #to_s gives # detailed information probably including the STS request id. class ServerError < Common::Exception attr_reader :http_code, :error_code, :message, :request_id def initialize(response) @http_code = response.code @attrs = {} doc = Nokogiri::XML(response.body) do |config| config.options |= Nokogiri::XML::ParseOptions::NOBLANKS end rescue nil if doc and doc.root doc.root.children.each do |n| @attrs[n.name] = n.text end end @error_code = @attrs['Code'] @message = @attrs['Message'] @request_id = @attrs['RequestId'] end def message msg = @attrs['Message'] || "UnknownError[#{http_code}]." "#{msg} RequestId: #{request_id}" end def to_s @attrs.merge({'HTTPCode' => @http_code}).map do |k, v| [k, v].join(": ") end.join(", ") end end # ServerError # ClientError represents client exceptions caused mostly by # invalid parameters. class ClientError < Common::Exception end # ClientError end # STS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/sts/protocol.rb000066400000000000000000000076551371637147000243070ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'rest-client' require 'nokogiri' require 'time' module Aliyun module STS # Protocol implements the STS Open API which is low-level. User # should refer to {STS::Client} for normal use. class Protocol ENDPOINT = 'https://sts.aliyuncs.com' FORMAT = 'XML' API_VERSION = '2015-04-01' SIGNATURE_METHOD = 'HMAC-SHA1' SIGNATURE_VERSION = '1.0' include Common::Logging def initialize(config) @config = config end # Assume a role # @param role [String] the role arn # @param session [String] the session name # @param policy [STS::Policy] the policy # @param duration [Integer] the duration seconds for the # requested token # @return [STS::Token] the sts token def assume_role(role, session, policy = nil, duration = 3600) logger.info("Begin assume role, role: #{role}, session: #{session}, "\ "policy: #{policy}, duration: #{duration}") params = { 'Action' => 'AssumeRole', 'RoleArn' => role, 'RoleSessionName' => session, 'DurationSeconds' => duration.to_s } params.merge!({'Policy' => policy.serialize}) if policy body = do_request(params) doc = parse_xml(body) creds_node = doc.at_css("Credentials") creds = { session_name: session, access_key_id: get_node_text(creds_node, 'AccessKeyId'), access_key_secret: get_node_text(creds_node, 'AccessKeySecret'), security_token: get_node_text(creds_node, 'SecurityToken'), expiration: get_node_text( creds_node, 'Expiration') { |x| Time.parse(x) }, } logger.info("Done assume role, creds: #{creds}") Token.new(creds) end private # Generate a random signature nonce # @return [String] a random string def signature_nonce (rand * 1_000_000_000).to_s end # Do HTTP POST request with specified params # @param params [Hash] the parameters to STS # @return [String] the response body # @raise [ServerError] raise errors if the server responds with errors def do_request(params) query = params.merge( {'Format' => FORMAT, 'Version' => API_VERSION, 'AccessKeyId' => @config.access_key_id, 'SignatureMethod' => SIGNATURE_METHOD, 'SignatureVersion' => SIGNATURE_VERSION, 'SignatureNonce' => signature_nonce, 'Timestamp' => Time.now.utc.iso8601}) signature = Util.get_signature('POST', query, @config.access_key_secret) query.merge!({'Signature' => signature}) r = RestClient::Request.execute( :method => 'POST', :url => @config.endpoint || ENDPOINT, :payload => query ) do |response, &blk| if response.code >= 300 e = ServerError.new(response) logger.error(e.to_s) raise e else response.return!(&blk) end end logger.debug("Received HTTP response, code: #{r.code}, headers: "\ "#{r.headers}, body: #{r.body}") r.body end # Parse body content to xml document # @param content [String] the xml content # @return [Nokogiri::XML::Document] the parsed document def parse_xml(content) doc = Nokogiri::XML(content) do |config| config.options |= Nokogiri::XML::ParseOptions::NOBLANKS end doc end # Get the text of a xml node # @param node [Nokogiri::XML::Node] the xml node # @param tag [String] the node tag # @yield [String] the node text is given to the block def get_node_text(node, tag, &block) n = node.at_css(tag) if node value = n.text if n block && value ? yield(value) : value end end # Protocol end # STS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/sts/struct.rb000066400000000000000000000037051371637147000237620ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'json' require 'cgi' module Aliyun module STS # STS Policy. Referer to # https://help.aliyun.com/document_detail/ram/ram-user-guide/policy_reference/struct_def.html for details. class Policy < Common::Struct::Base VERSION = '1' attrs :rules # Add an 'Allow' rule # @param actions [Array] actions of the rule. e.g.: # oss:GetObject, oss:Get*, oss:* # @param resources [Array] resources of the rule. e.g.: # acs:oss:*:*:my-bucket, acs:oss:*:*:my-bucket/*, acs:oss:*:*:* def allow(actions, resources) add_rule(true, actions, resources) end # Add an 'Deny' rule # @param actions [Array] actions of the rule. e.g.: # oss:GetObject, oss:Get*, oss:* # @param resources [Array] resources of the rule. e.g.: # acs:oss:*:*:my-bucket, acs:oss:*:*:my-bucket/*, acs:oss:*:*:* def deny(actions, resources) add_rule(false, actions, resources) end # Serialize to rule to string def serialize {'Version' => VERSION, 'Statement' => @rules}.to_json end private def add_rule(allow, actions, resources) @rules ||= [] @rules << { 'Effect' => allow ? 'Allow' : 'Deny', 'Action' => actions, 'Resource' => resources } end end # STS token. User may use the credentials included to access # Alicloud resources(OSS, OTS, etc). # Attributes: # * access_key_id [String] the AccessKeyId # * access_key_secret [String] the AccessKeySecret # * security_token [String] the SecurityToken # * expiration [Time] the time when the token will be expired # * session_name [String] the session name for this token class Token < Common::Struct::Base attrs :access_key_id, :access_key_secret, :security_token, :expiration, :session_name end end # STS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/sts/util.rb000066400000000000000000000022371371637147000234120ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'time' require 'cgi' require 'base64' require 'openssl' require 'digest/md5' module Aliyun module STS ## # Util functions to help generate formatted Date, signatures, # etc. # module Util class << self include Common::Logging # Calculate request signatures def get_signature(verb, params, key) logger.debug("Sign, verb: #{verb}, params: #{params}") cano_query = params.sort.map { |k, v| [CGI.escape(k), CGI.escape(v)].join('=') }.join('&') string_to_sign = verb + '&' + CGI.escape('/') + '&' + CGI.escape(cano_query) logger.debug("String to sign: #{string_to_sign}") Util.sign(key + '&', string_to_sign) end # Sign a string using HMAC and BASE64 # @param [String] key the secret key # @param [String] string_to_sign the string to sign # @return [String] the signature def sign(key, string_to_sign) Base64.strict_encode64( OpenSSL::HMAC.digest('sha1', key, string_to_sign)) end end # self end # Util end # STS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/lib/aliyun/version.rb000066400000000000000000000001161371637147000233030ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun VERSION = "0.8.0" end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/rails/000077500000000000000000000000001371637147000203365ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/rails/Gemfile000066400000000000000000000000351371637147000216270ustar00rootroot00000000000000gem 'aliyun-sdk', '~> 0.1.0' aliyun-aliyun-oss-ruby-sdk-1d573e7/rails/aliyun_oss_callback_server.rb000066400000000000000000000033771371637147000262640ustar00rootroot00000000000000# coding: utf-8 require 'sinatra' require 'base64' require 'open-uri' require 'cgi' require 'openssl' require 'json' # 接受OSS上传回调的server示例,利用RSA公钥验证请求来自OSS,而非其 # 他恶意请求。具体签名/验证过程请参考: # https://help.aliyun.com/document_detail/oss/api-reference/object/Callback.html def get_header(name) key = "http_#{name.gsub('-', '_')}".upcase request.env[key] end PUB_KEY_URL_PREFIX = 'http://gosspublic.alicdn.com/' PUB_KEY_URL_PREFIX_S = 'https://gosspublic.alicdn.com/' # 1. Public key is cached so that we don't need fetching it for every # request # 2. Ensure pub_key_url is an authentic URL by asserting it starts # with the offical domain def get_public_key(pub_key_url, reload = false) unless pub_key_url.start_with?(PUB_KEY_URL_PREFIX) || pub_key_url.start_with?(PUB_KEY_URL_PREFIX_S) fail "Invalid public key URL: #{pub_key_url}" end if reload || @pub_key.nil? @pub_key = open(pub_key_url) { |f| f.read } end @pub_key end post '/*' do pub_key_url = Base64.decode64(get_header('x-oss-pub-key-url')) pub_key = get_public_key(pub_key_url) rsa = OpenSSL::PKey::RSA.new(pub_key) authorization = Base64.decode64(get_header('authorization')) req_body = request.body.read auth_str = if request.query_string.empty? CGI.unescape(request.path) + "\n" + req_body else CGI.unescape(request.path) + '?' + request.query_string + "\n" + req_body end valid = rsa.public_key.verify(OpenSSL::Digest::MD5.new, authorization, auth_str) if valid if request.content_type == 'application/www-form-urlencoded' body(URI.decode_www_form(req_body).to_h.to_json) else body(req_body) end else halt 400, "Authorization failed!" end end aliyun-aliyun-oss-ruby-sdk-1d573e7/rails/aliyun_oss_helper.rb000066400000000000000000000036021371637147000244100ustar00rootroot00000000000000# Put it under: RAILS-APP/app/helpers/ module AliyunOssHelper ## # Generate a form for upload file to OSS # @param [OSS::Bucket] bucket the bucket # @param [Hash] opts custom options # @option opts [String] :prefix the prefix of the result object # @option opts [String] :save_as the object key without prefix of # the result object # @option opts [ActiveSupport::Duration] :expiry the expiration time # of this form. Defaults to 60 seconds # @option opts [String] :redirect the page after successfully upload # the object # @example in controller # @bucket = OSS.client.get_bucket('my-bucket') # @options = {:prefix => '/foo/', :expiry => 60.seconds, # :redirect => 'http://my-domain.com'} # @example in views # <%= upload_form(@bucket, @options) do %> # # def upload_form(bucket, opts, &block) content = ActiveSupport::SafeBuffer.new content.safe_concat( form_tag_html( html_options_for_form(bucket.bucket_url, multipart: true))) key = if opts[:save_as] opts[:save_as] else "${filename}" end content << hidden_field_tag(:key, "#{opts[:prefix]}#{key}") content << hidden_field_tag(:OSSAccessKeyId, bucket.access_key_id) expiry = opts[:expiry] || 60.seconds policy = { 'expiration' => (Time.now + expiry).utc.iso8601.sub('Z', '.000Z'), 'conditions' => [{'bucket' => bucket.name}] } policy_string = Base64.strict_encode64(policy.to_json) content << hidden_field_tag(:policy, policy_string) content << hidden_field_tag(:Signature, bucket.sign(policy_string)) if opts[:redirect] content << hidden_field_tag(:success_action_redirect, opts[:redirect]) end content << capture(&block) content.safe_concat("") end end aliyun-aliyun-oss-ruby-sdk-1d573e7/rails/aliyun_oss_init.rb000066400000000000000000000013351371637147000240750ustar00rootroot00000000000000# Put it under: RAILS-APP/config/initializers/ require 'aliyun/oss' ## # Help initialize OSS Client. User can use it easily at any place in # your rails application. # @example # OSS.client.list_buckets # bucket = OSS.client.get_bucket('my-bucket') # bucket.list_objects module OSS def self.client unless @client Aliyun::Common::Logging.set_log_file('./log/oss_sdk.log') @client = Aliyun::OSS::Client.new( endpoint: Rails.application.secrets.aliyun_oss['endpoint'], access_key_id: Rails.application.secrets.aliyun_oss['access_key_id'], access_key_secret: Rails.application.secrets.aliyun_oss['access_key_secret'] ) end @client end end aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/000077500000000000000000000000001371637147000201565ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/000077500000000000000000000000001371637147000214575ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/000077500000000000000000000000001371637147000222635ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/bucket_spec.rb000066400000000000000000000635641371637147000251150ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module OSS describe "Bucket" do before :all do @endpoint = 'oss.aliyuncs.com' @protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy')) @bucket = 'rubysdk-bucket' end def request_path @bucket + "." + @endpoint end def mock_location(location) Nokogiri::XML::Builder.new do |xml| xml.CreateBucketConfiguration { xml.LocationConstraint location } end.to_xml end def mock_objects(objects, more = {}) Nokogiri::XML::Builder.new do |xml| xml.ListBucketResult { { :prefix => 'Prefix', :delimiter => 'Delimiter', :limit => 'MaxKeys', :marker => 'Marker', :next_marker => 'NextMarker', :truncated => 'IsTruncated', :encoding => 'EncodingType' }.map do |k, v| xml.send(v, more[k]) if more[k] end objects.each do |o| xml.Contents { xml.Key o xml.LastModified Time.now.to_s xml.Type 'Normal' xml.Size 1024 xml.StorageClass 'Standard' xml.Etag 'etag' xml.Owner { xml.ID '10086' xml.DisplayName 'CMCC' } } end (more[:common_prefixes] || []).each do |p| xml.CommonPrefixes { xml.Prefix p } end } end.to_xml end def mock_acl(acl) Nokogiri::XML::Builder.new do |xml| xml.AccessControlPolicy { xml.Owner { xml.ID 'owner_id' xml.DisplayName 'owner_name' } xml.AccessControlList { xml.Grant acl } } end.to_xml end def mock_logging(opts) Nokogiri::XML::Builder.new do |xml| xml.BucketLoggingStatus { if opts.enabled? xml.LoggingEnabled { xml.TargetBucket opts.target_bucket xml.TargetPrefix opts.target_prefix if opts.target_prefix } end } end.to_xml end def mock_versioning(opts) Nokogiri::XML::Builder.new do |xml| xml.VersioningConfiguration { xml.Status opts.status } end.to_xml end def mock_encryption(opts) Nokogiri::XML::Builder.new do |xml| xml.ServerSideEncryptionRule { xml.ApplyServerSideEncryptionByDefault { xml.SSEAlgorithm opts.sse_algorithm xml.KMSMasterKeyID opts.kms_master_key_id if opts.kms_master_key_id } } end.to_xml end def mock_website(opts) Nokogiri::XML::Builder.new do |xml| xml.WebsiteConfiguration { xml.IndexDocument { xml.Suffix opts.index } if opts.error xml.ErrorDocument { xml.Key opts.error } end } end.to_xml end def mock_referer(opts) Nokogiri::XML::Builder.new do |xml| xml.RefererConfiguration { xml.AllowEmptyReferer opts.allow_empty? xml.RefererList { opts.whitelist.each do |r| xml.Referer r end } } end.to_xml end def mock_lifecycle(rules) Nokogiri::XML::Builder.new do |xml| xml.LifecycleConfiguration { rules.each do |r| xml.Rule { xml.ID r.id if r.id xml.Status r.enabled? ? 'Enabled' : 'Disabled' xml.Prefix r.prefix xml.Expiration { if r.expiry.is_a?(Date) xml.Date Time.utc(r.expiry.year, r.expiry.month, r.expiry.day) .iso8601.sub('Z', '.000Z') else xml.Days r.expiry.to_i end } } end } end.to_xml end def mock_cors(rules) Nokogiri::XML::Builder.new do |xml| xml.CORSConfiguration { rules.each do |r| xml.CORSRule { r.allowed_origins.each do |x| xml.AllowedOrigin x end r.allowed_methods.each do |x| xml.AllowedMethod x end r.allowed_headers.each do |x| xml.AllowedHeader x end r.expose_headers.each do |x| xml.ExposeHeader x end xml.MaxAgeSeconds r.max_age_seconds if r.max_age_seconds } end } end.to_xml end def mock_error(code, message) Nokogiri::XML::Builder.new do |xml| xml.Error { xml.Code code xml.Message message xml.RequestId '0000' } end.to_xml end def err(msg, reqid = '0000') "#{msg} RequestId: #{reqid}" end context "Create bucket" do it "should PUT to create bucket" do stub_request(:put, request_path) @protocol.create_bucket(@bucket) expect(WebMock).to have_requested(:put, request_path) .with(:body => nil, :query => {}) end it "should set location when create bucket" do location = 'oss-cn-hangzhou' stub_request(:put, request_path).with(:body => mock_location(location)) @protocol.create_bucket(@bucket, :location => 'oss-cn-hangzhou') expect(WebMock).to have_requested(:put, request_path) .with(:body => mock_location(location), :query => {}) end end # create bucket context "List objects" do it "should list all objects" do stub_request(:get, request_path) @protocol.list_objects(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => {}) end it "should parse object response" do return_objects = ['hello', 'world', 'foo/bar'] stub_request(:get, request_path) .to_return(:body => mock_objects(return_objects)) objects, more = @protocol.list_objects(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => {}) expect(objects.map {|o| o.key}).to match_array(return_objects) expect(more).to be_empty end it "should list objects with prefix & delimiter" do # Webmock cannot capture the request_path encoded query parameters, # so we use 'foo-bar' instead of 'foo/bar' to work around # the problem opts = { :marker => 'foo-bar', :prefix => 'foo-', :delimiter => '-', :limit => 10, :encoding => KeyEncoding::URL} query = opts.clone query['max-keys'] = query.delete(:limit) query['encoding-type'] = query.delete(:encoding) stub_request(:get, request_path).with(:query => query) @protocol.list_objects(@bucket, opts) expect(WebMock).to have_requested(:get, request_path) .with(:body => "", :query => query) end it "should parse object and common prefixes response" do return_objects = ['hello', 'world', 'foo-bar'] return_more = { :marker => 'foo-bar', :prefix => 'foo-', :delimiter => '-', :limit => 10, :encoding => KeyEncoding::URL, :next_marker => 'foo-xxx', :truncated => true, :common_prefixes => ['foo/bar/', 'foo/xxx/'] } opts = { :marker => 'foo-bar', :prefix => 'foo-', :delimiter => '-', :limit => 10, :encoding => KeyEncoding::URL } query = opts.clone query['max-keys'] = query.delete(:limit) query['encoding-type'] = query.delete(:encoding) stub_request(:get, request_path).with(:query => query). to_return(:body => mock_objects(return_objects, return_more)) objects, more = @protocol.list_objects(@bucket, opts) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) expect(objects.map {|o| o.key}).to match_array(return_objects) expect(more).to eq(return_more) end it "should decode object key" do return_objects = ['中国のruby', 'world', 'foo/bar'] return_more = { :marker => '杭州のruby', :prefix => 'foo-', :delimiter => '分隔のruby', :limit => 10, :encoding => KeyEncoding::URL, :next_marker => '西湖のruby', :truncated => true, :common_prefixes => ['玉泉のruby', '苏堤のruby'] } es_objects = [CGI.escape('中国のruby'), 'world', 'foo/bar'] es_more = { :marker => CGI.escape('杭州のruby'), :prefix => 'foo-', :delimiter => CGI.escape('分隔のruby'), :limit => 10, :encoding => KeyEncoding::URL, :next_marker => CGI.escape('西湖のruby'), :truncated => true, :common_prefixes => [CGI.escape('玉泉のruby'), CGI.escape('苏堤のruby')] } stub_request(:get, request_path) .to_return(:body => mock_objects(es_objects, es_more)) objects, more = @protocol.list_objects(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => {}) expect(objects.map {|o| o.key}).to match_array(return_objects) expect(more).to eq(return_more) end end # list objects context "Delete bucket" do it "should send DELETE reqeust" do stub_request(:delete, request_path) @protocol.delete_bucket(@bucket) expect(WebMock).to have_requested(:delete, request_path) .with(:body => nil, :query => {}) end it "should raise Exception on error" do code = "NoSuchBucket" message = "The bucket to delete does not exist." stub_request(:delete, request_path).to_return( :status => 404, :body => mock_error(code, message)) expect { @protocol.delete_bucket(@bucket) }.to raise_error(ServerError, err(message)) end end # delete bucket context "acl, logging, versioning, encryption, website, referer, lifecycle" do it "should update acl" do query = {'acl' => nil} stub_request(:put, request_path).with(:query => query) @protocol.put_bucket_acl(@bucket, ACL::PUBLIC_READ) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => nil) end it "should get acl" do query = {'acl' => nil} return_acl = ACL::PUBLIC_READ stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_acl(return_acl)) acl = @protocol.get_bucket_acl(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:query => query, :body => nil) expect(acl).to eq(return_acl) end it "should enable logging" do query = {'logging' => nil} stub_request(:put, request_path).with(:query => query) logging_opts = BucketLogging.new( :enable => true, :target_bucket => 'target-bucket', :target_prefix => 'foo') @protocol.put_bucket_logging(@bucket, logging_opts) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => mock_logging(logging_opts)) end it "should enable logging without target prefix" do query = {'logging' => nil} stub_request(:put, request_path).with(:query => query) logging_opts = BucketLogging.new( :enable => true, :target_bucket => 'target-bucket') @protocol.put_bucket_logging(@bucket, logging_opts) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => mock_logging(logging_opts)) end it "should disable logging" do query = {'logging' => nil} stub_request(:put, request_path).with(:query => query) logging_opts = BucketLogging.new(:enable => false) @protocol.put_bucket_logging(@bucket, logging_opts) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => mock_logging(logging_opts)) end it "should get logging" do query = {'logging' => nil} logging_opts = BucketLogging.new( :enable => true, :target_bucket => 'target-bucket', :target_prefix => 'foo') stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_logging(logging_opts)) logging = @protocol.get_bucket_logging(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:query => query, :body => nil) expect(logging.to_s).to eq(logging_opts.to_s) end it "should get logging without target-bucket" do query = {'logging' => nil} logging_opts = BucketLogging.new( :enable => false, :target_bucket => nil, :target_prefix => nil) stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_logging(logging_opts)) logging = @protocol.get_bucket_logging(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:query => query, :body => nil) expect(logging.to_s).to eq(logging_opts.to_s) end it "should delete logging" do query = {'logging' => nil} stub_request(:delete, request_path).with(:query => query) @protocol.delete_bucket_logging(@bucket) expect(WebMock).to have_requested(:delete, request_path) .with(:query => query, :body => nil) end it "should raise Exception when enable logging" do query = {'logging' => nil} stub_request(:put, request_path).with(:query => query) logging_opts = BucketLogging.new( :enable => true, :target_bucket => nil, :target_prefix => nil) expect { @protocol.put_bucket_logging(@bucket, logging_opts) }.to raise_error(ClientError) end it "should update website" do query = {'website' => nil} stub_request(:put, request_path).with(:query => query) website_opts = BucketWebsite.new( :enable => true, :index => 'index.html', :error => 'error.html') @protocol.put_bucket_website(@bucket, website_opts) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => mock_website(website_opts)) end it "should update website with index only" do query = {'website' => nil} stub_request(:put, request_path).with(:query => query) website_opts = BucketWebsite.new( :enable => true, :index => 'index.html', :error => nil) @protocol.put_bucket_website(@bucket, website_opts) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => mock_website(website_opts)) end it "should get website" do query = {'website' => nil} website_opts = BucketWebsite.new( :enable => true, :index => 'index.html', :error => 'error.html') stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_website(website_opts)) opts = @protocol.get_bucket_website(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:query => query, :body => nil) expect(opts.to_s).to eq(website_opts.to_s) end it "should delete website" do query = {'website' => nil} stub_request(:delete, request_path).with(:query => query) @protocol.delete_bucket_website(@bucket) expect(WebMock).to have_requested(:delete, request_path) .with(:query => query, :body => nil) end it "should raise Exception when update website" do query = {'website' => nil} stub_request(:put, request_path).with(:query => query) website_opts = BucketWebsite.new( :enable => true, :index => nil) expect { @protocol.put_bucket_website(@bucket, website_opts) }.to raise_error(ClientError) end it "should update referer" do query = {'referer' => nil} stub_request(:put, request_path).with(:query => query) referer_opts = BucketReferer.new( :allow_empty => true, :whitelist => ['xxx', 'yyy']) @protocol.put_bucket_referer(@bucket, referer_opts) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => mock_referer(referer_opts)) end it "should get referer" do query = {'referer' => nil} referer_opts = BucketReferer.new( :allow_empty => true, :whitelist => ['xxx', 'yyy']) stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_referer(referer_opts)) opts = @protocol.get_bucket_referer(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:query => query, :body => nil) expect(opts.to_s).to eq(referer_opts.to_s) end it "should update lifecycle" do query = {'lifecycle' => nil} stub_request(:put, request_path).with(:query => query) rules = (1..5).map do |i| LifeCycleRule.new( :id => i, :enable => i % 2 == 0, :prefix => "foo#{i}", :expiry => (i % 2 == 1 ? Date.today : 10 + i)) end @protocol.put_bucket_lifecycle(@bucket, rules) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => mock_lifecycle(rules)) end it "should get lifecycle" do query = {'lifecycle' => nil} return_rules = (1..5).map do |i| LifeCycleRule.new( :id => i, :enable => i % 2 == 0, :prefix => "foo#{i}", :expiry => (i % 2 == 1 ? Date.today : 10 + i)) end stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_lifecycle(return_rules)) rules = @protocol.get_bucket_lifecycle(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:query => query, :body => nil) expect(rules.map(&:to_s)).to match_array(return_rules.map(&:to_s)) end it "should delete lifecycle" do query = {'lifecycle' => nil} stub_request(:delete, request_path).with(:query => query) @protocol.delete_bucket_lifecycle(@bucket) expect(WebMock).to have_requested(:delete, request_path) .with(:query => query, :body => nil) end it "should raise Exception when update lifecycle " do query = {'lifecycle' => nil} stub_request(:put, request_path).with(:query => query) rules = (1..5).map do |i| LifeCycleRule.new( :id => i, :enable => i % 2 == 0, :prefix => "foo#{i}", :expiry => 'invalid') end expect { @protocol.put_bucket_lifecycle(@bucket, rules) }.to raise_error(ClientError) end it "should raise Exception when get lifecycle " do query = {'lifecycle' => nil} body = ' delete after one day logs/ Enabled 1 2017-01-01T00:00:00.000Z ' stub_request(:get, request_path) .with(:query => query) .to_return(:body => body) expect { rules = @protocol.get_bucket_lifecycle(@bucket) }.to raise_error(ClientError) end it "should set cors" do query = {'cors' => nil} stub_request(:put, request_path).with(:query => query) rules = (1..5).map do |i| CORSRule.new( :allowed_origins => (1..3).map {|x| "origin-#{x}"}, :allowed_methods => ['PUT', 'GET'], :allowed_headers => (1..3).map {|x| "header-#{x}"}, :expose_headers => (1..3).map {|x| "header-#{x}"}) end @protocol.set_bucket_cors(@bucket, rules) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => mock_cors(rules)) end it "should set cors with MaxAgeSeconds " do query = {'cors' => nil} stub_request(:put, request_path).with(:query => query) rules = (1..5).map do |i| CORSRule.new( :allowed_origins => (1..3).map {|x| "origin-#{x}"}, :allowed_methods => ['PUT', 'GET'], :allowed_headers => (1..3).map {|x| "header-#{x}"}, :expose_headers => (1..3).map {|x| "header-#{x}"}, :max_age_seconds => 5) end @protocol.set_bucket_cors(@bucket, rules) expect(WebMock).to have_requested(:put, request_path) .with(:query => query, :body => mock_cors(rules)) end it "should get cors" do query = {'cors' => nil} return_rules = (1..5).map do |i| CORSRule.new( :allowed_origins => (1..3).map {|x| "origin-#{x}"}, :allowed_methods => ['PUT', 'GET'], :allowed_headers => (1..3).map {|x| "header-#{x}"}, :expose_headers => (1..3).map {|x| "header-#{x}"}) end stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_cors(return_rules)) rules = @protocol.get_bucket_cors(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:query => query, :body => nil) expect(rules.map(&:to_s)).to match_array(return_rules.map(&:to_s)) end it "should delete cors" do query = {'cors' => nil} stub_request(:delete, request_path).with(:query => query) @protocol.delete_bucket_cors(@bucket) expect(WebMock).to have_requested(:delete, request_path) .with(:query => query, :body => nil) end end # acl, logging, cors, etc context "crc" do it "should download crc enable equal config setting" do protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :download_crc_enable => 'true')) expect(protocol.download_crc_enable).to eq(true) protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :download_crc_enable => true)) expect(protocol.download_crc_enable).to eq(true) protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :download_crc_enable => 'false')) expect(protocol.download_crc_enable).to eq(false) protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :download_crc_enable => false)) expect(protocol.download_crc_enable).to eq(false) end it "should upload crc enable equal config setting" do protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => 'true')) expect(protocol.upload_crc_enable).to eq(true) protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => true)) expect(protocol.upload_crc_enable).to eq(true) protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => 'false')) expect(protocol.upload_crc_enable).to eq(false) protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => false)) expect(protocol.upload_crc_enable).to eq(false) end end # crc end # Bucket end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/client/000077500000000000000000000000001371637147000235415ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/client/bucket_spec.rb000066400000000000000000000772511371637147000263710ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module OSS describe Bucket do before :all do @endpoint = 'oss-cn-hangzhou.aliyuncs.com' @bucket_name = 'rubysdk-bucket' @bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy').get_bucket(@bucket_name) end def bucket_url "#{@bucket_name}.#{@endpoint}" end def object_url(object) "#{@bucket_name}.#{@endpoint}/#{object}" end def resource_path(object) "/#{@bucket_name}/#{object}" end def mock_objects(objects, more = {}) Nokogiri::XML::Builder.new do |xml| xml.ListBucketResult { { :prefix => 'Prefix', :delimiter => 'Delimiter', :limit => 'MaxKeys', :marker => 'Marker', :next_marker => 'NextMarker', :truncated => 'IsTruncated', :encoding => 'EncodingType' }.map do |k, v| xml.send(v, more[k]) if more[k] != nil end objects.each do |o| xml.Contents { xml.Key o.key xml.Size o.size xml.ETag o.etag } end (more[:common_prefixes] || []).each do |p| xml.CommonPrefixes { xml.Prefix p } end } end.to_xml end def mock_uploads(txns, more = {}) Nokogiri::XML::Builder.new do |xml| xml.ListMultipartUploadsResult { { :prefix => 'Prefix', :delimiter => 'Delimiter', :limit => 'MaxUploads', :key_marker => 'KeyMarker', :id_marker => 'UploadIdMarker', :next_key_marker => 'NextKeyMarker', :next_id_marker => 'NextUploadIdMarker', :truncated => 'IsTruncated', :encoding => 'EncodingType' }.map do |k, v| xml.send(v, more[k]) if more[k] != nil end txns.each do |t| xml.Upload { xml.Key t.object xml.UploadId t.id } end } end.to_xml end def mock_acl(acl) Nokogiri::XML::Builder.new do |xml| xml.AccessControlPolicy { xml.Owner { xml.ID 'owner_id' xml.DisplayName 'owner_name' } xml.AccessControlList { xml.Grant acl } } end.to_xml end def mock_error(code, message) Nokogiri::XML::Builder.new do |xml| xml.Error { xml.Code code xml.Message message xml.RequestId '0000' } end.to_xml end def err(msg, reqid = '0000') "#{msg} RequestId: #{reqid}" end context "bucket operations" do it "should get acl" do query = {'acl' => nil} return_acl = ACL::PUBLIC_READ stub_request(:get, bucket_url) .with(:query => query) .to_return(:body => mock_acl(return_acl)) acl = @bucket.acl expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query, :body => nil) expect(acl).to eq(return_acl) end it "should set acl" do query = {'acl' => nil} stub_request(:put, bucket_url).with(:query => query) @bucket.acl = ACL::PUBLIC_READ expect(WebMock).to have_requested(:put, bucket_url) .with(:query => query, :body => nil) end it "should delete logging setting" do query = {'logging' => nil} stub_request(:delete, bucket_url).with(:query => query) @bucket.logging = BucketLogging.new(:enable => false) expect(WebMock).to have_requested(:delete, bucket_url) .with(:query => query, :body => nil) end it "should set logging setting" do query = {'logging' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.BucketLoggingStatus { xml.LoggingEnabled { xml.TargetBucket 'target-bucket' } } end.to_xml stub_request(:put, bucket_url).with(:query => query) @bucket.logging = BucketLogging.new(:enable => true, :target_bucket => 'target-bucket') expect(WebMock).to have_requested(:put, bucket_url) .with(:query => query, :body => body) end it "should get logging setting" do query = {'logging' => nil} retbody = Nokogiri::XML::Builder.new do |xml| xml.BucketLoggingStatus { xml.LoggingEnabled { xml.TargetBucket 'target-bucket' xml.TargetPrefix 'target-prefix' } } end.to_xml stub_request(:get, bucket_url) .with(:query => query) .to_return(status: 200, :body => retbody) logging = @bucket.logging expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query, :body => nil) expect(logging.enable).to eq(true) expect(logging.target_bucket).to eq('target-bucket') end it "should set webseit" do query = {'website' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.WebsiteConfiguration { xml.IndexDocument { xml.Suffix 'index.html' } } end.to_xml stub_request(:put, bucket_url) .with(:query => query) .to_return(status: 200, :body => nil) @bucket.website = BucketWebsite.new(:enable => true, :index => 'index.html') expect(WebMock).to have_requested(:put, bucket_url) .with(:query => query, :body => body) end it "should delete webseit" do query = {'website' => nil} stub_request(:delete, bucket_url) .with(:query => query) .to_return(status: 204, :body => nil) @bucket.website = BucketWebsite.new(:enable => false) expect(WebMock).to have_requested(:delete, bucket_url) .with(:query => query, :body => nil) end it "should get webseit" do query = {'website' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.WebsiteConfiguration { xml.IndexDocument { xml.Suffix 'index.html' } xml.ErrorDocument { xml.Key 'error.html' } } end.to_xml stub_request(:get, bucket_url) .with(:query => query) .to_return(status: 200, :body => body) website = @bucket.website expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query, :body => nil) expect(website.enable).to eq(true) expect(website.index).to eq('index.html') expect(website.error).to eq('error.html') end it "should set referer" do query = {'referer' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.RefererConfiguration { xml.AllowEmptyReferer 'true' xml.RefererList { xml.Referer 'http://www.aliyun.com' } } end.to_xml stub_request(:put, bucket_url) .with(:query => query) .to_return(status: 200, :body => nil) @bucket.referer = BucketReferer.new(:allow_empty => true, :whitelist => ['http://www.aliyun.com']) expect(WebMock).to have_requested(:put, bucket_url) .with(:query => query, :body => body) end it "should get referer" do query = {'referer' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.RefererConfiguration { xml.AllowEmptyReferer 'true' xml.RefererList { xml.Referer 'http://www.aliyun.com' } } end.to_xml stub_request(:get, bucket_url) .with(:query => query) .to_return(status: 200, :body => body) referer = @bucket.referer expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query, :body => nil) expect(referer.allow_empty).to eq(true) expect(referer.whitelist).to eq(['http://www.aliyun.com']) end it "should set cors" do query = {'cors' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.CORSConfiguration { xml.CORSRule { xml.AllowedOrigin '*' xml.AllowedMethod 'PUT' xml.AllowedHeader 'Authorization' } } end.to_xml stub_request(:put, bucket_url) .with(:query => query) .to_return(status: 200, :body => nil) rules = [ CORSRule.new( :allowed_origins => ['*'], :allowed_methods => ['PUT'], :allowed_headers => ['Authorization'], :expose_headers =>[]) ] @bucket.cors = rules expect(WebMock).to have_requested(:put, bucket_url) .with(:query => query, :body => body) end it "should delete cors" do query = {'cors' => nil} stub_request(:delete, bucket_url) .with(:query => query) .to_return(status: 204, :body => nil) @bucket.cors = [] expect(WebMock).to have_requested(:delete, bucket_url) .with(:query => query, :body => nil) end it "should get cors" do query = {'cors' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.CORSConfiguration { xml.CORSRule { xml.AllowedOrigin '*' xml.AllowedMethod 'PUT' xml.AllowedHeader 'Authorization' } } end.to_xml stub_request(:get, bucket_url) .with(:query => query) .to_return(status: 200, :body => body) cors = @bucket.cors expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query, :body => nil) end it "should set versioning" do query = {'versioning' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.VersioningConfiguration { xml.Status 'Enabled' } end.to_xml stub_request(:put, bucket_url) .with(:query => query) .to_return(status: 200, :body => nil) @bucket.versioning = BucketVersioning.new(:status => 'Enabled') expect(WebMock).to have_requested(:put, bucket_url) .with(:query => query, :body => body) end it "should get versioning" do query = {'versioning' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.VersioningConfiguration { xml.Status 'Enabled' } end.to_xml stub_request(:get, bucket_url) .with(:query => query) .to_return(status: 200, :body => body) ret = @bucket.versioning expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query, :body => nil) expect(ret.status).to eq('Enabled') end it "should set encryption with aes256" do query = {'encryption' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.ServerSideEncryptionRule { xml.ApplyServerSideEncryptionByDefault { xml.SSEAlgorithm 'AES256' } } end.to_xml stub_request(:put, bucket_url) .with(:query => query) .to_return(status: 200, :body => nil) @bucket.encryption = BucketEncryption.new(:enable => true, :sse_algorithm => 'AES256') expect(WebMock).to have_requested(:put, bucket_url) .with(:query => query, :body => body) end it "should set encryption with KMS" do query = {'encryption' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.ServerSideEncryptionRule { xml.ApplyServerSideEncryptionByDefault { xml.SSEAlgorithm 'KMS' xml.KMSMasterKeyID 'kms-id' } } end.to_xml stub_request(:put, bucket_url) .with(:query => query) .to_return(status: 200, :body => nil) @bucket.encryption = BucketEncryption.new( :enable => true, :sse_algorithm => 'KMS', :kms_master_key_id => 'kms-id') expect(WebMock).to have_requested(:put, bucket_url) .with(:query => query, :body => body) end it "should get encryption" do query = {'encryption' => nil} body = Nokogiri::XML::Builder.new do |xml| xml.ServerSideEncryptionRule { xml.ApplyServerSideEncryptionByDefault { xml.SSEAlgorithm 'KMS' xml.KMSMasterKeyID 'kms-id' } } end.to_xml stub_request(:get, bucket_url) .with(:query => query) .to_return(status: 200, :body => body) ret = @bucket.encryption expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query, :body => nil) expect(ret.sse_algorithm).to eq('KMS') expect(ret.kms_master_key_id).to eq('kms-id') end it "should delete encryption" do query = {'encryption' => nil} stub_request(:delete, bucket_url) .with(:query => query) .to_return(status: 204, :body => nil) @bucket.encryption = BucketEncryption.new(:enable => false) expect(WebMock).to have_requested(:delete, bucket_url) .with(:query => query, :body => nil) end it "should get bucket url" do expect(@bucket.bucket_url) .to eq('http://rubysdk-bucket.oss-cn-hangzhou.aliyuncs.com/') end it "should get access key id" do expect(@bucket.access_key_id).to eq('xxx') end end # bucket operations context "object operations" do it "should list objects" do query_1 = { :prefix => 'list-', :delimiter => '-', 'encoding-type' => 'url' } return_obj_1 = (1..5).map{ |i| Object.new( :key => "obj-#{i}", :size => 1024 * i, :etag => "etag-#{i}")} return_more_1 = { :next_marker => 'foo', :truncated => true, :common_prefixes => ['hello', 'world'] } query_2 = { :prefix => 'list-', :delimiter => '-', :marker => 'foo', 'encoding-type' => 'url' } return_obj_2 = (6..8).map{ |i| Object.new( :key => "obj-#{i}", :size => 1024 * i, :etag => "etag-#{i}")} return_more_2 = { :next_marker => 'bar', :truncated => false, :common_prefixes => ['rock', 'suck'] } stub_request(:get, bucket_url) .with(:query => query_1) .to_return(:body => mock_objects(return_obj_1, return_more_1)) stub_request(:get, bucket_url) .with(:query => query_2) .to_return(:body => mock_objects(return_obj_2, return_more_2)) list = @bucket.list_objects :prefix => 'list-', :delimiter => '-' all = list.to_a expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query_1).times(1) expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query_2).times(1) objs = all.select{ |x| x.is_a?(Object) } common_prefixes = all.select{ |x| x.is_a?(String) } all_objs = (1..8).map{ |i| Object.new( :key => "obj-#{i}", :size => 1024 * i, :etag => "etag-#{i}")} expect(objs.map{ |o| o.to_s }).to match_array(all_objs.map{ |o| o.to_s }) all_prefixes = ['hello', 'world', 'rock', 'suck'] expect(common_prefixes).to match_array(all_prefixes) end it "should put object from file" do key = 'ruby' stub_request(:put, object_url(key)) content = (1..10).map{ |i| i.to_s.rjust(9, '0') }.join("\n") File.open('/tmp/x', 'w'){ |f| f.write(content) } @bucket.put_object(key, :file => '/tmp/x') expect(WebMock).to have_requested(:put, object_url(key)) .with(:body => content, :query => {}) end it "should put object with acl" do key = 'ruby' stub_request(:put, object_url(key)) @bucket.put_object(key, :acl => ACL::PUBLIC_READ) expect(WebMock) .to have_requested(:put, object_url(key)) .with(:headers => {'X-Oss-Object-Acl' => ACL::PUBLIC_READ}) end it "should put object with callback" do key = 'ruby' stub_request(:put, object_url(key)) callback = Callback.new( url: 'http://app.server.com/callback', query: {'id' => 1, 'name' => '杭州'}, body: 'hello world', host: 'server.com' ) @bucket.put_object(key, callback: callback) expect(WebMock).to have_requested(:put, object_url(key)) .with { |req| req.headers.key?('X-Oss-Callback') } end it "should raise CallbackError when callback failed" do key = 'ruby' code = 'CallbackFailed' message = 'Error status: 502.' stub_request(:put, object_url(key)) .to_return(:status => 203, :body => mock_error(code, message)) callback = Callback.new( url: 'http://app.server.com/callback', query: {'id' => 1, 'name' => '杭州'}, body: 'hello world', host: 'server.com' ) expect { @bucket.put_object(key, callback: callback) }.to raise_error(CallbackError, err(message)) expect(WebMock).to have_requested(:put, object_url(key)) .with { |req| req.headers.key?('X-Oss-Callback') } end it "should set custom headers when put object" do key = 'ruby' stub_request(:put, object_url(key)) @bucket.put_object( key, headers: {'cache-control' => 'xxx', 'expires' => 'yyy'}) headers = {} expect(WebMock).to have_requested(:put, object_url(key)) .with { |req| headers = req.headers } expect(headers['Cache-Control']).to eq('xxx') expect(headers['Expires']).to eq('yyy') end it "should set custom headers when append object" do key = 'ruby' query = {'append' => nil, 'position' => 11} stub_request(:post, object_url(key)).with(:query => query) @bucket.append_object( key, 11, headers: {'CACHE-CONTROL' => 'nocache', 'EXPIRES' => 'seripxe'}) headers = {} expect(WebMock).to have_requested(:post, object_url(key)) .with(:query => query) .with { |req| headers = req.headers } expect(headers['Cache-Control']).to eq('nocache') expect(headers['Expires']).to eq('seripxe') end it "should get object to file" do key = 'ruby' # 100 KB content = (1..1024).map{ |i| i.to_s.rjust(99, '0') }.join(",") stub_request(:get, object_url(key)).to_return(:body => content) @bucket.get_object(key, :file => '/tmp/x') expect(WebMock).to have_requested(:get, object_url(key)) .with(:body => nil, :query => {}) expect(File.read('/tmp/x')).to eq(content) end it "should only get meta when get object without :file or block" do key = 'ruby' last_modified = Time.now.rfc822 return_headers = { 'x-oss-object-type' => 'Normal', 'ETag' => 'xxxyyyzzz', 'Content-Length' => 1024, 'Last-Modified' => last_modified, 'x-oss-meta-year' => '2015', 'x-oss-meta-people' => 'mary' } stub_request(:head, object_url(key)) .to_return(:headers => return_headers) obj = @bucket.get_object(key) expect(WebMock).to have_requested(:head, object_url(key)) .with(:body => nil, :query => {}) expect(obj.key).to eq(key) expect(obj.type).to eq('Normal') expect(obj.etag).to eq('xxxyyyzzz') expect(obj.size).to eq(1024) expect(obj.last_modified.rfc822).to eq(last_modified) expect(obj.metas).to eq({'year' => '2015', 'people' => 'mary'}) end it "should append object from file" do key = 'ruby' query = {'append' => nil, 'position' => 11} stub_request(:post, object_url(key)).with(:query => query) content = (1..10).map{ |i| i.to_s.rjust(9, '0') }.join("\n") content = "" + content + "" File.open('/tmp/x.html', 'w'){ |f| f.write(content) } @bucket.append_object(key, 11, :file => '/tmp/x.html') expect(WebMock).to have_requested(:post, object_url(key)) .with(:query => query, :body => content, :headers => {'Content-Type' => 'text/html'}) end it "should append object with acl" do key = 'ruby' query = {'append' => nil, 'position' => 11} stub_request(:post, object_url(key)).with(:query => query) @bucket.append_object(key, 11, :acl => ACL::PUBLIC_READ_WRITE) expect(WebMock) .to have_requested(:post, object_url(key)) .with(:query => query, :headers => {'X-Oss-Object-Acl' => ACL::PUBLIC_READ_WRITE}) end it "should answer object exists?" do key = 'ruby' stub_request(:head, object_url(key)) .to_return(:status => 404).times(3) expect { @bucket.get_object(key) }.to raise_error(ServerError, err("UnknownError[404].", '')) expect(@bucket.object_exists?(key)).to be false expect(@bucket.object_exist?(key)).to be false last_modified = Time.now.rfc822 return_headers = { 'x-oss-object-type' => 'Normal', 'ETag' => 'xxxyyyzzz', 'Content-Length' => 1024, 'Last-Modified' => last_modified, 'x-oss-meta-year' => '2015', 'x-oss-meta-people' => 'mary' } stub_request(:head, object_url(key)) .to_return(:headers => return_headers).times(2) expect(@bucket.object_exists?(key)).to be true expect(@bucket.object_exist?(key)).to be true stub_request(:head, object_url(key)) .to_return(:status => 500) expect { @bucket.object_exists?(key) }.to raise_error(ServerError, err("UnknownError[500].", '')) end it "should update object metas" do key = 'ruby' stub_request(:put, object_url(key)) @bucket.update_object_metas( key, {'people' => 'mary', 'year' => '2016'}) expect(WebMock).to have_requested(:put, object_url(key)) .with(:body => nil, :headers => { 'x-oss-copy-source' => resource_path(key), 'x-oss-metadata-directive' => 'REPLACE', 'x-oss-meta-year' => '2016', 'x-oss-meta-people' => 'mary'}) end it "should get object url" do url = @bucket.object_url('yeah', false) object_url = 'http://rubysdk-bucket.oss-cn-hangzhou.aliyuncs.com/yeah' expect(url).to eq(object_url) url = @bucket.object_url('yeah') path = url[0, url.index('?')] expect(path).to eq(object_url) query = {} url[url.index('?') + 1, url.size].split('&') .each { |s| k, v = s.split('='); query[k] = v } expect(query.key?('Expires')).to be true expect(query['OSSAccessKeyId']).to eq('xxx') expires = query['Expires'] signature = CGI.unescape(query['Signature']) string_to_sign = "GET\n" + "\n\n" + "#{expires}\n" + "/rubysdk-bucket/yeah" sig = Util.sign('yyy', string_to_sign) expect(signature).to eq(sig) end context 'should use STS' do it "get object url" do sts_bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :sts_token => 'zzz').get_bucket(@bucket_name) object_url = 'http://rubysdk-bucket.oss-cn-hangzhou.aliyuncs.com/yeah' url = sts_bucket.object_url('yeah') path = url[0, url.index('?')] expect(path).to eq(object_url) query = {} url[url.index('?') + 1, url.size].split('&') .each { |s| k, v = s.split('='); query[k] = v } expect(query.key?('Expires')).to be true expect(query.key?('Signature')).to be true expect(query['OSSAccessKeyId']).to eq('xxx') expect(query['security-token']).to eq('zzz') end it "get object url with query string" do sts_bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :sts_token => 'zzz').get_bucket(@bucket_name) param = { 'x-oss-process' => 'image/resize,m_fill,h_100,w_100', } url = sts_bucket.object_url('ico.png', true, 60, param) path = url[0, url.index('?')] expect(path).to eq('http://rubysdk-bucket.oss-cn-hangzhou.aliyuncs.com/ico.png') query = {} url[url.index('?') + 1, url.size].split('&') .each { |s| k, v = s.split('='); query[k] = v } expect(query.key?('Expires')).to be true expect(query.key?('Signature')).to be true expect(query['OSSAccessKeyId']).to eq('xxx') expect(query['security-token']).to eq('zzz') expect(query['x-oss-process']).to eq('image%2Fresize%2Cm_fill%2Ch_100%2Cw_100') end end end # object operations context "multipart operations" do it "should list uploads" do query_1 = { :prefix => 'list-', 'encoding-type' => 'url', 'uploads' => nil } return_up_1 = (1..5).map{ |i| Multipart::Transaction.new( :id => "txn-#{i}", :object => "my-object", :bucket => @bucket_name )} return_more_1 = { :next_id_marker => "txn-5", :truncated => true } query_2 = { :prefix => 'list-', 'upload-id-marker' => 'txn-5', 'encoding-type' => 'url', 'uploads' => nil } return_up_2 = (6..8).map{ |i| Multipart::Transaction.new( :id => "txn-#{i}", :object => "my-object", :bucket => @bucket_name )} return_more_2 = { :next_id_marker => 'txn-8', :truncated => false, } stub_request(:get, bucket_url) .with(:query => query_1) .to_return(:body => mock_uploads(return_up_1, return_more_1)) stub_request(:get, bucket_url) .with(:query => query_2) .to_return(:body => mock_uploads(return_up_2, return_more_2)) txns = @bucket.list_uploads(prefix: 'list-').to_a expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query_1).times(1) expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query_2).times(1) all_txns = (1..8).map{ |i| Multipart::Transaction.new( :id => "txn-#{i}", :object => "my-object", :bucket => @bucket_name )} expect(txns.map(&:to_s)).to match_array(all_txns.map(&:to_s)) end end # multipart operations context "crc" do it "should download crc enable equal config setting" do bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :download_crc_enable => 'true').get_bucket(@bucket_name) expect(bucket.download_crc_enable).to eq(true) bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :download_crc_enable => true).get_bucket(@bucket_name) expect(bucket.download_crc_enable).to eq(true) bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :download_crc_enable => 'false').get_bucket(@bucket_name) expect(bucket.download_crc_enable).to eq(false) bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :download_crc_enable => false).get_bucket(@bucket_name) expect(bucket.download_crc_enable).to eq(false) # check default value bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy').get_bucket(@bucket_name) expect(bucket.download_crc_enable).to eq(false) end it "should upload crc enable equal config setting" do bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => 'true').get_bucket(@bucket_name) expect(bucket.upload_crc_enable).to eq(true) bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => true).get_bucket(@bucket_name) expect(bucket.upload_crc_enable).to eq(true) bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => 'false').get_bucket(@bucket_name) expect(bucket.upload_crc_enable).to eq(false) bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => false).get_bucket(@bucket_name) expect(bucket.upload_crc_enable).to eq(false) # check default value bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy').get_bucket(@bucket_name) expect(bucket.upload_crc_enable).to eq(true) end end # crc end # Bucket end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/client/client_spec.rb000066400000000000000000000242341371637147000263630ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module OSS describe Client do context "construct" do it "should setup endpoint and a/k" do endpoint = 'oss-cn-hangzhou.aliyuncs.com' client = Client.new( :endpoint => endpoint, :access_key_id => 'xxx ', :access_key_secret => ' yyy ', :sts_token => 'sts-token') config = client.instance_variable_get('@config') expect(config.endpoint.to_s).to eq("http://#{endpoint}") expect(config.access_key_id).to eq('xxx') expect(config.access_key_secret).to eq('yyy') expect(config.sts_token).to eq('sts-token') end it "should work with CNAME endpoint" do endpoint = 'rockuw.com' bucket = 'rubysdk-bucket' object = 'rubysdk-object' client = Client.new( access_key_id: 'xxx', access_key_secret: 'yyy', endpoint: endpoint, cname: true) # TODO: ignore queries here # bucket operations stub_request(:get, endpoint) .with(:query => {'encoding-type' => 'url'}) client.get_bucket(bucket).list_objects.take(1) expect(WebMock) .to have_requested(:get, endpoint) .with(:query => {'encoding-type' => 'url'}) # object operations stub_request(:get, "#{endpoint}/#{object}") client.get_bucket(bucket).get_object(object) {} expect(WebMock).to have_requested(:get, "#{endpoint}/#{object}") end it "should work with IP endpoint" do endpoint = 'http://127.0.0.1:3000' bucket = 'rubysdk-bucket' object = 'rubysdk-object' client = Client.new( access_key_id: 'xxx', access_key_secret: 'yyy', endpoint: endpoint) # TODO: ignore queries here # bucket operations stub_request(:get, "#{endpoint}/#{bucket}/") .with(:query => {'encoding-type' => 'url'}) client.get_bucket(bucket).list_objects.take(1) expect(WebMock) .to have_requested(:get, "#{endpoint}/#{bucket}/") .with(:query => {'encoding-type' => 'url'}) # object operations stub_request(:get, "#{endpoint}/#{bucket}/#{object}") client.get_bucket(bucket).get_object(object) {} expect(WebMock).to have_requested(:get, "#{endpoint}/#{bucket}/#{object}") end it "should not set Authorization with anonymous client" do endpoint = 'oss-cn-hangzhou.aliyuncs.com' bucket = 'rubysdk-bucket' object = 'rubysdk-object' client = Client.new(:endpoint => endpoint) stub_request(:get, "#{bucket}.#{endpoint}/#{object}") client.get_bucket(bucket).get_object(object) {} expect(WebMock) .to have_requested(:get, "#{bucket}.#{endpoint}/#{object}") .with{ |req| not req.headers.has_key?('Authorization') } end it "should set STS header" do endpoint = 'oss-cn-hangzhou.aliyuncs.com' bucket = 'rubysdk-bucket' object = 'rubysdk-object' client = Client.new( :endpoint => endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :sts_token => 'sts-token') stub_request(:get, "#{bucket}.#{endpoint}/#{object}") client.get_bucket(bucket).get_object(object) {} expect(WebMock) .to have_requested(:get, "#{bucket}.#{endpoint}/#{object}") .with{ |req| req.headers.key?('X-Oss-Security-Token') } end it "should construct different client" do bucket = 'rubysdk-bucket' object = 'rubysdk-object' ep1 = 'oss-cn-hangzhou.aliyuncs.com' c1 = Client.new( :endpoint => ep1, :access_key_id => 'xxx', :access_key_secret => 'yyy') ep2 = 'oss-cn-beijing.aliyuncs.com' c2 = Client.new( :endpoint => ep2, :access_key_id => 'aaa', :access_key_secret => 'bbb') stub_request(:get, "#{bucket}.#{ep1}/#{object}") stub_request(:put, "#{bucket}.#{ep2}/#{object}") c1.get_bucket(bucket).get_object(object) {} c2.get_bucket(bucket).put_object(object) expect(WebMock).to have_requested(:get, "#{bucket}.#{ep1}/#{object}") expect(WebMock).to have_requested(:put, "#{bucket}.#{ep2}/#{object}") end it "should fail with invalid bucket name" do bucket = 'INVALID' ep1 = 'oss-cn-hangzhou.aliyuncs.com' client = Client.new( :endpoint => ep1, :access_key_id => 'xxx', :access_key_secret => 'yyy') expect { client.create_bucket(bucket) }.to raise_error(ClientError, "The bucket name is invalid.") expect { client.delete_bucket(bucket) }.to raise_error(ClientError, "The bucket name is invalid.") expect { client.bucket_exists?(bucket) }.to raise_error(ClientError, "The bucket name is invalid.") expect { client.get_bucket(bucket) }.to raise_error(ClientError, "The bucket name is invalid.") end end # construct def mock_buckets(buckets, more = {}) Nokogiri::XML::Builder.new do |xml| xml.ListAllMyBucketsResult { xml.Owner { xml.ID 'owner_id' xml.DisplayName 'owner_name' } xml.Buckets { buckets.each do |b| xml.Bucket { xml.Location b.location xml.Name b.name xml.CreationDate b.creation_time.to_s } end } unless more.empty? xml.Prefix more[:prefix] xml.Marker more[:marker] xml.MaxKeys more[:limit].to_s xml.NextMarker more[:next_marker] xml.IsTruncated more[:truncated] end } end.to_xml end def mock_location(location) Nokogiri::XML::Builder.new do |xml| xml.CreateBucketConfiguration { xml.LocationConstraint location } end.to_xml end def mock_acl(acl) Nokogiri::XML::Builder.new do |xml| xml.AccessControlPolicy { xml.Owner { xml.ID 'owner_id' xml.DisplayName 'owner_name' } xml.AccessControlList { xml.Grant acl } } end.to_xml end context "bucket operations" do before :all do @endpoint = 'oss.aliyuncs.com' @client = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy') @bucket = 'rubysdk-bucket' end def bucket_url @bucket + "." + @endpoint end it "should create bucket" do location = 'oss-cn-hangzhou' stub_request(:put, bucket_url).with(:body => mock_location(location)) @client.create_bucket(@bucket, :location => 'oss-cn-hangzhou') expect(WebMock).to have_requested(:put, bucket_url) .with(:body => mock_location(location), :query => {}) end it "should delete bucket" do stub_request(:delete, bucket_url) @client.delete_bucket(@bucket) expect(WebMock).to have_requested(:delete, bucket_url) .with(:body => nil, :query => {}) end it "should paging list buckets" do return_buckets_1 = (1..5).map do |i| name = "rubysdk-bucket-#{i.to_s.rjust(3, '0')}" Bucket.new( :name => name, :location => 'oss-cn-hangzhou', :creation_time => Time.now) end more_1 = {:next_marker => return_buckets_1.last.name, :truncated => true} return_buckets_2 = (6..10).map do |i| name = "rubysdk-bucket-#{i.to_s.rjust(3, '0')}" Bucket.new( :name => name, :location => 'oss-cn-hangzhou', :creation_time => Time.now) end more_2 = {:truncated => false} stub_request(:get, /#{@endpoint}.*/) .to_return(:body => mock_buckets(return_buckets_1, more_1)).then .to_return(:body => mock_buckets(return_buckets_2, more_2)) buckets = @client.list_buckets expect(buckets.map {|b| b.to_s}.join(";")) .to eq((return_buckets_1 + return_buckets_2).map {|b| b.to_s}.join(";")) expect(WebMock).to have_requested(:get, /#{@endpoint}.*/).times(2) end it "should test bucket existence" do query = {'acl' => nil} return_acl = ACL::PUBLIC_READ stub_request(:get, bucket_url) .with(:query => query) .to_return(:body => mock_acl(return_acl)).then .to_return(:status => 404) exist = @client.bucket_exists?(@bucket) expect(exist).to be true exist = @client.bucket_exists?(@bucket) expect(exist).to be false expect(WebMock).to have_requested(:get, bucket_url) .with(:query => query, :body => nil).times(2) end it "should not list buckets when endpoint is cname" do cname_client = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :cname => true) expect { cname_client.list_buckets }.to raise_error(ClientError) end it "should use HTTPS" do stub_request(:put, "https://#{bucket_url}") https_client = Client.new( :endpoint => "https://#{@endpoint}", :access_key_id => 'xxx', :access_key_secret => 'yyy', :cname => false) https_client.create_bucket(@bucket) expect(WebMock).to have_requested(:put, "https://#{bucket_url}") end end # bucket operations end # Client end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/client/resumable_download_spec.rb000066400000000000000000000151101371637147000307440ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module OSS describe "Resumable download" do before :all do @endpoint = 'oss-cn-hangzhou.aliyuncs.com' @bucket_name = 'rubysdk-bucket' @object_key = 'resumable_file' @bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy').get_bucket(@bucket_name) @file = './download_file' end before :each do File.delete("#{@file}.cpt") if File.exist?("#{@file}.cpt") end def object_url "#{@bucket_name}.#{@endpoint}/#{@object_key}" end def mock_object(i) i.to_s.rjust(9, '0') + "\n" end def mock_range(i) "bytes=#{(i-1)*10}-#{i*10 - 1}" end def mock_error(code, message) Nokogiri::XML::Builder.new do |xml| xml.Error { xml.Code code xml.Message message xml.RequestId '0000' } end.to_xml end it "should download file when all goes well" do return_headers = { 'x-oss-object-type' => 'Normal', 'ETag' => 'xxxyyyzzz', 'Content-Length' => 100, 'Last-Modified' => Time.now.rfc822 } # get object meta stub_request(:head, object_url).to_return(:headers => return_headers) # get object by range stub_request(:get, object_url) .to_return((1..10).map{ |i| {:body => mock_object(i)} }) prg = [] @bucket.resumable_download( @object_key, @file, :part_size => 10) { |p| prg << p } ranges = [] expect(WebMock).to have_requested(:get, object_url).with{ |req| ranges << req.headers['Range'] }.times(10) expect(ranges).to match_array((1..10).map{ |i| mock_range(i) }) expect(File.exist?("#{@file}.cpt")).to be false expect(Dir.glob("#{@file}.part.*").empty?).to be true expect(File.read(@file).lines) .to match_array((1..10).map{ |i| mock_object(i) }) expect(prg.size).to eq(10) end it "should resume when download part fails" do return_headers = { 'x-oss-object-type' => 'Normal', 'ETag' => 'xxxyyyzzz', 'Content-Length' => 100, 'Last-Modified' => Time.now.rfc822 } # get object meta stub_request(:head, object_url).to_return(:headers => return_headers) code = 'Timeout' message = 'Request timeout.' # upload part stub_request(:get, object_url) .to_return((1..3).map{ |i| {:body => mock_object(i)} }).then .to_return(:status => 500, :body => mock_error(code, message)).times(2).then .to_return((4..9).map{ |i| {:body => mock_object(i)} }).then .to_return(:status => 500, :body => mock_error(code, message)).then .to_return((10..10).map{ |i| {:body => mock_object(i)} }) success = false 4.times do begin @bucket.resumable_download( @object_key, @file, :part_size => 10, :threads => 1) success = true rescue # pass end end expect(success).to be true ranges = [] expect(WebMock).to have_requested(:get, object_url).with{ |req| ranges << req.headers['Range'] }.times(13) expect(ranges.uniq).to match_array((1..10).map{ |i| mock_range(i) }) expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join) end it "should resume when checkpoint fails" do # Monkey patch to inject failures class ::Aliyun::OSS::Multipart::Download alias :old_checkpoint :checkpoint def checkpoint_fails @@fail_injections ||= [false, false, true, true, false, true, false] @@fail_injections.shift end def checkpoint t = checkpoint_fails if t == true raise ClientError.new("fail injection") end old_checkpoint end end return_headers = { 'x-oss-object-type' => 'Normal', 'ETag' => 'xxxyyyzzz', 'Content-Length' => 100, 'Last-Modified' => Time.now.rfc822 } # get object meta stub_request(:head, object_url).to_return(:headers => return_headers) # get object by range returns = [1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10] stub_request(:get, object_url) .to_return(returns.map{ |i| {:body => mock_object(i)} }) success = false 4.times do begin @bucket.resumable_download( @object_key, @file, :part_size => 10, :threads => 1) success = true rescue # pass end end expect(success).to be true expect(WebMock).to have_requested(:get, object_url).times(13) expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join) end it "should not resume when specify disable_cpt" do return_headers = { 'x-oss-object-type' => 'Normal', 'ETag' => 'xxxyyyzzz', 'Content-Length' => 100, 'Last-Modified' => Time.now.rfc822 } # get object meta stub_request(:head, object_url).to_return(:headers => return_headers) code = 'Timeout' message = 'Request timeout.' # upload part stub_request(:get, object_url) .to_return((1..3).map{ |i| {:body => mock_object(i)} }).then .to_return(:status => 500, :body => mock_error(code, message)).times(2).then .to_return((1..9).map{ |i| {:body => mock_object(i)} }).then .to_return(:status => 500, :body => mock_error(code, message)).then .to_return((1..10).map{ |i| {:body => mock_object(i)} }) cpt_file = "#{File.expand_path(@file)}.cpt" success = false 4.times do begin @bucket.resumable_download( @object_key, @file, :part_size => 10, :cpt_file => cpt_file, :disable_cpt => true, :threads => 1) success = true rescue # pass end expect(File.exists?(cpt_file)).to be false end expect(success).to be true expect(WebMock).to have_requested(:get, object_url).times(25) expect(File.read(@file)).to eq((1..10).map{ |i| mock_object(i) }.join) expect(Dir.glob("#{@file}.part.*").empty?).to be true end end # Resumable upload end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/client/resumable_upload_spec.rb000066400000000000000000000314701371637147000304300ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module OSS describe "Resumable upload" do before :all do @endpoint = 'oss-cn-hangzhou.aliyuncs.com' @bucket_name = 'rubysdk-bucket' @object_key = 'resumable_file' @bucket = Client.new( :endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy').get_bucket(@bucket_name) @file = './file_to_upload' # write 100B data File.open(@file, 'w') do |f| (1..10).each do |i| f.puts i.to_s.rjust(9, '0') end end end before :each do File.delete("#{@file}.cpt") if File.exist?("#{@file}.cpt") end def object_url "#{@bucket_name}.#{@endpoint}/#{@object_key}" end def parse_query_from_uri(uri) query = {} str = uri.to_s[uri.to_s.index('?')+1..-1] str.split("&").each do |q| v = q.split('=') query[v.at(0)] = v.at(1) end query end def mock_txn_id(txn_id) Nokogiri::XML::Builder.new do |xml| xml.InitiateMultipartUploadResult { xml.Bucket @bucket xml.Key @object xml.UploadId txn_id } end.to_xml end def mock_error(code, message) Nokogiri::XML::Builder.new do |xml| xml.Error { xml.Code code xml.Message message xml.RequestId '0000' } end.to_xml end def err(msg, reqid = '0000') "#{msg} RequestId: #{reqid}" end it "should upload file when all goes well" do stub_request(:post, /#{object_url}\?uploads.*/) .to_return(:body => mock_txn_id('upload_id')) stub_request(:put, /#{object_url}\?partNumber.*/) stub_request(:post, /#{object_url}\?uploadId.*/) prg = [] @bucket.resumable_upload( @object_key, @file, :part_size => 10) { |p| prg << p } expect(WebMock).to have_requested( :post, /#{object_url}\?uploads.*/).times(1) part_numbers = Set.new([]) upload_ids = Set.new([]) expect(WebMock).to have_requested( :put, /#{object_url}\?partNumber.*/).with{ |req| query = parse_query_from_uri(req.uri) part_numbers << query['partNumber'] upload_ids << query['uploadId'] }.times(10) expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s }) expect(upload_ids.to_a).to match_array(['upload_id']) expect(WebMock).to have_requested( :post, /#{object_url}\?uploadId.*/).times(1) expect(File.exist?("#{@file}.cpt")).to be false expect(prg.size).to eq(10) end it "should upload file with callback" do stub_request(:post, /#{object_url}\?uploads.*/) .to_return(:body => mock_txn_id('upload_id')) stub_request(:put, /#{object_url}\?partNumber.*/) stub_request(:post, /#{object_url}\?uploadId.*/) callback = Callback.new( url: 'http://app.server.com/callback', query: {'id' => 1, 'name' => '杭州'}, body: 'hello world', host: 'server.com' ) @bucket.resumable_upload( @object_key, @file, part_size: 10, callback: callback) expect(WebMock).to have_requested( :post, /#{object_url}\?uploads.*/).times(1) expect(WebMock).to have_requested( :put, /#{object_url}\?partNumber.*/) .times(10) expect(WebMock) .to have_requested( :post, /#{object_url}\?uploadId.*/) .with { |req| req.headers.key?('X-Oss-Callback') } .times(1) expect(File.exist?("#{@file}.cpt")).to be false end it "should raise CallbackError when callback failed" do stub_request(:post, /#{object_url}\?uploads.*/) .to_return(:body => mock_txn_id('upload_id')) stub_request(:put, /#{object_url}\?partNumber.*/) code = 'CallbackFailed' message = 'Error status: 502.' stub_request(:post, /#{object_url}\?uploadId.*/) .to_return(:status => 203, :body => mock_error(code, message)) callback = Callback.new( url: 'http://app.server.com/callback', query: {'id' => 1, 'name' => '杭州'}, body: 'hello world', host: 'server.com' ) expect { @bucket.resumable_upload( @object_key, @file, part_size: 10, callback: callback) }.to raise_error(CallbackError, err(message)) expect(WebMock).to have_requested( :post, /#{object_url}\?uploads.*/).times(1) expect(WebMock).to have_requested( :put, /#{object_url}\?partNumber.*/) .times(10) expect(WebMock) .to have_requested( :post, /#{object_url}\?uploadId.*/) .with { |req| req.headers.key?('X-Oss-Callback') } .times(1) expect(File.exist?("#{@file}.cpt")).to be true end it "should upload file with custom headers" do stub_request(:post, /#{object_url}\?uploads.*/) .to_return(:body => mock_txn_id('upload_id')) stub_request(:put, /#{object_url}\?partNumber.*/) stub_request(:post, /#{object_url}\?uploadId.*/) @bucket.resumable_upload( @object_key, @file, part_size: 10, headers: {'cache-CONTROL' => 'cacheit', 'CONTENT-disposition' => 'oh;yeah'}) headers = {} expect(WebMock).to have_requested( :post, /#{object_url}\?uploads.*/) .with { |req| headers = req.headers }.times(1) expect(headers['Cache-Control']).to eq('cacheit') expect(headers['Content-Disposition']).to eq('oh;yeah') expect(File.exist?("#{@file}.cpt")).to be false end it "should restart when begin txn fails" do code = 'Timeout' message = 'Request timeout.' stub_request(:post, /#{object_url}\?uploads.*/) .to_return(:status => 500, :body => mock_error(code, message)).then .to_return(:body => mock_txn_id('upload_id')) stub_request(:put, /#{object_url}\?partNumber.*/) stub_request(:post, /#{object_url}\?uploadId.*/) success = false 2.times do begin @bucket.resumable_upload(@object_key, @file, :part_size => 10) success = true rescue # pass end end expect(success).to be true expect(WebMock).to have_requested( :post, /#{object_url}\?uploads.*/).times(2) expect(WebMock).to have_requested( :put, /#{object_url}\?partNumber.*/).times(10) expect(WebMock).to have_requested( :post, /#{object_url}\?uploadId.*/).times(1) end it "should resume when upload part fails" do # begin multipart stub_request(:post, /#{object_url}\?uploads.*/) .to_return(:body => mock_txn_id('upload_id')) # commit multipart stub_request(:post, /#{object_url}\?uploadId.*/) code = 'Timeout' message = 'Request timeout.' # upload part stub_request(:put, /#{object_url}\?partNumber.*/) .to_return(:status => 200).times(3).then .to_return(:status => 500, :body => mock_error(code, message)).times(2).then .to_return(:status => 200).times(6).then .to_return(:status => 500, :body => mock_error(code, message)).then .to_return(:status => 200) success = false 4.times do begin @bucket.resumable_upload( @object_key, @file, part_size: 10, threads: 1) success = true rescue # pass end end expect(success).to be true expect(WebMock).to have_requested( :post, /#{object_url}\?uploads.*/).times(1) part_numbers = Set.new([]) upload_ids = Set.new([]) expect(WebMock).to have_requested( :put, /#{object_url}\?partNumber.*/).with{ |req| query = parse_query_from_uri(req.uri) part_numbers << query['partNumber'] upload_ids << query['uploadId'] }.times(13) expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s }) expect(upload_ids.to_a).to match_array(['upload_id']) expect(WebMock).to have_requested( :post, /#{object_url}\?uploadId.*/).times(1) end it "should resume when checkpoint fails" do # Monkey patch to inject failures class ::Aliyun::OSS::Multipart::Upload alias :old_checkpoint :checkpoint def checkpoint_fails @@fail_injections ||= [false, false, true, true, false, true, false] @@fail_injections.shift end def checkpoint t = checkpoint_fails if t == true raise ClientError.new("fail injection") end old_checkpoint end end stub_request(:post, /#{object_url}\?uploads.*/) .to_return(:body => mock_txn_id('upload_id')) stub_request(:put, /#{object_url}\?partNumber.*/) stub_request(:post, /#{object_url}\?uploadId.*/) success = false 4.times do begin @bucket.resumable_upload( @object_key, @file, part_size: 10, threads: 1) success = true rescue # pass end end expect(success).to be true expect(WebMock).to have_requested( :post, /#{object_url}\?uploads.*/).times(1) part_numbers = Set.new([]) expect(WebMock).to have_requested( :put, /#{object_url}\?partNumber.*/).with{ |req| query = parse_query_from_uri(req.uri) part_numbers << query['partNumber'] query['uploadId'] == 'upload_id' }.times(13) expect(part_numbers.to_a).to match_array((1..10).map{ |x| x.to_s }) expect(WebMock).to have_requested( :post, /#{object_url}\?uploadId.*/).times(1) end it "should resume when commit txn fails" do # begin multipart stub_request(:post, /#{object_url}\?uploads.*/) .to_return(:body => mock_txn_id('upload_id')) # upload part stub_request(:put, /#{object_url}\?partNumber.*/) code = 'Timeout' message = 'Request timeout.' # commit multipart stub_request(:post, /#{object_url}\?uploadId.*/) .to_return(:status => 500, :body => mock_error(code, message)).times(2).then .to_return(:status => 200) success = false 3.times do begin @bucket.resumable_upload( @object_key, @file, part_size: 10, threads: 1) success = true rescue # pass end end expect(success).to be true expect(WebMock).to have_requested( :post, /#{object_url}\?uploads.*/).times(1) expect(WebMock).to have_requested( :put, /#{object_url}\?partNumber.*/).times(10) expect(WebMock).to have_requested( :post, /#{object_url}\?uploadId.*/).with{ |req| query = parse_query_from_uri(req.uri) query['uploadId'] == 'upload_id' }.times(3) end it "should not write checkpoint when specify disable_cpt" do # begin multipart stub_request(:post, /#{object_url}\?uploads.*/) .to_return(:body => mock_txn_id('upload_id')) # upload part stub_request(:put, /#{object_url}\?partNumber.*/) code = 'Timeout' message = 'Request timeout.' # commit multipart stub_request(:post, /#{object_url}\?uploadId.*/) .to_return(:status => 500, :body => mock_error(code, message)).times(2).then .to_return(:status => 200) cpt_file = "#{File.expand_path(@file)}.cpt" success = false 3.times do begin @bucket.resumable_upload( @object_key, @file, :part_size => 10, :cpt_file => cpt_file, :disable_cpt => true) success = true rescue # pass end expect(File.exists?(cpt_file)).to be false end expect(success).to be true expect(WebMock).to have_requested( :post, /#{object_url}\?uploads.*/).times(3) expect(WebMock).to have_requested( :put, /#{object_url}\?partNumber.*/).times(30) expect(WebMock).to have_requested( :post, /#{object_url}\?uploadId.*/).with{ |req| query = parse_query_from_uri(req.uri) query['uploadId'] == 'upload_id' }.times(3) end end # Resumable upload end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/http_spec.rb000066400000000000000000000052701371637147000246050ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' module Aliyun module OSS describe HTTP do context HTTP::StreamWriter do it "should read out chunks that are written" do s = HTTP::StreamWriter.new do |sr| 100.times { sr << "x" } end 10.times do bytes, outbuf = 10, '' s.read(bytes, outbuf) expect(outbuf).to eq("x"*10) end outbuf = 'xxx' r = s.read(10, outbuf) expect(outbuf).to eq('') expect(r).to be nil r = s.read expect(outbuf.empty?).to be true expect(r).to eq('') end it "should convert chunk to string" do s = HTTP::StreamWriter.new do |sr| sr << 100 << 200 end r = s.read expect(r).to eq("100200") end it "should encode string to bytes" do s = HTTP::StreamWriter.new do |sr| 100.times { sr << "中" } end r = s.read(1) expect(r).to eq('中'.force_encoding(Encoding::ASCII_8BIT)[0]) s.read(2) r = s.read(3) expect(r.force_encoding(Encoding::UTF_8)).to eq('中') bytes = (100 - 2) * 3 outbuf = 'zzz' r = s.read(bytes, outbuf) expect(outbuf.size).to eq(bytes) expect(r.size).to eq(bytes) r = s.read expect(r).to eq('') end it "should read exactly bytes" do s = HTTP::StreamWriter.new do |sr| 100.times { sr << 'x' * 10 } end r = s.read(11) expect(r.size).to eq(11) r = s.read(25) expect(r.size).to eq(25) r = s.read(900) expect(r.size).to eq(900) r = s.read(1000) expect(r.size).to eq(64) end it "should read with a correct crc" do content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) s = HTTP::StreamWriter.new(true) do |sr| sr << content end r = s.read(content.size + 1) expect(r.size).to eq(content.size) expect(s.data_crc).to eq(content_crc) end it "should read with a correct crc when setting init_crc" do content = "hello world" init_crc = 100 content_crc = Aliyun::OSS::Util.crc(content, init_crc) s = HTTP::StreamWriter.new(true, init_crc) do |sr| sr << content end r = s.read(content.size + 1) expect(r.size).to eq(content.size) expect(s.data_crc).to eq(content_crc) end end # StreamWriter end # HTTP end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/multipart_spec.rb000066400000000000000000000570021371637147000256470ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module OSS describe Multipart do before :all do @endpoint = 'oss.aliyuncs.com' @protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy')) @bucket = 'rubysdk-bucket' @object = 'rubysdk-object' end def request_path "#{@bucket}.#{@endpoint}/#{@object}" end def crc_protocol Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => true, :download_crc_enable => true)) end def mock_txn_id(txn_id) Nokogiri::XML::Builder.new do |xml| xml.InitiateMultipartUploadResult { xml.Bucket @bucket xml.Key @object xml.UploadId txn_id } end.to_xml end def mock_multiparts(multiparts, more = {}) Nokogiri::XML::Builder.new do |xml| xml.ListMultipartUploadsResult { { :prefix => 'Prefix', :limit => 'MaxUploads', :id_marker => 'UploadIdMarker', :next_id_marker => 'NextUploadIdMarker', :key_marker => 'KeyMarker', :next_key_marker => 'NextKeyMarker', :truncated => 'IsTruncated', :encoding => 'EncodingType' }.map do |k, v| xml.send(v, more[k]) if more[k] end multiparts.each do |m| xml.Upload { xml.Key m.object xml.UploadId m.id xml.Initiated m.creation_time.rfc822 } end } end.to_xml end def mock_parts(parts, more = {}) Nokogiri::XML::Builder.new do |xml| xml.ListPartsResult { { :marker => 'PartNumberMarker', :next_marker => 'NextPartNumberMarker', :limit => 'MaxParts', :truncated => 'IsTruncated', :encoding => 'EncodingType' }.map do |k, v| xml.send(v, more[k]) if more[k] end parts.each do |p| xml.Part { xml.PartNumber p.number xml.LastModified p.last_modified.rfc822 xml.ETag p.etag xml.Size p.size } end } end.to_xml end def mock_error(code, message) Nokogiri::XML::Builder.new do |xml| xml.Error { xml.Code code xml.Message message xml.RequestId '0000' } end.to_xml end def err(msg, reqid = '0000') "#{msg} RequestId: #{reqid}" end context "Initiate multipart upload" do it "should POST to create transaction" do query = {'uploads' => nil} stub_request(:post, request_path).with(:query => query) @protocol.initiate_multipart_upload( @bucket, @object, :metas => { 'year' => '2015', 'people' => 'mary' }) expect(WebMock).to have_requested(:post, request_path) .with(:body => nil, :query => query, :headers => { 'x-oss-meta-year' => '2015', 'x-oss-meta-people' => 'mary' }) end it "should return transaction id" do query = {'uploads' => nil} return_txn_id = 'zyx' stub_request(:post, request_path). with(:query => query). to_return(:body => mock_txn_id(return_txn_id)) txn_id = @protocol.initiate_multipart_upload(@bucket, @object) expect(WebMock).to have_requested(:post, request_path) .with(:body => nil, :query => query) expect(txn_id).to eq(return_txn_id) end it "should raise Exception on error" do query = {'uploads' => nil} code = 'InvalidArgument' message = 'Invalid argument.' stub_request(:post, request_path) .with(:query => query) .to_return(:status => 400, :body => mock_error(code, message)) expect { @protocol.initiate_multipart_upload(@bucket, @object) }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:post, request_path) .with(:body => nil, :query => query) end end # initiate multipart context "Upload part" do it "should PUT to upload a part" do txn_id = 'xxxyyyzzz' part_no = 1 query = {'partNumber' => part_no, 'uploadId' => txn_id} stub_request(:put, request_path).with(:query => query) @protocol.upload_part(@bucket, @object, txn_id, part_no) {} expect(WebMock).to have_requested(:put, request_path) .with(:body => nil, :query => query) end it "should return part etag" do part_no = 1 txn_id = 'xxxyyyzzz' query = {'partNumber' => part_no, 'uploadId' => txn_id} return_etag = 'etag_1' stub_request(:put, request_path) .with(:query => query) .to_return(:headers => {'ETag' => return_etag}) body = 'hello world' p = @protocol.upload_part(@bucket, @object, txn_id, part_no) do |content| content << body end expect(WebMock).to have_requested(:put, request_path) .with(:body => body, :query => query) expect(p.number).to eq(part_no) expect(p.etag).to eq(return_etag) end it "should raise Exception on error" do txn_id = 'xxxyyyzzz' part_no = 1 query = {'partNumber' => part_no, 'uploadId' => txn_id} code = 'InvalidArgument' message = 'Invalid argument.' stub_request(:put, request_path) .with(:query => query) .to_return(:status => 400, :body => mock_error(code, message)) expect { @protocol.upload_part(@bucket, @object, txn_id, part_no) {} }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:put, request_path) .with(:body => nil, :query => query) end it "should raise crc exception on error" do txn_id = 'xxxyyyzzz' part_no = 1 query = {'partNumber' => part_no, 'uploadId' => txn_id} content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) stub_request(:put, request_path).with(:query => query).to_return( :status => 200, :headers => {:x_oss_hash_crc64ecma => content_crc + 1}) expect { crc_protocol.upload_part(@bucket, @object, txn_id, part_no) do |body| body << content end }.to raise_error(CrcInconsistentError, "The crc of put between client and oss is not inconsistent.") end it "should not raise crc exception on error" do txn_id = 'xxxyyyzzz' part_no = 1 query = {'partNumber' => part_no, 'uploadId' => txn_id} content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) stub_request(:put, request_path).with(:query => query).to_return( :status => 200, :headers => {:x_oss_hash_crc64ecma => content_crc}) expect { crc_protocol.upload_part(@bucket, @object, txn_id, part_no) do |body| body << content end }.not_to raise_error end end # upload part context "Upload part by copy object" do it "should PUT to upload a part by copy object" do txn_id = 'xxxyyyzzz' part_no = 1 copy_source = "/#{@bucket}/src_obj" query = {'partNumber' => part_no, 'uploadId' => txn_id} headers = {'x-oss-copy-source' => copy_source} stub_request(:put, request_path) .with(:query => query, :headers => headers) @protocol.upload_part_by_copy(@bucket, @object, txn_id, part_no, 'src_obj') expect(WebMock).to have_requested(:put, request_path) .with(:body => nil, :query => query, :headers => headers) end it "should upload a part by copy object from different bucket" do txn_id = 'xxxyyyzzz' part_no = 1 src_bucket = 'source-bucket' copy_source = "/#{src_bucket}/src_obj" query = {'partNumber' => part_no, 'uploadId' => txn_id} headers = {'x-oss-copy-source' => copy_source} stub_request(:put, request_path) .with(:query => query, :headers => headers) @protocol.upload_part_by_copy( @bucket, @object, txn_id, part_no, 'src_obj', :src_bucket => src_bucket) expect(WebMock).to have_requested(:put, request_path) .with(:body => nil, :query => query, :headers => headers) end it "should return part etag" do txn_id = 'xxxyyyzzz' part_no = 1 copy_source = "/#{@bucket}/src_obj" query = {'partNumber' => part_no, 'uploadId' => txn_id} headers = {'x-oss-copy-source' => copy_source} return_etag = 'etag_1' stub_request(:put, request_path) .with(:query => query, :headers => headers) .to_return(:headers => {'ETag' => return_etag}) p = @protocol.upload_part_by_copy(@bucket, @object, txn_id, part_no, 'src_obj') expect(WebMock).to have_requested(:put, request_path) .with(:body => nil, :query => query, :headers => headers) expect(p.number).to eq(part_no) expect(p.etag).to eq(return_etag) end it "should set range and conditions when copy" do txn_id = 'xxxyyyzzz' part_no = 1 copy_source = "/#{@bucket}/src_obj" query = {'partNumber' => part_no, 'uploadId' => txn_id} modified_since = Time.now unmodified_since = Time.now headers = { 'Range' => 'bytes=1-4', 'x-oss-copy-source' => copy_source, 'x-oss-copy-source-if-modified-since' => modified_since.httpdate, 'x-oss-copy-source-if-unmodified-since' => unmodified_since.httpdate, 'x-oss-copy-source-if-match' => 'me', 'x-oss-copy-source-if-none-match' => 'ume' } return_etag = 'etag_1' stub_request(:put, request_path) .with(:query => query, :headers => headers) .to_return(:headers => {'ETag' => return_etag}) p = @protocol.upload_part_by_copy( @bucket, @object, txn_id, part_no, 'src_obj', {:range => [1, 5], :condition => { :if_modified_since => modified_since, :if_unmodified_since => unmodified_since, :if_match_etag => 'me', :if_unmatch_etag => 'ume' }}) expect(WebMock).to have_requested(:put, request_path) .with(:body => nil, :query => query, :headers => headers) expect(p.number).to eq(part_no) expect(p.etag).to eq(return_etag) end it "should raise Exception on error" do txn_id = 'xxxyyyzzz' part_no = 1 copy_source = "/#{@bucket}/src_obj" query = {'partNumber' => part_no, 'uploadId' => txn_id} headers = {'x-oss-copy-source' => copy_source} code = 'PreconditionFailed' message = 'Precondition check failed.' stub_request(:put, request_path) .with(:query => query, :headers => headers) .to_return(:status => 412, :body => mock_error(code, message)) expect { @protocol.upload_part_by_copy(@bucket, @object, txn_id, part_no, 'src_obj') }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:put, request_path) .with(:body => nil, :query => query, :headers => headers) end end # upload part by copy object context "Commit multipart" do it "should POST to complete multipart" do txn_id = 'xxxyyyzzz' query = {'uploadId' => txn_id} parts = (1..5).map do |i| Multipart::Part.new(:number => i, :etag => "etag_#{i}") end stub_request(:post, request_path).with(:query => query) @protocol.complete_multipart_upload(@bucket, @object, txn_id, parts) parts_body = Nokogiri::XML::Builder.new do |xml| xml.CompleteMultipartUpload { parts.each do |p| xml.Part { xml.PartNumber p.number xml.ETag p.etag } end } end.to_xml expect(WebMock).to have_requested(:post, request_path) .with(:body => parts_body, :query => query) end it "should raise Exception on error" do txn_id = 'xxxyyyzzz' query = {'uploadId' => txn_id} code = 'InvalidDigest' message = 'Content md5 does not match.' stub_request(:post, request_path) .with(:query => query) .to_return(:status => 400, :body => mock_error(code, message)) expect { @protocol.complete_multipart_upload(@bucket, @object, txn_id, []) }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:post, request_path) .with(:query => query) end end # commit multipart context "Abort multipart" do it "should DELETE to abort multipart" do txn_id = 'xxxyyyzzz' query = {'uploadId' => txn_id} stub_request(:delete, request_path).with(:query => query) @protocol.abort_multipart_upload(@bucket, @object, txn_id) expect(WebMock).to have_requested(:delete, request_path) .with(:body => nil, :query => query) end it "should raise Exception on error" do txn_id = 'xxxyyyzzz' query = {'uploadId' => txn_id} code = 'NoSuchUpload' message = 'The multipart transaction does not exist.' stub_request(:delete, request_path) .with(:query => query) .to_return(:status => 404, :body => mock_error(code, message)) expect { @protocol.abort_multipart_upload(@bucket, @object, txn_id) }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:delete, request_path) .with(:body => nil, :query => query) end end # abort multipart context "List multiparts" do it "should GET to list multiparts" do request_path = "#{@bucket}.#{@endpoint}/" query = {'uploads' => nil} stub_request(:get, request_path).with(:query => query) @protocol.list_multipart_uploads(@bucket) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) end it "should send extra params when list multiparts" do request_path = "#{@bucket}.#{@endpoint}/" query = { 'uploads' => nil, 'prefix' => 'foo-', 'upload-id-marker' => 'id-marker', 'key-marker' => 'key-marker', 'max-uploads' => 10, 'encoding-type' => KeyEncoding::URL } stub_request(:get, request_path).with(:query => query) @protocol.list_multipart_uploads( @bucket, :prefix => 'foo-', :id_marker => 'id-marker', :key_marker => 'key-marker', :limit => 10, :encoding => KeyEncoding::URL ) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) end it "should get multipart transactions" do request_path = "#{@bucket}.#{@endpoint}/" query = { 'uploads' => nil, 'prefix' => 'foo-', 'upload-id-marker' => 'id-marker', 'key-marker' => 'key-marker', 'max-uploads' => 100, 'encoding-type' => KeyEncoding::URL } return_multiparts = (1..5).map do |i| Multipart::Transaction.new( :id => "id-#{i}", :object => "key-#{i}", :bucket => @bucket, :creation_time => Time.parse(Time.now.rfc822)) end return_more = { :prefix => 'foo-', :id_marker => 'id-marker', :key_marker => 'key-marker', :next_id_marker => 'next-id-marker', :next_key_marker => 'next-key-marker', :limit => 100, :truncated => true } stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_multiparts(return_multiparts, return_more)) txns, more = @protocol.list_multipart_uploads( @bucket, :prefix => 'foo-', :id_marker => 'id-marker', :key_marker => 'key-marker', :limit => 100, :encoding => KeyEncoding::URL) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) expect(txns.map {|x| x.to_s}.join(';')) .to eq(return_multiparts.map {|x| x.to_s}.join(';')) expect(more).to eq(return_more) end it "should decode object key" do request_path = "#{@bucket}.#{@endpoint}/" query = { 'uploads' => nil, 'prefix' => 'foo-', 'upload-id-marker' => 'id-marker', 'key-marker' => 'key-marker', 'max-uploads' => 100, 'encoding-type' => KeyEncoding::URL } return_multiparts = (1..5).map do |i| Multipart::Transaction.new( :id => "id-#{i}", :object => "中国-#{i}", :bucket => @bucket, :creation_time => Time.parse(Time.now.rfc822)) end es_multiparts = return_multiparts.map do |x| Multipart::Transaction.new( :id => x.id, :object => CGI.escape(x.object), :creation_time => x.creation_time) end return_more = { :prefix => 'foo-', :id_marker => 'id-marker', :key_marker => '杭州のruby', :next_id_marker => 'next-id-marker', :next_key_marker => '西湖のruby', :limit => 100, :truncated => true, :encoding => KeyEncoding::URL } es_more = { :prefix => 'foo-', :id_marker => 'id-marker', :key_marker => CGI.escape('杭州のruby'), :next_id_marker => 'next-id-marker', :next_key_marker => CGI.escape('西湖のruby'), :limit => 100, :truncated => true, :encoding => KeyEncoding::URL } stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_multiparts(es_multiparts, es_more)) txns, more = @protocol.list_multipart_uploads( @bucket, :prefix => 'foo-', :id_marker => 'id-marker', :key_marker => 'key-marker', :limit => 100, :encoding => KeyEncoding::URL) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) expect(txns.map {|x| x.to_s}.join(';')) .to eq(return_multiparts.map {|x| x.to_s}.join(';')) expect(more).to eq(return_more) end it "should raise Exception on error" do request_path = "#{@bucket}.#{@endpoint}/" query = {'uploads' => nil} code = 'InvalidArgument' message = 'Invalid argument.' stub_request(:get, request_path) .with(:query => query) .to_return(:status => 400, :body => mock_error(code, message)) expect { @protocol.list_multipart_uploads(@bucket) }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) end end # list multiparts context "List parts" do it "should GET to list parts" do txn_id = 'yyyxxxzzz' query = {'uploadId' => txn_id} stub_request(:get, request_path).with(:query => query) @protocol.list_parts(@bucket, @object, txn_id) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) end it "should send extra params when list parts" do txn_id = 'yyyxxxzzz' query = { 'uploadId' => txn_id, 'part-number-marker' => 'foo-', 'max-parts' => 100, 'encoding-type' => KeyEncoding::URL } stub_request(:get, request_path).with(:query => query) @protocol.list_parts(@bucket, @object, txn_id, :marker => 'foo-', :limit => 100, :encoding => KeyEncoding::URL) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) end it "should get parts" do txn_id = 'yyyxxxzzz' query = { 'uploadId' => txn_id, 'part-number-marker' => 'foo-', 'max-parts' => 100, 'encoding-type' => KeyEncoding::URL } return_parts = (1..5).map do |i| Multipart::Part.new( :number => i, :etag => "etag-#{i}", :size => 1024, :last_modified => Time.parse(Time.now.rfc822)) end return_more = { :marker => 'foo-', :next_marker => 'bar-', :limit => 100, :truncated => true } stub_request(:get, request_path) .with(:query => query) .to_return(:body => mock_parts(return_parts, return_more)) parts, more = @protocol.list_parts(@bucket, @object, txn_id, :marker => 'foo-', :limit => 100, :encoding => KeyEncoding::URL) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) part_numbers = return_parts.map {|x| x.number} expect(parts.map {|x| x.number}).to match_array(part_numbers) expect(more).to eq(return_more) end it "should raise Exception on error" do txn_id = 'yyyxxxzzz' query = {'uploadId' => txn_id} code = 'InvalidArgument' message = 'Invalid argument.' stub_request(:get, request_path) .with(:query => query) .to_return(:status => 400, :body => mock_error(code, message)) expect { @protocol.list_parts(@bucket, @object, txn_id) }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:get, request_path) .with(:body => nil, :query => query) end end # list parts end # Multipart end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/object_spec.rb000066400000000000000000001051071371637147000250740ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module OSS describe "Object" do before :all do @endpoint = 'oss.aliyuncs.com' @protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy')) @bucket = 'rubysdk-bucket' end def crc_protocol Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy', :upload_crc_enable => true, :download_crc_enable => true)) end def get_request_path(object = nil) p = "#{@bucket}.#{@endpoint}/" p += CGI.escape(object) if object p end def get_resource_path(object, bucket = nil) "/#{bucket || @bucket}/#{object}" end def mock_copy_object(last_modified, etag) builder = Nokogiri::XML::Builder.new do |xml| xml.CopyObjectResult { xml.LastModified last_modified.to_s xml.ETag etag } end builder.to_xml end def mock_acl(acl) Nokogiri::XML::Builder.new do |xml| xml.AccessControlPolicy { xml.Owner { xml.ID 'owner_id' xml.DisplayName 'owner_name' } xml.AccessControlList { xml.Grant acl } } end.to_xml end def mock_delete(objects, opts = {}) # It may have invisible chars in object key which will corrupt # libxml. So we're constructing xml body manually here. body = '' body << '' body << '' << (opts[:quiet]? true : false).to_s << '' objects.each { |k| body << '' << k << '' } body << '' end def mock_delete_result(deleted, opts = {}) Nokogiri::XML::Builder.new do |xml| xml.DeleteResult { xml.EncodingType opts[:encoding] if opts[:encoding] deleted.each do |o| xml.Deleted { xml.Key o } end } end.to_xml end def mock_error(code, message) Nokogiri::XML::Builder.new do |xml| xml.Error { xml.Code code xml.Message message xml.RequestId '0000' } end.to_xml end def err(msg, reqid = '0000') "#{msg} RequestId: #{reqid}" end context "Put object" do it "should PUT to create object" do object_name = 'ruby' url = get_request_path(object_name) stub_request(:put, url) content = "hello world" @protocol.put_object(@bucket, object_name) do |c| c << content end expect(WebMock).to have_requested(:put, url) .with(:body => content, :query => {}) end it "should raise Exception on error" do object_name = 'ruby' url = get_request_path(object_name) code = 'NoSuchBucket' message = 'The bucket does not exist.' stub_request(:put, url).to_return( :status => 404, :body => mock_error(code, message)) content = "hello world" expect { @protocol.put_object(@bucket, object_name) do |c| c << content end }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:put, url) .with(:body => content, :query => {}) end it "should use default content-type" do object_name = 'ruby' url = get_request_path(object_name) stub_request(:put, url) @protocol.put_object(@bucket, object_name) do |content| content << 'hello world' end expect(WebMock).to have_requested(:put, url) .with(:body => 'hello world', :headers => {'Content-Type' => HTTP::DEFAULT_CONTENT_TYPE}) end it "should use customized content-type" do object_name = 'ruby' url = get_request_path(object_name) stub_request(:put, url) @protocol.put_object( @bucket, object_name, :content_type => 'application/ruby' ) do |content| content << 'hello world' end expect(WebMock).to have_requested(:put, url) .with(:body => 'hello world', :headers => {'Content-Type' => 'application/ruby'}) end it "should support non-ascii object name" do object_name = '中国のruby' url = get_request_path(object_name) stub_request(:put, url) content = "hello world" @protocol.put_object(@bucket, object_name) do |c| c << content end expect(WebMock).to have_requested(:put, url) .with(:body => content, :query => {}) end it "should set user defined metas" do object_name = 'ruby' url = get_request_path(object_name) stub_request(:put, url) @protocol.put_object( @bucket, object_name, :metas => {'year' => '2015', 'people' => 'mary'} ) do |content| content << 'hello world' end expect(WebMock).to have_requested(:put, url) .with(:body => 'hello world', :headers => { 'x-oss-meta-year' => '2015', 'x-oss-meta-people' => 'mary'}) end it "should raise crc exception on error" do object_name = 'ruby' url = get_request_path(object_name) content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) stub_request(:put, url).to_return( :status => 200, :headers => {:x_oss_hash_crc64ecma => content_crc.to_i + 1}) expect(crc_protocol.upload_crc_enable).to eq(true) expect { crc_protocol.put_object(@bucket, object_name) do |c| c << content end }.to raise_error(CrcInconsistentError, "The crc of put between client and oss is not inconsistent.") end it "should not raise crc exception on error" do object_name = 'ruby' url = get_request_path(object_name) content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) stub_request(:put, url).to_return( :status => 200, :headers => {:x_oss_hash_crc64ecma => content_crc}) expect(crc_protocol.upload_crc_enable).to eq(true) expect { crc_protocol.put_object(@bucket, object_name) do |c| c << content end }.not_to raise_error end end # put object context "Append object" do it "should POST to append object" do object_name = 'ruby' url = get_request_path(object_name) query = {'append' => nil, 'position' => 11} return_headers = {'x-oss-next-append-position' => '101'} stub_request(:post, url).with(:query => query) .to_return(:headers => return_headers) content = "hello world" next_pos = @protocol.append_object(@bucket, object_name, 11) do |c| c << content end expect(WebMock).to have_requested(:post, url) .with(:body => content, :query => query) expect(next_pos).to eq(101) end it "should raise Exception on error" do object_name = 'ruby' url = get_request_path(object_name) query = {'append' => nil, 'position' => 11} code = 'ObjectNotAppendable' message = 'Normal object cannot be appended.' stub_request(:post, url).with(:query => query). to_return(:status => 409, :body => mock_error(code, message)) content = "hello world" expect { @protocol.append_object(@bucket, object_name, 11) do |c| c << content end }.to raise_error(Exception, err(message)) expect(WebMock).to have_requested(:post, url) .with(:body => content, :query => query) end it "should use default content-type" do object_name = 'ruby' url = get_request_path(object_name) query = {'append' => nil, 'position' => 0} stub_request(:post, url).with(:query => query) @protocol.append_object(@bucket, object_name, 0) do |content| content << 'hello world' end expect(WebMock).to have_requested(:post, url) .with(:body => 'hello world', :query => query, :headers => {'Content-Type' => HTTP::DEFAULT_CONTENT_TYPE}) end it "should use customized content-type" do object_name = 'ruby' url = get_request_path(object_name) query = {'append' => nil, 'position' => 0} stub_request(:post, url).with(:query => query) @protocol.append_object( @bucket, object_name, 0, :content_type => 'application/ruby' ) do |content| content << 'hello world' end expect(WebMock).to have_requested(:post, url) .with(:body => 'hello world', :query => query, :headers => {'Content-Type' => 'application/ruby'}) end it "should set user defined metas" do object_name = 'ruby' url = get_request_path(object_name) query = {'append' => nil, 'position' => 0} stub_request(:post, url).with(:query => query) @protocol.append_object( @bucket, object_name, 0, :metas => {'year' => '2015', 'people' => 'mary'} ) do |content| content << 'hello world' end expect(WebMock).to have_requested(:post, url) .with(:query => query, :body => 'hello world', :headers => { 'x-oss-meta-year' => '2015', 'x-oss-meta-people' => 'mary'}) end it "should raise crc exception on error" do object_name = 'ruby' url = get_request_path(object_name) content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) query = {'append' => nil, 'position' => 11} return_headers = {'x-oss-next-append-position' => '101', :x_oss_hash_crc64ecma => content_crc.to_i + 1} stub_request(:post, url).with(:query => query) .to_return(:headers => return_headers) expect(crc_protocol.upload_crc_enable).to eq(true) expect { crc_protocol.append_object(@bucket, object_name, 11, :init_crc => 0) do |c| c << content end }.to raise_error(CrcInconsistentError, "The crc of append between client and oss is not inconsistent.") end it "should not raise crc exception with init_crc" do object_name = 'ruby' url = get_request_path(object_name) content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) query = {'append' => nil, 'position' => 11} return_headers = {'x-oss-next-append-position' => '101', :x_oss_hash_crc64ecma => content_crc} stub_request(:post, url).with(:query => query) .to_return(:headers => return_headers) expect(crc_protocol.upload_crc_enable).to eq(true) next_pos = 0 expect { next_pos = crc_protocol.append_object(@bucket, object_name, 11, :init_crc => 0) do |c| c << content end }.not_to raise_error expect(WebMock).to have_requested(:post, url) .with(:body => content, :query => query) expect(next_pos).to eq(101) end it "should not raise crc exception without init_crc" do object_name = 'ruby' url = get_request_path(object_name) content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) query = {'append' => nil, 'position' => 11} return_headers = {'x-oss-next-append-position' => '101', :x_oss_hash_crc64ecma => content_crc + 1} stub_request(:post, url).with(:query => query) .to_return(:headers => return_headers) expect(crc_protocol.upload_crc_enable).to eq(true) next_pos = 0 expect { next_pos = crc_protocol.append_object(@bucket, object_name, 11) do |c| c << content end }.not_to raise_error expect(WebMock).to have_requested(:post, url) .with(:body => content, :query => query) expect(next_pos).to eq(101) end end # append object context "Copy object" do it "should copy object" do src_object = 'ruby' dst_object = 'rails' url = get_request_path(dst_object) last_modified = Time.parse(Time.now.rfc822) etag = '0000' stub_request(:put, url).to_return( :body => mock_copy_object(last_modified, etag)) result = @protocol.copy_object(@bucket, src_object, dst_object) expect(WebMock).to have_requested(:put, url) .with(:body => nil, :headers => { 'x-oss-copy-source' => get_resource_path(src_object)}) expect(result[:last_modified]).to eq(last_modified) expect(result[:etag]).to eq(etag) end it "should copy object of different buckets" do src_bucket = 'source-bucket' src_object = 'ruby' dst_object = 'rails' url = get_request_path(dst_object) last_modified = Time.parse(Time.now.rfc822) etag = '0000' stub_request(:put, url).to_return( :body => mock_copy_object(last_modified, etag)) result = @protocol.copy_object( @bucket, src_object, dst_object, :src_bucket => src_bucket) expect(WebMock).to have_requested(:put, url) .with(:body => nil, :headers => { 'x-oss-copy-source' => get_resource_path(src_object, src_bucket)}) expect(result[:last_modified]).to eq(last_modified) expect(result[:etag]).to eq(etag) end it "should set acl and conditions when copy object" do src_object = 'ruby' dst_object = 'rails' url = get_request_path(dst_object) modified_since = Time.now unmodified_since = Time.now last_modified = Time.parse(Time.now.rfc822) etag = '0000' headers = { 'x-oss-copy-source' => get_resource_path(src_object), 'x-oss-object-acl' => ACL::PRIVATE, 'x-oss-metadata-directive' => MetaDirective::REPLACE, 'x-oss-copy-source-if-modified-since' => modified_since.httpdate, 'x-oss-copy-source-if-unmodified-since' => unmodified_since.httpdate, 'x-oss-copy-source-if-match' => 'me', 'x-oss-copy-source-if-none-match' => 'ume' } stub_request(:put, url).to_return( :body => mock_copy_object(last_modified, etag)) result = @protocol.copy_object( @bucket, src_object, dst_object, {:acl => ACL::PRIVATE, :meta_directive => MetaDirective::REPLACE, :condition => { :if_modified_since => modified_since, :if_unmodified_since => unmodified_since, :if_match_etag => 'me', :if_unmatch_etag => 'ume' } }) expect(WebMock).to have_requested(:put, url) .with(:body => nil, :headers => headers) expect(result[:last_modified]).to eq(last_modified) expect(result[:etag]).to eq(etag) end it "should set user defined metas" do src_object = 'ruby' dst_object = 'rails' url = get_request_path(dst_object) stub_request(:put, url) @protocol.copy_object(@bucket, src_object, dst_object, :metas => { 'year' => '2015', 'people' => 'mary' }) expect(WebMock).to have_requested(:put, url) .with(:body => nil, :headers => { 'x-oss-meta-year' => '2015', 'x-oss-meta-people' => 'mary'}) end it "should raise Exception on error" do src_object = 'ruby' dst_object = 'rails' url = get_request_path(dst_object) code = 'EntityTooLarge' message = 'The object to copy is too large.' stub_request(:put, url).to_return( :status => 400, :headers => {'x-oss-request-id' => '0000'}, :body => mock_error(code, message)) begin @protocol.copy_object(@bucket, src_object, dst_object) expect(false).to be true rescue ServerError => e expect(e.http_code).to eq(400) expect(e.error_code).to eq(code) expect(e.message).to eq(err(message)) expect(e.request_id).to eq('0000') end expect(WebMock).to have_requested(:put, url) .with(:body => nil, :headers => { 'x-oss-copy-source' => get_resource_path(src_object)}) end end # copy object context "Get object" do it "should GET to get object" do object_name = 'ruby' url = get_request_path(object_name) return_content = "hello world" stub_request(:get, url).to_return(:body => return_content) content = "" @protocol.get_object(@bucket, object_name) {|c| content << c} expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => {}) expect(content).to eq(return_content) end it "should return object meta" do object_name = 'ruby' url = get_request_path(object_name) last_modified = Time.now.rfc822 return_headers = { 'x-oss-object-type' => 'Normal', 'ETag' => 'xxxyyyzzz', 'Content-Length' => 1024, 'Last-Modified' => last_modified, 'x-oss-meta-year' => '2015', 'x-oss-meta-people' => 'mary' } return_content = "hello world" stub_request(:get, url) .to_return(:headers => return_headers, :body => return_content) content = "" obj = @protocol.get_object(@bucket, object_name) {|c| content << c} expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => {}) expect(content).to eq(return_content) expect(obj.key).to eq(object_name) expect(obj.type).to eq('Normal') expect(obj.etag).to eq('xxxyyyzzz') expect(obj.size).to eq(1024) expect(obj.last_modified.rfc822).to eq(last_modified) expect(obj.metas).to eq({'year' => '2015', 'people' => 'mary'}) end it "should raise Exception on error" do object_name = 'ruby' url = get_request_path(object_name) code = 'NoSuchKey' message = 'The object does not exist' stub_request(:get, url).to_return( :status => 404, :body => mock_error(code, message)) expect { @protocol.get_object(@bucket, object_name) {|c| true} }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => {}) end it "should get object range" do object_name = 'ruby' url = get_request_path(object_name) stub_request(:get, url) @protocol.get_object(@bucket, object_name, {:range => [0, 10]}) {} expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => {}, :headers => { 'Range' => 'bytes=0-9' }) end it "should raise Exception on error when setting invalid range" do object_name = 'ruby' url = get_request_path(object_name) stub_request(:get, url) expect { @protocol.get_object(@bucket, object_name, {:range => [0, 10, 5]}) {} }.to raise_error(ClientError) end it "should match modify time and etag" do object_name = 'ruby' url = get_request_path(object_name) stub_request(:get, url) modified_since = Time.now unmodified_since = Time.now etag = 'xxxyyyzzz' not_etag = 'aaabbbccc' @protocol.get_object( @bucket, object_name, {:condition => { :if_modified_since => modified_since, :if_unmodified_since => unmodified_since, :if_match_etag => etag, :if_unmatch_etag => not_etag}}) {} expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => {}, :headers => { 'If-Modified-Since' => modified_since.httpdate, 'If-Unmodified-since' => unmodified_since.httpdate, 'If-Match' => etag, 'If-None-Match' => not_etag}) end it "should rewrite response headers" do object_name = 'ruby' url = get_request_path(object_name) expires = Time.now rewrites = { :content_type => 'ct', :content_language => 'cl', :expires => expires, :cache_control => 'cc', :content_disposition => 'cd', :content_encoding => 'ce' } query = Hash[rewrites.map {|k, v| ["response-#{k.to_s.sub('_', '-')}", v]}] query['response-expires'] = rewrites[:expires].httpdate stub_request(:get, url).with(:query => query) @protocol.get_object(@bucket, object_name, :rewrite => rewrites) {} expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => query) end it "should get object with headers" do object_name = 'ruby' url = get_request_path(object_name) headers = { 'Range' => 'bytes=0-9' } stub_request(:get, url) @protocol.get_object(@bucket, object_name, {:headers => headers}) {} expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => {}, :headers => { 'Range' => 'bytes=0-9' }) end it "should raise crc exception on error" do object_name = 'ruby' url = get_request_path(object_name) content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) stub_request(:get, url).to_return( :status => 200, :body => content, :headers => {:x_oss_hash_crc64ecma => content_crc.to_i + 1}) response_content = "" expect(crc_protocol.download_crc_enable).to eq(true) expect { crc_protocol.get_object(@bucket, object_name) {|c| response_content << c} }.to raise_error(CrcInconsistentError, "The crc of get between client and oss is not inconsistent.") end it "should not raise crc exception on error" do object_name = 'ruby' url = get_request_path(object_name) content = "hello world" content_crc = Aliyun::OSS::Util.crc(content) stub_request(:get, url).to_return( :status => 200, :body => content, :headers => {:x_oss_hash_crc64ecma => content_crc}) response_content = "" expect(crc_protocol.download_crc_enable).to eq(true) expect { crc_protocol.get_object(@bucket, object_name) {|c| response_content << c} }.not_to raise_error expect(response_content).to eq(content) end it "should not raise crc exception on error when setting range" do object_name = 'ruby' url = get_request_path(object_name) content = "hello world 0123456789" content_crc = Aliyun::OSS::Util.crc(content) stub_request(:get, url).to_return( :status => 200, :body => content, :headers => {:x_oss_hash_crc64ecma => content_crc.to_i + 1}) response_content = "" expect(crc_protocol.download_crc_enable).to eq(true) expect { crc_protocol.get_object(@bucket, object_name, {range: [0, 10]}) {|c| response_content << c} }.not_to raise_error expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => {}, :headers => { 'Range' => 'bytes=0-9' }) end it "should get to get object with special chars" do object_name = 'ruby///adfadfa//!@#%^*//?key=value&aabc#abc=ad' url = get_request_path(object_name) return_content = "hello world" stub_request(:get, url).to_return(:body => return_content) content = "" @protocol.get_object(@bucket, object_name) {|c| content << c} expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => {}) expect(content).to eq(return_content) end end # Get object context "Get object meta" do it "should get object meta" do object_name = 'ruby' url = get_request_path(object_name) last_modified = Time.now.rfc822 return_headers = { 'x-oss-object-type' => 'Normal', 'ETag' => 'xxxyyyzzz', 'Content-Length' => 1024, 'Last-Modified' => last_modified, 'x-oss-meta-year' => '2015', 'x-oss-meta-people' => 'mary' } stub_request(:head, url).to_return(:headers => return_headers) obj = @protocol.get_object_meta(@bucket, object_name) expect(WebMock).to have_requested(:head, url) .with(:body => nil, :query => {}) expect(obj.key).to eq(object_name) expect(obj.type).to eq('Normal') expect(obj.etag).to eq('xxxyyyzzz') expect(obj.size).to eq(1024) expect(obj.last_modified.rfc822).to eq(last_modified) expect(obj.metas).to eq({'year' => '2015', 'people' => 'mary'}) end it "should set conditions" do object_name = 'ruby' url = get_request_path(object_name) stub_request(:head, url) modified_since = Time.now unmodified_since = Time.now etag = 'xxxyyyzzz' not_etag = 'aaabbbccc' @protocol.get_object_meta( @bucket, object_name, :condition => { :if_modified_since => modified_since, :if_unmodified_since => unmodified_since, :if_match_etag => etag, :if_unmatch_etag => not_etag}) expect(WebMock).to have_requested(:head, url) .with(:body => nil, :query => {}, :headers => { 'If-Modified-Since' => modified_since.httpdate, 'If-Unmodified-since' => unmodified_since.httpdate, 'If-Match' => etag, 'If-None-Match' => not_etag}) end end # Get object meta context "Delete object" do it "should DELETE to delete object" do object_name = 'ruby' url = get_request_path(object_name) stub_request(:delete, url) @protocol.delete_object(@bucket, object_name) expect(WebMock).to have_requested(:delete, url) .with(:body => nil, :query => {}) end it "should raise Exception on error" do object_name = 'ruby' url = get_request_path(object_name) code = 'NoSuchBucket' message = 'The bucket does not exist.' stub_request(:delete, url).to_return( :status => 404, :body => mock_error(code, message)) expect { @protocol.delete_object(@bucket, object_name) }.to raise_error(ServerError, err(message)) expect(WebMock).to have_requested(:delete, url) .with(:body => nil, :query => {}) end it "should batch delete objects" do url = get_request_path query = {'delete' => nil} object_names = (1..5).map do |i| "object-#{i}" end stub_request(:post, url) .with(:query => query) .to_return(:body => mock_delete_result(object_names)) opts = {:quiet => false} deleted = @protocol.batch_delete_objects(@bucket, object_names, opts) expect(WebMock).to have_requested(:post, url) .with(:query => query, :body => mock_delete(object_names, opts)) expect(deleted).to match_array(object_names) end it "should decode object key in batch delete response" do url = get_request_path query = {'delete' => nil, 'encoding-type' => KeyEncoding::URL} object_names = (1..5).map do |i| "对象-#{i}" end es_objects = (1..5).map do |i| CGI.escape "对象-#{i}" end opts = {:quiet => false, :encoding => KeyEncoding::URL} stub_request(:post, url) .with(:query => query) .to_return(:body => mock_delete_result(es_objects, opts)) deleted = @protocol.batch_delete_objects(@bucket, object_names, opts) expect(WebMock).to have_requested(:post, url) .with(:query => query, :body => mock_delete(object_names, opts)) expect(deleted).to match_array(object_names) end it "should batch delete objects in quiet mode" do url = get_request_path query = {'delete' => nil} object_names = (1..5).map do |i| "object-#{i}" end stub_request(:post, url) .with(:query => query) .to_return(:body => "") opts = {:quiet => true} deleted = @protocol.batch_delete_objects(@bucket, object_names, opts) expect(WebMock).to have_requested(:post, url) .with(:query => query, :body => mock_delete(object_names, opts)) expect(deleted).to match_array([]) end it "should rasie Exception wiht invalid responsed body" do url = get_request_path query = {'delete' => nil} body = ' invaid multipart.data test.jpg demo.jpg ' object_names = (1..5).map do |i| "object-#{i}" end stub_request(:post, url) .with(:query => query) .to_return(:body => body) opts = {:quiet => false} expect { deleted = @protocol.batch_delete_objects(@bucket, object_names, opts) }.to raise_error(ClientError) end end # delete object context "acl" do it "should update acl" do object_name = 'ruby' url = get_request_path(object_name) query = {'acl' => nil} stub_request(:put, url).with(:query => query) @protocol.put_object_acl(@bucket, object_name, ACL::PUBLIC_READ) expect(WebMock).to have_requested(:put, url) .with(:query => query, :headers => {'x-oss-object-acl' => ACL::PUBLIC_READ}, :body => nil) end it "should get acl" do object_name = 'ruby' url = get_request_path(object_name) query = {'acl' => nil} return_acl = ACL::PUBLIC_READ stub_request(:get, url) .with(:query => query) .to_return(:body => mock_acl(return_acl)) acl = @protocol.get_object_acl(@bucket, object_name) expect(WebMock).to have_requested(:get, url) .with(:body => nil, :query => query) expect(acl).to eq(return_acl) end end # acl context "cors" do it "should get object cors" do object_name = 'ruby' url = get_request_path(object_name) return_rule = CORSRule.new( :allowed_origins => 'origin', :allowed_methods => 'PUT', :allowed_headers => 'Authorization', :expose_headers => 'x-oss-test', :max_age_seconds => 10 ) stub_request(:options, url).to_return( :headers => { 'Access-Control-Allow-Origin' => return_rule.allowed_origins, 'Access-Control-Allow-Methods' => return_rule.allowed_methods, 'Access-Control-Allow-Headers' => return_rule.allowed_headers, 'Access-Control-Expose-Headers' => return_rule.expose_headers, 'Access-Control-Max-Age' => return_rule.max_age_seconds } ) rule = @protocol.get_object_cors( @bucket, object_name, 'origin', 'PUT', ['Authorization']) expect(WebMock).to have_requested(:options, url) .with(:body => nil, :query => {}) expect(rule.to_s).to eq(return_rule.to_s) end end # cors context "callback" do it "should encode callback" do callback = Callback.new( url: 'http://app.server.com/callback', query: {'id' => 1, 'name' => '杭州'}, body: 'hello world', host: 'server.com' ) encoded = "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly9hcHAuc2VydmVyLmNvbS9jYWxsYmFjaz9pZD0xJm5hbWU9JUU2JTlEJUFEJUU1JUI3JTlFIiwiY2FsbGJhY2tCb2R5IjoiaGVsbG8gd29ybGQiLCJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkIiwiY2FsbGJhY2tIb3N0Ijoic2VydmVyLmNvbSJ9" expect(callback.serialize).to eq(encoded) end it "should not accept url with query string" do expect { Callback.new(url: 'http://app.server.com/callback?id=1').serialize }.to raise_error(ClientError, "Query parameters should not appear in URL.") end end end # Object end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/service_spec.rb000066400000000000000000000106261371637147000252670ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module OSS describe "Service" do before :all do @endpoint = 'oss.aliyuncs.com' @protocol = Protocol.new( Config.new(:endpoint => @endpoint, :access_key_id => 'xxx', :access_key_secret => 'yyy')) @all_buckets = [] (1..10).each do |i| name = "rubysdk-bucket-#{i.to_s.rjust(3, '0')}" @all_buckets << Bucket.new( :name => name, :location => 'oss-cn-hangzhou', :creation_time => Time.now) end end # 生成list_buckets返回的响应,包含bucket列表和more信息 def mock_response(buckets, more) builder = Nokogiri::XML::Builder.new do |xml| xml.ListAllMyBucketsResult { xml.Owner { xml.ID 'owner_id' xml.DisplayName 'owner_name' } xml.Buckets { buckets.each do |b| xml.Bucket { xml.Location b.location xml.Name b.name xml.CreationDate b.creation_time.to_s } end } unless more.empty? xml.Prefix more[:prefix] xml.Marker more[:marker] xml.MaxKeys more[:limit].to_s xml.NextMarker more[:next_marker] xml.IsTruncated more[:truncated] end } end builder.to_xml end context "List all buckets" do # 测试list_buckets正确地发送了HTTP请求 it "should send correct request" do stub_request(:get, @endpoint) @protocol.list_buckets expect(WebMock).to have_requested(:get, @endpoint). with(:body => nil, :query => {}) end # 测试list_buckets正确地解析了list_buckets的返回 it "should correctly parse response" do stub_request(:get, @endpoint).to_return( {:body => mock_response(@all_buckets, {})}) buckets, more = @protocol.list_buckets bucket_names = buckets.map {|b| b.name} all_bucket_names = @all_buckets.map {|b| b.name} expect(bucket_names).to match_array(all_bucket_names) expect(more).to be_empty end end context "Paging buckets" do # 测试list_buckets的请求中包含prefix/marker/maxkeys等信息 it "should set prefix/max-keys param" do prefix = 'rubysdk-bucket-00' marker = 'rubysdk-bucket-002' limit = 5 stub_request(:get, @endpoint).with( :query => {'prefix' => prefix, 'marker' => marker, 'max-keys' => limit}) @protocol.list_buckets( :prefix => prefix, :limit => limit, :marker => marker) expect(WebMock).to have_requested(:get, @endpoint). with(:body => nil, :query => {'prefix' => prefix, 'marker' => marker, 'max-keys' => limit}) end # 测试list_buckets正确地解析了HTTP响应,包含more信息 it "should parse next marker" do prefix = 'rubysdk-bucket-00' marker = 'rubysdk-bucket-002' limit = 5 # returns ['rubysdk-bucket-003', ..., 'rubysdk-bucket-007'] return_buckets = @all_buckets[2, 5] next_marker = 'rubysdk-bucket-007' more = {:prefix => prefix, :marker => marker, :limit => limit, :next_marker => next_marker, :truncated => true} stub_request(:get, @endpoint).with( :query => {'prefix' => prefix, 'marker' => marker, 'max-keys' => limit} ).to_return( {:body => mock_response(return_buckets, more)}) buckets, more = @protocol.list_buckets( :prefix => prefix, :limit => limit, :marker => marker) bucket_names = buckets.map {|b| b.name} return_bucket_names = return_buckets.map {|b| b.name} expect(bucket_names).to match_array(return_bucket_names) expect(more).to eq({ :prefix => prefix, :marker => marker, :limit => limit, :next_marker => next_marker, :truncated => true}) end end end # Bucket end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/oss/util_spec.rb000066400000000000000000000113121371637147000245750ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' module Aliyun module OSS describe Util do # 测试对body content的md5编码是否正确 it "should get correct content md5" do content = "" md5 = Util.get_content_md5(content) expect(md5).to eq("1B2M2Y8AsgTpgAmY7PhCfg==") content = "hello world" md5 = Util.get_content_md5(content) expect(md5).to eq("XrY7u+Ae7tCTyyK7j1rNww==") end # 测试签名是否正确 it "should get correct signature" do key = 'helloworld' date = 'Fri, 30 Oct 2015 07:21:00 GMT' signature = Util.get_signature(key, 'GET', {'date' => date}, {}) expect(signature).to eq("u8QKAAj/axKX4JhHXa5DYfYSPxE=") signature = Util.get_signature( key, 'PUT', {'date' => date}, {:path => '/bucket'}) expect(signature).to eq("lMKrMCJIuGygd8UsdMA+S0QOAsQ=") signature = Util.get_signature( key, 'PUT', {'date' => date, 'x-oss-copy-source' => '/bucket/object-old'}, {:path => '/bucket/object-new'}) expect(signature).to eq("McYUmBaErN//yvE9voWRhCgvsIc=") signature = Util.get_signature( key, 'PUT', {'date' => date}, {:path => '/bucket/object-new', :sub_res => {'append' => nil, 'position' => 0}}) expect(signature).to eq("7Oh2wobzeg6dw/cWYbF/2m6s6qc=") end # 测试CRC计算是否正确 it "should calculate a correct data crc" do content = "" crc = Util.crc(content) expect(crc).to eq(0) content = "hello world" crc = Util.crc(content) expect(crc).to eq(5981764153023615706) content = "test\0hello\1world\2!\3" crc = Util.crc(content) expect(crc).to eq(6745424696046691431) end # 测试CRC Combine计算是否正确 it "should calculate a correct crc that crc_a combine with crc_b" do content_a = "test\0hello\1world\2!\3" crc_a = Util.crc(content_a) expect(crc_a).to eq(6745424696046691431) content_b = "hello world" crc_b = Util.crc(content_b) expect(crc_b).to eq(5981764153023615706) crc_c = Util.crc_combine(crc_a, crc_b, content_b.size) expect(crc_c).to eq(13027479509578346683) crc_ab = Util.crc(content_a + content_b) expect(crc_ab).to eq(crc_c) crc_ab = Util.crc(content_b, crc_a) expect(crc_ab).to eq(crc_c) end # 测试CRC校验和异常处理是否正确 it "should check inconsistent crc" do expect { Util.crc_check(6745424696046691431, 6745424696046691431, 'put') }.not_to raise_error expect { Util.crc_check(6745424696046691431, 5981764153023615706, 'append') }.to raise_error(CrcInconsistentError, "The crc of append between client and oss is not inconsistent.") expect { Util.crc_check(6745424696046691431, -1, 'post') }.to raise_error(CrcInconsistentError, "The crc of post between client and oss is not inconsistent.") end it "should check bucket name valid" do expect { Util.ensure_bucket_name_valid('abc') }.not_to raise_error expect { Util.ensure_bucket_name_valid('abc123-321cba') }.not_to raise_error expect { Util.ensure_bucket_name_valid('abcdefghijklmnopqrstuvwxyz1234567890-0987654321zyxwuvtsrqponmlk') }.not_to raise_error #>63 expect { Util.ensure_bucket_name_valid('abcdefghijklmnopqrstuvwxyz1234567890-0987654321zyxwuvtsrqponmlkj') }.to raise_error(ClientError, "The bucket name is invalid.") #<3 expect { Util.ensure_bucket_name_valid('12') }.to raise_error(ClientError, "The bucket name is invalid.") #not [a-z0-9-] expect { Util.ensure_bucket_name_valid('Aabc') }.to raise_error(ClientError, "The bucket name is invalid.") expect { Util.ensure_bucket_name_valid('abc/') }.to raise_error(ClientError, "The bucket name is invalid.") expect { Util.ensure_bucket_name_valid('abc#') }.to raise_error(ClientError, "The bucket name is invalid.") expect { Util.ensure_bucket_name_valid('abc?') }.to raise_error(ClientError, "The bucket name is invalid.") #start & end not - expect { Util.ensure_bucket_name_valid('-abc') }.to raise_error(ClientError, "The bucket name is invalid.") expect { Util.ensure_bucket_name_valid('abc-') }.to raise_error(ClientError, "The bucket name is invalid.") end end # Util end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/sts/000077500000000000000000000000001371637147000222705ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/sts/client_spec.rb000066400000000000000000000115321371637147000251070ustar00rootroot00000000000000require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module STS describe Client do context "construct" do it "should setup a/k" do client = Client.new( :access_key_id => ' xxx', :access_key_secret => ' yyy ') config = client.instance_variable_get('@config') expect(config.access_key_id).to eq('xxx') expect(config.access_key_secret).to eq('yyy') end end def mock_sts(id, key, token, expiration) Nokogiri::XML::Builder.new do |xml| xml.AssumeRoleResponse { xml.RequestId '0000' xml.AssumedRoleUser { xml.arn 'arn-001' xml.AssumedRoleUserId 'id-001' } xml.Credentials { xml.AccessKeyId id xml.AccessKeySecret key xml.SecurityToken token xml.Expiration expiration.utc.iso8601 } } end.to_xml end def mock_error(code, message) Nokogiri::XML::Builder.new do |xml| xml.Error { xml.Code code xml.Message message xml.RequestId '0000' } end.to_xml end def err(msg, reqid = '0000') "#{msg} RequestId: #{reqid}" end before :all do @url = 'https://sts.aliyuncs.com' @client = Client.new(access_key_id: 'xxx', access_key_secret: 'yyy') end context "assume role" do it "should assume role" do expiration = Time.parse(Time.now.utc.iso8601) stub_request(:post, @url) .to_return(:body => mock_sts( 'sts_id', 'sts_key', 'sts_token', expiration)) token = @client.assume_role('role-1', 'app-1') rbody = nil expect(WebMock).to have_requested(:post, @url) .with { |req| rbody = req.body } params = rbody.split('&').reduce({}) { |h, i| v = i.split('=') h.merge({v[0] => v[1]}) } expect(params['Action']).to eq('AssumeRole') expect(params['RoleArn']).to eq('role-1') expect(params['RoleSessionName']).to eq('app-1') expect(params['DurationSeconds']).to eq('3600') expect(params['Format']).to eq('XML') expect(params['Version']).to eq('2015-04-01') expect(params['AccessKeyId']).to eq('xxx') expect(params.key?('Signature')).to be true expect(params.key?('SignatureNonce')).to be true expect(params['SignatureMethod']).to eq('HMAC-SHA1') expect(token.access_key_id).to eq('sts_id') expect(token.access_key_secret).to eq('sts_key') expect(token.security_token).to eq('sts_token') expect(token.expiration).to eq(expiration) end it "should raise error" do code = "InvalidParameter" message = "Bla bla bla." stub_request(:post, @url) .to_return(:status => 400, :body => mock_error(code, message)) expect { @client.assume_role('role-1', 'app-1') }.to raise_error(ServerError, err(message)) end it "should set policy and duration" do expiration = Time.parse(Time.now.utc.iso8601) stub_request(:post, @url) .to_return(:body => mock_sts( 'sts_id', 'sts_key', 'sts_token', expiration)) policy = Policy.new policy.allow( ['oss:Get*', 'oss:PutObject'], ['acs:oss:*:*:bucket', 'acs::oss:*:*:bucket/*']) duration = 300 token = @client.assume_role('role-1', 'app-1', policy, duration) rbody = nil expect(WebMock).to have_requested(:post, @url) .with { |req| rbody = req.body } params = rbody.split('&').reduce({}) { |h, i| v = i.split('=') h.merge({v[0] => CGI.unescape(v[1])}) } expect(params['Action']).to eq('AssumeRole') expect(params['RoleArn']).to eq('role-1') expect(params['RoleSessionName']).to eq('app-1') expect(params['DurationSeconds']).to eq('300') expect(params['Format']).to eq('XML') expect(params['Version']).to eq('2015-04-01') expect(params['AccessKeyId']).to eq('xxx') expect(params.key?('Signature')).to be true expect(params.key?('SignatureNonce')).to be true expect(params['SignatureMethod']).to eq('HMAC-SHA1') expect(params['Policy']).to eq(policy.serialize) expect(token.access_key_id).to eq('sts_id') expect(token.access_key_secret).to eq('sts_key') expect(token.security_token).to eq('sts_token') expect(token.expiration).to eq(expiration) end end end # Client end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/aliyun/sts/util_spec.rb000066400000000000000000000017111371637147000246040ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'spec_helper' require 'yaml' require 'nokogiri' module Aliyun module STS describe Util do it "should get correct signature" do key = 'helloworld' ts = '2015-12-07T07:18:41Z' params = { 'Action' => 'AssumeRole', 'RoleArn' => 'role-1', 'RoleSessionName' => 'app-1', 'DurationSeconds' => '300', 'Format' => 'XML', 'Version' => '2015-04-01', 'AccessKeyId' => 'xxx', 'SignatureMethod' => 'HMAC-SHA1', 'SignatureVersion' => '1.0', 'SignatureNonce' => '3.14159', 'Timestamp' => ts } signature = Util.get_signature('POST', params, key) expect(signature).to eq("92ta30QopCT4YTbRCaWtS31kyeg=") signature = Util.get_signature('GET', params, key) expect(signature).to eq("nvMmnOSxGrfK+1zf0oFR5RB2M7k=") end end # Util end # OSS end # Aliyun aliyun-aliyun-oss-ruby-sdk-1d573e7/spec/spec_helper.rb000066400000000000000000000006601371637147000227760ustar00rootroot00000000000000# -*- encoding: utf-8 -*- require 'coveralls' Coveralls.wear! require 'webmock/rspec' require 'aliyun/oss' require 'aliyun/sts' RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end end Aliyun::Common::Logging::set_log_level(Logger::DEBUG) aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/000077500000000000000000000000001371637147000203665ustar00rootroot00000000000000aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/config.rb000066400000000000000000000013251371637147000221610ustar00rootroot00000000000000class TestConf class << self def creds { access_key_id: ENV['RUBY_SDK_OSS_ID'], access_key_secret: ENV['RUBY_SDK_OSS_KEY'], download_crc_enable: ENV['RUBY_SDK_OSS_DOWNLOAD_CRC_ENABLE'], upload_crc_enable: ENV['RUBY_SDK_OSS_UPLOAD_CRC_ENABLE'], endpoint: ENV['RUBY_SDK_OSS_ENDPOINT'] } end def bucket ENV['RUBY_SDK_OSS_BUCKET'] end def sts_creds { access_key_id: ENV['RUBY_SDK_STS_ID'], access_key_secret: ENV['RUBY_SDK_STS_KEY'], endpoint: ENV['RUBY_SDK_STS_ENDPOINT'] } end def sts_role ENV['RUBY_SDK_STS_ROLE'] end def sts_bucket ENV['RUBY_SDK_STS_BUCKET'] end end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/example.jpg000066400000000000000000000525171371637147000225350ustar00rootroot00000000000000JFIFC    !'"#%%%),($+!$%$C   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?jPJ#u\L%Wf5HL1qZ[x6y\:*ݑFp0}+=6 Y4MJn.ZfhO$TbqWtfD X<{˜M[8,HZJU$56DsqsOKT^)L\@[vHY#U=j$ݑޮEv+qbtt\*r 桻ێj^Z㩇)X;XSDKMCxIҤ6ڪx1֬%q=dc*&WGrRMЍty⦿GUl:~" -CiÎDGJ:-#$$D-%BH*Gjɱκ*s]d.LdcEUX)6pkFSӚ=-ӓ+KTZ`;{T*H3F9< &#)tuw.Lt ڧ].:g>͔\ރcY^z~ہ֏bY|zoj}@Y#Ҩl\ﴮsV⫰I-꼗94J#d{z=j6ØЕ9Y3V3^)OcRkCZy'vjv68⬭ʸ*9$VT8Fi4qق:U튵Y^אkו{]fGڥka-< mkbYJ&,9ٲBV[P: pʼniw8& -ҙ%lIjxL$_ &rCp"k%̭׺1<^l'Bxl}1д3H}dɭ.1¯aEi7Nc j`0 h"aEa** Mj?9j΅- R97INS /,i&4˅)%,`ъuZflM2 `q]4ZJșj 2N_<>Kmuo*'eO9k񃊥uHH L:];+o>x~xeY ggot.-;8%z'95<஝̧YϙGJaAO>;fz ,nHToZIP;j\{]L7QJ7,Rz{Tl5^"f'IZ9R&lGթ#b潩Fz ׌);qFLVn p3ұm/&r+V8.:¨1Lx8QC޸d+ig#jA$ajE-^:"j.-jYG{2\ *G4yO,tUpyQwLSVz;)欥:DGUGg8BuoJc L\T[ ^3 ItG9ҵ<13X^nI 1a Riѳ28lV*Ŏǧz຀έCnZ;nXhTO011WQaI\(g־W[4ڒ|Px>ab;ceйU<2DG8'VN6 o'V2I)1Fj0oriA BÌV^;vus CY37b;ʻaTO]EĮ"Mk*Z]ϟuVǭ?1~85[TPjQ.ct"kq&O / F恵Nuԣ B8ajҷ9G>⺨"0RQgH8L=k2՞Uc j=#. Jĸvb8 z)&+4s^2m<[CZ~偵-`{Z{en;7D Y,Kp%8V5[5t\f6};E6UBFx.Iǵ|=4ұgs>oQ<+mk bSc'kk#\Bb}?tu>KDq4럳|WxńS"M#^MH.0q{W#~!"33dG=sڜؘ!0q}G4Y >Zfi.;c=ҽ0XTZ,uj%]L\bg5i܈_|FHfpG8]3H;ힻqZ$HNɑãcdzV2]8^E៉-mCY([HH!#8$ 8qҴqm9﹫ensX~/X@'i kR읾JG~kvN 60>]Zcl c0TmՖiff1ɫvWѻ,3@dxscS5nDADE'Lr*(b6*,ǻD8Ɛzⷮ"VMՙf⺩ovSU'@2J 6tk\LW!~gǤʒ*pkxJ-*0"ċ(}2EO`rܕ N8y t54{U!⒓e@-h%N9_=}K)HS_?<:vy1۵hʪsVӳWE)XUց(t`H4<ϵN{Tr7g 湜<9aDD%p0j[X|z֊)E t.88;zPn]yjex FG ^iQaE>785Ŗf k 0Wmzy(, J R6:q\: W⹝b/"Q2'8=56dUznҕR@[ʤjqkXI=Q-4w 8C ppXj%>W(neʥXg] 2Q@Fx"xVA<"G]Nk88;2 P:zU8n@* bHwr؛5v/ܟ-AsqƼ+"&Fqu+/+z5[mJ #IF6G]z6U^#y BH 7WWD-\34pŝEzeYָ4֛wY6{Y⌟'&]|r+N\;aZuo!ɵ "-ǩSJܱG9 s]<4ճ~n@C޸'9'%xjnY<Qhs]9tQT`bvk$u,٪?Jiik 0j9 dS#"TL~,P)8W[cO:jxb4[^MϥggbqZ[:ZFҰYBf8Iq+ӎ*6j9)Hu"hHjZU1pmi)f}ٓQkmcÎQ>`qSyb*y8jDlNZb8ޕ{ŷ{Kk5%2|΃pfuUz m2(c<^Se,}Fe}+IS#b^Ydymnpp=Gƺ{MA%`KnVj14K&D=_(Vt2u_͡^^W7Fwhag3)VVSzWOu_Jw\+- `N֧Qh0QvH_εc,) ŴEnrų߁ֽnG Ҽ;>)h6Rq»Sv,p+5Պ%%Dʀs#=*{cSi,XZ.[4s҈䔭xİi-F1^W=ђMspvOaxRnWڔgưĔ֮4i.5jďo2+Q V~uތ9bnڵƨE~ie~?Z-̡ ӽqU.kD7kYBMuq=s\5x֜퀬e<JLaw['OZ_L'z_K!VeJ(#tʁTJJ ӷd¾ ZgٲD|co^cZ'~,;Fy=ZRQ zגPrxsS[iV-9VQ>ppzak(=I+L] ? m>R<:DK D^}l2yɭPBQ_8ryMYx=Y3#_8LvAMt{T60䲹d?ʬ[ڛP7ye;xѝ J x]l[Hjil>Uaȶ)%C[#7J-Q899b n>)#V? gӊ1j_yI$t%rx `8$n 7-P*j)Aq뺱r4H|6pߙMr;T PO:!E+N -ni|&!l =rK2Mx cGF!pC csI YZ}&- ެ=+itA+皂Z 5 *JtCR\fpW[p+LRquŔkksctJ=}? ȆX~ ^7 a*^:1Wtps֣N@aźgZ11NQZ7$F@֮l%՜(9-JmV>Մ]0T{Fu5p2'Q s?c!qJY/ɔE/ڜLK i.rY?NaHw]nw6%wAЪ {tN)E~\Zn\5uՍkx%wc3:&w|+~1y$hdm y z414l{mU095ѼT8LL^jFJϕ 1OWҶuʝ5ËԔ-ȫvV\{W,XD5Yif HxL&e X^2yO6E?θU:@6)9M>>Gٚ@v؋ u{PX28^hSwOm9\Qa<$~N'}! {p>wv rykslʃ\ zWKAc]lFʶ8 ޼K]e tTcmi20 %Hbs"n C+gxs'V4-5%sY@ ki1aؙ2{wΤ/;W]qgu$֍np8 T^ҹW۞1޸tr6yVs>_]Ts 7ړYiMI6Rrͻ,A/zYg8OiKNC~J%Is긍Hb՝7Po)yX^ZY 獣"Thbsf[nN7u5nb2Ձ&]>Q4jtE' RоP#/O?Z'54uf#jLrFH)#ރN4UZ]h䟀P=4r s׊;E=Z!@8+B)ٝ/ t.UYH0+~V u Ar?N^$dv?\JtJdwַ 5k1jP̤sv3vӵSIy?1W88''*5*jyπ>bsҼHrG۟[^bgb YFl ~FY~洢`^UcœHIh͗~o'˝[]2j H 68vo"azX"^F<8{Wd3޺zzݰ[زͱ#"ZxIi^dy6mUbO FJuew(ǭY(!kle6&`9>dm%@,hbw[1@I<0##5dLopz4YFDȫ\zqS[-aw;mꊁ#l`RnŨ3o{(aJ"~?*̸Qk5R&@k[x&n.Q%IYfL`װY̪\lkuB\N N!Fz2c),b 8ܚ^M^$I ~?ioX@\l#K FK;vZ̶cpEra^\OBup (<]|SpT `6ҧHY#P99G;=pz+Ta5=ධfa$D4LWڌp#w⺭/P;c&Z:ӻn&Rw}MzX:O*6UdڪS} W+"ITtѧwvW-ЃZ> =?IVsԊ40=\FaҢR xkgQ]V4g 2C @q%B@}?Jffi#)'$W *fFWOiZ~2{O/*_κI4 ~|ʤ~BVVm@ṃ,L~~ +svȯ^_΍ެz0kXç:]HOf,8=<#lq38,N@;b;֎8 \HCv 8t^'sִ!*RT7S~5EX&3?8Wu t12>J^]adpJ]+廻1t6)O1бO*+feXBeL;Y~@]z+[FBQ8ZM2p@9PW[>U^wNFڮXOnVe7P?e+K F#s}=Um/,V{5Zی󑻡'}j7\ޟĺC]i! ϵUE <s\MuA4Yi?$0OsM{Hnlgr8H#Ӆ9 >(k٘#lu;IQj#RJMbiVOdfeh\g#}1S=,)n~eYfTB0;iSλK{EwY&G,d8?n:Ȧj:N6ҥgo+X^8`88 ݃}uY[FfH @]gUg,V>H Ԯm[ǹfvI'#^,ZMNÂNl3MLTbc5_Yw?<.A0cp,N=|wn6ye# 0#X^ d`ą<Cʮ\Lfe XdĒX{} `!#R7)/0 `߶+rjxݴe| ?R:r0^I{2,2VL55 N-!8=1@>Z8Ʒ4uZ־IJ1RUA,N2o)p _i\`qtVȓBD*zG"H>C|Piw,ϖIޣ+?#x$>Rْ7 /%=rQE`.kw8$JȠuʞq:b 2"i T y8cq$2WNH|G"/?Skfu$+8נݩᾔ$fcv_ ~k@|7N d1e' 9A5ֱQq{j.y$ԫʻ9!y@Ңpյq::qzҦR n G4~QRwsr\|dY Σ:Yn)5K%rd02IzU!0FX2[ j9SjjOwTU~)Jznbln $ڙ[Dvʆ᏿jdft{mIvQnҩ\2]@v sw%GřQI[C4ż"hWtSw=9R6&zLy(< t zd~*\383Vх-02`;tKc#zTt>$0E#$3*Nj+C|ȀXuHFpzf[kV$`yiXr[uvno{Q-e d(;E[ĊͼN6H;ΚmZWC9pjje8fBϐppjW=;hR G|n<~)n4XL#W$eu;O8jV^^$]ZR2 pFjNJ쎺WZH,-+.^I#<9.\JѤS3bxA#iB0T:}u=^'9gKrFs֎^$q6C19۾;;X]]oQ_ΑՁʐ81~Aі%%iYC9s.ig=lv>L $pG|9^CwI%Ȭd4]dFFQryo+#oj:H-i\3VՁD5.m獕>==;k/lϷlcӭ?G  >4q^/H"*<Cy@9g]oL=* [k5:RAF*Ƥ dׅ%5;;s}V;xsMIxg2j}hNX62x#tBn USxkG}s}W. $NTXCı\[m n,ZHHYt99]Jk]G1 Cc*ˎ@TIECgwP=W2&dXوة'zd2H׀M4tnO_Ueg7W)!s=2z29= $MT _1-8 RzS^hu e4o* ܎vN?/LVKuxj {]^ ӕDrn mG9qbnWs]:P_DZoL9/#>Q@8 ɽR1-9y}Gkw\d[h#vITysF@vXw:Bky2dˑQ]<'cyu{h|+yǶZp 03]^@ЭgmEö~WH+܅ztm9oPSee>H,Y],u?_w"H FTb%E:QZksU0Wյ&oh̶wpr7p31[ %pW$ ' <-3VQrUR0Fx99v͏HKn"Kga#)CU+Wm..7P]A'+{sOV1o\Lm aH`dZn,ɉY.#$`S-6HVFrXb g3 "II(t׆#杂o4xn$g+f=c5vK CVhw2RNR VdO2M9#&`syG Td װ^),bH%20rjrm^+i ZFa#g Iqd,vr[uD7vv)ܤtޟϞkxfD[L!#}q }Ύo!灎"AF .vq`[L3 e^'kAhp=`ܞ{jt: H@=qVSX[8s|q9X̹8 qc= #W@y#mA\SdoO5g9a^s3ꑛ[BQ,e)O'Mb\|EJ}Y&-ҳtkS4 ۬0\9 c Ӟ;uMD|=1#arsp#2Usi#( pǷ*I{Dն4&kBy1'bAnY>uH#ge` )#ֳH7!nӮ% %cߞ.c;[mGcgSw]b` Pq\p+3<{X~Ug 71\blW4̶³5&09`Ҷ,[O9`>s̰'PCg? U#TKP.N<J˳/eG1 >'#c]v./CRy$i$`".`'烐3U7z]Fufz[rpqNWd3ۖa-?*~3^ gqi[yZĚ^^*0kۂY#pOJ %{poف9dE%Gg;I$&t y $Cpyhn Ei^9G,kp}^PN?VV2<;R#)*C߯sO3GI~U @+EtP uV|te;ҍeRkVhΥnMidD xӏ7PHsjw (+#:E†A)X㯥UދӊdL]=[=[tDwxy?5.u&(ɑ|5x溴ERIzW>]r2v;RU-rߠLŏFӏws5JLmp+e'#v#k+yEˍ>nO&YU lv"HQ;y,8q̐G0F8{Tz#>VP-nڇEcR[;Kg摕 \|ÃQ&F (xQ׶j NF哳=+;@IoewfBx0M\-Hcj揼63s#ڜfEE DT=W8Hѓܻ=O{v=ڱL]p`&~sssaV#Y!\m4`38=y=8oz䦸+$eTPLYtgn0xYԦnmJ;2j6lJ@8Ý'>k-mlu=^#vY1qc)>M}bPmf\b+ԧ'ڳ"C7 z d2yPi 6Gʳ)myKo޻2v H-˓k2My$Mv N~ EFa=kp\D^,G+reK&eEAWʢedG$Qkv!<$8^{ZEj@'˖F 0r#+r1d8;qRw0*LBS*JQm{ScC'qv#0HU۰ z͞$->°Q4yqלba'1]zT Ϩ۵nU ?@H U5i#dEd'VvPYdqG_eLWm|/:U.iF`}k%]Paliyun-aliyun-oss-ruby-sdk-1d573e7/tests/helper.rb000066400000000000000000000004161371637147000221730ustar00rootroot00000000000000# -*- encoding: utf-8 -*- module Aliyun module Test module Helper def random_string(n) (1...n).map { (65 + rand(26)).chr }.join + "\n" end def random_bytes(n) (1...n).map { rand(255).chr }.join + "\n" end end end endaliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_bucket.rb000066400000000000000000000037341371637147000232360ustar00rootroot00000000000000require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require 'time' require_relative 'config' class TestBucket < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) @client = Aliyun::OSS::Client.new(TestConf.creds) @bucket_name = TestConf.bucket + Time.now.to_i.to_s @client.create_bucket(@bucket_name) @bucket = @client.get_bucket(@bucket_name) end def teardown @client.delete_bucket(@bucket_name) end def test_bucket_versioning ret = @bucket.versioning assert_nil ret.status @bucket.versioning = Aliyun::OSS::BucketVersioning.new(:status => 'Enabled') ret = @bucket.versioning assert_equal 'Enabled', ret.status @bucket.versioning = Aliyun::OSS::BucketVersioning.new(:status => 'Suspended') ret = @bucket.versioning assert_equal 'Suspended', ret.status end def test_bucket_encryption begin ret = @bucket.encryption assert_raises "should not here" rescue => exception end @bucket.encryption = Aliyun::OSS::BucketEncryption.new( :enable => true, :sse_algorithm => 'KMS') ret = @bucket.encryption assert_equal 'KMS', ret.sse_algorithm assert_nil ret.kms_master_key_id @bucket.encryption = Aliyun::OSS::BucketEncryption.new( :enable => true, :sse_algorithm => 'KMS', :kms_master_key_id => 'kms-id') ret = @bucket.encryption assert_equal 'KMS', ret.sse_algorithm assert_equal 'kms-id', ret.kms_master_key_id @bucket.encryption = Aliyun::OSS::BucketEncryption.new( :enable => true, :sse_algorithm => 'AES256') ret = @bucket.encryption assert_equal 'AES256', ret.sse_algorithm assert_nil ret.kms_master_key_id @bucket.encryption = Aliyun::OSS::BucketEncryption.new( :enable => false) begin ret = @bucket.encryption assert_raises "should not here" rescue => exception end end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_content_encoding.rb000066400000000000000000000024361371637147000252770ustar00rootroot00000000000000require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require 'zlib' require_relative 'config' class TestContentEncoding < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @prefix = "tests/content_encoding/" end def get_key(k) "#{@prefix}#{k}" end def test_gzip_encoding key = get_key('gzip') File.open('/tmp/x', 'w') do |f| 1000.times { f.write 'hello world' * 1024 } end @bucket.put_object( key, file: '/tmp/x', content_type: 'text/plain') @bucket.get_object( key, file: '/tmp/y', headers: {'accept-encoding' => 'gzip'}) assert File.exist?('/tmp/y') diff = `diff /tmp/x /tmp/y` assert diff.empty? end def test_deflate_encoding key = get_key('deflate') File.open('/tmp/x', 'w') do |f| 1000.times { f.write 'hello world' * 1024 } end @bucket.put_object( key, file: '/tmp/x', content_type: 'text/plain') @bucket.get_object( key, file: '/tmp/y', headers: {'accept-encoding' => 'deflate'}) assert File.exist?('/tmp/y') diff = `diff /tmp/x /tmp/y` assert diff.empty? end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_content_type.rb000066400000000000000000000071741371637147000244760ustar00rootroot00000000000000require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require_relative 'config' class TestContentType < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @types = { "html" => "text/html", "js" => "application/javascript", "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xltx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template", "potx" => "application/vnd.openxmlformats-officedocument.presentationml.template", "ppsx" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow", "pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation", "sldx" => "application/vnd.openxmlformats-officedocument.presentationml.slide", "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "dotx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template", "xlam" => "application/vnd.ms-excel.addin.macroEnabled.12", "xlsb" => "application/vnd.ms-excel.sheet.binary.macroEnabled.12", "apk" => "application/vnd.android.package-archive", "" => "application/octet-stream" } @prefix = "tests/content_type/" end def get_key(p, k) "#{@prefix}#{p}obj" + (k.empty? ? "" : ".#{k}") end def test_type_from_key @types.each do |k, v| key = get_key('from_key', k) @bucket.put_object(key) assert_equal v, @bucket.get_object(key).headers[:content_type] copy_key = get_key('copy.from_key', k) @bucket.copy_object(key, copy_key) assert_equal v, @bucket.get_object(copy_key).headers[:content_type] append_key = get_key('append.from_key', k) @bucket.append_object(append_key, 0) assert_equal v, @bucket.get_object(append_key).headers[:content_type] end end def test_type_from_file @types.each do |k, v| upload_file = "/tmp/upload_file" upload_file += ".#{k}" unless k.empty? `touch #{upload_file}` key = get_key('from_file', k) @bucket.put_object(key, :file => upload_file) assert_equal v, @bucket.get_object(key).headers[:content_type] append_key = get_key('append.from_file', k) @bucket.append_object(append_key, 0, :file => upload_file) assert_equal v, @bucket.get_object(append_key).headers[:content_type] multipart_key = get_key('multipart.from_file', k) @bucket.resumable_upload(multipart_key, upload_file) assert_equal v, @bucket.get_object(multipart_key).headers[:content_type] end end def test_type_from_user @types.each do |k, v| upload_file = "/tmp/upload_file.html" `touch #{upload_file}` key = get_key('from_user', k) @bucket.put_object(key, :file => upload_file, :content_type => v) assert_equal v, @bucket.get_object(key).headers[:content_type] copy_key = get_key('copy.from_user', k) @bucket.copy_object(key, copy_key, :content_type => v) assert_equal v, @bucket.get_object(copy_key).headers[:content_type] append_key = get_key('append.from_user', k) @bucket.append_object(append_key, 0, :file => upload_file, :content_type => v) assert_equal v, @bucket.get_object(append_key).headers[:content_type] multipart_key = get_key('multipart.from_file', k) @bucket.resumable_upload(multipart_key, upload_file, :content_type => v) assert_equal v, @bucket.get_object(multipart_key).headers[:content_type] end end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_crc_check.rb000066400000000000000000000125211371637147000236570ustar00rootroot00000000000000require 'minitest/autorun' require 'yaml' require 'tempfile' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require_relative 'config' require_relative 'helper' class TestCrcCheck < Minitest::Test include Aliyun::Test::Helper @@tests_run = 0 @@test_file = nil def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @prefix = 'tests/crc_check/' if @@tests_run == 0 @@test_file = Tempfile.new('oss_ruby_sdk_test_crc') (10 * 1024).times { @@test_file.write(random_bytes(1024)) } end @@tests_run += 1 end def teardown if @@tests_run == TestCrcCheck.runnable_methods.length @@test_file.unlink unless @@test_file.nil? end end def get_key(k) @prefix + k end def test_put_object skip unless TestConf.creds[:upload_crc_enable] # Check crc status assert(@bucket.upload_crc_enable) # Create a test file with 10MB random bytes to put. key = get_key('put_file') @bucket.put_object(key, :file => @@test_file.path) test_object = @bucket.get_object(key) assert_equal(test_object.size, 10 * 1024 * 1024) # Check crc wrong case. assert_raises Aliyun::OSS::CrcInconsistentError do @bucket.put_object(key, {:init_crc => 1, :file => @@test_file.path}) do |content| content << 'hello world.' end end # Put a string to oss. key = get_key('put_string') @bucket.put_object(key, :init_crc => 0) do |content| content << 'hello world.' end test_object = @bucket.get_object(key) assert_equal(test_object.size, 'hello world.'.size) # Check crc wrong case. assert_raises Aliyun::OSS::CrcInconsistentError do @bucket.put_object(key, :init_crc => 1) do |content| content << 'hello world.' end end ensure @bucket.delete_object(key) end def test_append_object skip unless TestConf.creds[:upload_crc_enable] key = get_key('append_file') # Check crc status assert(@bucket.upload_crc_enable) # Check $key object doesn't exist. test_object = @bucket.get_object(key) rescue 0 @bucket.delete_object(key) if test_object.size # Create a append object to oss with a string. position = @bucket.append_object(key, 0, :init_crc => 0) do |content| content << 'hello world.' end test_object = @bucket.get_object(key) assert_equal(test_object.size, 'hello world.'.size) # Append a test file to oss $key object. @bucket.append_object(key, position, {:init_crc => test_object.headers[:x_oss_hash_crc64ecma], :file => @@test_file.path}) test_object = @bucket.get_object(key) assert_equal(test_object.size, 'hello world.'.size + (10 * 1024 * 1024)) # No crc check when init_crc is nil position = @bucket.append_object(key, test_object.size) do |content| content << 'hello world.' end test_object = @bucket.get_object(key) assert_equal(test_object.size, 'hello world.'.size * 2 + (10 * 1024 * 1024)) # Check crc wrong case. assert_raises Aliyun::OSS::CrcInconsistentError do position = @bucket.append_object(key, test_object.size, :init_crc => 0) do |content| content << 'hello world.' end end # Check crc wrong case. test_object = @bucket.get_object(key) assert_raises Aliyun::OSS::CrcInconsistentError do @bucket.append_object(key, test_object.size, {:init_crc => 0, :file => @@test_file.path}) end ensure @bucket.delete_object(key) end def test_upload_object skip unless TestConf.creds[:upload_crc_enable] key = get_key('upload_file') # Check crc status assert(@bucket.upload_crc_enable) @bucket.resumable_upload(key, @@test_file.path, :cpt_file => "#{@@test_file.path}.cpt", threads: 2, :part_size => 1024 * 1024) test_object = @bucket.get_object(key) assert_equal(test_object.size, (10 * 1024 * 1024)) ensure @bucket.delete_object(key) end def test_get_small_object skip unless TestConf.creds[:download_crc_enable] # Check crc status assert(@bucket.download_crc_enable) # Put a string to oss. key = get_key('get_small_object') @bucket.put_object(key) do |content| content << 'hello world.' end temp_buf = "" test_object = @bucket.get_object(key) { |c| temp_buf << c } assert_equal(test_object.size, 'hello world.'.size) # Check crc wrong case. assert_raises Aliyun::OSS::CrcInconsistentError do @bucket.get_object(key, {:init_crc => 1}) { |c| temp_buf << c } end ensure @bucket.delete_object(key) end def test_get_large_object skip unless TestConf.creds[:download_crc_enable] # Check crc status assert(@bucket.download_crc_enable) # put a test file with 10MB random bytes to oss for testing get. key = get_key('get_file') @bucket.put_object(key, :file => @@test_file.path) get_temp_file = Tempfile.new('oss_ruby_sdk_test_crc_get') test_object = @bucket.get_object(key, {:file => get_temp_file}) assert_equal(test_object.size, 10 * 1024 * 1024) # Check crc wrong case. assert_raises Aliyun::OSS::CrcInconsistentError do @bucket.get_object(key, {:file => get_temp_file, :init_crc => 1}) end ensure get_temp_file.unlink unless get_temp_file.nil? @bucket.delete_object(key) end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_custom_headers.rb000066400000000000000000000046771371637147000247750ustar00rootroot00000000000000require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require 'zlib' require_relative 'config' class TestCustomHeaders < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @prefix = "tests/custom_headers/" end def get_key(k) "#{@prefix}#{k}" end def test_custom_headers key = get_key('ruby') cache_control = 'max-age: 3600' @bucket.put_object(key, headers: {'cache-control' => cache_control}) obj = @bucket.get_object(key) assert_equal cache_control, obj.headers[:cache_control] content_disposition = 'attachment; filename="fname.ext"' @bucket.put_object( key, headers: {'cache-control' => cache_control, 'CONTENT-DISPOSITION' => content_disposition}) obj = @bucket.get_object(key) assert_equal cache_control, obj.headers[:cache_control] assert_equal content_disposition, obj.headers[:content_disposition] content_encoding = 'deflate' expires = (Time.now + 3600).httpdate @bucket.put_object( key, headers: {'cache-control' => cache_control, 'CONTENT-DISPOSITION' => content_disposition, 'content-ENCODING' => content_encoding, 'EXPIRES' => expires }) do |s| s << Zlib::Deflate.deflate('hello world') end content = '' obj = nil if @bucket.download_crc_enable assert_raises Aliyun::OSS::CrcInconsistentError do obj = @bucket.get_object(key) { |c| content << c } end else obj = @bucket.get_object(key) { |c| content << c } assert_equal 'hello world', content assert_equal cache_control, obj.headers[:cache_control] assert_equal content_disposition, obj.headers[:content_disposition] assert_equal content_encoding, obj.headers[:content_encoding] assert_equal expires, obj.headers[:expires] end end def test_headers_overwrite key = get_key('rails') @bucket.put_object( key, content_type: 'text/html', metas: {'hello' => 'world'}, headers: {'content-type' => 'application/json', 'x-oss-meta-hello' => 'bar'}) { |s| s << 'hello world' } obj = @bucket.get_object(key) assert_equal 'application/json', obj.headers[:content_type] assert_equal ({'hello' => 'bar'}), obj.metas end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_encoding.rb000066400000000000000000000040101371637147000235330ustar00rootroot00000000000000# coding: utf-8 require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require_relative 'config' class TestEncoding < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @prefix = 'tests/encoding/' end def get_key(k) @prefix + k end def test_utf_8 key = get_key('utf-8') @bucket.put_object(key) do |stream| stream << '中国' << 'Ruby' end put = '中国Ruby'.force_encoding(Encoding::ASCII_8BIT) got = '' @bucket.get_object(key) { |c| got << c } assert_equal put, got File.open('/tmp/x', 'w') { |f| f.write('中国Ruby') } @bucket.put_object(key, :file => '/tmp/x') got = '' @bucket.get_object(key) { |c| got << c } assert_equal put, got end def test_gbk key = get_key('gbk') @bucket.put_object(key) do |stream| stream << '中国'.encode(Encoding::GBK) << 'Ruby'.encode(Encoding::GBK) end put = '中国Ruby'.encode(Encoding::GBK).force_encoding(Encoding::ASCII_8BIT) got = '' @bucket.get_object(key) { |c| got << c } assert_equal put, got File.open('/tmp/x', 'w') { |f| f.write('中国Ruby'.encode(Encoding::GBK)) } @bucket.put_object(key, :file => '/tmp/x') got = '' @bucket.get_object(key) { |c| got << c } assert_equal put, got end def encode_number(i) [i].pack('N') end def test_binary key = get_key('bin') @bucket.put_object(key) do |stream| (0..1024).each { |i| stream << encode_number(i) } end put = (0..1024).reduce('') { |s, i| s << encode_number(i) } got = '' @bucket.get_object(key) { |c| got << c } assert_equal put, got File.open('/tmp/x', 'w') { |f| (0..1024).each { |i| f.write(encode_number(i)) } } @bucket.put_object(key, :file => '/tmp/x') got = '' @bucket.get_object(key) { |c| got << c } assert_equal put, got end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_large_file.rb000066400000000000000000000032241371637147000240440ustar00rootroot00000000000000# coding: utf-8 require 'minitest/autorun' require 'benchmark' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require_relative 'config' class TestLargeFile < Minitest::Test def setup client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @prefix = 'tests/large_file/' end def get_key(k) @prefix + k end def test_large_file_1gb skip "don't run it by default" key = get_key("large_file_1gb") Benchmark.bm(32) do |bm| bm.report("Upload with put_object: ") do @bucket.put_object(key, :file => './large_file_1gb') end bm.report("Upload with resumable_upload: ") do @bucket.resumable_upload(key, './large_file_1gb') end bm.report("Download with get_object: ") do @bucket.get_object(key, :file => './large_file_1gb') end bm.report("Download with resumable_download: ") do @bucket.resumable_download(key, './large_file_1gb') end end end def test_large_file_8gb skip "don't run it by default" key = get_key("large_file_8gb") Benchmark.bm(32) do |bm| bm.report("Upload with put_object: ") do @bucket.put_object(key, :file => './large_file_8gb') end bm.report("Upload with resumable_upload: ") do @bucket.resumable_upload(key, './large_file_8gb') end bm.report("Download with get_object: ") do @bucket.get_object(key, :file => './large_file_8gb') end bm.report("Download with resumable_download: ") do @bucket.resumable_download(key, './large_file_8gb') end end end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_multipart.rb000066400000000000000000000046371371637147000240050ustar00rootroot00000000000000# coding: utf-8 require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require_relative 'config' class TestMultipart < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket_name = TestConf.bucket @bucket = client.get_bucket(TestConf.bucket) @protocol = Aliyun::OSS::Protocol.new(Aliyun::OSS::Config.new(TestConf.creds)) @prefix = 'tests/multipart/' end def get_key(k) @prefix + k end def clear_uploads all = @bucket.list_uploads.to_a all.each { |t| @bucket.abort_upload(t.id, t.object) } end def test_key_marker clear_uploads # initiate 5 uploads ids = [] 5.times { |i| id = @protocol.initiate_multipart_upload(@bucket_name, get_key("obj-#{i}")) ids << id } all = @bucket.list_uploads(limit: 1).to_a assert_equal ids, all.map(&:id) after_1 = @bucket.list_uploads(key_marker: get_key("obj-0")).to_a assert_equal ids[1, 5], after_1.map(&:id) after_5 = @bucket.list_uploads(key_marker: get_key("obj-4")).to_a assert after_5.empty?, after_5.to_s end def test_id_marker clear_uploads # initiate 5 uploads ids = [] 5.times { |i| id = @protocol.initiate_multipart_upload(@bucket_name, get_key("object")) ids << id } ids.sort! all = @bucket.list_uploads.to_a assert_equal ids, all.map(&:id) # id_marker is ignored after_1 = @bucket.list_uploads(id_marker: ids[0]).to_a assert_equal ids, after_1.map(&:id) # id_marker is ignored after_5 = @bucket.list_uploads(id_marker: ids[4]).to_a assert_equal ids, after_5.map(&:id) end def test_id_key_marker clear_uploads # initiate 5 uploads foo_ids = [] 5.times { |i| id = @protocol.initiate_multipart_upload(@bucket_name, get_key("foo")) foo_ids << id } foo_ids.sort! bar_ids = [] 5.times { |i| id = @protocol.initiate_multipart_upload(@bucket_name, get_key("bar")) bar_ids << id } bar_ids.sort! after_1 = @bucket.list_uploads( id_marker: bar_ids[0], key_marker: get_key("bar"), limit: 1).to_a assert_equal bar_ids[1, 5] + foo_ids, after_1.map(&:id) after_5 = @bucket.list_uploads( id_marker: bar_ids[4], key_marker: get_key("bar")).to_a assert_equal foo_ids, after_5.map(&:id) end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_object_acl.rb000066400000000000000000000022711371637147000240410ustar00rootroot00000000000000require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require_relative 'config' class TestObjectACL < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @prefix = "tests/object_acl/" end def get_key(k) "#{@prefix}#{k}" end def test_put_object key = get_key('put') @bucket.put_object(key, acl: Aliyun::OSS::ACL::PRIVATE) acl = @bucket.get_object_acl(key) assert_equal Aliyun::OSS::ACL::PRIVATE, acl @bucket.put_object(key, acl: Aliyun::OSS::ACL::PUBLIC_READ) acl = @bucket.get_object_acl(key) assert_equal Aliyun::OSS::ACL::PUBLIC_READ, acl end def test_append_object key = get_key('append-1') @bucket.append_object(key, 0, acl: Aliyun::OSS::ACL::PRIVATE) acl = @bucket.get_object_acl(key) assert_equal Aliyun::OSS::ACL::PRIVATE, acl key = get_key('append-2') @bucket.put_object(key, acl: Aliyun::OSS::ACL::PUBLIC_READ) acl = @bucket.get_object_acl(key) assert_equal Aliyun::OSS::ACL::PUBLIC_READ, acl end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_object_key.rb000066400000000000000000000044541371637147000240770ustar00rootroot00000000000000# coding: utf-8 require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require_relative 'config' class TestObjectKey < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @prefix = 'tests/object_key/' @keys = { simple: 'simple_key', chinese: '杭州・中国', space: '是 空格 yeah +-/\\&*#', invisible: '' << 1 << 10 << 12 << 7 << 80 << 99, specail1: 'testkey/', specail2: 'testkey/?key=value#abc=def', xml: 'ad +' } end def get_key(sym) @prefix + @keys[sym] end def test_simple key = get_key(:simple) @bucket.put_object(key) all = @bucket.list_objects(prefix: @prefix).map(&:key) assert_includes all, key assert_equal key, @bucket.get_object(key).key end def test_chinese key = get_key(:chinese) @bucket.put_object(key) all = @bucket.list_objects(prefix: @prefix).map(&:key) assert_includes all, key assert_equal key, @bucket.get_object(key).key end def test_space key = get_key(:space) @bucket.put_object(key) all = @bucket.list_objects(prefix: @prefix).map(&:key) assert_includes all, key assert_equal key, @bucket.get_object(key).key end def test_invisible key = get_key(:invisible) @bucket.put_object(key) all = @bucket.list_objects(prefix: @prefix).map(&:key) assert_includes all, key assert_equal key, @bucket.get_object(key).key end def test_specail1 key = get_key(:specail1) @bucket.put_object(key) all = @bucket.list_objects(prefix: @prefix).map(&:key) assert_includes all, key assert_equal key, @bucket.get_object(key).key end def test_specail2 key = get_key(:specail2) @bucket.put_object(key) all = @bucket.list_objects(prefix: @prefix).map(&:key) assert_includes all, key assert_equal key, @bucket.get_object(key).key end def test_batch_delete keys = @keys.map { |k, _| get_key(k) } keys.each { |k| @bucket.put_object(k) } ret = @bucket.batch_delete_objects(keys) assert_equal keys, ret all = @bucket.list_objects(prefix: @prefix).map(&:key) assert all.empty?, all.to_s end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_object_url.rb000066400000000000000000000044521371637147000241070ustar00rootroot00000000000000require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require 'aliyun/sts' require 'rest-client' require_relative 'config' class TestObjectUrl < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @prefix = "tests/object_url/" end def get_key(k) "#{@prefix}#{k}" end def test_signed_url_for_get key = get_key('object-for-get') @bucket.put_object(key, acl: Aliyun::OSS::ACL::PRIVATE) plain_url = @bucket.object_url(key, false) begin r = RestClient.get(plain_url) assert false, 'GET plain object url should receive 403' rescue => e assert_equal 403, e.response.code end signed_url = @bucket.object_url(key) r = RestClient.get(signed_url) assert_equal 200, r.code end def test_signed_url_with_sts key = get_key('object-with-sts') sts_client = Aliyun::STS::Client.new(TestConf.sts_creds) token = sts_client.assume_role(TestConf.sts_role, 'app') bucket = Aliyun::OSS::Client.new( :endpoint => TestConf.creds[:endpoint], :sts_token => token.security_token, :access_key_id => token.access_key_id, :access_key_secret => token.access_key_secret) .get_bucket(TestConf.sts_bucket) bucket.put_object(key, acl: Aliyun::OSS::ACL::PRIVATE) plain_url = bucket.object_url(key, false) begin r = RestClient.get(plain_url) assert false, 'GET plain object url should receive 403' rescue => e assert_equal 403, e.response.code end signed_url = bucket.object_url(key) r = RestClient.get(signed_url) assert_equal 200, r.code end def test_signed_url_with_parameters key = get_key('example.jpg') @bucket.put_object(key, :file => 'tests/example.jpg', acl: Aliyun::OSS::ACL::PRIVATE) meta = @bucket.get_object(key) assert_equal 21839, meta.size parameters = { 'x-oss-process' => 'image/resize,m_fill,h_100,w_100', } signed_url = @bucket.object_url(key, true, 60, parameters) r = RestClient.get(signed_url) lenth = r.headers[:content_length].to_i assert_equal 200, r.code assert_equal true, lenth < meta.size end end aliyun-aliyun-oss-ruby-sdk-1d573e7/tests/test_resumable.rb000066400000000000000000000017401371637147000237330ustar00rootroot00000000000000require 'minitest/autorun' require 'yaml' $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) require 'aliyun/oss' require_relative 'config' class TestResumable < Minitest::Test def setup Aliyun::Common::Logging.set_log_level(Logger::DEBUG) client = Aliyun::OSS::Client.new(TestConf.creds) @bucket = client.get_bucket(TestConf.bucket) @prefix = 'tests/resumable/' end def get_key(k) @prefix + k end def random_string(n) (1...n).map { (65 + rand(26)).chr }.join + "\n" end def test_correctness key = get_key('resumable') # generate 10M random data File.open('/tmp/x', 'w') do |f| (10 * 1024).times { f.write(random_string(1024)) } end # clear checkpoints `rm -rf /tmp/x.cpt && rm -rf /tmp/y.cpt` @bucket.resumable_upload(key, '/tmp/x', :part_size => 100 * 1024) @bucket.resumable_download(key, '/tmp/y', :part_size => 100 * 1024) diff = `diff /tmp/x /tmp/y` assert diff.empty?, diff end end