yubin.byb пре 10 година
родитељ
комит
2ea5044cb4
59 измењених фајлова са 16030 додато и 0 уклоњено
  1. 154 0
      README.md
  2. 100 0
      doc/index.json
  3. 593 0
      doc/上传文件.md
  4. 294 0
      doc/下载文件.md
  5. 37 0
      doc/安装.md
  6. 185 0
      doc/快速开始.md
  7. 169 0
      doc/管理Bucket.md
  8. 474 0
      doc/管理文件.md
  9. 78 0
      doc/管理生命周期.md
  10. 74 0
      doc/自定义域名绑定.md
  11. 76 0
      doc/设置访问日志.md
  12. 96 0
      doc/设置访问权限.md
  13. 88 0
      doc/设置跨域资源共享.md
  14. 73 0
      doc/设置防盗链.md
  15. 90 0
      doc/错误.md
  16. 67 0
      doc/静态网站托管.md
  17. 92 0
      oss/auth.go
  18. 462 0
      oss/bucket.go
  19. 1648 0
      oss/bucket_test.go
  20. 648 0
      oss/client.go
  21. 1204 0
      oss/client_test.go
  22. 49 0
      oss/conf.go
  23. 339 0
      oss/conn.go
  24. 125 0
      oss/conn_test.go
  25. 79 0
      oss/const.go
  26. 60 0
      oss/error.go
  27. 245 0
      oss/mime.go
  28. 337 0
      oss/multipart.go
  29. 935 0
      oss/multipart_test.go
  30. 287 0
      oss/option.go
  31. 251 0
      oss/option_test.go
  32. 424 0
      oss/type.go
  33. 127 0
      oss/type_test.go
  34. 159 0
      oss/utils.go
  35. 106 0
      oss/utils_test.go
  36. 35 0
      sample.go
  37. BIN
      sample/BingWallpaper-2015-11-07.jpg
  38. 3663 0
      sample/The Go Programming Language.html
  39. 156 0
      sample/append_object.go
  40. 43 0
      sample/bucket_acl.go
  41. 71 0
      sample/bucket_cors.go
  42. 68 0
      sample/bucket_lifecycle.go
  43. 90 0
      sample/bucket_logging.go
  44. 57 0
      sample/bucket_referer.go
  45. 96 0
      sample/cname_sample.go
  46. 123 0
      sample/comm.go
  47. 25 0
      sample/config.go
  48. 89 0
      sample/copy_object.go
  49. 50 0
      sample/create_bucket.go
  50. 108 0
      sample/delete_object.go
  51. 143 0
      sample/get_object.go
  52. 128 0
      sample/list_buckets.go
  53. 147 0
      sample/list_objects.go
  54. 223 0
      sample/multipart_copy.go
  55. 230 0
      sample/multipart_upload.go
  56. 50 0
      sample/new_bucket.go
  57. 44 0
      sample/object_acl.go
  58. 73 0
      sample/object_meta.go
  59. 93 0
      sample/put_object.go

+ 154 - 0
README.md

@@ -0,0 +1,154 @@
+# Aliyun OSS SDK for Go
+## 关于
+> - 此Go SDK基于[阿里云对象存储服务](http://www.aliyun.com/product/oss/)官方API构建。
+> - 阿里云对象存储(Object Storage Service,简称OSS),是阿里云对外提供的海量,安全,低成本,高可靠的云存储服务。
+> - OSS适合存放任意文件类型,适合各种网站、开发企业及开发者使用。
+> - 使用此SDK,用户可以方便地在任何应用、任何时间、任何地点上传,下载和管理数据。
+
+## 版本
+> 当前版本:0.1.1
+
+## 运行环境
+> - 推荐使用Go 1.4及以上。
+
+## 安装方法
+### GitHub安装
+> - 执行命令`go get github.com/aliyun/aliyun-oss-go-sdk/oss`获取远程代码包。
+> - 在您的代码中使用`import "github.com/aliyun/aliyun-oss-go-sdk/oss"`引入OSS Go SDK的包。
+
+## 快速使用
+#### 获取存储空间列表(List Bucket)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    lsRes, err := client.ListBuckets()
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    for _, bucket := range lsRes.Buckets {
+        fmt.Println("Buckets:", bucket.Name)
+    }
+```
+
+#### 创建存储空间(Create Bucket)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.CreateBucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+    
+#### 删除存储空间(Delete Bucket)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.DeleteBucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### 上传文件(Put Object)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.PutObjectFromFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### 下载文件 (Get Object)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.GetObjectToFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### 获取文件列表(List Objects)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    lsRes, err := bucket.ListObjects()
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    for _, object := range lsRes.Objects {
+        fmt.Println("Objects:", object.Key)
+    }
+```
+    
+#### 删除文件(Delete Object)
+```go
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.DeleteObject("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### 其它
+更多的示例程序,请参看OSS Go SDK安装路径(即GOPATH变量中的第一个路径)下的`src\github.com\aliyun\aliyun-oss-go-sdk\sample`,该目录下为示例程序,
+或者参看`https://github.com/aliyun/aliyun-oss-go-sdk`下sample目录中的示例文件。
+
+## 注意事项
+### 运行sample
+> - 拷贝示例文件。到OSS Go SDK的安装路径(即GOPATH变量中的第一个路径),进入OSS Go SDK的代码目录`src\github.com\aliyun\aliyun-oss-go-sdk`,
+把其下的sample目录和sample.go复制到您的测试工程src目录下。
+> - 修改sample/config.go里的endpoint、AccessKeyId、AccessKeySecret、BucketName等配置。
+> - 请在您的工程目录下执行`go run src/sample.go`。
+
+## 联系我们
+- [阿里云OSS官方网站](http://oss.aliyun.com)
+- [阿里云OSS官方论坛](http://bbs.aliyun.com)
+- [阿里云OSS官方文档中心](http://www.aliyun.com/product/oss#Docs)
+- 阿里云官方技术支持:[提交工单](https://workorder.console.aliyun.com/#/ticket/createIndex)

+ 100 - 0
doc/index.json

@@ -0,0 +1,100 @@
+[
+  {
+    "key": "安装",
+    "name_en": "install",
+    "name_cn": "安装",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "快速开始",
+    "name_en": "getting-started",
+    "name_cn": "快速开始",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "管理Bucket",
+    "name_en": "bucket-management",
+    "name_cn": "管理Bucket",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "上传文件",
+    "name_en": "put-object",
+    "name_cn": "上传文件",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "下载文件",
+    "name_en": "get-object",
+    "name_cn": "下载文件",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "管理文件",
+    "name_en": "object-management",
+    "name_cn": "管理文件",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "自定义域名绑定",
+    "name_en": "custom-domain",
+    "name_cn": "自定义域名绑定",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "设置访问权限",
+    "name_en": "acl",
+    "name_cn": "设置访问权限",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "管理生命周期",
+    "name_en": "lifecycle-management",
+    "name_cn": "管理生命周期",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "设置访问日志",
+    "name_en": "object-logging",
+    "name_cn": "设置访问日志",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "静态网站托管",
+    "name_en": "website-hosting",
+    "name_cn": "静态网站托管",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "设置防盗链",
+    "name_en": "anti-hotlinking",
+    "name_cn": "设置防盗链",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "设置跨域资源共享",
+    "name_en": "cors",
+    "name_cn": "设置跨域资源共享",
+    "tag": [],
+    "isFolder": false
+  },
+  {
+    "key": "错误",
+    "name_en": "error",
+    "name_cn": "错误",
+    "tag": [],
+    "isFolder": false
+  }
+]

+ 593 - 0
doc/上传文件.md

@@ -0,0 +1,593 @@
+# 上传文件
+在OSS中,用户操作的基本数据单元是文件(Object)。
+单个文件最大允许大小根据上传数据方式不同而不同,Put Object方式最大不能超过5GB, 使用multipart上传方式文件大小不能超过48TB。
+
+## 简单上传
+
+### 从数据流(io.Reader)中读取数据上传
+
+通过Bucket.PutObject完成简单上传。
+
+> 提示:
+> 
+> - 简单上传的示例代码在`sample/put_object.go`。
+
+#### 字符串(string)上传
+```go
+    import "strings"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+  
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.PutObject("my-object", strings.NewReader("MyObjectValue"))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### byte数组上传
+```go
+    import "bytes"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+   
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.PutObject("my-object", bytes.NewReader([]byte("MyObjectValue")))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+#### 本地文件上传
+```go
+    import "os"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+   
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    fd, err := os.Open("LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+    defer fd.Close()
+    
+    err = bucket.PutObject("my-object", fd)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+### 根据本地文件名上传
+
+通过Bucket.PutObjectFromFile可以上传指定的本地文件,把本地文件内容作为Object的值。
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.PutObjectFromFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 注意:
+> 
+> - 使用上述方法上传最大文件不能超过5G。如果超过请使用分片上传。
+
+
+#### 上传时指定元信息
+
+使用数据流上传文件时,用户可以指定一个或多个文件(Object)的元信息。元数据的名称大小写不敏感,比如用户上传文件时,定义名字为“name”的meta,使用Bucket.GetObjectDetailedMeta读取结果是:“X-Oss-Meta-Name”,比较/读取时请忽略大小写。
+
+可以指定的元信息如下:
+
+| 参数 | 说明 |
+| :--- | :--- |
+| CacheControl | 指定该Object被下载时的网页的缓存行为。|
+| ContentDisposition | 指定该Object被下载时的名称。|
+| ContentEncoding | 指定该Object被下载时的内容编码格式。|
+| Expires | 指定过期时间。用户自定义格式,建议使用http.TimeFormat格式。|
+| ServerSideEncryption | 指定oss创建object时的服务器端加密编码算法。合法值:AES256。|
+| ObjectACL | 指定oss创建object时的访问权限。|
+| Meta | 自定义参数,以"X-Oss-Meta-"为前缀的参数。|
+
+```go
+    import (
+        "strings"
+        "time"
+        "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+    
+	client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+	if err != nil {
+		// HandleError(err)
+	}
+
+	bucket, err := client.Bucket("my-bucket")
+	if err != nil {
+		// HandleError(err)
+	}
+
+	expires := time.Date(2049, time.January, 10, 23, 0, 0, 0, time.UTC)
+	options := []oss.Option{
+		oss.Expires(expires),
+		oss.ObjectACL(oss.ACLPublicRead),
+		oss.Meta("MyProp", "MyPropVal"),
+	}
+	err = bucket.PutObject("my-object", strings.NewReader("MyObjectValue"), options...)
+	if err != nil {
+		// HandleError(err)
+	}
+```
+
+> 提示:
+> - Bucket.PutObject、Bucket.PutObjectFromFile都支持上传时指定元数据。
+
+
+## 创建模拟文件夹
+
+OSS服务是没有文件夹这个概念的,所有元素都是以文件来存储。但给用户提供了创建模拟文件夹的方式,如下代码:
+```go
+    import "strings"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+   
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.PutObject("my-dir/", strings.NewReader(""))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 提示:
+> 
+> - 创建模拟文件夹本质上来说是创建了一个空文件。
+> - 对于这个文件照样可以上传下载,只是控制台会对以"/"结尾的文件以文件夹的方式展示。
+> - 所以用户可以使用上述方式来实现创建模拟文件夹。
+> - 而对文件夹的访问可以参看[文件夹功能模拟]({{doc/[5]SDK/Go-SDK/管理文件.md}})
+>
+
+## 追加上传
+OSS支持可追加的文件类型,通过`Bucket.AppendObject`来上传可追加的文件,
+调用时需要指定文件追加的位置,对于新创建文件,这个位置是0;对于已经存
+在的文件,这个位置必须是追加前文件的长度。
+
+- 文件不存在时,调用`AppendObject`会创建一个可追加的文件;
+- 文件存在时,调用`AppendObject`会向文件末尾追加内容。
+
+> 提示:
+> - 追加上传的示例代码在`sample/append_object.go`。
+
+```go
+    import "strings"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    var nextPos int64 = 0
+    // 第一次追加的位置是0,返回值为下一次追加的位置
+    nextPos, err = bucket.AppendObject("my-object", strings.NewReader("YourObjectValue"), nextPos)
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 第二次追加
+    nextPos, err = bucket.AppendObject("my-object", strings.NewReader("YourObjectValue"), nextPos)
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 您还可以进行多次Append
+```
+
+> 注意:
+> 
+> - 只能向可追加的文件(即通过`AppendObject`创建的文件)追加内容
+> - 可追加的文件不能被拷贝
+> 
+
+第一次追加时,即位置开始位置是0的追加,您可以指定文件(Object)的元信息;除了第一次追加,其它追加不能指定元信息。
+```go
+    // 第一次追加指定元信息
+    nextPos, err = bucket.AppendObject("my-object", strings.NewReader("YourObjectValue"), 0, oss.Meta("MyProp", "MyPropVal"))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## 分片上传
+除了通过PutObject接口上传文件到OSS以外,OSS还提供了另外一种上传模式 —— Multipart Upload。
+用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload上传模式,如:
+
+- 需要支持断点上传。
+- 上传超过100MB大小的文件。
+- 网络条件较差,和OSS的服务器之间的链接经常断开。
+- 上传文件之前,无法确定上传文件的大小。
+
+### 封装后的分片上传
+其实现原理是将要上传的文件分成若干个分片上传,最后所有分片都上传成功后,完成整个文件的上传。
+所以用户需要指定分片的大小,单位是Bytes。分片的最小值是100KB,最大值是5GB,请根据网络情况选择合适的分片大小。
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 分片大小是1MB
+    err = bucket.UploadFile("my-object", "LocalFile", 1024 * 1024)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 提示:
+> 
+> - 分片上传Bucket.UploadFile时,您可以指定文件(Object)的元信息。
+>
+```go
+    imur, err := bucket.UploadFile("my-object", "LocalFile", 1024 * 1024, oss.Meta("MyProp", "MyPropVal"))
+    if err != nil {
+            // HandleError(err)
+    }
+```
+>
+
+### 分步完成MultipartUpload
+
+分片上传(MultipartUpload)一般的流程如下:
+
+1. 初始化一个分片上传任务(InitiateMultipartUpload)
+2. 逐个或并行上传分片(UploadPart)
+3. 完成上传(CompleteMultipartUpload)
+
+> 提示:
+> 
+> - 分片上传的示例代码在`samples/multipart_upload.go`
+
+#### 按照片数/片大小分片上传
+本地有一个大文件bigfile.zip,将其分成10片上传到OSS中。
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 按照指定片数分割大文件
+    chunks, err := oss.SplitFileByPartNum("bigfile.zip", 10)
+    if err != nil {
+        // HandleError(err)
+    }
+
+    // 初始化分片上传任务
+    imur, err := bucket.InitiateMultipartUpload("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 上传分片
+    parts := []oss.UploadPart{}
+    for _, chunk := range chunks {
+        part, err := bucket.UploadPartFromFile(imur, "bigfile.zip", chunk.Offset,
+            chunk.Size, chunk.Number)
+        if err != nil {
+            // HandleError(err)
+        }
+        parts = append(parts, part)
+    }
+
+    // 完成上传
+    _, err = bucket.CompleteMultipartUpload(imur, parts)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 注意:
+> 
+> - 上面程序的核心是调用UploadPart方法来上传每一个分片,但是要注意以下几点:
+> - UploadPart 方法要求除最后一个Part以外,其他的Part大小都要大于100KB。但是Upload Part接口并不会立即校验上传
+Part的大小(因为不知道是否为最后一片);只有当Complete Multipart Upload的时候才会校验。
+> - OSS会将服务器端收到Part数据的MD5值放在ETag头内返回给用户。
+> - 为了保证数据在网络传输过程中不出现错误,SDK会自动设置Content-MD5,OSS会计算上传数据的MD5值与SDK计算的MD5值比较,
+如果不一致返回InvalidDigest错误码。
+> - Part号码的范围是1~10000。如果超出这个范围,OSS将返回InvalidArgument的错误码。
+> - 每次上传part时都要把流定位到此次上传片开头所对应的位置。
+> - 每次上传part之后,OSS的返回结果会包含一个 PartETag 对象,他是上传片的ETag与片编号(PartNumber)的组合,
+> - 在后续完成分片上传的步骤中会用到它,因此我们需要将其保存起来。一般来讲我们将这些 PartETag 对象保存到List中。
+    
+    
+> 提示:
+> 
+> - 您还可以调用oss.SplitFileByPartSize把文件按照大小切分,然后分片删除。如bigfile.zip按照1M分片:
+>
+```go
+    chunks, err := oss.SplitFileByPartSize("bigfile.zip", 1024*1024)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+>
+> - 初始化上传任务时候,您可以指定文件(Object)的元信息。
+>
+```go
+    imur, err := bucket.InitiateMultipartUpload("my-object", oss.Meta("MyProp", "MyPropVal"))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+>
+> - 初始化上传任务后,或者上传一部分分片后,由于某种原因需要取消任务,您可以使用AbortMultipartUpload取消分片上传任务,其中参数imur是InitiateMultipartUpload的返回值。
+>
+```go
+    err = bucket.AbortMultipartUpload(imur)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+>
+
+#### 分片分组上传
+上面的分片上传需要先知道文件的大小,但是某种情况下文件大小是不可预知,这种情况可以通过指定文件的偏移量和片大小分别上传分片。
+```go
+    import "os"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 上传文件的前3个分片,分片大小是1MB
+    chunks := []oss.FileChunk {
+        {Number: 1, Offset: 0 * 1024 * 1024 , Size: 1024 * 1024},
+        {Number: 2, Offset: 1 * 1024 * 1024 , Size: 1024 * 1024},
+        {Number: 3, Offset: 2 * 1024 * 1024 , Size: 1024 * 1024},
+    }
+    
+    fd, err := os.Open("bigfile.zip")
+    if err != nil {
+        // HandleError(err)
+    }
+    defer fd.Close()
+    
+    // 初始化分片上传任务
+    imur, err := bucket.InitiateMultipartUpload("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 上传前3个分片
+    parts := []oss.UploadPart{}
+    for _, chunk := range chunks {
+        fd.Seek(chunk.Offset, os.SEEK_SET)
+        part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number)
+        if err != nil {
+            // HandleError(err)
+        }
+        parts = append(parts, part)
+    }
+    
+    // 后面的分片在文件增长后再上传
+    // ... ...
+    
+    // 所有分片上传完毕后,完成上传
+    _, err = bucket.CompleteMultipartUpload(imur, parts)
+    if err != nil {
+        // HandleError(err)
+    }
+```   
+
+#### 并发上传分片
+分片上传时,多个分片可以并发上传,或者由不同进程甚至不同机器完成。
+```go
+    import (
+        "sync"
+        "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+    
+	client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 把文件分成10片
+    partNum := 10
+	chunks, err := oss.SplitFileByPartNum("LocalFile", partNum)
+	if err != nil {
+		// HandleError(err)
+	}
+
+    // 初始化上传任务
+	imur, err := bucket.InitiateMultipartUpload("my-object")
+	if err != nil {
+		// HandleError(err)
+	}
+
+	// 并发上传分片
+	var waitgroup sync.WaitGroup
+	var parts = make([]oss.UploadPart, partNum)
+	for _, chunk := range chunks {
+		waitgroup.Add(1)
+		go func(chunk oss.FileChunk) {
+			part, err := bucket.UploadPartFromFile(imur, "LocalFile", chunk.Offset,
+				chunk.Size, chunk.Number)
+			if err != nil {
+				// HandleError(err)
+			}
+			parts[chunk.Number - 1] = part
+			waitgroup.Done()
+		}(chunk)
+	}
+	
+	// 等待分片上传完成
+	waitgroup.Wait()
+
+	// 提交上传任务
+	_, err = bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		// HandleError(err)
+	}
+```
+
+
+### 获取所有已上传的片信息
+您可以用Bucket.ListUploadedParts获取某个上传事件所有已上传的分片。
+```go 
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    imur, err := bucket.InitiateMultipartUpload("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    lsRes, err := bucket.ListUploadedParts(imur)
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Parts:", lsRes.UploadedParts)
+```
+
+
+### 获取所有分片上传的任务
+通过`Bucket.ListMultipartUploads`来列出当前分片上传任务。主要的参数如下:
+
+| 参数 | 说明 |
+| :--- | :--- |
+| Delimiter | 用于对Object名字进行分组的字符。所有名字包含指定的前缀且第一次出现delimiter字符之间的object作为一组元素。 
+| MaxUploads | 限定此次返回Multipart Uploads事件的最大数目,默认为1000,max-uploads取值不能大于1000。
+| KeyMarker | 所有Object名字的字典序大于KeyMarker参数值的Multipart事件。 
+| Prefix  | 限定返回的文件名(object)必须以Prefix作为前缀。注意使用Prefix查询时,返回的文件名(Object)中仍会包含Prefix。   
+
+#### 使用默认参数
+```go 
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    lsRes, err := bucket.ListMultipartUploads()
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Uploads:", lsRes.Uploads)
+```
+
+#### 指定前缀
+```go     
+    lsRes, err := bucket.ListMultipartUploads(oss.Prefix("my-object-"))
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Uploads:", lsRes.Uploads)
+```
+
+#### 指定最多返回100条结果数据
+```go     
+    lsRes, err := bucket.ListMultipartUploads(oss.MaxUploads(100))
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Uploads:", lsRes.Uploads)
+```
+
+#### 同时指定前缀和最大返回条数
+```go     
+    lsRes, err := bucket.ListMultipartUploads(oss.Prefix("my-object-"), oss.MaxUploads(100))
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Uploads:", lsRes.Uploads)
+```

+ 294 - 0
doc/下载文件.md

@@ -0,0 +1,294 @@
+# 下载文件(Object)
+
+OSS Go SDK提供了丰富的文件下载接口,用户可以通过以下方式从OSS中下载文件(Object):
+
+- 下载到数据流io.ReadCloser
+- 下载到本地文件
+- 分段下载
+
+## 简单下载
+
+> 提示:
+> 
+> - 简单下载的示例代码在`sample/get_object.go`。
+> 
+
+### 下载文件到数据流io.ReadCloser
+```go
+    import (
+        "fmt"
+        "io/ioutil"
+        "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+	body, err := bucket.GetObject("my-object")
+	if err != nil {
+		// HandleError(err)
+	}
+	data, err := ioutil.ReadAll(body)
+	if err != nil {
+		// HandleError(err)
+	}
+	body.Close()
+	fmt.Println("data:", string(data))
+```
+
+> 注意:
+> 
+> - io.ReadCloser数据读取完毕后,需要调用Close关闭。
+> 
+
+### 下载文件到缓存中
+```go
+    import (
+        "bytes"
+        "io"
+        "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+    
+	client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    body, err := bucket.GetObject("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+	buf := new(bytes.Buffer)
+	io.Copy(buf, body)
+	body.Close()
+```
+
+### 下载文件到本地文件流中
+```go
+    import (
+            "io"
+            "os"
+            "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+
+	client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    body, err := bucket.GetObject("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+    defer body.Close()
+	
+	fd, err := os.OpenFile("LocalFile", os.O_WRONLY|os.O_CREATE, 0660)
+    if err != nil {
+        // HandleError(err)  
+    }
+    defer fd.Close()
+	
+	io.Copy(fd, body)
+```
+
+### 下载文件(Object)到本地文件
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.GetObjectToFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+### 下载文件时指定限定条件
+
+下载文件时,用户可以指定一个或多个限定条件,所有的限定条件都满足时下载,不满足时报错不下载文件。
+可以使用的限定条件如下:
+
+|参数|说明|
+|:---|:---|
+|IfModifiedSince|如果指定的时间早于实际修改时间,则正常传送。否则返回错误。|
+|IfUnmodifiedSince|如果传入参数中的时间等于或者晚于文件实际修改时间,则正常传输文件;否则返回错误。|
+|IfMatch|如果传入期望的ETag和object的 ETag匹配,则正常传输;否则返回错误。|
+|IfNoneMatch|如果传入的ETag值和Object的ETag不匹配,则正常传输;否则返回错误。|
+
+```go
+    import "time"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    date := time.Date(2015, time.November, 10, 23, 0, 0, 0, time.UTC)
+    
+    // 限定条件不满足,不下载文件
+    err = bucket.GetObjectToFile("my-object", "LocalFile", oss.IfModifiedSince(date))
+    if err == nil {
+        // HandleError(err)
+    }
+    
+    // 满足限定条件,下载文件
+    err = bucket.GetObjectToFile("my-object", "LocalFile", oss.IfUnmodifiedSince(date))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 提示:
+> 
+> - ETag的值可以通过Bucket.GetObjectDetailedMeta获取。
+> - Bucket.GetObject,Bucket.GetObjectToFile都支持限定条件。
+>
+
+### 文件压缩下载
+文件可以压缩下载,目前支持GZIP压缩。
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.GetObjectToFile("my-object.txt", "LocalFile.gzip", oss.AcceptEncoding("gzip"))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## 分段下载
+当下载大文件时,如果网络不稳定或者程序崩溃了,则整个下载就失败了。用户
+不得不重头再来,这样做不仅浪费资源,在网络不稳定的情况下,往往重试多次
+还是无法完成下载。分段下载就是把文件分成多个小段,逐个下载。
+
+### 封装分段下载
+可以调用Bucket.DownloadFile完成分段下载。分段下载是将大文件分成段,分别下载每段。它有以下参数:
+
+- "my-object" 要下载的Object名字。
+- filePath 下载到本地文件的路径。
+- partSize 分段大小,单位是Bytes,最小值1B,最大5GB。
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 分段大小是1MB
+    err = bucket.DownloadFile("my-object", "LocalFile", 1024 * 1024)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 提示:
+> 
+> - Bucket.DownloadFile支持IfModifiedSince、IfUnmodifiedSince、IfMatch、IfNoneMatch限定条件。
+>
+
+### GetObject实现分段下载
+Bucket.GetObject/Bucket.GetObjectToFile,支持选项Range指定下载文件(Object)的范围。如设定Range为 bytes=0-9,表示传送第0到第9这10个字符,如果不符合范围规范,则传送全部内容。通过Range参数可以支持分段下载。
+
+> 提示:
+> 
+> - 分段下载的示例代码在`sample/get_object.go`。
+
+```go
+    import (
+        "io"
+        "os"
+        "strconv"
+        "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 获取文件(Object)长度
+    meta, err := bucket.GetObjectDetailedMeta("my-object")
+    if err != nil {
+        // HandleError(err)  
+    }
+    
+    // 分段大小是1MB
+    var partSize int64 = 1024 * 1024
+    objectSize, err := strconv.ParseInt(meta.Get(oss.HTTPHeaderContentLength), 10, 0)
+    
+    fd, err := os.OpenFile("LocalFile", os.O_WRONLY|os.O_CREATE, 0660)
+    if err != nil {
+        // HandleError(err)  
+    }
+    defer fd.Close()
+    
+    // 分段下载
+    for i := int64(0); i < objectSize; i += partSize {
+        option := oss.Range(i, oss.GetPartEnd(i, objectSize, partSize))
+		body, err := bucket.GetObject("my-object", option)
+		if err != nil {
+			// HandleError(err)
+		}
+		io.Copy(fd, body)
+		body.Close()
+    }
+```
+
+> 提示:
+> 
+> - 通过Range参数,您可以并发下载大文件。
+> - 分段大小,单位是Bytes,最小值1B,最大5GB。
+> - 使用GetObject下载,LocalFile需要用户创建并打开。
+> - GetObjectToFile每次写文件都是追加写,支持分段下载。

+ 37 - 0
doc/安装.md

@@ -0,0 +1,37 @@
+# SDK安装
+
+- github地址:https://github.com/aliyun/aliyun-oss-go-sdk
+- API文档地址:http://www.godoc.info/gems/aliyun-sdk/
+
+### 要求
+
+- 开通阿里云OSS服务,并创建了AccessKeyId 和AccessKeySecret。
+- 如果您还没有开通或者还不了解阿里云OSS服务,请登录
+  [OSS产品主页](http://www.aliyun.com/product/oss)了解。
+- 如果还没有创建AccessKeyId和AccessKeySecret,请到
+  [阿里云Access Key管理](https://ak-console.aliyun.com/#/accesskey)创
+  建Access Key。
+- 您已经安装了Go编译运行环境。如果您未安装,请参考[Go安装](https://golang.org/doc/install/source)下载安装编译运行环境。
+  下载安装Go编译运行环境。Go安装完毕后请正确设置GOPATH变量,如果您需要了解更多GOPATH,请执行命令`go help gopath`。
+  
+### 安装
+
+#### GitHub安装
+> - 执行命令`go get github.com/aliyun/aliyun-oss-go-sdk/oss`获取远程代码包。
+> - 在您的代码中使用`import "github.com/aliyun/aliyun-oss-go-sdk/oss"`引入OSS Go SDK的包。
+> - 如果您需要查看示例程序,请到`https://github.com/aliyun/aliyun-oss-go-sdk/`下的smaple目录。
+>
+
+> 提示:
+> 
+> - 使用`go get`命令安装过程中,界面不会打印提示,如果网络较差,需要一段时间,请耐心等待。如果安装过程中发生超时,请再次执行`go get`安装。 
+> - 安装成功后,在您的安装路径下(即GOPATH变量中的第一个路径),会有Go Sdk的库`pkg/linux_amd64/github.com/aliyun/aliyun-oss-go-sdk/oss.a`(win在`pkg\windows_amd64\github.com\aliyun\aliyun-oss-go-sdk\oss.a`),
+源文件在`src/github.com/aliyun/aliyun-oss-go-sdk`,如果没有请重新安装。
+
+### 运行sample
+
+#### GitHub安装运行sample
+> - 拷贝示例文件。到OSS Go SDK的安装路径(即GOPATH变量中的第一个路径),进入OSS Go SDK的代码目录`src\github.com\aliyun\aliyun-oss-go-sdk`,
+把其下的sample目录和sample.go复制到您的测试工程src目录下。
+> - 修改sample/config.go里的endpoint、AccessKeyId、AccessKeySecret、BucketName等配置。
+> - 请在您的工程目录下执行`go run src/sample.go`。

+ 185 - 0
doc/快速开始.md

@@ -0,0 +1,185 @@
+# 快速开始
+
+下面介绍如何使用OSS Go SDK来访问OSS服务,包括查看Bucket列表,查看文
+件列表,上传/下载文件和删除文件。以下操作代码您需要引入oss的包,使用`import "oss"`(源码安装)或`import github.com/aliyun/aliyun-oss-go-sdk/oss`(GitHub安装),后面的示例中全部使用`import "oss"`,不再特殊说明。
+
+## 初始化Client
+
+初始化Client,即创建Client:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 提示:
+> 
+> - Endpoint的是OSS的访问域名,如杭州数据中的访问域名是`http://oss-cn-hangzhou.aliyuncs.com`,更
+> 详细的说明请参考[OSS访问域名](https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/endpoint.html)。
+> - AccessKeyId和AccessKeySecret是OSS的访问密钥。更详细的说明请参考[OSS访问控制](https://help.aliyun.com/document_detail/oss/user_guide/security_management/access_control.html)。
+> - 您运行示例程序时,请将Endpoint,AccessKeyId和AccessKeySecret替换成您的实际配置。
+>
+
+## 查看Bucket列表
+
+通过Client.ListBuckets查看Bucket列表:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    lsRes, err := client.ListBuckets()
+    if err != nil {
+        // HandleError(err)
+    } 
+
+    for _, bucket := range lsRes.Buckets {
+        fmt.Println("bucket:", bucket.Name)
+    }
+```
+
+> **注:**
+> 
+> 1. Bucket的命名规范请查看[创建Bucket]({{doc/[2]Get-Started/快速开始.md}})
+> 2. Bucket名字不能与OSS服务中其他用户已有的Bucket重复,所以你需要选择一个独
+>    特的Bucket名字以避免创建失败
+
+## 获取Bucket
+Bucket的操作有Client的方法完成,如创建/删除Bucket、设置/清除Bucket的权限/生命周期/防盗链等,Object的操作有Bucket的方法完成,如上传/下载/删除文件、设置Object的访问权限等。用户可以通过Client.Bucket获取指定Bucket的操作句柄。
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## 查看文件列表
+
+通过Bucket.ListObjects查看Bucket中的文件列表:
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    lsRes, err := bucket.ListObjects()
+    if err != nil {
+        // HandleError(err)
+    }
+
+    for _, object := range lsRes.Objects {
+        fmt.Println("Object:", object.Key)
+    }
+```
+
+## 上传文件
+
+通过Bucket.PutObjectFromFile上传文件:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.PutObjectFromFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+其中`LocalFile`是需要上传的本地文件的路径。上传成功后,可以通过
+`Bucket.ListObjects`来查看。
+
+## 下载文件
+
+通过Bucket.GetObject下载文件:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.GetObjectToFile("my-object", "LocalFile")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+其中`LocalFile`是文件保存的路径。下载成功后,可以打开文件查看其内容。
+
+## 删除文件
+
+通过Bucket.DeleteObject从Bucket中删除文件:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    err = bucket.DeleteObject("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+删除文件后可以通过`Bucket.ListObjects`来查看文件确实已经被删除。
+
+
+## 了解更多
+
+- [管理Bucket]({{doc/[5]SDK/Go-SDK/管理Bucket.md}})
+- [上传文件]({{doc/[5]SDK/Go-SDK/上传文件.md}})
+- [下载文件]({{doc/[5]SDK/Go-SDK/下载文件.md}})
+- [管理文件]({{doc/[5]SDK/Go-SDK/管理文件.md}})
+- [自定义域名绑定]({{doc/[5]SDK/Go-SDK/自定义域名绑定.md}})
+- [设置访问权限]({{doc/[5]SDK/Go-SDK/设置访问权限.md}})
+- [管理生命周期]({{doc/[5]SDK/Go-SDK/管理生命周期.md}})
+- [设置访问日志]({{doc/[5]SDK/Go-SDK/设置访问日志.md}})
+- [静态网站托管]({{doc/[5]SDK/Go-SDK/静态网站托管.md}})
+- [设置防盗链]({{doc/[5]SDK/Go-SDK/设置防盗链.md}})
+- [设置跨域资源共享]({{doc/[5]SDK/Go-SDK/设置跨域资源共享.md}})
+- [错误]({{doc/[5]SDK/Go-SDK/错误.md}})
+

+ 169 - 0
doc/管理Bucket.md

@@ -0,0 +1,169 @@
+# 管理存储空间(Bucket)
+
+存储空间(Bucket)是OSS上的命名空间,也是计费、权限控制、日志记录等高级功能的管理实体。
+
+## 查看所有Bucket
+
+使用`Client.ListBuckets`接口列出当前用户下的所有Bucket,用户还可以指
+定`Prefix`等参数,列出Bucket名字为特定前缀的所有Bucket:
+
+> 提示:
+> 
+> - ListBuckets的示例代码在`sample/list_buckets.go`。
+>
+
+```go
+    import (
+        "fmt"
+        "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+  
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 列出Bucket,默认100条。
+    lsRes, err := client.ListBuckets()
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("buckets:", lsRes.Buckets)
+    
+    // 指定前缀筛选
+    lsRes, err = client.ListBuckets(oss.Prefix("my-bucket"))
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("buckets:", lsRes.Buckets)
+```
+
+## 创建Bucket
+
+> 提示:
+> 
+> - CreateBucket的示例代码在`sample/create_bucket.go`。
+>
+
+使用`Client.CreateBucket`接口创建一个Bucket,用户需要指定Bucket的名字:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.CreateBucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+创建Bucket时不指定权限,使用默认权限oss.ACLPrivate。创建时用户可以指定Bucket的权限:
+```go
+    err = client.CreateBucket("my-bucket", oss.ACL(oss.ACLPublicRead))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 注意:
+> 
+> - Bucket的命名规范请查看[创建Bucket]({{doc/[2]Get-Started/快速开始.md}})
+> - 由于存储空间的名字是全局唯一的,所以必须保证您的Bucket名字不与别人的重复
+
+## 删除Bucket
+
+使用`Client.DeleteBucket`接口删除一个Bucket,用户需要指定Bucket的名字:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.DeleteBucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 注意:
+> 
+> - 如果该Bucket下还有文件存在,则需要先删除所有文件才能删除Bucket
+> - 如果该Bucket下还有未完成的上传请求,则需要通过`Bucket.ListMultipartUploads`和
+>   `Bucket.AbortMultipartUpload`先取消那些请求才能删除Bucket。用法请参考
+>   [API文档][sdk-api]
+
+## 查看Bucket是否存在
+
+用户可以通过`Client.IsBucketExist`接口查看当前用户的某个Bucket是否存在:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    isExist, err := client.IsBucketExist("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## Bucket访问权限
+
+用户可以设置Bucket的访问权限,允许或者禁止匿名用户对其内容进行读写。更
+多关于访问权限的内容请参考[访问权限][bucket-acl]
+
+> 提示:
+> 
+> - Bucket访问权限的示例代码`sample/bucket_acl.go`。
+>
+
+### 查看Bucket的访问权限
+
+通过`Client.GetBucketACL`查看Bucket的ACL:
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    aclRes, err := client.GetBucketACL("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Bucket ACL:", aclRes.ACL)
+```
+
+### 设置Bucket的访问权限(ACL)
+
+通过`Client.SetBucketACL`设置Bucket的ACL:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.SetBucketACL("my-bucket", oss.ACLPublicRead)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 提示:
+> 
+> - Bucket有三种权限私有读写、公共读私有写、公共读写,分布对应Go sdk的常量ACLPrivate、ACLPublicRead、ACLPublicReadWrite。
+>
+
+[sdk-api]: http://www.rubydoc.info/gems/aliyun-sdk/0.1.6
+[oss-regions]: http://help.aliyun.com/document_detail/oss/user_guide/oss_concept/endpoint.html
+[bucket-acl]: http://help.aliyun.com/document_detail/oss/user_guide/security_management/access_control.html

+ 474 - 0
doc/管理文件.md

@@ -0,0 +1,474 @@
+# 管理文件
+
+一个Bucket下可能有非常多的文件,SDK提供一系列的接口方便用户管理文件。
+
+## 列出存储空间中的文件
+通过`Bucket.ListObjects`来列出当前Bucket下的文件。主要的参数如下:
+
+| 参数 | 说明 |
+| :--- | :--- |
+| Delimiter | 用于对Object名字进行分组的字符。所有名字包含指定的前缀且第一次出现delimiter字符之间的object作为一组元素CommonPrefixes |
+| Prefix | 限定返回的object key必须以prefix作为前缀。注意使用prefix查询时,返回的key中仍会包含prefix |
+| MaxKeys | 限定此次返回object的最大数,如果不设定,默认为100,max-keys取值不能大于1000 |
+| Marker | 设定结果从marker之后按字母排序的第一个开始返回 | 
+
+> 提示:
+> 
+> - ListObjects的示例代码在`sample/list_objects.go`
+>
+
+### 使用默认参数获取存储空间的文件列表,默认返回100条Object。
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    lsRes, err := bucket.ListObjects()
+    if err != nil {
+        // HandleError(err)
+    }
+
+    fmt.Println("Objects:", lsRes.Objects)
+```
+
+### 指定最大返回数量,最多不能超过1000条。
+```go
+    lsRes, err := bucket.ListObjects(oss.MaxKeys(200))
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Objects:", lsRes.Objects)
+```
+
+### 返回指定前缀的Object,默认最多返回100条。
+```go
+    lsRes, err := bucket.ListObjects(oss.Prefix("my-object-"))
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Objects:", lsRes.Objects)
+```
+
+### 指定从某个Object(my-object-xx)后返回,默认最多100条。
+```go
+    lsRes, err := bucket.ListObjects(oss.Marker("my-object-xx"))
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Objects:", lsRes.Objects)
+```
+
+### 分页获取所有Object,每次返回200条。
+```go
+    marker := oss.Marker("")
+    for {
+        lsRes, err := bucket.ListObjects(oss.MaxKeys(200), marker)
+        if err != nil {
+            HandleError(err)
+        }
+        marker = oss.Marker(lsRes.NextMarker)
+        
+        fmt.Println("Objects:", lsRes.Objects)
+        
+        if !lsRes.IsTruncated {
+            break
+        }
+    }
+```
+
+### 分页所有获取从某个(my-object-xx)之后的object,每次返回50条。
+```go
+    marker = oss.Marker("my-object-xx")
+    for {
+        lsRes, err := bucket.ListObjects(oss.MaxKeys(50), marker)
+        if err != nil {
+            // HandleError(err)
+        }
+        
+        marker = oss.Marker(lsRes.NextMarker)
+        
+        fmt.Println("Objects:", lsRes.Objects)
+        
+        if !lsRes.IsTruncated {
+            break
+        }
+    }
+```
+ 
+### 分页所有获取前缀为my-object-的object,每次返回80个。
+```go
+    prefix := oss.Prefix("my-object-")
+    marker := oss.Marker("")
+    for {
+        lsRes, err := bucket.ListObjects(oss.MaxKeys(80), marker, prefix)
+        if err != nil {
+            // HandleError(err)
+        }
+        
+        prefix = oss.Prefix(lsRes.Prefix)
+        marker = oss.Marker(lsRes.NextMarker)
+        
+        fmt.Println("Objects:", lsRes.Objects)
+        
+        if !lsRes.IsTruncated {
+            break
+        }
+    }
+```
+
+### 模拟目录结构
+
+OSS是基于对象的存储服务,没有目录的概念。存储在一个Bucket中所有文件都
+是通过文件的key唯一标识,并没有层级的结构。这种结构可以让OSS的存储非常
+高效,但是用户管理文件时希望能够像传统的文件系统一样把文件分门别类放到
+不同的“目录”下面。通过OSS提供的“公共前缀”的功能,也可以很方便地模拟目录
+结构。公共前缀的概念请参考 [列出Object]({{doc/[8]用户手册/管理文件/列出Object.md}}) 。
+
+假设Bucket中已有如下文件:
+
+    foo/x
+    foo/y
+    foo/bar/a
+    foo/bar/b
+    foo/hello/C/1
+    foo/hello/C/2
+
+通过ListObjects,列出指定目录下的文件和子目录:
+
+```go
+    lsRes, err := bucket.ListObjects(oss.Prefix("foo/"), oss.Delimiter("/"))
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Objects:", lsRes.Objects,"SubDir:", lsRes.CommonPrefixes)
+```
+
+结果中lsRes.Objects为文件,包括foo/x、foo/y;lsRes.CommonPrefixes即子目录,包括foo/bar/、foo/hello/。
+
+
+## 判断文件是否存在
+
+通过Bucket.IsObjectExist来判断文件是否存在。
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    isExist, err := bucket.IsObjectExist("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+``` 
+
+
+## 删除一个文件
+
+通过`Bucket.DeleteObject`来删除某个文件:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.DeleteObject("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## 删除多个文件
+
+通过`Bucket.DeleteObjects`来删除多个文件,用户可以通过`DeleteObjectsQuiet`参
+数来指定是否返回删除的结果。默认返回删除结果。
+
+> 提示:
+> - 删除文件的示例代码在`sample/delete_object.go`
+
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 默认返回删除成功的文件
+    delRes, err := bucket.DeleteObjects([]string{"my-object-1", "my-object-2"})
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Deleted Objects:", delRes.DeletedObjects)
+    
+    // 不返回删除的结果
+    _, err = bucket.DeleteObjects([]string{"my-object-3", "my-object-4"}, 
+        oss.DeleteObjectsQuiet(true))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 注意:
+> 
+> - Bucket.DeleteObjects至少有一个ObjectKey,不能为空。
+> - Bucket.DeleteObjects使用的Go的xml包,该包实现了XML1.0标准,XML1.0不支持的特性请不要使用。
+>
+
+## 获取文件的文件元信息(Object Meta)
+
+OSS上传/拷贝文件时,除了文件内容,还可以指定文件的一些属性信息,称为“元信息”。这些信息在上传时与文件一起存储,在下载时与文件一起返回。
+
+在SDK中文件元信息用一个`Map`表示,其他key和value都是`string`类型,并
+且都**只能是简单的ASCII可见字符,不能包含换行。** 所有元信息的总大小不
+能超过8KB。
+
+> 注意:
+> 
+> - 因为文件元信息在上传/下载时是附在HTTP Headers中, HTTP协议规定不能
+>   包含复杂字符。
+> - 元数据的名称大小写不敏感,比较/读取时请忽略大小写。
+
+使用Bucket.GetObjectDetailedMeta来获取Object的元信息。
+
+> 提示:
+> - 元信息的示例代码在`sample/object_meta.go`
+
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    props, err := bucket.GetObjectDetailedMeta("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Object Meta:", props)
+``` 
+
+> 提示:
+> 
+> - Bucket.GetObjectMeta的结果中不包括Object的权限,获取Object权限通过Bucket.GetObjectACL。
+> 
+
+## 修改文件元信息(Object Meta)
+
+用户一次修改一条或多条元信息,可用元信息如下:
+
+|元信息|说明|
+|:---|:---|
+|CacheControl|指定新Object被下载时的网页的缓存行为。|
+|ContentDisposition|指定新Object被下载时的名称。|
+|ContentEncoding|指定新Object被下载时的内容编码格式。|
+|Expires|指定新Object过期时间,建议使用GMT格式。|
+|Meta|自定义参数,以"X-Oss-Meta-"为前缀的参数。|
+
+使用Bucket.SetObjectMeta来设置Object的元信息。
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 一次修改一条Meta
+    err = bucket.SetObjectMeta("my-object", oss.Meta("MyMeta", "MyMetaValue"))
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 修改多条Meta
+    options := []oss.Option{
+        oss.Meta("MyMeta", "MyMetaValue"),
+        oss.Meta("MyObjectLocation", "HangZhou"),
+    }
+    err = bucket.SetObjectMeta("my-object", options...)
+    if err != nil {
+        // HandleError(err)
+    }
+```  
+
+## 拷贝文件
+
+使用`Bucket.CopyObject`或`Bucket.CopyObjectToBucket`拷贝文件,前者是同一个Bucket内的文件拷贝,后者是Bucket之间的文件拷贝。
+
+> 提示:
+> - 拷贝文件的示例代码在`sample/copy_objects.go`
+
+### 同一个Bucket内的文件拷贝
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    _, err = bucket.CopyObject("my-object", "descObjectKey")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+### 不同Bucket之间的文件拷贝
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    _, err = bucket.CopyObjectToBucket("my-object", "my-bucket-desc", "descObjectKey")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+### 拷贝时处理文件元信息
+
+文件拷贝(CopyObject/CopyObjectToBucket)时对文件元信息的处理有两种选择,通过`MetadataDirective`参数指定:
+
+- oss.MetaCopy 与源文件相同,即拷贝源文件的元信息
+- oss.MetaReplace 使用新的元信息覆盖源文件的信息
+
+默认值是oss.MetaCopy。
+
+COPY时,MetadataDirective为MetaReplace时,用户可以指定新对象的如下的元信息:
+
+|元信息|说明|
+|:---|:---|
+|CacheControl|指定新Object被下载时的网页的缓存行为。|
+|ContentDisposition|指定新Object被下载时的名称。|
+|ContentEncoding|指定新Object被下载时的内容编码格式。|
+|Expires|指定新Object过期时间(seconds)更详细描述请参照RFC2616。|
+|ServerSideEncryption|指定OSS创建新Object时的服务器端加密编码算法。|
+|ObjectACL|指定OSS创建新Object时的访问权限。 |
+|Meta|自定义参数,以"X-Oss-Meta-"为前缀的参数。|
+
+```go
+    import (
+        "time"
+        "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+        
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    expires := time.Date(2049, time.January, 10, 23, 0, 0, 0, time.UTC)
+    options := []oss.Option{
+        oss.MetadataDirective(oss.MetaReplace),
+        oss.Expires(expires),
+        oss.ObjectACL(oss.ACLPublicRead),
+        oss.Meta("MyMeta", "MyMetaValue")}
+    
+    _, err = bucket.CopyObject("my-object", "descObjectKey", options...)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+### 限定拷贝条件
+文件拷贝时可以设置限定条件,条件满足时拷贝,不满足时报错不拷贝。可以使用的限定条件如下:
+
+|参数|说明|
+|:---|:---|
+|CopySourceIfMatch|如果源Object的ETAG值和用户提供的ETAG相等,则执行拷贝操作;否则返回错误。|
+|CopySourceIfNoneMatch|如果源Object的ETAG值和用户提供的ETAG不相等,则执行拷贝操作;否则返回错误。|
+|CopySourceIfModifiedSince|如果传入参数中的时间等于或者晚于源文件实际修改时间,则正常拷贝;否则返回错误。|
+|CopySourceIfUnmodifiedSince|如果源Object自从用户指定的时间以后被修改过,则执行拷贝操作;否则返回错误。|
+
+```go
+    import (
+        "fmt"
+        "time"
+        "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    date := time.Date(2015, time.November, 10, 23, 0, 0, 0, time.UTC)
+    // 约束条件不满足,拷贝没有执行
+    _, err = bucket.CopyObject("my-object", "descObjectKey", oss.CopySourceIfModifiedSince(date))
+    fmt.Println("CopyObjectError:", err)
+    
+    // 约束条件满足,拷贝执行
+    _, err = bucket.CopyObject("my-object", "descObjectKey", oss.CopySourceIfUnmodifiedSince(date))
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 提示:
+> 
+> - Bucket.CopyObject、Bucket.CopyObjectToBucket都支持拷贝时处理文件元信息、限定拷贝条件。
+> 

+ 78 - 0
doc/管理生命周期.md

@@ -0,0 +1,78 @@
+# 管理生命周期(Lifecycle)
+
+OSS允许用户对Bucket设置生命周期规则,以自动淘汰过期掉的文件,节省存储
+空间。用户可以同时设置多条规则,一条规则包含:
+
+- 规则ID,用于标识一条规则,不能重复
+- 受影响的文件前缀,此规则只作用于符合前缀的文件
+- 过期时间,有两种指定方式:
+  1. 指定距文件最后修改时间N天过期
+  2. 指定在具体的某一天过期,即在那天之后符合前缀的文件将会过期,**而
+     不论文件的最后修改时间**。不推荐使用。
+- 是否生效
+
+更多关于生命周期的内容请参考 [文件生命周期]({{doc/[8]用户手册/管理文件/Object生命周期管理.md}})
+
+> 提示:
+> 
+> - 管理生命周期的示例代码在`sample/bucket_lifecycle.go`。
+
+## 设置生命周期规则
+
+通过`Client.SetBucketLifecycle`来设置生命周期规则:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // id:"rule1", enable:true, prefix:"foo/", expiry:Days 3
+    rule1 := oss.BuildLifecycleRuleByDays("rule1", "foo/", true, 3)
+    // id:"rule2", enable:false, prefix:"bar/", expiry:Date 2016/1/1
+    rule2 := oss.BuildLifecycleRuleByDate("rule2", "bar/", true, 2016, 1, 1)
+    rules := []oss.LifecycleRule{rule1, rule2}
+    
+    err = client.SetBucketLifecycle("my-bucket", rules)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## 查看生命周期规则
+
+通过`Client.GetBucketLifecycle`来查看生命周期规则:
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    lcRes, err := client.GetBucketLifecycle("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Lifecycle Rules:", lcRes.Rules)
+```
+
+## 清空生命周期规则
+
+通过`Client.DeleteBucketLifecycle`设置一个空的Rule数组来清空生命周期规则:
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.DeleteBucketLifecycle("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```   

+ 74 - 0
doc/自定义域名绑定.md

@@ -0,0 +1,74 @@
+# 自定义域名绑定
+
+OSS支持用户将自定义的域名绑定到OSS服务上,这样能够支持用户无缝地将存储
+迁移到OSS上。例如用户的域名是my-domain.com,之前用户的所有图片资源都是
+形如`http://img.my-domain.com/x.jpg`的格式,用户将图片存储迁移到OSS之
+后,通过绑定自定义域名,仍可以使用原来的地址访问到图片:
+
+- 开通OSS服务并创建Bucket
+- 将img.my-domain.com与创建的Bucket绑定
+- 将图片上传到OSS的这个Bucket中
+- 修改域名的DNS配置,增加一个CNAME记录,将img.my-domain.com指向OSS服务
+  的endpoint(如my-bucket.oss-cn-hangzhou.aliyuncs.com)
+
+这样就可以通过原地址`http://img.my-domain.com/x.jpg`访问到存储在OSS上
+的图片。
+绑定自定义域名请参考[自定义域名绑定]({{doc/[8]用户手册/基本概念/自定义域名访问OSS.md}})
+
+在使用SDK时,也可以使用自定义域名作为endpoint,这时需要将`UseCname`参数
+设置为true,如下面的例子:
+
+> 提示:
+> 
+> - 跨域资源共享的示例代码在`sample/cname_sample.go`。
+
+```go
+    import ( 
+    	"fmt"
+        "io/ioutil"
+        "strings"
+        "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    )
+        
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret", oss.UseCname(true))
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = bucket.PutObject("my-object", strings.NewReader("MyObjectValue"))
+    if err != nil {
+        // HandleError(err)
+    }
+    
+	body, err := bucket.GetObject("my-object")
+	if err != nil {
+		// HandleError(err)
+	}
+	data, err := ioutil.ReadAll(body)
+	if err != nil {
+		// HandleError(err)
+	}
+	body.Close()
+	data = data  // 处理数据
+    
+    lsRes, err := bucket.ListObjects()
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Objects:", lsRes.Objects)
+    
+    err = bucket.DeleteObject("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+> 注意:
+> 
+> - 使用CNAME时,无法使用list_buckets接口。(因为自定义域名已经绑定到
+>   某个特定的Bucket)

+ 76 - 0
doc/设置访问日志.md

@@ -0,0 +1,76 @@
+# 设置访问日志(Logging)
+
+OSS允许用户对Bucket设置访问日志记录,设置之后对于Bucket的访问会被记录
+成日志,日志存储在OSS上由用户指定的Bucket中,文件的格式为:
+
+    <TargetPrefix><SourceBucket>-YYYY-mm-DD-HH-MM-SS-UniqueString
+
+其中`TargetPrefix`由用户指定。日志规则由以下3项组成:
+
+- enable,是否开启
+- target_bucket,存放日志文件的Bucket
+- target_prefix,指定最终被保存的访问日志文件前缀
+
+更多关于访问日志的内容请参考 [Bucket访问日志]({{doc/[8]用户手册/安全管理/设置访问日志记录.md}})
+
+> 提示:
+> 
+> - Bucket访问权限设置的示例代码在`sample/bucket_logging.go`。
+
+## 开启Bucket日志
+
+通过`Client.SetBucketLogging`来开启日志功能:
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // target_bucket:"my-target-bucket", target_prefix:"my-object-", enable: true
+    err = client.SetBucketLogging("my-bucket", "my-target-bucket", "my-object-", true)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## 查看Bucket日志设置
+
+通过`Client.GetBucketLogging`来查看日志设置:
+
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    logRes, err := client.GetBucketLogging("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Target Bucket:", logRes.LoggingEnabled.TargetBucket, 
+                "Target Prefix:", logRes.LoggingEnabled.TargetPrefix)
+```
+
+## 关闭Bucket日志
+
+通过`Bucket.DeleteBucketLogging`来关闭日志功能:
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.DeleteBucketLogging("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```

+ 96 - 0
doc/设置访问权限.md

@@ -0,0 +1,96 @@
+# 设置访问权限(ACL)
+
+OSS允许用户对Bucket和Object分别设置访问权限,方便用户控制自己的资源可
+以被如何访问。对于Bucket,有三种访问权限:
+
+- public-read-write 允许匿名用户向该Bucket中创建/获取/删除Object
+- public-read 允许匿名用户获取该Bucket中的Object
+- private 不允许匿名访问,所有的访问都要经过签名
+
+创建Bucket时,默认是private权限。之后用户可以通过`Client.SetBucketACL`来设置
+Bucket的权限。上面三种权限分布对应Go SDK中的常量ACLPublicReadWrite、ACLPublicRead、ACLPrivate。
+
+## Bucket访问权限
+
+> 提示:
+> 
+> - Bucket访问权限设置的示例代码在`sample/bucket_acl.go`。
+
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 设置Bucket ACL
+    err = client.SetBucketACL("my-bucket", oss.ACLPublicRead)
+    if err != nil {
+        // HandleError(err)
+    }
+
+    // 查看Bucket ACL
+    aclRes, err := client.GetBucketACL("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Bucket ACL:", aclRes.ACL)
+```
+
+## Object访问权限
+
+对于Object,有四种访问权限:
+
+- default 继承所属的Bucket的访问权限,即与所属Bucket的权限值一样
+- public-read-write 允许匿名用户读写该Object
+- public-read 允许匿名用户读该Object
+- private 不允许匿名访问,所有的访问都要经过签名
+
+创建Object时,默认为default权限。之后用户可以通过
+`Bucket.SetObjectACL`来设置Object的权限。上面四种权限分布对应
+Go SDK中的常量ACLDefault、ACLPublicReadWrite、ACLPublicRead、ACLPrivate。
+
+> 提示:
+> 
+> - Object访问权限设置的示例代码在`sample/object_acl.go`。
+
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    bucket, err := client.Bucket("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // 设置Object的访问权限
+    err = bucket.SetObjectACL("my-object", oss.ACLPrivate)
+    if err != nil {
+        // HandleError(err)
+    }
+
+    // 查看Object的访问权限
+    aclRes, err := bucket.GetObjectACL("my-object")
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Object ACL:", aclRes.ACL)
+    
+```
+
+
+> 注意:
+>
+> - 如果设置了Object的权限(非default),则访问该Object时进行权限认证时
+   会优先判断Object的权限,而Bucket的权限设置会被忽略。
+> -  允许匿名访问时(设置了public-read或者public-read-write权限),用户
+   可以直接通过浏览器访问,例如 : `http://bucket-name.oss-cn-hangzhou.aliyuncs.com/object.jpg`
+
+更多关于访问权限控制的内容请参考 [访问控制]({{doc/[8]用户手册/安全管理/访问控制.md}})

+ 88 - 0
doc/设置跨域资源共享.md

@@ -0,0 +1,88 @@
+# 设置跨域资源共享(CORS)
+
+跨域资源共享(CORS)允许web端的应用程序访问不属于本域的资源。OSS提供接口
+方便开发者控制跨域访问的权限。更多关于跨域资源共享的内容请参考
+[OSS跨域资源共享]({{doc/[8]用户手册/安全管理/设置跨域访问.md}})
+
+OSS的跨域共享设置由一条或多条CORS规则组成,每条CORS规则包含以下设置:
+
+- allowed_origins,允许的跨域请求的来源,如www.my-domain.com, *
+- allowed_methods,允许的跨域请求的HTTP方法(PUT/POST/GET/DELETE/HEAD)
+- allowed_headers,在OPTIONS预取指令中允许的header,如x-oss-test, *
+- expose_headers,允许用户从应用程序中访问的响应头
+- max_age_seconds, 浏览器对特定资源的预取(OPTIONS)请求返回结果的缓存时间
+
+> 提示:
+> 
+> - 跨域资源共享的示例代码在`sample/bucket_cors.go`。
+
+## 设置CORS规则
+
+通过`Client.SetBucketCORS`设置CORS规则:
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    rule1 := oss.CORSRule{
+        AllowedOrigin: []string{"*"},
+        AllowedMethod: []string{"PUT", "GET"},
+        AllowedHeader: []string{},
+        ExposeHeader:  []string{},
+        MaxAgeSeconds: 200,
+    }
+    
+    rule2 := oss.CORSRule{
+        AllowedOrigin: []string{"http://www.a.com", "http://www.b.com"},
+        AllowedMethod: []string{"POST"},
+        AllowedHeader: []string{"Authorization"},
+        ExposeHeader:  []string{"x-oss-test", "x-oss-test1"},
+        MaxAgeSeconds: 100,
+    }
+    
+    err = client.SetBucketCORS("my-bucket", []oss.CORSRule{rule1, rule2})
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## 查看CORS规则
+
+通过`Client.GetBucketCORS`查看CORS规则:
+
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    corsRes, err := client.GetBucketCORS("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Bucket CORS:", corsRes.CORSRules)
+```
+
+## 清空CORS规则
+
+通过`Client.DeleteBucketCORS`清空CORS规则
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.DeleteBucketCORS("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```

+ 73 - 0
doc/设置防盗链.md

@@ -0,0 +1,73 @@
+# 设置防盗链(Referer)
+
+OSS是按使用收费的服务,为了防止用户在OSS上的数据被其他人盗链,OSS支持
+基于HTTP header中表头字段referer的防盗链方法。更多OSS防盗链请参考:
+[OSS防盗链]({{doc/[8]用户手册/安全管理/设置防盗链.md}})
+
+> 提示:
+> 
+> - 设置防盗链的示例代码在`sample/bucket_referer.go`。
+
+## 设置Referer白名单
+
+通过`Bucket.SetBucketReferer`设置Referer白名单,该函数有三个参数:
+    
+- bucketName 存储空间名称。 
+- referers 访问白名单列表。一个bucket可以支持多个referer参数。eferer参数支持通配符"*"和"?"。
+- allowEmptyReferer 指定是否允许referer字段为空的请求访问。默认配置为true。
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    referers := []string{"http://www.aliyun.com",
+                         "http://www.???.aliyuncs.com",
+                         "http://www.*.com"}
+    err = client.SetBucketReferer("my-bucket", referers, false)
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## 查看Referer白名单
+
+通过`Bucket.GetBucketReferer`设置Referer白名单:
+
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    refRes, err := client.GetBucketReferer("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+    fmt.Println("Referers:", refRes.RefererList, 
+                "AllowEmptyReferer:", refRes.AllowEmptyReferer)
+```
+
+## 清空Referer白名单
+
+清空Referer白名单,即把白名单设置成空,allowEmptyReferer为true:
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.SetBucketReferer("my-bucket", []string{}, true)
+    if err != nil {
+        // HandleError(err)
+    }
+```

+ 90 - 0
doc/错误.md

@@ -0,0 +1,90 @@
+# 错误(error)
+
+Go中调用出错,会统一返回接口error,该接口定义如下:
+```go
+    type error interface {
+        Error() string
+    }
+```
+其它的错误继承于该接口。比如HTTP错误请求返回的错误如下:
+```go
+    //net.Error 
+    type Error interface {
+        error
+        Timeout() bool   // Is the error a timeout
+        Temporary() bool // Is the error temporary
+    }
+```
+使用OSS Go SDK时如果请求出错,会有相应的error返回。HTTP请求、IO等错误会返回Go预定的错误。
+OSS Server处理请求出错,返回如下的错误,该错误实现了error接口。
+```go
+    type ServiceError struct {
+        Code       string    // OSS返回给用户的错误码
+        Message    string    // OSS给出的详细错误信息
+        RequestId  string    // 用于唯一标识该次请求的UUID
+        HostId     string    // 用于标识访问的OSS集群
+        StatusCode int       // HTTP状态码
+    }
+```
+如果OSS返回的HTTP状态码与预期不符返回如下错误,该错误也实现了error接口。
+```go
+    type UnexpectedStatusCodeError struct {
+        allowed []int    // 预期OSS返回HTTP状态码
+        got     int      // OSS实际返回HTTP状态码
+    }
+```
+
+## OSS的错误码
+OSS的错误码列表如下:
+
+| 错误码 | 描述 | HTTP状态码 |
+| :--- | :--- | :--- |
+|AccessDenied | 拒绝访问 | 403 |
+|BucketAlreadyExists | Bucket已经存在 | 409 |
+|BucketNotEmpty | Bucket不为空 | 409 |
+|EntityTooLarge | 实体过大 | 400 |
+|EntityTooSmall | 实体过小 | 400 |
+|FileGroupTooLarge | 文件组过大 | 400 |
+|InvalidLinkName | Object Link与指向的Object同名 | 400 |
+|LinkPartNotExist | Object Link中指向的Object不存在 | 400 |
+|ObjectLinkTooLarge | Object Link中Object个数过多 | 400 |
+|FieldItemTooLong | Post请求中表单域过大 | 400 |
+|FilePartInterity | 文件Part已改变 | 400 |
+|FilePartNotExist | 文件Part不存在 | 400 |
+|FilePartStale | 文件Part过时 | 400 |
+|IncorrectNumberOfFilesInPOSTRequest | Post请求中文件个数非法 | 400 |
+|InvalidArgument | 参数格式错误 | 400 |
+|InvalidAccessKeyId | AccessKeyId不存在 | 403 |
+|InvalidBucketName | 无效的Bucket名字 | 400 |
+|InvalidDigest | 无效的摘要 | 400 |
+|InvalidEncryptionAlgorithmError | 指定的熵编码加密算法错误 | 400 |
+|InvalidObjectName | 无效的Object名字 | 400 |
+|InvalidPart | 无效的Part | 400 |
+|InvalidPartOrder | 无效的part顺序 | 400 |
+|InvalidPolicyDocument | 无效的Policy文档 | 400 |
+|InvalidTargetBucketForLogging | Logging操作中有无效的目标bucket | 400 |
+|InternalError | OSS内部发生错误 | 500 |
+|MalformedXML | XML格式非法 | 400 |
+|MalformedPOSTRequest | Post请求的body格式非法 | 400 |
+|MaxPOSTPreDataLengthExceededError | Post请求上传文件内容之外的body过大 | 400 |
+|MethodNotAllowed | 不支持的方法 | 405 |
+|MissingArgument | 缺少参数 | 411 |
+|MissingContentLength | 缺少内容长度 | 411 |
+|NoSuchBucket | Bucket不存在 | 404 |
+|NoSuchKey | 文件不存在 | 404 |
+|NoSuchUpload | Multipart Upload ID不存在 | 404 |
+|NotImplemented | 无法处理的方法 | 501 |
+|PreconditionFailed | 预处理错误 | 412 |
+|RequestTimeTooSkewed | 发起请求的时间和服务器时间超出15分钟 | 403 |
+|RequestTimeout | 请求超时 | 400 |
+|RequestIsNotMultiPartContent | Post请求content-type非法 | 400 |
+|SignatureDoesNotMatch | 签名错误 | 403 |
+|TooManyBuckets | 用户的Bucket数目超过限制 | 400 |
+|InvalidEncryptionAlgorithmError | 指定的熵编码加密算法错误 | 400 |
+
+
+> 提示:
+> 
+> - 上表的错误码即OssServiceError.Code,HTTP状态码即OssServiceError.StatusCode。
+> - 如果试图以OSS不支持的操作来访问某个资源,返回405 Method Not Allowed错误。
+> 

+ 67 - 0
doc/静态网站托管.md

@@ -0,0 +1,67 @@
+# 托管静态网站(Website)
+
+在[自定义域名绑定]({{doc/[5]SDK/Ruby-SDK/自定义域名绑定.md}})中提到,OSS
+允许用户将自己的域名指向OSS服务的地址。这样用户访问他的网站的时候,实
+际上是在访问OSS的Bucket。对于网站,需要指定首页(index)和出错页(error)
+分别对应的Bucket中的文件名。
+
+更多关于静态网站托管的内容请参考 [OSS静态网站托管]({{doc/[8]用户手册/静态网站托管.md}})
+
+## 设置托管页面
+
+通过`Client.SetBucketWebsite`来设置托管页面:
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    // bucketName:"my-bucket", indexWebsite:"index.html", errorWebsite:"error.html"
+    err = client.SetBucketWebsite("my-bucket", "index.html", "error.html")
+    if err != nil {
+        // HandleError(err)
+    }
+```
+
+## 查看托管页面
+
+通过`Client.GetBucketWebsite`来查看托管页面:
+
+```go
+    import "fmt"
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    wsRes, err := client.GetBucketWebsite("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+
+    fmt.Println("indexWebsite:", wsRes.IndexDocument.Suffix, 
+                "errorWebsite:", wsRes.ErrorDocument.Key)
+```
+
+## 清除托管页面
+
+通过`Client.DeleteBucketWebsite`来清除托管页面:
+
+```go
+    import "github.com/aliyun/aliyun-oss-go-sdk/oss"
+    
+    client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret")
+    if err != nil {
+        // HandleError(err)
+    }
+    
+    err = client.DeleteBucketWebsite("my-bucket")
+    if err != nil {
+        // HandleError(err)
+    }
+```

+ 92 - 0
oss/auth.go

@@ -0,0 +1,92 @@
+package oss
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha1"
+	"encoding/base64"
+	"hash"
+	"io"
+	"net/http"
+	"sort"
+	"strings"
+)
+
+// 用于signHeader的字典排序存放容器。
+type headerSorter struct {
+	Keys []string
+	Vals []string
+}
+
+// 生成签名方法(直接设置请求的Header)。
+func (conn Conn) signHeader(req *http.Request, canonicalizedResource string) {
+	// Find out the "x-oss-"'s address in this request'header
+	temp := make(map[string]string)
+
+	for k, v := range req.Header {
+		if strings.HasPrefix(strings.ToLower(k), "x-oss-") {
+			temp[strings.ToLower(k)] = v[0]
+		}
+	}
+	hs := newHeaderSorter(temp)
+
+	// Sort the temp by the Ascending Order
+	hs.Sort()
+
+	// Get the CanonicalizedOSSHeaders
+	canonicalizedOSSHeaders := ""
+	for i := range hs.Keys {
+		canonicalizedOSSHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n"
+	}
+
+	// Give other parameters values
+	date := req.Header.Get(HTTPHeaderDate)
+	contentType := req.Header.Get(HTTPHeaderContentType)
+	contentMd5 := req.Header.Get(HTTPHeaderContentMD5)
+
+	signStr := req.Method + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedOSSHeaders + canonicalizedResource
+	h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(conn.config.AccessKeySecret))
+	io.WriteString(h, signStr)
+	signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
+
+	// Get the final Authorization' string
+	authorizationStr := "OSS " + conn.config.AccessKeyID + ":" + signedStr
+
+	// Give the parameter "Authorization" value
+	req.Header.Set(HTTPHeaderAuthorization, authorizationStr)
+}
+
+// Additional function for function SignHeader.
+func newHeaderSorter(m map[string]string) *headerSorter {
+	hs := &headerSorter{
+		Keys: make([]string, 0, len(m)),
+		Vals: make([]string, 0, len(m)),
+	}
+
+	for k, v := range m {
+		hs.Keys = append(hs.Keys, k)
+		hs.Vals = append(hs.Vals, v)
+	}
+	return hs
+}
+
+// Additional function for function SignHeader.
+func (hs *headerSorter) Sort() {
+	sort.Sort(hs)
+}
+
+// Additional function for function SignHeader.
+func (hs *headerSorter) Len() int {
+	return len(hs.Vals)
+}
+
+// Additional function for function SignHeader.
+func (hs *headerSorter) Less(i, j int) bool {
+	return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0
+}
+
+// Additional function for function SignHeader.
+func (hs *headerSorter) Swap(i, j int) {
+	hs.Vals[i], hs.Vals[j] = hs.Vals[j], hs.Vals[i]
+	hs.Keys[i], hs.Keys[j] = hs.Keys[j], hs.Keys[i]
+}

+ 462 - 0
oss/bucket.go

@@ -0,0 +1,462 @@
+package oss
+
+import (
+	"bytes"
+	"encoding/xml"
+	"io"
+	"net/http"
+	"os"
+	"strconv"
+)
+
+// Bucket implements the operations of object.
+type Bucket struct {
+	Client     Client
+	BucketName string
+}
+
+//
+// PutObject 新建Object,如果Object已存在,覆盖原有Object。
+//
+// objectKey  上传对象的名称,使用UTF-8编码、长度必须在1-1023字节之间、不能以“/”或者“\”字符开头。
+// reader     io.Reader读取object的数据。
+// options    上传对象时可以指定对象的属性,可用选项有CacheControl、ContentDisposition、ContentEncoding、
+// Expires、ServerSideEncryption、ObjectACL、Meta,具体含义请参看
+// https://help.aliyun.com/document_detail/oss/api-reference/object/PutObject.html
+//
+// error  操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) PutObject(objectKey string, reader io.Reader, options ...Option) error {
+	opts := addContentType(options, objectKey)
+	resp, err := bucket.do("PUT", objectKey, "", "", opts, reader)
+	if err != nil {
+		return err
+	}
+
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// PutObjectFromFile 新建Object,内容从本地文件中读取。
+//
+// objectKey 上传对象的名称。
+// filePath  本地文件,上传对象的值为该文件内容。
+// options   上传对象时可以指定对象的属性。详见PutObject的options。
+//
+// error  操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) PutObjectFromFile(objectKey, filePath string, options ...Option) error {
+	fd, err := os.Open(filePath)
+	if err != nil {
+		return err
+	}
+	defer fd.Close()
+
+	opts := addContentType(options, filePath, objectKey)
+
+	resp, err := bucket.do("PUT", objectKey, "", "", opts, fd)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// GetObject 下载文件。
+//
+// objectKey 下载的文件名称。
+// options   对象的属性限制项,可选值有Range、IfModifiedSince、IfUnmodifiedSince、IfMatch、
+// IfNoneMatch、AcceptEncoding,详细请参考
+// https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
+//
+// io.ReadCloser  reader,读取数据后需要close。error为nil时有效。
+// error  操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) GetObject(objectKey string, options ...Option) (io.ReadCloser, error) {
+	resp, err := bucket.do("GET", objectKey, "", "", options, nil)
+	if err != nil {
+		return nil, err
+	}
+	return resp.body, nil
+}
+
+//
+// GetObjectToFile 下载文件。
+//
+// objectKey  下载的文件名称。
+// filePath   下载对象的内容写到该本地文件。
+// options    对象的属性限制项。详见GetObject的options。
+//
+// error  操作无错误时返回error为nil,非nil为错误说明。
+//
+func (bucket Bucket) GetObjectToFile(objectKey, filePath string, options ...Option) error {
+	resp, err := bucket.do("GET", objectKey, "", "", options, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+
+	fd, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0660)
+	if err != nil {
+		return err
+	}
+	defer fd.Close()
+
+	_, err = io.Copy(fd, resp.body)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+//
+// CopyObject 同一个bucket内拷贝Object。
+//
+// srcObjectKey  Copy的源对象。
+// destObjectKey Copy的目标对象。
+// options  Copy对象时,您可以指定源对象的限制条件,满足限制条件时copy,不满足时返回错误,您可以选择如下选项CopySourceIfMatch、
+// CopySourceIfNoneMatch、CopySourceIfModifiedSince、CopySourceIfUnmodifiedSince、MetadataDirective。
+// Copy对象时,您可以指定目标对象的属性,如CacheControl、ContentDisposition、ContentEncoding、Expires、
+// ServerSideEncryption、ObjectACL、Meta,选项的含义请参看
+// https://help.aliyun.com/document_detail/oss/api-reference/object/CopyObject.html
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
+	var out CopyObjectResult
+	options = append(options, CopySource(bucket.BucketName, srcObjectKey))
+	resp, err := bucket.do("PUT", destObjectKey, "", "", options, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// CopyObjectToBucket bucket间拷贝object。
+//
+// srcObjectKey   Copy的源对象。
+// destBucket     Copy的目标Bucket。
+// destObjectKey  Copy的目标Object。
+// options        Copy选项,详见CopyObject的options。
+//
+// error  操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) CopyObjectToBucket(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) {
+	var out CopyObjectResult
+	options = append(options, CopySource(bucket.BucketName, srcObjectKey))
+	headers := make(map[string]string)
+	err := handleOptions(headers, options)
+	if err != nil {
+		return out, err
+	}
+	resp, err := bucket.Client.Conn.Do("PUT", destBucketName, destObjectKey, "", "", headers, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// AppendObject 追加方式上传。
+//
+// AppendObject参数必须包含position,其值指定从何处进行追加。首次追加操作的position必须为0,
+// 后续追加操作的position是Object的当前长度。例如,第一次Append Object请求指定position值为0,
+// content-length是65536;那么,第二次Append Object需要指定position为65536。
+// 每次操作成功后,响应头部x-oss-next-append-position也会标明下一次追加的position。
+//
+// objectKey  需要追加的Object。
+// reader     io.Reader,读取追的内容。
+// appendPosition  object追加的起始位置。
+// destObjectProperties  第一次追加时指定新对象的属性,如CacheControl、ContentDisposition、ContentEncoding、
+// Expires、ServerSideEncryption、ObjectACL。
+// int64 下次追加的开始位置,error为nil空时有效。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) AppendObject(objectKey string, reader io.Reader, appendPosition int64, options ...Option) (int64, error) {
+	var nextAppendPosition int64
+	params := "append&position=" + strconv.Itoa(int(appendPosition))
+	opts := addContentType(options, objectKey)
+	resp, err := bucket.do("POST", objectKey, params, params, opts, reader)
+	if err != nil {
+		return nextAppendPosition, err
+	}
+	defer resp.body.Close()
+
+	napint, err := strconv.Atoi(resp.headers.Get(HTTPHeaderOssNextAppendPosition))
+	if err != nil {
+		return nextAppendPosition, err
+	}
+	nextAppendPosition = int64(napint)
+	return nextAppendPosition, nil
+}
+
+//
+// DeleteObject 删除Object。
+//
+// objectKey 待删除Object。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) DeleteObject(objectKey string) error {
+	resp, err := bucket.do("DELETE", objectKey, "", "", nil, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
+
+//
+// DeleteObjects 批量删除object。
+//
+// objectKeys 待删除object类表。
+// options 删除选项,DeleteObjectsQuiet,是否是安静模式,默认不使用。
+//
+// DeleteObjectsResult 非安静模式的的返回值。
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) DeleteObjects(objectKeys []string, options ...Option) (DeleteObjectsResult, error) {
+	out := DeleteObjectsResult{}
+	dxml := deleteXML{}
+	for _, key := range objectKeys {
+		dxml.Objects = append(dxml.Objects, DeleteObject{Key: key})
+	}
+	isQuietStr, _ := findOption(options, deleteObjectsQuiet, "FALSE")
+	isQuiet, _ := strconv.ParseBool(isQuietStr)
+	dxml.Quiet = isQuiet
+	encode := "&encoding-type=url"
+
+	bs, err := xml.Marshal(dxml)
+	if err != nil {
+		return out, err
+	}
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+
+	contentType := http.DetectContentType(buffer.Bytes())
+	options = append(options, ContentType(contentType))
+	resp, err := bucket.do("POST", "", "delete"+encode, "delete", options, buffer)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	if !dxml.Quiet {
+		if err = xmlUnmarshal(resp.body, &out); err == nil {
+			err = decodeDeleteObjectsResult(&out)
+		}
+	}
+	return out, err
+}
+
+//
+// IsObjectExist object是否存在。
+//
+// bool  object是否存在,true存在,false不存在。error为nil时有效。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) IsObjectExist(objectKey string) (bool, error) {
+	listRes, err := bucket.ListObjects(Prefix(objectKey), MaxKeys(1))
+	if err != nil {
+		return false, err
+	}
+
+	if len(listRes.Objects) == 1 && listRes.Objects[0].Key == objectKey {
+		return true, nil
+	}
+	return false, nil
+}
+
+//
+// ListObjects 获得Bucket下筛选后所有的object的列表。
+//
+// options  ListObject的筛选行为。Prefix指定的前缀、MaxKeys最大数目、Marker第一个开始、Delimiter对Object名字进行分组的字符。
+//
+// 您有如下8个object,my-object-1, my-object-11, my-object-2, my-object-21,
+// my-object-22, my-object-3, my-object-31, my-object-32。如果您指定了Prefix为my-object-2,
+// 则返回my-object-2, my-object-21, my-object-22三个object。如果您指定了Marker为my-object-22,
+// 则返回my-object-3, my-object-31, my-object-32三个object。如果您指定MaxKeys则每次最多返回MaxKeys个,
+// 最后一次可能不足。这三个参数可以组合使用,实现分页等功能。如果把prefix设为某个文件夹名,就可以罗列以此prefix开头的文件,
+// 即该文件夹下递归的所有的文件和子文件夹。如果再把delimiter设置为"/"时,返回值就只罗列该文件夹下的文件,该文件夹下的子文件名
+// 返回在CommonPrefixes部分,子文件夹下递归的文件和文件夹不被显示。例如一个bucket存在三个object,fun/test.jpg、
+// fun/movie/001.avi、fun/movie/007.avi。若设定prefix为"fun/",则返回三个object;如果增加设定
+// delimiter为"/",则返回文件"fun/test.jpg"和前缀"fun/movie/",即实现了文件夹的逻辑。
+//
+// 常用场景,请参数示例sample/list_object.go。
+//
+// ListObjectsResponse  操作成功后的返回值,成员Objects为bucket中对象列表。error为nil时该返回值有效。
+//
+func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) {
+	var out ListObjectsResult
+
+	options = append(options, EncodingType("url"))
+	params, err := handleParams(options)
+	if err != nil {
+		return out, err
+	}
+
+	resp, err := bucket.do("GET", "", params, "", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	if err != nil {
+		return out, err
+	}
+
+	err = decodeListObjectsResult(&out)
+	return out, err
+}
+
+//
+// SetObjectMeta 设置Object的Meta。
+//
+// objectKey object
+// options 指定对象的属性,有以下可选项CacheControl、ContentDisposition、ContentEncoding、Expires、
+// ServerSideEncryption、Meta。
+//
+// error 操作无错误时error为nil,非nil为错误信息。
+//
+func (bucket Bucket) SetObjectMeta(objectKey string, options ...Option) error {
+	options = append(options, MetadataDirective(MetaReplace))
+	_, err := bucket.CopyObject(objectKey, objectKey, options...)
+	return err
+}
+
+//
+// GetObjectDetailedMeta 查询Object的头信息。
+//
+// objectKey object名称。
+// objectPropertyConstraints 对象的属性限制项,满足时正常返回,不满足时返回错误。现在项有IfModifiedSince、IfUnmodifiedSince、
+// IfMatch、IfNoneMatch。具体含义请参看 https://help.aliyun.com/document_detail/oss/api-reference/object/HeadObject.html
+//
+// http.Header  对象的meta,error为nil时有效。
+// error  操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) (http.Header, error) {
+	resp, err := bucket.do("HEAD", objectKey, "", "", options, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.body.Close()
+
+	return resp.headers, nil
+}
+
+//
+// GetObjectMeta 查询Object的头信息。
+//
+// GetObjectMeta相比GetObjectDetailedMeta更轻量,仅返回指定Object的少量基本meta信息,
+// 包括该Object的ETag、Size(对象大小)、LastModified,其中Size由响应头Content-Length的数值表示。
+//
+// objectKey object名称。
+//
+// http.Header 对象的meta,error为nil时有效。
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) GetObjectMeta(objectKey string) (http.Header, error) {
+	resp, err := bucket.do("GET", objectKey, "?objectMeta", "", nil, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.body.Close()
+
+	return resp.headers, nil
+}
+
+//
+// SetObjectACL 修改Object的ACL权限。
+//
+// 只有Bucket Owner才有权限调用PutObjectACL来修改Object的ACL。Object ACL优先级高于Bucket ACL。
+// 例如Bucket ACL是private的,而Object ACL是public-read-write的,则访问这个Object时,
+// 先判断Object的ACL,所以所有用户都拥有这个Object的访问权限,即使这个Bucket是private bucket。
+// 如果某个Object从来没设置过ACL,则访问权限遵循Bucket ACL。
+//
+// Object的读操作包括GetObject,HeadObject,CopyObject和UploadPartCopy中的对source object的读;
+// Object的写操作包括:PutObject,PostObject,AppendObject,DeleteObject,
+// DeleteMultipleObjects,CompleteMultipartUpload以及CopyObject对新的Object的写。
+//
+// objectKey 设置权限的object。
+// objectAcl 对象权限。可选值PrivateACL(私有读写)、PublicReadACL(公共读私有写)、PublicReadWriteACL(公共读写)。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType) error {
+	options := []Option{ObjectACL(objectACL)}
+	resp, err := bucket.do("PUT", objectKey, "", "", options, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// GetObjectACL 获取对象的ACL权限。
+//
+// objectKey 获取权限的object。
+//
+// GetObjectAclResponse 获取权限操作返回值,error为nil时有效。GetObjectAclResponse.Acl为对象的权限。
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (bucket Bucket) GetObjectACL(objectKey string) (GetObjectACLResult, error) {
+	var out GetObjectACLResult
+	resp, err := bucket.do("GET", objectKey, "acl", "acl", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+// Private
+func (bucket Bucket) do(method, objectName, urlParams, subResource string,
+	options []Option, data io.Reader) (*Response, error) {
+	headers := make(map[string]string)
+	err := handleOptions(headers, options)
+	if err != nil {
+		return nil, err
+	}
+	return bucket.Client.Conn.Do(method, bucket.BucketName, objectName,
+		urlParams, subResource, headers, data)
+}
+
+func (bucket Bucket) getConfig() *Config {
+	return bucket.Client.Config
+}
+
+// Private
+func addContentType(options []Option, keys ...string) []Option {
+	typ := TypeByExtension("")
+	for _, key := range keys {
+		typ = TypeByExtension(key)
+		if typ != "" {
+			break
+		}
+	}
+
+	if typ == "" {
+		typ = "application/octet-stream"
+	}
+
+	opts := []Option{ContentType(typ)}
+	opts = append(opts, options...)
+
+	return opts
+}

+ 1648 - 0
oss/bucket_test.go

@@ -0,0 +1,1648 @@
+// bucket test
+
+package oss
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math/rand"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+
+	. "gopkg.in/check.v1"
+)
+
+type OssBucketSuite struct {
+	client *Client
+	bucket *Bucket
+}
+
+var _ = Suite(&OssBucketSuite{})
+
+const (
+	bucketName       = "mygobucketobj"
+	objectNamePrefix = "myobject"
+)
+
+var (
+	pastDate   = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+	futureDate = time.Date(2049, time.January, 10, 23, 0, 0, 0, time.UTC)
+)
+
+// Run once when the suite starts running
+func (s *OssBucketSuite) SetUpSuite(c *C) {
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+	s.client = client
+
+	err = s.client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := s.client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+	s.bucket = bucket
+
+	fmt.Println("SetUpSuite")
+}
+
+// Run before each test or benchmark starts running
+func (s *OssBucketSuite) TearDownSuite(c *C) {
+	// Delete Objects
+	lor, err := s.bucket.ListObjects()
+	c.Assert(err, IsNil)
+
+	for _, object := range lor.Objects {
+		err = s.bucket.DeleteObject(object.Key)
+		c.Assert(err, IsNil)
+	}
+
+	// Delete Bucket
+	err = s.client.DeleteBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	fmt.Println("TearDownSuite")
+}
+
+// Run after each test or benchmark runs
+func (s *OssBucketSuite) SetUpTest(c *C) {
+	err := removeTempFiles("../oss", ".jpg")
+	c.Assert(err, IsNil)
+
+	fmt.Println("SetUpTest")
+}
+
+// Run once after all tests or benchmarks have finished running
+func (s *OssBucketSuite) TearDownTest(c *C) {
+	err := removeTempFiles("../oss", ".jpg")
+	c.Assert(err, IsNil)
+
+	err = removeTempFiles("../oss", ".txt")
+	c.Assert(err, IsNil)
+
+	err = removeTempFiles("../oss", ".txt1")
+	c.Assert(err, IsNil)
+
+	err = removeTempFiles("../oss", ".txt2")
+	c.Assert(err, IsNil)
+
+	fmt.Println("TearDownTest")
+}
+
+// TestPutObject
+func (s *OssBucketSuite) TestPutObject(c *C) {
+	objectName := objectNamePrefix + "tpo"
+	objectValue := "大江东去,浪淘尽,千古风流人物。 故垒西边,人道是、三国周郎赤壁。 乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。" +
+		"遥想公谨当年,小乔初嫁了,雄姿英发。 羽扇纶巾,谈笑间、樯橹灰飞烟灭。故国神游,多情应笑我,早生华发,人生如梦,一尊还酹江月。"
+
+	// string put
+	err := s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// Check
+	body, err := s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	acl, err := s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("aclRes:", acl)
+	c.Assert(acl.ACL, Equals, "default")
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// bytes put
+	err = s.bucket.PutObject(objectName, bytes.NewReader([]byte(objectValue)))
+	c.Assert(err, IsNil)
+
+	// Check
+	body, err = s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// file put
+	err = createFileAndWrite(objectName+".txt", []byte(objectValue))
+	c.Assert(err, IsNil)
+	fd, err := os.Open(objectName + ".txt")
+	c.Assert(err, IsNil)
+
+	err = s.bucket.PutObject(objectName, fd)
+	c.Assert(err, IsNil)
+	os.Remove(objectName + ".txt")
+
+	// Check
+	body, err = s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// Put with properties
+	objectName = objectNamePrefix + "tpox"
+	options := []Option{
+		Expires(futureDate),
+		ObjectACL(ACLPublicRead),
+		Meta("myprop", "mypropval"),
+	}
+	err = s.bucket.PutObject(objectName, strings.NewReader(objectValue), options...)
+	c.Assert(err, IsNil)
+
+	// Check
+	body, err = s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	acl, err = s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectACL:", acl)
+	c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+	c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestPutObjectType
+func (s *OssBucketSuite) TestPutObjectType(c *C) {
+	objectName := objectNamePrefix + "tptt"
+	objectValue := "乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。"
+
+	// Put
+	err := s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// Check
+	body, err := s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("Content-Type"), Equals, "application/octet-stream")
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// Put
+	err = s.bucket.PutObject(objectName+".txt", strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	meta, err = s.bucket.GetObjectDetailedMeta(objectName + ".txt")
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("Content-Type"), Equals, "text/plain; charset=utf-8")
+
+	err = s.bucket.DeleteObject(objectName + ".txt")
+	c.Assert(err, IsNil)
+
+	// Put
+	err = s.bucket.PutObject(objectName+".apk", strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	meta, err = s.bucket.GetObjectDetailedMeta(objectName + ".apk")
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("Content-Type"), Equals, "application/vnd.android.package-archive")
+
+	err = s.bucket.DeleteObject(objectName + ".txt")
+	c.Assert(err, IsNil)
+}
+
+// TestPutObject
+func (s *OssBucketSuite) TestPutObjectKeyChars(c *C) {
+	objectName := objectNamePrefix + "tpokc"
+	objectValue := "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。"
+
+	// Put
+	objectKey := objectName + "十步杀一人,千里不留行。事了拂衣去,深藏身与名"
+	err := s.bucket.PutObject(objectKey, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// Check
+	body, err := s.bucket.GetObject(objectKey)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectKey)
+	c.Assert(err, IsNil)
+
+	// Put
+	objectKey = objectName + "ごきげん如何ですかおれの顔をよく拝んでおけ"
+	err = s.bucket.PutObject(objectKey, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// Check
+	body, err = s.bucket.GetObject(objectKey)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectKey)
+	c.Assert(err, IsNil)
+
+	// Put
+	objectKey = objectName + "~!@#$%^&*()_-+=|\\[]{}<>,./?"
+	err = s.bucket.PutObject(objectKey, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// Check
+	body, err = s.bucket.GetObject(objectKey)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectKey)
+	c.Assert(err, IsNil)
+
+	// Put
+	objectKey = "go/中国 日本 +-#&=*"
+	err = s.bucket.PutObject(objectKey, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// Check
+	body, err = s.bucket.GetObject(objectKey)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectKey)
+	c.Assert(err, IsNil)
+}
+
+// TestPutObjectNegative
+func (s *OssBucketSuite) TestPutObjectNegative(c *C) {
+	objectName := objectNamePrefix + "tpon"
+	objectValue := "大江东去,浪淘尽,千古风流人物。 "
+
+	// Put
+	objectName = objectNamePrefix + "tpox"
+	err := s.bucket.PutObject(objectName, strings.NewReader(objectValue),
+		Meta("meta-my", "myprop"))
+	c.Assert(err, IsNil)
+
+	// Check meta
+	body, err := s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("X-Oss-Meta-My"), Not(Equals), "myprop")
+	c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "")
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// invalid option
+	err = s.bucket.PutObject(objectName, strings.NewReader(objectValue),
+		IfModifiedSince(pastDate))
+	c.Assert(err, NotNil)
+
+	err = s.bucket.PutObjectFromFile(objectName, "bucket.go", IfModifiedSince(pastDate))
+	c.Assert(err, NotNil)
+
+	err = s.bucket.PutObjectFromFile(objectName, "/tmp/xxx")
+	c.Assert(err, NotNil)
+}
+
+// TestPutObjectFromFile
+func (s *OssBucketSuite) TestPutObjectFromFile(c *C) {
+	objectName := objectNamePrefix + "tpoff"
+	localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+	newFile := "newpic11.jpg"
+
+	// Put
+	err := s.bucket.PutObjectFromFile(objectName, localFile)
+	c.Assert(err, IsNil)
+
+	// Check
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+	eq, err := compareFiles(localFile, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	acl, err := s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("aclRes:", acl)
+	c.Assert(acl.ACL, Equals, "default")
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// Put with properties
+	options := []Option{
+		Expires(futureDate),
+		ObjectACL(ACLPublicRead),
+		Meta("myprop", "mypropval"),
+	}
+	os.Remove(newFile)
+	err = s.bucket.PutObjectFromFile(objectName, localFile, options...)
+	c.Assert(err, IsNil)
+
+	// Check
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+	eq, err = compareFiles(localFile, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	acl, err = s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectACL:", acl)
+	c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+	c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+}
+
+// TestPutObjectFromFile
+func (s *OssBucketSuite) TestPutObjectFromFileType(c *C) {
+	objectName := objectNamePrefix + "tpoffwm"
+	localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+	newFile := "newpic11.jpg"
+
+	// Put
+	err := s.bucket.PutObjectFromFile(objectName, localFile)
+	c.Assert(err, IsNil)
+
+	// Check
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+	eq, err := compareFiles(localFile, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("Content-Type"), Equals, "image/jpeg")
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+}
+
+// TestGetObject
+func (s *OssBucketSuite) TestGetObject(c *C) {
+	objectName := objectNamePrefix + "tgo"
+	objectValue := "长忆观潮,满郭人争江上望。来疑沧海尽成空,万面鼓声中。弄潮儿向涛头立,手把红旗旗不湿。别来几向梦中看,梦觉尚心寒。"
+
+	// Put
+	err := s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// Check
+	body, err := s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	data, err := ioutil.ReadAll(body)
+	body.Close()
+	str := string(data)
+	c.Assert(str, Equals, objectValue)
+	fmt.Println("GetObjec:", str)
+
+	// Range
+	var subObjectValue = string(([]byte(objectValue))[15:36])
+	body, err = s.bucket.GetObject(objectName, Range(15, 35))
+	c.Assert(err, IsNil)
+	data, err = ioutil.ReadAll(body)
+	body.Close()
+	str = string(data)
+	c.Assert(str, Equals, subObjectValue)
+	fmt.Println("GetObject:", str, ",", subObjectValue)
+
+	// If-Modified-Since
+	_, err = s.bucket.GetObject(objectName, IfModifiedSince(futureDate))
+	c.Assert(err, NotNil)
+
+	// If-Unmodified-Since
+	body, err = s.bucket.GetObject(objectName, IfUnmodifiedSince(futureDate))
+	c.Assert(err, IsNil)
+	data, err = ioutil.ReadAll(body)
+	body.Close()
+	c.Assert(string(data), Equals, objectValue)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+
+	// If-Match
+	body, err = s.bucket.GetObject(objectName, IfMatch(meta.Get("Etag")))
+	c.Assert(err, IsNil)
+	data, err = ioutil.ReadAll(body)
+	body.Close()
+	c.Assert(string(data), Equals, objectValue)
+
+	// If-None-Match
+	_, err = s.bucket.GetObject(objectName, IfNoneMatch(meta.Get("Etag")))
+	c.Assert(err, NotNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestGetObjectNegative
+func (s *OssBucketSuite) TestGetObjectToWriterNegative(c *C) {
+	objectName := objectNamePrefix + "tgotwn"
+	objectValue := "长忆观潮,满郭人争江上望。"
+
+	// object not exist
+	_, err := s.bucket.GetObject("NotExist")
+	c.Assert(err, NotNil)
+
+	// constraint invalid
+	err = s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// out of range
+	_, err = s.bucket.GetObject(objectName, Range(15, 1000))
+	c.Assert(err, IsNil)
+
+	// no exist
+	err = s.bucket.GetObjectToFile(objectName, "/tmp/x")
+	c.Assert(err, NotNil)
+
+	// invalid option
+	_, err = s.bucket.GetObject(objectName, ACL(ACLPublicRead))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(objectName, "newpic15.jpg", ACL(ACLPublicRead))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestGetObjectToFile
+func (s *OssBucketSuite) TestGetObjectToFile(c *C) {
+	objectName := objectNamePrefix + "tgotf"
+	objectValue := "江南好,风景旧曾谙;日出江花红胜火,春来江水绿如蓝。能不忆江南?江南忆,最忆是杭州;山寺月中寻桂子,郡亭枕上看潮头。何日更重游!"
+	newFile := "newpic15.jpg"
+
+	// Put
+	var val = []byte(objectValue)
+	err := s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// Check
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+	eq, err := compareFileData(newFile, val)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+	os.Remove(newFile)
+
+	// Range
+	err = s.bucket.GetObjectToFile(objectName, newFile, Range(15, 35))
+	c.Assert(err, IsNil)
+	eq, err = compareFileData(newFile, val[15:36])
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+	os.Remove(newFile)
+
+	// If-Modified-Since
+	err = s.bucket.GetObjectToFile(objectName, newFile, IfModifiedSince(futureDate))
+	c.Assert(err, NotNil)
+
+	// If-Unmodified-Since
+	err = s.bucket.GetObjectToFile(objectName, newFile, IfUnmodifiedSince(futureDate))
+	c.Assert(err, IsNil)
+	eq, err = compareFileData(newFile, val)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+	os.Remove(newFile)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+
+	// If-Match
+	err = s.bucket.GetObjectToFile(objectName, newFile, IfMatch(meta.Get("Etag")))
+	c.Assert(err, IsNil)
+	eq, err = compareFileData(newFile, val)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+	os.Remove(newFile)
+
+	// If-None-Match
+	err = s.bucket.GetObjectToFile(objectName, newFile, IfNoneMatch(meta.Get("Etag")))
+	c.Assert(err, NotNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestListObjects
+func (s *OssBucketSuite) TestListObjects(c *C) {
+	objectName := objectNamePrefix + "tlo"
+
+	// list empty bucket
+	lor, err := s.bucket.ListObjects()
+	c.Assert(err, IsNil)
+	left := len(lor.Objects)
+
+	// Put three object
+	err = s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+	c.Assert(err, IsNil)
+	err = s.bucket.PutObject(objectName+"2", strings.NewReader(""))
+	c.Assert(err, IsNil)
+	err = s.bucket.PutObject(objectName+"3", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	// list
+	lor, err = s.bucket.ListObjects()
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, left+3)
+
+	// list with prefix
+	lor, err = s.bucket.ListObjects(Prefix(objectName + "2"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 1)
+
+	lor, err = s.bucket.ListObjects(Prefix(objectName + "22"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 0)
+
+	// list with max keys
+	lor, err = s.bucket.ListObjects(Prefix(objectName), MaxKeys(2))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 2)
+
+	// list with marker
+	lor, err = s.bucket.ListObjects(Marker(objectName+"1"), MaxKeys(1))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 1)
+
+	err = s.bucket.DeleteObject(objectName + "1")
+	c.Assert(err, IsNil)
+	err = s.bucket.DeleteObject(objectName + "2")
+	c.Assert(err, IsNil)
+	err = s.bucket.DeleteObject(objectName + "3")
+	c.Assert(err, IsNil)
+}
+
+// TestListObjects
+func (s *OssBucketSuite) TestListObjectsEncodingType(c *C) {
+	objectName := objectNamePrefix + "床前明月光,疑是地上霜。举头望明月,低头思故乡。" + "tloet"
+
+	for i := 0; i < 10; i++ {
+		err := s.bucket.PutObject(objectName+strconv.Itoa(i), strings.NewReader(""))
+		c.Assert(err, IsNil)
+	}
+
+	lor, err := s.bucket.ListObjects(Prefix(objectNamePrefix + "床前明月光,"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 10)
+
+	lor, err = s.bucket.ListObjects(Prefix(objectNamePrefix + "床前明月光,"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 10)
+
+	lor, err = s.bucket.ListObjects(Marker(objectNamePrefix + "床前明月光,疑是地上霜。举头望明月,低头思故乡。"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 10)
+
+	lor, err = s.bucket.ListObjects(Prefix(objectNamePrefix + "床前明月光"))
+	c.Assert(err, IsNil)
+	for i, obj := range lor.Objects {
+		c.Assert(obj.Key, Equals, "myobject床前明月光,疑是地上霜。举头望明月,低头思故乡。tloet"+strconv.Itoa(i))
+	}
+
+	for i := 0; i < 10; i++ {
+		err = s.bucket.DeleteObject(objectName + strconv.Itoa(i))
+		c.Assert(err, IsNil)
+	}
+
+	// 特殊字符
+	objectName = "go go ` ~ ! @ # $ % ^ & * () - _ + =[] {} \\ | < > , . ? / 0"
+	err = s.bucket.PutObject(objectName, strings.NewReader("明月几时有,把酒问青天"))
+	c.Assert(err, IsNil)
+
+	lor, err = s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 1)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	objectName = "go/中国  日本  +-#&=*"
+	err = s.bucket.PutObject(objectName, strings.NewReader("明月几时有,把酒问青天"))
+	c.Assert(err, IsNil)
+
+	lor, err = s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 1)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestIsBucketExist
+func (s *OssBucketSuite) TestIsObjectExist(c *C) {
+	objectName := objectNamePrefix + "tibe"
+
+	// Put three object
+	err := s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+	c.Assert(err, IsNil)
+	err = s.bucket.PutObject(objectName+"11", strings.NewReader(""))
+	c.Assert(err, IsNil)
+	err = s.bucket.PutObject(objectName+"111", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	// exist
+	exist, err := s.bucket.IsObjectExist(objectName + "11")
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, true)
+
+	exist, err = s.bucket.IsObjectExist(objectName + "1")
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, true)
+
+	exist, err = s.bucket.IsObjectExist(objectName + "111")
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, true)
+
+	// not exist
+	exist, err = s.bucket.IsObjectExist(objectName + "1111")
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, false)
+
+	exist, err = s.bucket.IsObjectExist(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, false)
+
+	err = s.bucket.DeleteObject(objectName + "1")
+	c.Assert(err, IsNil)
+	err = s.bucket.DeleteObject(objectName + "11")
+	c.Assert(err, IsNil)
+	err = s.bucket.DeleteObject(objectName + "111")
+	c.Assert(err, IsNil)
+}
+
+// TestDeleteObject
+func (s *OssBucketSuite) TestDeleteObject(c *C) {
+	objectName := objectNamePrefix + "tdo"
+
+	err := s.bucket.PutObject(objectName, strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	lor, err := s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 1)
+
+	// delete
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// duplicate delete
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	lor, err = s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 0)
+}
+
+// TestDeleteObjects
+func (s *OssBucketSuite) TestDeleteObjects(c *C) {
+	objectName := objectNamePrefix + "tdos"
+
+	// delete object
+	err := s.bucket.PutObject(objectName, strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	res, err := s.bucket.DeleteObjects([]string{objectName})
+	c.Assert(err, IsNil)
+	c.Assert(len(res.DeletedObjects), Equals, 1)
+
+	lor, err := s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 0)
+
+	// delete objects
+	err = s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.PutObject(objectName+"2", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	res, err = s.bucket.DeleteObjects([]string{objectName + "1", objectName + "2"})
+	c.Assert(err, IsNil)
+	c.Assert(len(res.DeletedObjects), Equals, 2)
+
+	lor, err = s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 0)
+
+	// delete 0
+	_, err = s.bucket.DeleteObjects([]string{})
+	c.Assert(err, NotNil)
+
+	// DeleteObjectsQuiet
+	err = s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.PutObject(objectName+"2", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	res, err = s.bucket.DeleteObjects([]string{objectName + "1", objectName + "2"},
+		DeleteObjectsQuiet(false))
+	c.Assert(err, IsNil)
+	c.Assert(len(res.DeletedObjects), Equals, 2)
+
+	lor, err = s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 0)
+
+	// DeleteObjectsQuiet
+	err = s.bucket.PutObject(objectName+"1", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.PutObject(objectName+"2", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	res, err = s.bucket.DeleteObjects([]string{objectName + "1", objectName + "2"},
+		DeleteObjectsQuiet(true))
+	c.Assert(err, IsNil)
+	c.Assert(len(res.DeletedObjects), Equals, 0)
+
+	lor, err = s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 0)
+
+	// EncodingType
+	err = s.bucket.PutObject("中国人", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	res, err = s.bucket.DeleteObjects([]string{"中国人"})
+	c.Assert(err, IsNil)
+	c.Assert(len(res.DeletedObjects), Equals, 1)
+	c.Assert(res.DeletedObjects[0], Equals, "中国人")
+
+	// EncodingType
+	err = s.bucket.PutObject("中国人", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	res, err = s.bucket.DeleteObjects([]string{"中国人"}, DeleteObjectsQuiet(false))
+	c.Assert(err, IsNil)
+	c.Assert(len(res.DeletedObjects), Equals, 1)
+	c.Assert(res.DeletedObjects[0], Equals, "中国人")
+
+	// EncodingType
+	err = s.bucket.PutObject("中国人", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	res, err = s.bucket.DeleteObjects([]string{"中国人"}, DeleteObjectsQuiet(true))
+	c.Assert(err, IsNil)
+	c.Assert(len(res.DeletedObjects), Equals, 0)
+
+	// 特殊字符
+	key := "A ' < > \" & ~ ` ! @ # $ % ^ & * ( ) [] {} - _ + = / | \\ ? . , : ; A"
+	err = s.bucket.PutObject(key, strings.NewReader("value"))
+	c.Assert(err, IsNil)
+
+	_, err = s.bucket.DeleteObjects([]string{key})
+	c.Assert(err, IsNil)
+
+	ress, err := s.bucket.ListObjects(Prefix(key))
+	c.Assert(err, IsNil)
+	c.Assert(len(ress.Objects), Equals, 0)
+
+	// not exist
+	_, err = s.bucket.DeleteObjects([]string{"NotExistObject"})
+	c.Assert(err, IsNil)
+}
+
+// TestSetObjectMeta
+func (s *OssBucketSuite) TestSetObjectMeta(c *C) {
+	objectName := objectNamePrefix + "tsom"
+
+	err := s.bucket.PutObject(objectName, strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.SetObjectMeta(objectName,
+		Expires(futureDate),
+		Meta("myprop", "mypropval"))
+	c.Assert(err, IsNil)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("Meta:", meta)
+	c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+	c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+	acl, err := s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(acl.ACL, Equals, "default")
+
+	// invalid option
+	err = s.bucket.SetObjectMeta(objectName, AcceptEncoding("url"))
+	c.Assert(err, IsNil)
+
+	// invalid option value
+	err = s.bucket.SetObjectMeta(objectName, ServerSideEncryption("invalid"))
+	c.Assert(err, NotNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// no exist
+	err = s.bucket.SetObjectMeta(objectName, Expires(futureDate))
+	c.Assert(err, NotNil)
+}
+
+// TestGetObjectMeta
+func (s *OssBucketSuite) TestGetObjectMeta(c *C) {
+	objectName := objectNamePrefix + "tgom"
+
+	// Put
+	err := s.bucket.PutObject(objectName, strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	meta, err := s.bucket.GetObjectMeta(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(len(meta) > 0, Equals, true)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	_, err = s.bucket.GetObjectMeta("NotExistObject")
+	c.Assert(err, NotNil)
+}
+
+// TestGetObjectDetailedMeta
+func (s *OssBucketSuite) TestGetObjectDetailedMeta(c *C) {
+	objectName := objectNamePrefix + "tgodm"
+
+	// Put
+	err := s.bucket.PutObject(objectName, strings.NewReader(""),
+		Expires(futureDate), Meta("myprop", "mypropval"))
+	c.Assert(err, IsNil)
+
+	// Check
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+	c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+	c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+	c.Assert(meta.Get("Content-Length"), Equals, "0")
+	c.Assert(len(meta.Get("Date")) > 0, Equals, true)
+	c.Assert(len(meta.Get("X-Oss-Request-Id")) > 0, Equals, true)
+	c.Assert(len(meta.Get("Last-Modified")) > 0, Equals, true)
+
+	// IfModifiedSince/IfModifiedSince
+	time.Sleep(time.Second * 3)
+	_, err = s.bucket.GetObjectDetailedMeta(objectName, IfModifiedSince(futureDate))
+	c.Assert(err, NotNil)
+
+	meta, err = s.bucket.GetObjectDetailedMeta(objectName, IfUnmodifiedSince(futureDate))
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+	c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+	// IfMatch/IfNoneMatch
+	_, err = s.bucket.GetObjectDetailedMeta(objectName, IfNoneMatch(meta.Get("Etag")))
+	c.Assert(err, NotNil)
+
+	meta, err = s.bucket.GetObjectDetailedMeta(objectName, IfMatch(meta.Get("Etag")))
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+	c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	_, err = s.bucket.GetObjectDetailedMeta("NotExistObject")
+	c.Assert(err, NotNil)
+}
+
+// TestSetAndGetObjectAcl
+func (s *OssBucketSuite) TestSetAndGetObjectAcl(c *C) {
+	objectName := objectNamePrefix + "tsgba"
+
+	err := s.bucket.PutObject(objectName, strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	// default
+	acl, err := s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(acl.ACL, Equals, "default")
+
+	// set ACL_PUBLIC_RW
+	err = s.bucket.SetObjectACL(objectName, ACLPublicReadWrite)
+	c.Assert(err, IsNil)
+
+	acl, err = s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite))
+
+	// set ACL_PRIVATE
+	err = s.bucket.SetObjectACL(objectName, ACLPrivate)
+	c.Assert(err, IsNil)
+
+	acl, err = s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(acl.ACL, Equals, string(ACLPrivate))
+
+	// set ACL_PUBLIC_R
+	err = s.bucket.SetObjectACL(objectName, ACLPublicRead)
+	c.Assert(err, IsNil)
+
+	acl, err = s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestCopyObject
+func (s *OssBucketSuite) TestCopyObject(c *C) {
+	objectName := objectNamePrefix + "tco"
+	objectValue := "男儿何不带吴钩,收取关山五十州。请君暂上凌烟阁,若个书生万户侯?"
+
+	err := s.bucket.PutObject(objectName, strings.NewReader(objectValue),
+		ACL(ACLPublicRead), Meta("my", "myprop"))
+	c.Assert(err, IsNil)
+
+	// copy
+	var objectNameDest = objectName + "dest"
+	_, err = s.bucket.CopyObject(objectName, objectNameDest)
+	c.Assert(err, IsNil)
+
+	// check
+	lor, err := s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	fmt.Println("objects:", lor.Objects)
+	c.Assert(len(lor.Objects), Equals, 2)
+
+	body, err := s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectNameDest)
+	c.Assert(err, IsNil)
+
+	// copy with constraints x-oss-copy-source-if-modified-since
+	_, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfModifiedSince(futureDate))
+	c.Assert(err, NotNil)
+	fmt.Println("CopyObject:", err)
+
+	// copy with constraints x-oss-copy-source-if-unmodified-since
+	_, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfUnmodifiedSince(futureDate))
+	c.Assert(err, IsNil)
+
+	// check
+	lor, err = s.bucket.ListObjects(Prefix(objectName))
+	c.Assert(err, IsNil)
+	fmt.Println("objects:", lor.Objects)
+	c.Assert(len(lor.Objects), Equals, 2)
+
+	body, err = s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectNameDest)
+	c.Assert(err, IsNil)
+
+	// copy with constraints x-oss-copy-source-if-match
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+
+	_, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfMatch(meta.Get("Etag")))
+	c.Assert(err, IsNil)
+
+	// check
+	body, err = s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectNameDest)
+	c.Assert(err, IsNil)
+
+	// copy with constraints x-oss-copy-source-if-none-match
+	_, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfNoneMatch(meta.Get("Etag")))
+	c.Assert(err, NotNil)
+
+	// copy with constraints x-oss-metadata-directive
+	_, err = s.bucket.CopyObject(objectName, objectNameDest, Meta("my", "mydescprop"),
+		MetadataDirective(MetaCopy))
+	c.Assert(err, IsNil)
+
+	// check
+	body, err = s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	destMeta, err := s.bucket.GetObjectDetailedMeta(objectNameDest)
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+
+	acl, err := s.bucket.GetObjectACL(objectNameDest)
+	c.Assert(err, IsNil)
+	c.Assert(acl.ACL, Equals, "default")
+
+	err = s.bucket.DeleteObject(objectNameDest)
+	c.Assert(err, IsNil)
+
+	// copy with constraints x-oss-metadata-directive and self defined desc object meta
+	options := []Option{
+		ObjectACL(ACLPublicReadWrite),
+		Meta("my", "mydescprop"),
+		MetadataDirective(MetaReplace),
+	}
+	_, err = s.bucket.CopyObject(objectName, objectNameDest, options...)
+	c.Assert(err, IsNil)
+
+	// check
+	body, err = s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	destMeta, err = s.bucket.GetObjectDetailedMeta(objectNameDest)
+	c.Assert(err, IsNil)
+	c.Assert(destMeta.Get("X-Oss-Meta-My"), Equals, "mydescprop")
+
+	acl, err = s.bucket.GetObjectACL(objectNameDest)
+	c.Assert(err, IsNil)
+	c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite))
+
+	err = s.bucket.DeleteObject(objectNameDest)
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+func (s *OssBucketSuite) TestCopyObjectToBucket(c *C) {
+	objectName := objectNamePrefix + "tcotb"
+	objectValue := "男儿何不带吴钩,收取关山五十州。请君暂上凌烟阁,若个书生万户侯?"
+	descBucket := "my-bucket-desc"
+	objectNameDest := objectName + "dest"
+
+	err := s.client.CreateBucket(descBucket)
+	c.Assert(err, IsNil)
+
+	descBuck, err := s.client.Bucket(descBucket)
+	c.Assert(err, IsNil)
+
+	err = s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// copy
+	_, err = s.bucket.CopyObjectToBucket(objectName, descBucket, objectNameDest)
+	c.Assert(err, IsNil)
+
+	// check
+	body, err := s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = descBuck.DeleteObject(objectNameDest)
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	err = s.client.DeleteBucket(descBucket)
+	c.Assert(err, IsNil)
+}
+
+// TestAppendObject
+func (s *OssBucketSuite) TestAppendObject(c *C) {
+	objectName := objectNamePrefix + "tao"
+	objectValue := "昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,却道海棠依旧。知否?知否?应是绿肥红瘦。"
+	var val = []byte(objectValue)
+	var localFile = "testx.txt"
+	var nextPos int64
+	var midPos = 1 + rand.Intn(len(val)-1)
+
+	var err = createFileAndWrite(localFile+"1", val[0:midPos])
+	c.Assert(err, IsNil)
+	err = createFileAndWrite(localFile+"2", val[midPos:])
+	c.Assert(err, IsNil)
+
+	// string append
+	nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader("昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,"), nextPos)
+	c.Assert(err, IsNil)
+	nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader("却道海棠依旧。知否?知否?应是绿肥红瘦。"), nextPos)
+	c.Assert(err, IsNil)
+
+	body, err := s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// byte append
+	nextPos = 0
+	nextPos, err = s.bucket.AppendObject(objectName, bytes.NewReader(val[0:midPos]), nextPos)
+	c.Assert(err, IsNil)
+	nextPos, err = s.bucket.AppendObject(objectName, bytes.NewReader(val[midPos:]), nextPos)
+	c.Assert(err, IsNil)
+
+	body, err = s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// file append
+	options := []Option{
+		ObjectACL(ACLPublicReadWrite),
+		Meta("my", "myprop"),
+	}
+
+	fd, err := os.Open(localFile + "1")
+	c.Assert(err, IsNil)
+	defer fd.Close()
+	nextPos = 0
+	nextPos, err = s.bucket.AppendObject(objectName, fd, nextPos, options...)
+	c.Assert(err, IsNil)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta, ",", nextPos)
+	c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Appendable")
+	c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+	c.Assert(meta.Get("x-oss-Meta-Mine"), Equals, "")
+	c.Assert(meta.Get("X-Oss-Next-Append-Position"), Equals, strconv.FormatInt(nextPos, 10))
+
+	acl, err := s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectACL:", acl)
+	c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite))
+
+	// second append
+	options = []Option{
+		ObjectACL(ACLPublicRead),
+		Meta("my", "myproptwo"),
+		Meta("mine", "mypropmine"),
+	}
+	fd, err = os.Open(localFile + "2")
+	c.Assert(err, IsNil)
+	defer fd.Close()
+	nextPos, err = s.bucket.AppendObject(objectName, fd, nextPos, options...)
+	c.Assert(err, IsNil)
+
+	body, err = s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	meta, err = s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta xxx:", meta)
+	c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Appendable")
+	c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+	c.Assert(meta.Get("x-Oss-Meta-Mine"), Equals, "")
+	c.Assert(meta.Get("X-Oss-Next-Append-Position"), Equals, strconv.FormatInt(nextPos, 10))
+
+	acl, err = s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(acl.ACL, Equals, string(ACLPublicRead))
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestContentType
+func (s *OssBucketSuite) TestAddContentType(c *C) {
+	opts := addContentType(nil, "abc.txt")
+	typ, err := findOption(opts, HTTPHeaderContentType, "")
+	c.Assert(err, IsNil)
+	c.Assert(typ, Equals, "text/plain; charset=utf-8")
+
+	opts = addContentType(nil)
+	typ, err = findOption(opts, HTTPHeaderContentType, "")
+	c.Assert(err, IsNil)
+	c.Assert(len(opts), Equals, 1)
+	c.Assert(typ, Equals, "application/octet-stream")
+
+	opts = addContentType(nil, "abc.txt", "abc.pdf")
+	typ, err = findOption(opts, HTTPHeaderContentType, "")
+	c.Assert(err, IsNil)
+	c.Assert(typ, Equals, "text/plain; charset=utf-8")
+
+	opts = addContentType(nil, "abc", "abc.txt", "abc.pdf")
+	typ, err = findOption(opts, HTTPHeaderContentType, "")
+	c.Assert(err, IsNil)
+	c.Assert(typ, Equals, "text/plain; charset=utf-8")
+
+	opts = addContentType(nil, "abc", "abc", "edf")
+	typ, err = findOption(opts, HTTPHeaderContentType, "")
+	c.Assert(err, IsNil)
+	c.Assert(typ, Equals, "application/octet-stream")
+
+	opts = addContentType([]Option{Meta("meta", "my")}, "abc", "abc.txt", "abc.pdf")
+	typ, err = findOption(opts, HTTPHeaderContentType, "")
+	c.Assert(err, IsNil)
+	c.Assert(len(opts), Equals, 2)
+	c.Assert(typ, Equals, "text/plain; charset=utf-8")
+}
+
+func (s *OssBucketSuite) TestGetConfig(c *C) {
+	client, err := New(endpoint, accessID, accessKey, UseCname(true),
+		Timeout(11, 12), SecurityToken("token"))
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+
+	c.Assert(bucket.getConfig().HTTPTimeout.ConnectTimeout, Equals, time.Second*11)
+	c.Assert(bucket.getConfig().HTTPTimeout.ReadWriteTimeout, Equals, time.Second*12)
+	c.Assert(bucket.getConfig().HTTPTimeout.HeaderTimeout, Equals, time.Second*12)
+	c.Assert(bucket.getConfig().HTTPTimeout.LongTimeout, Equals, time.Second*12*10)
+
+	c.Assert(bucket.getConfig().SecurityToken, Equals, "token")
+	c.Assert(bucket.getConfig().IsCname, Equals, true)
+}
+
+// TestSTSTonek
+func (s *OssBucketSuite) _TestSTSTonek(c *C) {
+	objectName := objectNamePrefix + "tst"
+	objectValue := "红藕香残玉簟秋。轻解罗裳,独上兰舟。云中谁寄锦书来?雁字回时,月满西楼。"
+
+	stsRes, err := getSTSToken(stsServer)
+	c.Assert(err, IsNil)
+	fmt.Println("sts:", stsRes)
+
+	client, err := New(stsEndpoint, stsRes.AccessID, stsRes.AccessKey,
+		SecurityToken(stsRes.SecurityToken))
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(stsBucketName)
+	c.Assert(err, IsNil)
+
+	// Put
+	err = bucket.PutObject(objectName, strings.NewReader(objectValue))
+	c.Assert(err, IsNil)
+
+	// Get
+	body, err := s.bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, objectValue)
+
+	// List
+	lor, err := bucket.ListObjects()
+	c.Assert(err, IsNil)
+	fmt.Println("Objects:", lor.Objects)
+
+	// Delete
+	err = bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+func (s *OssBucketSuite) TestSTSTonekNegative(c *C) {
+	objectName := objectNamePrefix + "tstg"
+	localFile := objectName + ".jpg"
+
+	client, err := New(endpoint, accessID, accessKey, SecurityToken("Invalid"))
+	c.Assert(err, IsNil)
+
+	_, err = client.ListBuckets()
+	c.Assert(err, NotNil)
+
+	bucket, err := client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+
+	err = bucket.PutObject(objectName, strings.NewReader(""))
+	c.Assert(err, NotNil)
+
+	err = bucket.PutObjectFromFile(objectName, "")
+	c.Assert(err, NotNil)
+
+	_, err = bucket.GetObject(objectName)
+	c.Assert(err, NotNil)
+
+	err = bucket.GetObjectToFile(objectName, "")
+	c.Assert(err, NotNil)
+
+	_, err = bucket.ListObjects()
+	c.Assert(err, NotNil)
+
+	err = bucket.SetObjectACL(objectName, ACLPublicRead)
+	c.Assert(err, NotNil)
+
+	_, err = bucket.GetObjectACL(objectName)
+	c.Assert(err, NotNil)
+
+	err = bucket.UploadFile(objectName, localFile, MinPartSize)
+	c.Assert(err, NotNil)
+
+	err = bucket.DownloadFile(objectName, localFile, MinPartSize)
+	c.Assert(err, NotNil)
+
+	_, err = bucket.IsObjectExist(objectName)
+	c.Assert(err, NotNil)
+
+	_, err = bucket.ListMultipartUploads()
+	c.Assert(err, NotNil)
+
+	err = bucket.DeleteObject(objectName)
+	c.Assert(err, NotNil)
+
+	_, err = bucket.DeleteObjects([]string{objectName})
+	c.Assert(err, NotNil)
+
+	err = client.DeleteBucket(bucketName)
+	c.Assert(err, NotNil)
+
+	_, err = getSTSToken("")
+	c.Assert(err, NotNil)
+
+	_, err = getSTSToken("http://me.php")
+	c.Assert(err, NotNil)
+}
+
+func (s *OssBucketSuite) TestUploadBigFile(c *C) {
+	objectName := objectNamePrefix + "tubf"
+	bigFile := "D:\\tmp\\bigfile.zip"
+	newFile := "D:\\tmp\\newbigfile.zip"
+
+	exist, err := isFileExist(bigFile)
+	c.Assert(err, IsNil)
+	if !exist {
+		return
+	}
+
+	// Put
+	start := GetNowSec()
+	err = s.bucket.PutObjectFromFile(objectName, bigFile)
+	c.Assert(err, IsNil)
+	end := GetNowSec()
+	fmt.Println("Put big file:", bigFile, "use sec:", end-start)
+
+	// Check
+	start = GetNowSec()
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+	end = GetNowSec()
+	fmt.Println("Get big file:", bigFile, "use sec:", end-start)
+
+	start = GetNowSec()
+	eq, err := compareFiles(bigFile, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+	end = GetNowSec()
+	fmt.Println("Compare big file:", bigFile, "use sec:", end-start)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// private
+func createFileAndWrite(fileName string, data []byte) error {
+	os.Remove(fileName)
+
+	fo, err := os.Create(fileName)
+	if err != nil {
+		return err
+	}
+	defer fo.Close()
+
+	bytes, err := fo.Write(data)
+	if err != nil {
+		return err
+	}
+
+	if bytes != len(data) {
+		return fmt.Errorf(fmt.Sprintf("write %s bytes not equal data length %s", bytes, len(data)))
+	}
+
+	return nil
+}
+
+// compare the content between fileL and fileR
+func compareFiles(fileL string, fileR string) (bool, error) {
+	finL, err := os.Open(fileL)
+	if err != nil {
+		return false, err
+	}
+	defer finL.Close()
+
+	finR, err := os.Open(fileR)
+	if err != nil {
+		return false, err
+	}
+	defer finR.Close()
+
+	statL, err := finL.Stat()
+	if err != nil {
+		return false, err
+	}
+
+	statR, err := finR.Stat()
+	if err != nil {
+		return false, err
+	}
+
+	if statL.Size() != statR.Size() {
+		return false, nil
+	}
+
+	size := statL.Size()
+	if size > 102400 {
+		size = 102400
+	}
+
+	bufL := make([]byte, size)
+	bufR := make([]byte, size)
+	for {
+		n, _ := finL.Read(bufL)
+		if 0 == n {
+			break
+		}
+
+		n, _ = finR.Read(bufR)
+		if 0 == n {
+			break
+		}
+
+		if !bytes.Equal(bufL, bufR) {
+			return false, nil
+		}
+	}
+
+	return true, nil
+}
+
+// compare the content of file and data
+func compareFileData(file string, data []byte) (bool, error) {
+	fin, err := os.Open(file)
+	if err != nil {
+		return false, err
+	}
+	defer fin.Close()
+
+	stat, err := fin.Stat()
+	if err != nil {
+		return false, err
+	}
+
+	if stat.Size() != (int64)(len(data)) {
+		return false, nil
+	}
+
+	buf := make([]byte, stat.Size())
+	n, err := fin.Read(buf)
+	if err != nil {
+		return false, err
+	}
+	if stat.Size() != (int64)(n) {
+		return false, errors.New("read error")
+	}
+
+	if !bytes.Equal(buf, data) {
+		return false, nil
+	}
+
+	return true, nil
+}
+
+func walkDir(dirPth, suffix string) ([]string, error) {
+	var files = []string{}
+	suffix = strings.ToUpper(suffix)
+	err := filepath.Walk(dirPth,
+		func(filename string, fi os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+			if fi.IsDir() {
+				return nil
+			}
+			if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) {
+				files = append(files, filename)
+			}
+			return nil
+		})
+	return files, err
+}
+
+func removeTempFiles(path string, prefix string) error {
+	files, err := walkDir(path, prefix)
+	if err != nil {
+		return nil
+	}
+
+	for _, file := range files {
+		fmt.Println("Remove file:", file)
+		os.Remove(file)
+	}
+
+	return nil
+}
+
+func isFileExist(filename string) (bool, error) {
+	_, err := os.Stat(filename)
+	if err != nil && os.IsNotExist(err) {
+		return false, nil
+	} else if err != nil {
+		return false, err
+	} else {
+		return true, nil
+	}
+}
+
+// STS Server的GET请求返回的数据
+type getSTSResult struct {
+	Status        int    `json:"status"`        // 返回状态码, 200表示获取成功,非200表示失败
+	AccessID      string `json:"accessId"`      //STS AccessId
+	AccessKey     string `json:"accessKey"`     // STS AccessKey
+	Expiration    string `json:"expiration"`    // STS Token
+	SecurityToken string `json:"securityToken"` // Token失效的时间, GMT时间
+	Bucket        string `json:"bucket"`        // 可以使用的bucket
+	Endpoint      string `json:"bucket"`        // 要访问的endpoint
+}
+
+// 从STS Server获取STS信息。返回值中当error为nil时,GetSTSResult有效。
+func getSTSToken(STSServer string) (getSTSResult, error) {
+	result := getSTSResult{}
+	resp, err := http.Get(STSServer)
+	if err != nil {
+		return result, err
+	}
+	defer resp.Body.Close()
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return result, err
+	}
+
+	err = json.Unmarshal(body, &result)
+	if err != nil {
+		return result, err
+	}
+
+	if result.Status != 200 {
+		return result, errors.New("Server Return Status:" + strconv.Itoa(result.Status))
+	}
+
+	return result, nil
+}
+
+func readBody(body io.ReadCloser) (string, error) {
+	data, err := ioutil.ReadAll(body)
+	body.Close()
+	if err != nil {
+		return "", err
+	}
+	return string(data), nil
+}

+ 648 - 0
oss/client.go

@@ -0,0 +1,648 @@
+// Package oss implements functions for access oss service.
+// It has two main struct Client and Bucket.
+package oss
+
+import (
+	"bytes"
+	"encoding/xml"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+	"time"
+)
+
+//
+// Client Sdk的入口,Client的方法可以完成bucket的各种操作,如create/delete bucket,
+// set/get acl/lifecycle/referer/logging/website等。文件(object)的上传下载通过Bucket完成。
+// 用户用oss.New创建Client。
+//
+type (
+	// Client oss client
+	Client struct {
+		Config *Config // Oss Client configure
+		Conn   *Conn   // Send http request
+	}
+
+	// ClientOption client option such as UseCname, Timeout, SecurityToken.
+	ClientOption func(*Client)
+)
+
+//
+// New 生成一个新的Client。
+//
+// endpoint        用户Bucket所在数据中心的访问域名,如http://oss-cn-hangzhou.aliyuncs.com。
+// accessKeyId     用户标识。
+// accessKeySecret 用户密钥。
+//
+// Client 生成的新Client。error为nil时有效。
+// error  操作无错误时为nil,非nil时表示操作出错。
+//
+func New(endpoint, accessKeyID, accessKeySecret string, options ...ClientOption) (*Client, error) {
+	config := getDefaultOssConfig()
+	config.Endpoint = endpoint
+	config.AccessKeyID = accessKeyID
+	config.AccessKeySecret = accessKeySecret
+
+	url := &urlMaker{}
+	url.Init(config.Endpoint, config.IsCname)
+	conn := &Conn{config, url}
+
+	client := &Client{
+		config,
+		conn,
+	}
+
+	for _, option := range options {
+		option(client)
+	}
+
+	return client, nil
+}
+
+//
+// Bucket 取存储空间(Bucket)的对象实例。
+//
+// bucketName 存储空间名称。
+// Bucket     新的Bucket。error为nil时有效。
+//
+// error 操作无错误时返回nil,非nil为错误信息。
+//
+func (client Client) Bucket(bucketName string) (*Bucket, error) {
+	return &Bucket{
+		client,
+		bucketName,
+	}, nil
+}
+
+//
+// CreateBucket 创建Bucket。
+//
+// bucketName bucket名称,在整个OSS中具有全局唯一性,且不能修改。bucket名称的只能包括小写字母,数字和短横线-,
+// 必须以小写字母或者数字开头,长度必须在3-255字节之间。
+// options  创建bucket的选项。您可以使用选项ACL,指定bucket的访问权限。Bucket有以下三种访问权限,私有读写(ACLPrivate)、
+// 公共读私有写(ACLPublicRead),公共读公共写(ACLPublicReadWrite),默认访问权限是私有读写。
+//
+// error 操作无错误时返回nil,非nil为错误信息。
+//
+func (client Client) CreateBucket(bucketName string, options ...Option) error {
+	headers := make(map[string]string)
+	handleOptions(headers, options)
+
+	resp, err := client.do("PUT", bucketName, "", "", headers, nil)
+	if err != nil {
+		return err
+	}
+
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// ListBuckets 获取当前用户下的bucket。
+//
+// options 指定ListBuckets的筛选行为,Prefix、Marker、MaxKeys三个选项。Prefix限定前缀。
+// Marker设定从Marker之后的第一个开始返回。MaxKeys限定此次返回的最大数目,默认为100。
+// 常用使用场景的实现,参数示例程序list_bucket.go。
+// ListBucketsResponse 操作成功后的返回值,error为nil时该返回值有效。
+//
+// error 操作无错误时返回nil,非nil为错误信息。
+//
+func (client Client) ListBuckets(options ...Option) (ListBucketsResult, error) {
+	var out ListBucketsResult
+
+	params, err := handleParams(options)
+	if err != nil {
+		return out, err
+	}
+
+	resp, err := client.do("GET", "", params, "", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// IsBucketExist Bucket是否存在。
+//
+// bucketName 存储空间名称。
+//
+// bool  存储空间是否存在。error为nil时有效。
+// error 操作无错误时返回nil,非nil为错误信息。
+//
+func (client Client) IsBucketExist(bucketName string) (bool, error) {
+	listRes, err := client.ListBuckets(Prefix(bucketName), MaxKeys(1))
+	if err != nil {
+		return false, err
+	}
+
+	if len(listRes.Buckets) == 1 && listRes.Buckets[0].Name == bucketName {
+		return true, nil
+	}
+	return false, nil
+}
+
+//
+// DeleteBucket 删除空存储空间。非空时请先清理Object、Upload。
+//
+// bucketName 存储空间名称。
+//
+// error 操作无错误时返回nil,非nil为错误信息。
+//
+func (client Client) DeleteBucket(bucketName string) error {
+	resp, err := client.do("DELETE", bucketName, "", "", nil, nil)
+	if err != nil {
+		return err
+	}
+
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
+
+//
+// GetBucketLocation 查看Bucket所属数据中心位置的信息。
+//
+// 如果您想了解"访问域名和数据中心"详细信息,请参看
+// https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/endpoint.html
+//
+// bucketName 存储空间名称。
+//
+// string Bucket所属的数据中心位置信息。
+// error  操作无错误时返回nil,非nil为错误信息。
+//
+func (client Client) GetBucketLocation(bucketName string) (string, error) {
+	resp, err := client.do("GET", bucketName, "location", "location", nil, nil)
+	if err != nil {
+		return "", err
+	}
+	defer resp.body.Close()
+
+	var LocationConstraint string
+	err = xmlUnmarshal(resp.body, &LocationConstraint)
+	return LocationConstraint, err
+}
+
+//
+// SetBucketACL 修改Bucket的访问权限。
+//
+// bucketName 存储空间名称。
+// bucketAcl  bucket的访问权限。Bucket有以下三种访问权限,Bucket有以下三种访问权限,私有读写(ACLPrivate)、
+// 公共读私有写(ACLPublicRead),公共读公共写(ACLPublicReadWrite)。
+//
+// error 操作无错误时返回nil,非nil为错误信息。
+//
+func (client Client) SetBucketACL(bucketName string, bucketACL ACLType) error {
+	headers := map[string]string{HTTPHeaderOssACL: string(bucketACL)}
+	resp, err := client.do("PUT", bucketName, "", "", headers, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// GetBucketACL 获得Bucket的访问权限。
+//
+// bucketName 存储空间名称。
+//
+// GetBucketAclResponse 操作成功后的返回值,error为nil时该返回值有效。
+// error 操作无错误时返回nil,非nil为错误信息。
+//
+func (client Client) GetBucketACL(bucketName string) (GetBucketACLResult, error) {
+	var out GetBucketACLResult
+	resp, err := client.do("GET", bucketName, "acl", "acl", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// SetBucketLifecycle 修改Bucket的生命周期设置。
+//
+// OSS提供Object生命周期管理来为用户管理对象。用户可以为某个Bucket定义生命周期配置,来为该Bucket的Object定义各种规则。
+// Bucket的拥有者可以通过SetBucketLifecycle来设置Bucket的Lifecycle配置。Lifecycle开启后,OSS将按照配置,
+// 定期自动删除与Lifecycle规则相匹配的Object。如果您想了解更多的生命周期的信息,请参看
+// https://help.aliyun.com/document_detail/oss/user_guide/manage_object/object_lifecycle.html
+//
+// bucketName 存储空间名称。
+// rules 生命周期规则列表。生命周期规则有两种格式,指定绝对和相对过期时间,分布由days和year/month/day控制。
+// 具体用法请参考示例程序sample/bucket_lifecycle.go。
+//
+// error 操作无错误时返回error为nil,非nil为错误信息。
+//
+func (client Client) SetBucketLifecycle(bucketName string, rules []LifecycleRule) error {
+	lxml := lifecycleXML{Rules: convLifecycleRule(rules)}
+	bs, err := xml.Marshal(lxml)
+	if err != nil {
+		return err
+	}
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+
+	fmt.Println("xml:", string(bs))
+
+	contentType := http.DetectContentType(buffer.Bytes())
+	headers := map[string]string{}
+	headers[HTTPHeaderContentType] = contentType
+
+	resp, err := client.do("PUT", bucketName, "lifecycle", "lifecycle", headers, buffer)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// DeleteBucketLifecycle 删除Bucket的生命周期设置。
+//
+//
+// bucketName 存储空间名称。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) DeleteBucketLifecycle(bucketName string) error {
+	resp, err := client.do("DELETE", bucketName, "lifecycle", "lifecycle", nil, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
+
+//
+// GetBucketLifecycle 查看Bucket的生命周期设置。
+//
+// bucketName 存储空间名称。
+//
+// GetBucketLifecycleResponse 操作成功的返回值,error为nil时该返回值有效。Rules为该bucket上的规则列表。
+// error 操作无错误时为nil,非nil为错误信息。
+//
+func (client Client) GetBucketLifecycle(bucketName string) (GetBucketLifecycleResult, error) {
+	var out GetBucketLifecycleResult
+	resp, err := client.do("GET", bucketName, "lifecycle", "lifecycle", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// SetBucketReferer 设置bucket的referer访问白名单和是否允许referer字段为空的请求访问。
+//
+// 防止用户在OSS上的数据被其他人盗用,OSS支持基于HTTP header中表头字段referer的防盗链方法。可以通过OSS控制台或者API的方式对
+// 一个bucket设置referer字段的白名单和是否允许referer字段为空的请求访问。例如,对于一个名为oss-example的bucket,
+// 设置其referer白名单为http://www.aliyun.com。则所有referer为http://www.aliyun.com的请求才能访问oss-example
+// 这个bucket中的object。如果您还需要了解更多信息,请参看
+// https://help.aliyun.com/document_detail/oss/user_guide/security_management/referer.html
+//
+// bucketName  存储空间名称。
+// referers  访问白名单列表。一个bucket可以支持多个referer参数。referer参数支持通配符"*"和"?"。
+// 用法请参看示例sample/bucket_referer.go
+// allowEmptyReferer  指定是否允许referer字段为空的请求访问。 默认为true。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) SetBucketReferer(bucketName string, referers []string, allowEmptyReferer bool) error {
+	rxml := RefererXML{}
+	rxml.AllowEmptyReferer = allowEmptyReferer
+	if referers == nil {
+		rxml.RefererList = append(rxml.RefererList, "")
+	} else {
+		for _, referer := range referers {
+			rxml.RefererList = append(rxml.RefererList, referer)
+		}
+	}
+
+	bs, err := xml.Marshal(rxml)
+	if err != nil {
+		return err
+	}
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+
+	contentType := http.DetectContentType(buffer.Bytes())
+	headers := map[string]string{}
+	headers[HTTPHeaderContentType] = contentType
+
+	resp, err := client.do("PUT", bucketName, "referer", "referer", headers, buffer)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// GetBucketReferer 获得Bucket的白名单地址。
+//
+// bucketName 存储空间名称。
+//
+// GetBucketRefererResponse 操作成功的返回值,error为nil时该返回值有效。
+// error 操作无错误时为nil,非nil为错误信息。
+//
+func (client Client) GetBucketReferer(bucketName string) (GetBucketRefererResult, error) {
+	var out GetBucketRefererResult
+	resp, err := client.do("GET", bucketName, "referer", "referer", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// SetBucketLogging 修改Bucket的日志设置。
+//
+// OSS为您提供自动保存访问日志记录功能。Bucket的拥有者可以开启访问日志记录功能。当一个bucket开启访问日志记录功能后,
+// OSS自动将访问这个bucket的请求日志,以小时为单位,按照固定的命名规则,生成一个Object写入用户指定的bucket中。
+// 如果您需要更多,请参看 https://help.aliyun.com/document_detail/oss/user_guide/security_management/logging.html
+//
+// bucketName   需要记录访问日志的Bucket。
+// targetBucket 访问日志记录到的Bucket。
+// targetPrefix bucketName中需要存储访问日志记录的object前缀。为空记录所有object的访问日志。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) SetBucketLogging(bucketName, targetBucket, targetPrefix string,
+	isEnable bool) error {
+	var err error
+	var bs []byte
+	if isEnable {
+		lxml := LoggingXML{}
+		lxml.LoggingEnabled.TargetBucket = targetBucket
+		lxml.LoggingEnabled.TargetPrefix = targetPrefix
+		bs, err = xml.Marshal(lxml)
+	} else {
+		lxml := loggingXMLEmpty{}
+		bs, err = xml.Marshal(lxml)
+	}
+
+	if err != nil {
+		return err
+	}
+
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+	fmt.Println(isEnable, "; xml: ", string(bs))
+
+	contentType := http.DetectContentType(buffer.Bytes())
+	headers := map[string]string{}
+	headers[HTTPHeaderContentType] = contentType
+
+	resp, err := client.do("PUT", bucketName, "logging", "logging", headers, buffer)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// DeleteBucketLogging 删除Bucket的日志设置。
+//
+// bucketName 需要删除访问日志的Bucket。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) DeleteBucketLogging(bucketName string) error {
+	resp, err := client.do("DELETE", bucketName, "logging", "logging", nil, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
+
+//
+// GetBucketLogging 获得Bucket的日志设置。
+//
+// bucketName  需要删除访问日志的Bucket。
+// GetBucketLoggingResponse  操作成功的返回值,error为nil时该返回值有效。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) GetBucketLogging(bucketName string) (GetBucketLoggingResult, error) {
+	var out GetBucketLoggingResult
+	resp, err := client.do("GET", bucketName, "logging", "logging", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// SetBucketWebsite 设置/修改Bucket的默认首页以及错误页。
+//
+// OSS支持静态网站托管,Website操作可以将一个bucket设置成静态网站托管模式 。您可以将自己的Bucket配置成静态网站托管模式。
+// 如果您需要更多,请参看 https://help.aliyun.com/document_detail/oss/user_guide/static_host_website.html
+//
+// bucketName     需要设置Website的Bucket。
+// indexDocument  索引文档。
+// errorDocument  错误文档。
+//
+// error  操作无错误为nil,非nil为错误信息。
+//
+func (client Client) SetBucketWebsite(bucketName, indexDocument, errorDocument string) error {
+	wxml := WebsiteXML{}
+	wxml.IndexDocument.Suffix = indexDocument
+	wxml.ErrorDocument.Key = errorDocument
+
+	bs, err := xml.Marshal(wxml)
+	if err != nil {
+		return err
+	}
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+
+	contentType := http.DetectContentType(buffer.Bytes())
+	headers := make(map[string]string)
+	headers[HTTPHeaderContentType] = contentType
+
+	resp, err := client.do("PUT", bucketName, "website", "website", headers, buffer)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// DeleteBucketWebsite 删除Bucket的Website设置。
+//
+// bucketName  需要删除website设置的Bucket。
+//
+// error  操作无错误为nil,非nil为错误信息。
+//
+func (client Client) DeleteBucketWebsite(bucketName string) error {
+	resp, err := client.do("DELETE", bucketName, "website", "website", nil, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
+
+//
+// GetBucketWebsite 获得Bucket的默认首页以及错误页。
+//
+// bucketName 存储空间名称。
+//
+// GetBucketWebsiteResponse 操作成功的返回值,error为nil时该返回值有效。
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) GetBucketWebsite(bucketName string) (GetBucketWebsiteResult, error) {
+	var out GetBucketWebsiteResult
+	resp, err := client.do("GET", bucketName, "website", "website", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// SetBucketCORS 设置Bucket的跨域访问(CORS)规则。
+//
+// 跨域访问的更多信息,请参看 https://help.aliyun.com/document_detail/oss/user_guide/security_management/cors.html
+//
+// bucketName 需要设置Website的Bucket。
+// corsRules  待设置的CORS规则。用法请参看示例代码sample/bucket_cors.go。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) SetBucketCORS(bucketName string, corsRules []CORSRule) error {
+	corsxml := CORSXML{}
+	for _, v := range corsRules {
+		cr := CORSRule{}
+		cr.AllowedMethod = v.AllowedMethod
+		cr.AllowedOrigin = v.AllowedOrigin
+		cr.AllowedHeader = v.AllowedHeader
+		cr.ExposeHeader = v.ExposeHeader
+		cr.MaxAgeSeconds = v.MaxAgeSeconds
+		corsxml.CORSRules = append(corsxml.CORSRules, cr)
+	}
+
+	bs, err := xml.Marshal(corsxml)
+	if err != nil {
+		return err
+	}
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+
+	contentType := http.DetectContentType(buffer.Bytes())
+	headers := map[string]string{}
+	headers[HTTPHeaderContentType] = contentType
+
+	resp, err := client.do("PUT", bucketName, "cors", "cors", headers, buffer)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusOK})
+}
+
+//
+// DeleteBucketCORS 删除Bucket的Website设置。
+//
+// bucketName 需要删除cors设置的Bucket。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) DeleteBucketCORS(bucketName string) error {
+	resp, err := client.do("DELETE", bucketName, "cors", "cors", nil, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
+
+//
+// GetBucketCORS 获得Bucket的CORS设置。
+//
+//
+// bucketName  存储空间名称。
+// GetBucketCORSResult  操作成功的返回值,error为nil时该返回值有效。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) GetBucketCORS(bucketName string) (GetBucketCORSResult, error) {
+	var out GetBucketCORSResult
+	resp, err := client.do("GET", bucketName, "cors", "cors", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// UseCname 设置是否使用CNAME,默认不使用。
+//
+// isUseCname true设置endpoint格式是cname格式,false为非cname格式,默认false
+//
+func UseCname(isUseCname bool) ClientOption {
+	return func(client *Client) {
+		client.Config.IsCname = isUseCname
+		client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname)
+	}
+}
+
+//
+// Timeout 设置HTTP超时时间。
+//
+// connectTimeoutSec HTTP链接超时时间,单位是秒,默认10秒。0表示永不超时。
+// readWriteTimeout  HTTP发送接受数据超时时间,单位是秒,默认20秒。0表示永不超时。
+//
+func Timeout(connectTimeoutSec, readWriteTimeout int64) ClientOption {
+	return func(client *Client) {
+		client.Config.HTTPTimeout.ConnectTimeout =
+			time.Second * time.Duration(connectTimeoutSec)
+		client.Config.HTTPTimeout.ReadWriteTimeout =
+			time.Second * time.Duration(readWriteTimeout)
+		client.Config.HTTPTimeout.HeaderTimeout =
+			time.Second * time.Duration(readWriteTimeout)
+		client.Config.HTTPTimeout.LongTimeout =
+			time.Second * time.Duration(readWriteTimeout*10)
+	}
+}
+
+//
+// SecurityToken 临时用户设置SecurityToken
+//
+// token STS token
+//
+func SecurityToken(token string) ClientOption {
+	return func(client *Client) {
+		client.Config.SecurityToken = strings.TrimSpace(token)
+	}
+}
+
+// Private
+func (client Client) do(method, bucketName, urlParams, subResource string,
+	headers map[string]string, data io.Reader) (*Response, error) {
+	return client.Conn.Do(method, bucketName, "", urlParams,
+		subResource, headers, data)
+}

+ 1204 - 0
oss/client_test.go

@@ -0,0 +1,1204 @@
+// client test
+// use gocheck, install gocheck to execute "go get gopkg.in/check.v1",
+// see https://labix.org/gocheck
+
+package oss
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	. "gopkg.in/check.v1"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) {
+	TestingT(t)
+}
+
+type OssClientSuite struct{}
+
+var _ = Suite(&OssClientSuite{})
+
+const (
+	// Update before running test
+	endpoint         = "<endpoint>"
+	accessID         = "<AccessKeyId>"
+	accessKey        = "<AccessKeySecret>"
+	bucketNamePrefix = "<my-go-bucket>"
+	stsServer        = "<STSServerHost>"
+	stsEndpoint      = "<endpoint>"
+	stsBucketName    = "<my-sts-bucket>"
+)
+
+// Run once when the suite starts running
+func (s *OssClientSuite) SetUpSuite(c *C) {
+}
+
+// Run before each test or benchmark starts running
+func (s *OssClientSuite) TearDownSuite(c *C) {
+}
+
+// Run after each test or benchmark runs
+func (s *OssClientSuite) SetUpTest(c *C) {
+}
+
+// Run once after all tests or benchmarks have finished running
+func (s *OssClientSuite) TearDownTest(c *C) {
+}
+
+// TestCreateBucket
+func (s *OssClientSuite) TestCreateBucket(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tcb"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// Create
+	client.DeleteBucket(bucketNameTest)
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// Check
+	lbr, err := client.ListBuckets()
+	c.Assert(err, IsNil)
+
+	found := s.checkBucket(lbr.Buckets, bucketNameTest)
+	c.Assert(found, Equals, true)
+
+	res, err := client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// Create with ACLPublicRead
+	err = client.CreateBucket(bucketNameTest, ACL(ACLPublicRead))
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPublicRead))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// ACLPublicReadWrite
+	err = client.CreateBucket(bucketNameTest, ACL(ACLPublicReadWrite))
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPublicReadWrite))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// ACLPrivate
+	err = client.CreateBucket(bucketNameTest, ACL(ACLPrivate))
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+	// Delete
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestCreateBucketNegative
+func (s *OssClientSuite) TestCreateBucketNegative(c *C) {
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// BucketName invalid
+	err = client.CreateBucket("xx")
+	c.Assert(err, NotNil)
+
+	err = client.CreateBucket("XXXX")
+	c.Assert(err, NotNil)
+	fmt.Println(err)
+
+	err = client.CreateBucket("_bucket")
+	c.Assert(err, NotNil)
+	fmt.Println(err)
+
+	// Acl invalid
+	err = client.CreateBucket(bucketNamePrefix+"tcbn", ACL("InvaldAcl"))
+	c.Assert(err, NotNil)
+	fmt.Println(err)
+}
+
+// TestDeleteBucket
+func (s *OssClientSuite) TestDeleteBucket(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tdb"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// Create
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// Check
+	lbr, err := client.ListBuckets()
+	c.Assert(err, IsNil)
+
+	found := s.checkBucket(lbr.Buckets, bucketNameTest)
+	c.Assert(found, Equals, true)
+
+	// Delete
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	time.Sleep(time.Second * 1)
+
+	// Check
+	lbr, err = client.ListBuckets()
+	c.Assert(err, IsNil)
+
+	// Sometimes failed because of cache
+	found = s.checkBucket(lbr.Buckets, bucketNameTest)
+	//    c.Assert(found, Equals, false)
+
+	err = client.DeleteBucket(bucketNameTest)
+	//    c.Assert(err, IsNil)
+}
+
+// TestDeleteBucketNegative
+func (s *OssClientSuite) TestDeleteBucketNegative(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tdbn"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// BucketName invalid
+	err = client.DeleteBucket("xx")
+	c.Assert(err, NotNil)
+
+	err = client.DeleteBucket("XXXX")
+	c.Assert(err, NotNil)
+
+	err = client.DeleteBucket("_bucket")
+	c.Assert(err, NotNil)
+
+	// Delete no exist
+	err = client.DeleteBucket("notexist")
+	c.Assert(err, NotNil)
+
+	// No permission to delete, this ak/sk for js sdk
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	accessID := "<AccessKeyId>"
+	accessKey := "<AccessKeySecret>"
+	clientOtherUser, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = clientOtherUser.DeleteBucket(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestListBucket
+func (s *OssClientSuite) TestListBucket(c *C) {
+	var bucketNameLbOne = bucketNamePrefix + "tlb1"
+	var bucketNameLbTwo = bucketNamePrefix + "tlb2"
+	var bucketNameLbThree = bucketNamePrefix + "tlb3"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// CreateBucket
+	err = client.CreateBucket(bucketNameLbOne)
+	c.Assert(err, IsNil)
+	err = client.CreateBucket(bucketNameLbTwo)
+	c.Assert(err, IsNil)
+	err = client.CreateBucket(bucketNameLbThree)
+	c.Assert(err, IsNil)
+
+	// ListBuckets, specified prefix
+	lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(2))
+	c.Assert(err, IsNil)
+	c.Assert(len(lbr.Buckets), Equals, 2)
+
+	// ListBuckets, specified max keys
+	lbr, err = client.ListBuckets(MaxKeys(2))
+	c.Assert(err, IsNil)
+	c.Assert(len(lbr.Buckets), Equals, 2)
+
+	// ListBuckets, specified max keys
+	lbr, err = client.ListBuckets(Marker(bucketNameLbOne), MaxKeys(1))
+	c.Assert(err, IsNil)
+	c.Assert(len(lbr.Buckets), Equals, 1)
+
+	// ListBuckets, specified max keys
+	lbr, err = client.ListBuckets(Marker(bucketNameLbOne))
+	c.Assert(err, IsNil)
+	c.Assert(len(lbr.Buckets) == 2 || len(lbr.Buckets) == 3, Equals, true)
+
+	// DeleteBucket
+	err = client.DeleteBucket(bucketNameLbOne)
+	c.Assert(err, IsNil)
+	err = client.DeleteBucket(bucketNameLbTwo)
+	c.Assert(err, IsNil)
+	err = client.DeleteBucket(bucketNameLbThree)
+	c.Assert(err, IsNil)
+}
+
+// TestListBucket
+func (s *OssClientSuite) TestIsBucketExist(c *C) {
+	var bucketNameLbOne = bucketNamePrefix + "tibe1"
+	var bucketNameLbTwo = bucketNamePrefix + "tibe11"
+	var bucketNameLbThree = bucketNamePrefix + "tibe111"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// CreateBucket
+	err = client.CreateBucket(bucketNameLbOne)
+	c.Assert(err, IsNil)
+	err = client.CreateBucket(bucketNameLbTwo)
+	c.Assert(err, IsNil)
+	err = client.CreateBucket(bucketNameLbThree)
+	c.Assert(err, IsNil)
+
+	// exist
+	exist, err := client.IsBucketExist(bucketNameLbTwo)
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, true)
+
+	exist, err = client.IsBucketExist(bucketNameLbThree)
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, true)
+
+	exist, err = client.IsBucketExist(bucketNameLbOne)
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, true)
+
+	// not exist
+	exist, err = client.IsBucketExist(bucketNamePrefix + "tibe")
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, false)
+
+	exist, err = client.IsBucketExist(bucketNamePrefix + "tibe1111")
+	c.Assert(err, IsNil)
+	c.Assert(exist, Equals, false)
+
+	// negative
+	exist, err = client.IsBucketExist("BucketNameInvalid")
+	c.Assert(err, NotNil)
+
+	// DeleteBucket
+	err = client.DeleteBucket(bucketNameLbOne)
+	c.Assert(err, IsNil)
+	err = client.DeleteBucket(bucketNameLbTwo)
+	c.Assert(err, IsNil)
+	err = client.DeleteBucket(bucketNameLbThree)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketAcl
+func (s *OssClientSuite) TestSetBucketAcl(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsba"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// Private
+	client.DeleteBucket(bucketNameTest)
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+	// set ACL_PUBLIC_R
+	err = client.SetBucketACL(bucketNameTest, ACLPublicRead)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPublicRead))
+
+	// set ACL_PUBLIC_RW
+	err = client.SetBucketACL(bucketNameTest, ACLPublicReadWrite)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPublicReadWrite))
+
+	// set ACL_PUBLIC_RW
+	err = client.SetBucketACL(bucketNameTest, ACLPrivate)
+	c.Assert(err, IsNil)
+	err = client.SetBucketACL(bucketNameTest, ACLPrivate)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketAclNegative
+func (s *OssClientSuite) TestBucketAclNegative(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsban"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.SetBucketACL(bucketNameTest, "InvalidACL")
+	c.Assert(err, NotNil)
+	fmt.Println(err)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestGetBucketAcl
+func (s *OssClientSuite) TestGetBucketAcl(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tgba"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// Private
+	client.DeleteBucket(bucketNameTest)
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// PublicRead
+	err = client.CreateBucket(bucketNameTest, ACL(ACLPublicRead))
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPublicRead))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// PublicReadWrite
+	err = client.CreateBucket(bucketNameTest, ACL(ACLPublicReadWrite))
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPublicReadWrite))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestGetBucketAcl
+func (s *OssClientSuite) TestGetBucketLocation(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tgbl"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// Private
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	loc, err := client.GetBucketLocation(bucketNameTest)
+	c.Assert(loc, Equals, "oss-cn-hangzhou")
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestGetBucketLocationNegative
+func (s *OssClientSuite) TestGetBucketLocationNegative(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tgblg"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// not exist
+	_, err = client.GetBucketLocation(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// not exist
+	_, err = client.GetBucketLocation("InvalidBucketName_")
+	c.Assert(err, NotNil)
+}
+
+// TestSetBucketLifecycle
+func (s *OssClientSuite) TestSetBucketLifecycle(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsbl"
+	var rule1 = BuildLifecycleRuleByDate("idone", "one", true, 2015, 11, 11)
+	var rule2 = BuildLifecycleRuleByDays("idtwo", "two", true, 3)
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set single rule
+	var rules = []LifecycleRule{rule1}
+	err = client.SetBucketLifecycle(bucketNameTest, rules)
+	c.Assert(err, IsNil)
+	// double set rule
+	err = client.SetBucketLifecycle(bucketNameTest, rules)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketLifecycle(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(len(res.Rules), Equals, 1)
+	c.Assert(res.Rules[0].ID, Equals, "idone")
+
+	err = client.DeleteBucketLifecycle(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set two rules
+	rules = []LifecycleRule{rule1, rule2}
+	err = client.SetBucketLifecycle(bucketNameTest, rules)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketLifecycle(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(len(res.Rules), Equals, 2)
+	c.Assert(res.Rules[0].ID, Equals, "idone")
+	c.Assert(res.Rules[1].ID, Equals, "idtwo")
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestDeleteBucketLifecycle
+func (s *OssClientSuite) TestDeleteBucketLifecycle(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tdbl"
+
+	var rule1 = BuildLifecycleRuleByDate("idone", "one", true, 2015, 11, 11)
+	var rule2 = BuildLifecycleRuleByDays("idtwo", "two", true, 3)
+	var rules = []LifecycleRule{rule1, rule2}
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucketLifecycle(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.SetBucketLifecycle(bucketNameTest, rules)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketLifecycle(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(len(res.Rules), Equals, 2)
+
+	// delete
+	err = client.DeleteBucketLifecycle(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketLifecycle(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// eliminate effect of cache
+	time.Sleep(time.Second * 3)
+
+	// delete when not set
+	err = client.DeleteBucketLifecycle(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketLifecycleNegative
+func (s *OssClientSuite) TestBucketLifecycleNegative(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsbln"
+	var rules = []LifecycleRule{}
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set with no rule
+	err = client.SetBucketLifecycle(bucketNameTest, rules)
+	c.Assert(err, NotNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// not exist
+	err = client.SetBucketLifecycle(bucketNameTest, rules)
+	c.Assert(err, NotNil)
+
+	// not exist
+	_, err = client.GetBucketLifecycle(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// not exist
+	err = client.DeleteBucketLifecycle(bucketNameTest)
+	c.Assert(err, NotNil)
+}
+
+// TestSetBucketReferer
+func (s *OssClientSuite) TestSetBucketReferer(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsbr"
+	var referers = []string{"http://www.aliyun.com", "https://www.aliyun.com"}
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketReferer(bucketNameTest)
+	c.Assert(res.AllowEmptyReferer, Equals, true)
+	c.Assert(len(res.RefererList), Equals, 0)
+
+	// set referers
+	err = client.SetBucketReferer(bucketNameTest, referers, false)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketReferer(bucketNameTest)
+	c.Assert(res.AllowEmptyReferer, Equals, false)
+	c.Assert(len(res.RefererList), Equals, 2)
+	c.Assert(res.RefererList[0], Equals, "http://www.aliyun.com")
+	c.Assert(res.RefererList[1], Equals, "https://www.aliyun.com")
+
+	// reset referer, referers empty
+	referers = []string{""}
+	err = client.SetBucketReferer(bucketNameTest, referers, true)
+	c.Assert(err, IsNil)
+
+	referers = []string{}
+	err = client.SetBucketReferer(bucketNameTest, referers, true)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketReferer(bucketNameTest)
+	c.Assert(res.AllowEmptyReferer, Equals, true)
+	c.Assert(len(res.RefererList), Equals, 0)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketRefererNegative
+func (s *OssClientSuite) TestBucketRefererNegative(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsbrn"
+	var referers = []string{""}
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// not exist
+	_, err = client.GetBucketReferer(bucketNameTest)
+	c.Assert(err, NotNil)
+	fmt.Println(err)
+
+	// not exist
+	err = client.SetBucketReferer(bucketNameTest, referers, true)
+	c.Assert(err, NotNil)
+	fmt.Println(err)
+}
+
+// TestSetBucketLogging
+func (s *OssClientSuite) TestSetBucketLogging(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsbl"
+	var bucketNameTarget = bucketNamePrefix + "tsblt"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+	err = client.CreateBucket(bucketNameTarget)
+	c.Assert(err, IsNil)
+
+	// set logging
+	err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", true)
+	c.Assert(err, IsNil)
+	// reset
+	err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", false)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketLogging(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.LoggingEnabled.TargetBucket, Equals, "")
+	c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "")
+
+	err = client.DeleteBucketLogging(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set to self
+	err = client.SetBucketLogging(bucketNameTest, bucketNameTest, "prefix", true)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+	err = client.DeleteBucket(bucketNameTarget)
+	c.Assert(err, IsNil)
+}
+
+// TestDeleteBucketLogging
+func (s *OssClientSuite) TestDeleteBucketLogging(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tdbl"
+	var bucketNameTarget = bucketNamePrefix + "tdblt"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+	err = client.CreateBucket(bucketNameTarget)
+	c.Assert(err, IsNil)
+
+	// get when not set
+	res, err := client.GetBucketLogging(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.LoggingEnabled.TargetBucket, Equals, "")
+	c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "")
+
+	// set
+	err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", true)
+	c.Assert(err, IsNil)
+
+	// get
+	res, err = client.GetBucketLogging(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.LoggingEnabled.TargetBucket, Equals, bucketNameTarget)
+	c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "prefix")
+
+	// set
+	err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", false)
+	c.Assert(err, IsNil)
+
+	// get
+	res, err = client.GetBucketLogging(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.LoggingEnabled.TargetBucket, Equals, "")
+	c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "")
+
+	// delete
+	err = client.DeleteBucketLogging(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// get after delete
+	res, err = client.GetBucketLogging(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.LoggingEnabled.TargetBucket, Equals, "")
+	c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "")
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+	err = client.DeleteBucket(bucketNameTarget)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketLoggingNegative
+func (s *OssClientSuite) TestSetBucketLoggingNegative(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsbln"
+	var bucketNameTarget = bucketNamePrefix + "tsblnt"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// not exist
+	_, err = client.GetBucketLogging(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// not exist
+	err = client.SetBucketLogging(bucketNameTest, "targetbucket", "prefix", true)
+	c.Assert(err, NotNil)
+
+	// not exist
+	err = client.DeleteBucketLogging(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// target bucket not exist
+	err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", true)
+	c.Assert(err, NotNil)
+
+	//    err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", false)
+	//    c.Assert(err, NotNil)
+
+	// parameter invalid
+	err = client.SetBucketLogging(bucketNameTest, "XXXX", "prefix", true)
+	c.Assert(err, NotNil)
+
+	//    err = client.SetBucketLogging(bucketNameTest, "XXXX", "prefix", false)
+	//    c.Assert(err, NotNil)
+
+	err = client.SetBucketLogging(bucketNameTest, "xx", "prefix", true)
+	c.Assert(err, NotNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketWebsite
+func (s *OssClientSuite) TestSetBucketWebsite(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsbw"
+	var indexWebsite = "myindex.html"
+	var errorWebsite = "myerror.html"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set
+	err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+	c.Assert(err, IsNil)
+
+	// double set
+	err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite)
+	c.Assert(res.ErrorDocument.Key, Equals, errorWebsite)
+
+	// reset
+	err = client.SetBucketWebsite(bucketNameTest, "your"+indexWebsite, "your"+errorWebsite)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.IndexDocument.Suffix, Equals, "your"+indexWebsite)
+	c.Assert(res.ErrorDocument.Key, Equals, "your"+errorWebsite)
+
+	err = client.DeleteBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set after delete
+	err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite)
+	c.Assert(res.ErrorDocument.Key, Equals, errorWebsite)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestDeleteBucketWebsite
+func (s *OssClientSuite) TestDeleteBucketWebsite(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tdbw"
+	var indexWebsite = "myindex.html"
+	var errorWebsite = "myerror.html"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// get
+	res, err := client.GetBucketWebsite(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// detele without set
+	err = client.DeleteBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set
+	err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite)
+	c.Assert(res.ErrorDocument.Key, Equals, errorWebsite)
+
+	// detele
+	err = client.DeleteBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketWebsite(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// detele after delete
+	err = client.DeleteBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketWebsiteNegative
+func (s *OssClientSuite) TestSetBucketWebsiteNegative(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tdbw"
+	var indexWebsite = "myindex.html"
+	var errorWebsite = "myerror.html"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+
+	// not exist
+	_, err = client.GetBucketWebsite(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	err = client.DeleteBucketWebsite(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite)
+	c.Assert(err, NotNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set
+	err = client.SetBucketWebsite(bucketNameTest, "myindex", "myerror")
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.IndexDocument.Suffix, Equals, "myindex")
+	c.Assert(res.ErrorDocument.Key, Equals, "myerror")
+
+	// detele
+	err = client.DeleteBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	_, err = client.GetBucketWebsite(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// detele after delete
+	err = client.DeleteBucketWebsite(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketWebsite
+func (s *OssClientSuite) TestSetBucketCORS(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsbc"
+	var rule1 = CORSRule{
+		AllowedOrigin: []string{"*"},
+		AllowedMethod: []string{"PUT", "GET", "POST"},
+		AllowedHeader: []string{},
+		ExposeHeader:  []string{},
+		MaxAgeSeconds: 100,
+	}
+
+	var rule2 = CORSRule{
+		AllowedOrigin: []string{"http://www.a.com", "http://www.b.com"},
+		AllowedMethod: []string{"GET"},
+		AllowedHeader: []string{"Authorization"},
+		ExposeHeader:  []string{"x-oss-test", "x-oss-test1"},
+		MaxAgeSeconds: 200,
+	}
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set
+	err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1})
+	c.Assert(err, IsNil)
+
+	gbcr, err := client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(len(gbcr.CORSRules), Equals, 1)
+	c.Assert(len(gbcr.CORSRules[0].AllowedOrigin), Equals, 1)
+	c.Assert(len(gbcr.CORSRules[0].AllowedMethod), Equals, 3)
+	c.Assert(len(gbcr.CORSRules[0].AllowedHeader), Equals, 0)
+	c.Assert(len(gbcr.CORSRules[0].ExposeHeader), Equals, 0)
+	c.Assert(gbcr.CORSRules[0].MaxAgeSeconds, Equals, 100)
+
+	// double set
+	err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1})
+	c.Assert(err, IsNil)
+
+	gbcr, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(len(gbcr.CORSRules), Equals, 1)
+	c.Assert(len(gbcr.CORSRules[0].AllowedOrigin), Equals, 1)
+	c.Assert(len(gbcr.CORSRules[0].AllowedMethod), Equals, 3)
+	c.Assert(len(gbcr.CORSRules[0].AllowedHeader), Equals, 0)
+	c.Assert(len(gbcr.CORSRules[0].ExposeHeader), Equals, 0)
+	c.Assert(gbcr.CORSRules[0].MaxAgeSeconds, Equals, 100)
+
+	// set rule2
+	err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule2})
+	c.Assert(err, IsNil)
+
+	gbcr, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(len(gbcr.CORSRules), Equals, 1)
+	c.Assert(len(gbcr.CORSRules[0].AllowedOrigin), Equals, 2)
+	c.Assert(len(gbcr.CORSRules[0].AllowedMethod), Equals, 1)
+	c.Assert(len(gbcr.CORSRules[0].AllowedHeader), Equals, 1)
+	c.Assert(len(gbcr.CORSRules[0].ExposeHeader), Equals, 2)
+	c.Assert(gbcr.CORSRules[0].MaxAgeSeconds, Equals, 200)
+
+	// reset
+	err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1, rule2})
+	c.Assert(err, IsNil)
+
+	gbcr, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(len(gbcr.CORSRules), Equals, 2)
+
+	// set after delete
+	err = client.DeleteBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1, rule2})
+	c.Assert(err, IsNil)
+
+	gbcr, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(len(gbcr.CORSRules), Equals, 2)
+
+	err = client.DeleteBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketCORSNegative
+func (s *OssClientSuite) TestDeleteBucketCORS(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tdbc"
+	var rule = CORSRule{
+		AllowedOrigin: []string{"*"},
+		AllowedMethod: []string{"PUT", "GET", "POST"},
+		AllowedHeader: []string{},
+		ExposeHeader:  []string{},
+		MaxAgeSeconds: 100,
+	}
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// delete not set
+	err = client.DeleteBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// set
+	err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule})
+	c.Assert(err, IsNil)
+
+	_, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// detele
+	err = client.DeleteBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	_, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// detele after delete
+	err = client.DeleteBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestSetBucketCORSNegative
+func (s *OssClientSuite) TestSetBucketCORSNegative(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tsbcn"
+	var rule = CORSRule{
+		AllowedOrigin: []string{"*"},
+		AllowedMethod: []string{"PUT", "GET", "POST"},
+		AllowedHeader: []string{},
+		ExposeHeader:  []string{},
+		MaxAgeSeconds: 100,
+	}
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+
+	// not exist
+	_, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	err = client.DeleteBucketCORS(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule})
+	c.Assert(err, NotNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	_, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// set
+	err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule})
+	c.Assert(err, IsNil)
+
+	_, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// detele
+	err = client.DeleteBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	_, err = client.GetBucketCORS(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// detele after delete
+	err = client.DeleteBucketCORS(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestEndpointFormat
+func (s *OssClientSuite) TestEndpointFormat(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tef"
+
+	// http://host
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	// http://host:port
+	client, err = New(endpoint+":80", accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	res, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestCname
+func (s *OssClientSuite) TestCname(c *C) {
+	var bucketNameTest = "<my-bucket-cname>"
+
+	client, err := New("<endpoint>","<AccessKeyId>", "<AccessKeySecret>", UseCname(true))
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	_, err = client.ListBuckets()
+	c.Assert(err, NotNil)
+
+	res, err := client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPrivate))
+}
+
+// TestCname
+func (s *OssClientSuite) TestCnameNegative(c *C) {
+	var bucketNameTest = "<my-bucket-cname>"
+
+	client, err := New("<endpoint>","<AccessKeyId>", "<AccessKeySecret>", UseCname(true))
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	_, err = client.ListBuckets()
+	c.Assert(err, NotNil)
+
+	_, err = client.GetBucketACL(bucketNameTest)
+	c.Assert(err, NotNil)
+}
+
+// TestHttps
+func (s *OssClientSuite) TestHttps(c *C) {
+	var bucketNameTest = "<my-bucket-https>"
+
+	client, err := New("<endpoint>","<AccessKeyId>", "<AccessKeySecret>")
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketACL(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.ACL, Equals, string(ACLPrivate))
+
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestClientOption
+func (s *OssClientSuite) TestClientOption(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tco"
+
+	client, err := New(endpoint, accessID, accessKey, UseCname(true),
+		Timeout(11, 12), SecurityToken("token"))
+	c.Assert(err, IsNil)
+
+	// Create
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	c.Assert(client.Conn.config.HTTPTimeout.ConnectTimeout, Equals, time.Second*11)
+	c.Assert(client.Conn.config.HTTPTimeout.ReadWriteTimeout, Equals, time.Second*12)
+	c.Assert(client.Conn.config.HTTPTimeout.HeaderTimeout, Equals, time.Second*12)
+	c.Assert(client.Conn.config.HTTPTimeout.LongTimeout, Equals, time.Second*12*10)
+
+	c.Assert(client.Conn.config.SecurityToken, Equals, "token")
+	c.Assert(client.Conn.config.IsCname, Equals, true)
+}
+
+// private
+func (s *OssClientSuite) checkBucket(buckets []BucketProperties, bucket string) bool {
+	for _, v := range buckets {
+		if v.Name == bucket {
+			return true
+		}
+	}
+	return false
+}

+ 49 - 0
oss/conf.go

@@ -0,0 +1,49 @@
+package oss
+
+import (
+	"time"
+)
+
+// HTTPTimeout http timeout
+type HTTPTimeout struct {
+	ConnectTimeout   time.Duration
+	ReadWriteTimeout time.Duration
+	HeaderTimeout    time.Duration
+	LongTimeout      time.Duration
+}
+
+// Config oss configure
+type Config struct {
+	Endpoint        string      // oss地址
+	AccessKeyID     string      // accessId
+	AccessKeySecret string      // accessKey
+	RetryTimes      uint        // 失败重试次数,默认5
+	UserAgent       string      // SDK名称/版本/系统信息
+	IsDebug         bool        // 是否开启调试模式,默认false
+	Timeout         uint        // 超时时间,默认60s
+	SecurityToken   string      // STS Token
+	IsCname         bool        // Endpoint是否是CNAME
+	HTTPTimeout     HTTPTimeout // HTTP的超时时间设置
+}
+
+// 获取默认配置
+func getDefaultOssConfig() *Config {
+	config := Config{}
+
+	config.Endpoint = ""
+	config.AccessKeyID = ""
+	config.AccessKeySecret = ""
+	config.RetryTimes = 5
+	config.IsDebug = false
+	config.UserAgent = userAgent
+	config.Timeout = 60 // seconds
+	config.SecurityToken = ""
+	config.IsCname = false
+
+	config.HTTPTimeout.ConnectTimeout = time.Second * 30   // 30s
+	config.HTTPTimeout.ReadWriteTimeout = time.Second * 60 // 60s
+	config.HTTPTimeout.HeaderTimeout = time.Second * 60    // 60s
+	config.HTTPTimeout.LongTimeout = time.Second * 300     // 300s
+
+	return &config
+}

+ 339 - 0
oss/conn.go

@@ -0,0 +1,339 @@
+package oss
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/base64"
+	"encoding/xml"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Conn oss conn
+type Conn struct {
+	config *Config
+	url    *urlMaker
+}
+
+// Response Http response from oss
+type Response struct {
+	statusCode int
+	headers    http.Header
+	body       io.ReadCloser
+}
+
+// Do 处理请求,返回响应结果。
+func (conn Conn) Do(method, bucketName, objectName, urlParams, subResource string,
+	headers map[string]string, data io.Reader) (*Response, error) {
+	uri := conn.url.getURL(bucketName, objectName, urlParams)
+	resource := conn.url.getResource(bucketName, objectName, subResource)
+	return conn.doRequest(method, uri, resource, headers, data)
+}
+
+func (conn Conn) doRequest(method string, uri *url.URL, canonicalizedResource string,
+	headers map[string]string, data io.Reader) (*Response, error) {
+	httpTimeOut := conn.config.HTTPTimeout
+	method = strings.ToUpper(method)
+	uri.Opaque = uri.Path
+	req := &http.Request{
+		Method:     method,
+		URL:        uri,
+		Proto:      "HTTP/1.1",
+		ProtoMajor: 1,
+		ProtoMinor: 1,
+		Header:     make(http.Header),
+		Host:       uri.Host,
+	}
+	conn.handleBody(req, data)
+
+	date := time.Now().UTC().Format(http.TimeFormat)
+	req.Header.Set(HTTPHeaderDate, date)
+	req.Header.Set(HTTPHeaderHost, conn.config.Endpoint)
+	req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent)
+	if conn.config.SecurityToken != "" {
+		req.Header.Set(HTTPHeaderOssSecurityToken, conn.config.SecurityToken)
+	}
+
+	if headers != nil {
+		for k, v := range headers {
+			req.Header.Set(k, v)
+		}
+	}
+
+	conn.signHeader(req, canonicalizedResource)
+
+	timeoutClient := &http.Client{Transport: &http.Transport{
+		Dial: func(netw, addr string) (net.Conn, error) {
+			conn, err := net.DialTimeout(netw, addr, httpTimeOut.ConnectTimeout)
+			if err != nil {
+				return nil, err
+			}
+			return newTimeoutConn(conn, httpTimeOut.ReadWriteTimeout, httpTimeOut.LongTimeout), nil
+		},
+		ResponseHeaderTimeout: httpTimeOut.HeaderTimeout,
+		MaxIdleConnsPerHost:   2000,
+	}}
+
+	resp, err := timeoutClient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	return conn.handleResponse(resp)
+}
+
+// handle request body
+func (conn Conn) handleBody(req *http.Request, body io.Reader) {
+	rc, ok := body.(io.ReadCloser)
+	if !ok && body != nil {
+		rc = ioutil.NopCloser(body)
+	}
+	req.Body = rc
+	switch v := body.(type) {
+	case *bytes.Buffer:
+		req.ContentLength = int64(v.Len())
+	case *bytes.Reader:
+		req.ContentLength = int64(v.Len())
+	case *strings.Reader:
+		req.ContentLength = int64(v.Len())
+	case *os.File:
+		req.ContentLength = tryGetFileSize(v)
+	}
+	req.Header.Set(HTTPHeaderContentLength, strconv.FormatInt(req.ContentLength, 10))
+
+	// md5
+	if req.Body != nil {
+		buf, _ := ioutil.ReadAll(req.Body)
+		req.Body = ioutil.NopCloser(bytes.NewReader(buf))
+		sum := md5.Sum(buf)
+		b64 := base64.StdEncoding.EncodeToString(sum[:])
+		req.Header.Set(HTTPHeaderContentMD5, b64)
+	}
+}
+
+func tryGetFileSize(f *os.File) int64 {
+	fInfo, _ := f.Stat()
+	return fInfo.Size()
+}
+
+// handle response
+func (conn Conn) handleResponse(resp *http.Response) (*Response, error) {
+	statusCode := resp.StatusCode
+	if statusCode >= 400 && statusCode <= 505 {
+		// 4xx and 5xx indicate that the operation has error occurred
+		var respBody []byte
+		respBody, err := readResponseBody(resp)
+		if err != nil {
+			return nil, err
+		}
+
+		if len(respBody) == 0 {
+			// no error in response body
+			err = fmt.Errorf("oss: service returned without a response body (%s)", resp.Status)
+		} else {
+			// response contains storage service error object, unmarshal
+			srvErr, errIn := serviceErrFromXML(respBody, resp.StatusCode,
+				resp.Header.Get(HTTPHeaderOssRequestID))
+			if err != nil { // error unmarshaling the error response
+				err = errIn
+			}
+			err = srvErr
+		}
+		return &Response{
+			statusCode: resp.StatusCode,
+			headers:    resp.Header,
+			body:       ioutil.NopCloser(bytes.NewReader(respBody)), // restore the body//
+		}, err
+	} else if statusCode >= 300 && statusCode <= 307 {
+		// oss use 3xx, but response has no body
+		err := fmt.Errorf("oss: service returned %d,%s", resp.StatusCode, resp.Status)
+		return &Response{
+			statusCode: resp.StatusCode,
+			headers:    resp.Header,
+			body:       resp.Body,
+		}, err
+	}
+
+	// 2xx, successful
+	return &Response{
+		statusCode: resp.StatusCode,
+		headers:    resp.Header,
+		body:       resp.Body,
+	}, nil
+}
+
+func readResponseBody(resp *http.Response) ([]byte, error) {
+	defer resp.Body.Close()
+	out, err := ioutil.ReadAll(resp.Body)
+	if err == io.EOF {
+		err = nil
+	}
+	return out, err
+}
+
+func serviceErrFromXML(body []byte, statusCode int, requestID string) (ServiceError, error) {
+	var storageErr ServiceError
+	if err := xml.Unmarshal(body, &storageErr); err != nil {
+		return storageErr, err
+	}
+	storageErr.StatusCode = statusCode
+	storageErr.RequestID = requestID
+	return storageErr, nil
+}
+
+func xmlUnmarshal(body io.Reader, v interface{}) error {
+	data, err := ioutil.ReadAll(body)
+	if err != nil {
+		return err
+	}
+	return xml.Unmarshal(data, v)
+}
+
+// Handle http timeout
+type timeoutConn struct {
+	conn        net.Conn
+	timeout     time.Duration
+	longTimeout time.Duration
+}
+
+func newTimeoutConn(conn net.Conn, timeout time.Duration, longTimeout time.Duration) *timeoutConn {
+	conn.SetReadDeadline(time.Now().Add(longTimeout))
+	return &timeoutConn{
+		conn:        conn,
+		timeout:     timeout,
+		longTimeout: longTimeout,
+	}
+}
+
+func (c *timeoutConn) Read(b []byte) (n int, err error) {
+	c.SetReadDeadline(time.Now().Add(c.timeout))
+	n, err = c.conn.Read(b)
+	c.SetReadDeadline(time.Now().Add(c.longTimeout))
+	return n, err
+}
+
+func (c *timeoutConn) Write(b []byte) (n int, err error) {
+	c.SetWriteDeadline(time.Now().Add(c.timeout))
+	n, err = c.conn.Write(b)
+	c.SetReadDeadline(time.Now().Add(c.longTimeout))
+	return n, err
+}
+
+func (c *timeoutConn) Close() error {
+	return c.conn.Close()
+}
+
+func (c *timeoutConn) LocalAddr() net.Addr {
+	return c.conn.LocalAddr()
+}
+
+func (c *timeoutConn) RemoteAddr() net.Addr {
+	return c.conn.RemoteAddr()
+}
+
+func (c *timeoutConn) SetDeadline(t time.Time) error {
+	return c.conn.SetDeadline(t)
+}
+
+func (c *timeoutConn) SetReadDeadline(t time.Time) error {
+	return c.conn.SetReadDeadline(t)
+}
+
+func (c *timeoutConn) SetWriteDeadline(t time.Time) error {
+	return c.conn.SetWriteDeadline(t)
+}
+
+// UrlMaker - build url and resource
+const (
+	urlTypeCname  = 1
+	urlTypeIP     = 2
+	urlTypeAliyun = 3
+)
+
+type urlMaker struct {
+	Scheme string // http or https
+	NetLoc string // host or ip
+	Type   int    // 1 CNAME 2 IP 3 ALIYUN
+}
+
+// Parse endpoint
+func (um *urlMaker) Init(endpoint string, isCname bool) {
+	if strings.HasPrefix(endpoint, "http://") {
+		um.Scheme = "http"
+		um.NetLoc = endpoint[len("http://"):]
+	} else if strings.HasPrefix(endpoint, "https://") {
+		um.Scheme = "https"
+		um.NetLoc = endpoint[len("https://"):]
+	} else {
+		um.Scheme = "http"
+		um.NetLoc = endpoint
+	}
+
+	host, _, err := net.SplitHostPort(um.NetLoc)
+	if err != nil {
+		host = um.NetLoc
+	}
+	ip := net.ParseIP(host)
+	if ip != nil {
+		um.Type = urlTypeIP
+	} else if isCname {
+		um.Type = urlTypeCname
+	} else {
+		um.Type = urlTypeAliyun
+	}
+}
+
+// Build URL
+func (um urlMaker) getURL(bucket, object, params string) *url.URL {
+	var host = ""
+	var path = ""
+	object = url.QueryEscape(object)
+	if um.Type == urlTypeCname {
+		host = um.NetLoc
+		path = "/" + object
+	} else if um.Type == urlTypeIP {
+		if bucket == "" {
+			host = um.NetLoc
+			path = "/"
+		} else {
+			host = um.NetLoc
+			path = fmt.Sprintf("/%s/%s", bucket, object)
+		}
+	} else {
+		if bucket == "" {
+			host = um.NetLoc
+			path = "/"
+		} else {
+			host = bucket + "." + um.NetLoc
+			path = "/" + object
+		}
+	}
+
+	uri := &url.URL{
+		Scheme:   um.Scheme,
+		Host:     host,
+		Path:     path,
+		RawQuery: params,
+	}
+
+	return uri
+}
+
+// Canonicalized Resource
+func (um urlMaker) getResource(bucketName, objectName, subResource string) string {
+	if subResource != "" {
+		subResource = "?" + subResource
+	}
+	if bucketName == "" {
+		return fmt.Sprintf("/%s%s", bucketName, subResource)
+	}
+	return fmt.Sprintf("/%s/%s%s", bucketName, objectName, subResource)
+}

+ 125 - 0
oss/conn_test.go

@@ -0,0 +1,125 @@
+package oss
+
+import (
+	"fmt"
+	"net/http"
+
+	. "gopkg.in/check.v1"
+)
+
+type OssConnSuite struct{}
+
+var _ = Suite(&OssConnSuite{})
+
+func (s *OssConnSuite) TestURLMarker(c *C) {
+	um := urlMaker{}
+	um.Init("docs.github.com", true)
+	c.Assert(um.Type, Equals, urlTypeCname)
+	c.Assert(um.Scheme, Equals, "http")
+	c.Assert(um.NetLoc, Equals, "docs.github.com")
+
+	c.Assert(um.getURL("bucket", "object", "params").String(), Equals, "http://docs.github.com/object?params")
+	c.Assert(um.getURL("bucket", "object", "").String(), Equals, "http://docs.github.com/object")
+	c.Assert(um.getURL("", "object", "").String(), Equals, "http://docs.github.com/object")
+	c.Assert(um.getResource("bucket", "object", "subres"), Equals, "/bucket/object?subres")
+	c.Assert(um.getResource("bucket", "object", ""), Equals, "/bucket/object")
+	c.Assert(um.getResource("", "object", ""), Equals, "/")
+
+	um.Init("https://docs.github.com", true)
+	c.Assert(um.Type, Equals, urlTypeCname)
+	c.Assert(um.Scheme, Equals, "https")
+	c.Assert(um.NetLoc, Equals, "docs.github.com")
+
+	um.Init("http://docs.github.com", true)
+	c.Assert(um.Type, Equals, urlTypeCname)
+	c.Assert(um.Scheme, Equals, "http")
+	c.Assert(um.NetLoc, Equals, "docs.github.com")
+
+	um.Init("docs.github.com:8080", false)
+	c.Assert(um.Type, Equals, urlTypeAliyun)
+	c.Assert(um.Scheme, Equals, "http")
+	c.Assert(um.NetLoc, Equals, "docs.github.com:8080")
+
+	c.Assert(um.getURL("bucket", "object", "params").String(), Equals, "http://bucket.docs.github.com:8080/object?params")
+	c.Assert(um.getURL("bucket", "object", "").String(), Equals, "http://bucket.docs.github.com:8080/object")
+	c.Assert(um.getURL("", "object", "").String(), Equals, "http://docs.github.com:8080/")
+	c.Assert(um.getResource("bucket", "object", "subres"), Equals, "/bucket/object?subres")
+	c.Assert(um.getResource("bucket", "object", ""), Equals, "/bucket/object")
+	c.Assert(um.getResource("", "object", ""), Equals, "/")
+
+	um.Init("https://docs.github.com:8080", false)
+	c.Assert(um.Type, Equals, urlTypeAliyun)
+	c.Assert(um.Scheme, Equals, "https")
+	c.Assert(um.NetLoc, Equals, "docs.github.com:8080")
+
+	um.Init("127.0.0.1", false)
+	c.Assert(um.Type, Equals, urlTypeIP)
+	c.Assert(um.Scheme, Equals, "http")
+	c.Assert(um.NetLoc, Equals, "127.0.0.1")
+
+	um.Init("http://127.0.0.1", false)
+	c.Assert(um.Type, Equals, urlTypeIP)
+	c.Assert(um.Scheme, Equals, "http")
+	c.Assert(um.NetLoc, Equals, "127.0.0.1")
+
+	um.Init("https://127.0.0.1:8080", false)
+	c.Assert(um.Type, Equals, urlTypeIP)
+	c.Assert(um.Scheme, Equals, "https")
+	c.Assert(um.NetLoc, Equals, "127.0.0.1:8080")
+}
+
+func (s *OssConnSuite) TestAuth(c *C) {
+	endpoint := "https://github.com/"
+	cfg := getDefaultOssConfig()
+	um := urlMaker{}
+	um.Init(endpoint, false)
+	conn := Conn{cfg, &um}
+	uri := um.getURL("bucket", "object", "")
+	req := &http.Request{
+		Method:     "PUT",
+		URL:        uri,
+		Proto:      "HTTP/1.1",
+		ProtoMajor: 1,
+		ProtoMinor: 1,
+		Header:     make(http.Header),
+		Host:       uri.Host,
+	}
+
+	req.Header.Set("Content-Type", "text/html")
+	req.Header.Set("Date", "Thu, 17 Nov 2005 18:49:58 GMT")
+	req.Header.Set("Host", endpoint)
+	req.Header.Set("X-OSS-Meta-Your", "your")
+	req.Header.Set("X-OSS-Meta-Author", "foo@bar.com")
+	req.Header.Set("X-OSS-Magic", "abracadabra")
+	req.Header.Set("Content-Md5", "ODBGOERFMDMzQTczRUY3NUE3NzA5QzdFNUYzMDQxNEM=")
+
+	conn.signHeader(req, um.getResource("bucket", "object", ""))
+	fmt.Println("AUTHORIZATION:", req.Header.Get(HTTPHeaderAuthorization))
+}
+
+func (s *OssConnSuite) TestConnToolFunc(c *C) {
+	err := checkRespCode(202, []int{})
+	c.Assert(err, NotNil)
+
+	err = checkRespCode(202, []int{404})
+	c.Assert(err, NotNil)
+
+	err = checkRespCode(202, []int{202, 404})
+	c.Assert(err, IsNil)
+
+	srvErr, err := serviceErrFromXML([]byte(""), 312, "")
+	c.Assert(err, NotNil)
+	c.Assert(srvErr.StatusCode, Equals, 0)
+
+	srvErr, err = serviceErrFromXML([]byte("ABC"), 312, "")
+	c.Assert(err, NotNil)
+	c.Assert(srvErr.StatusCode, Equals, 0)
+
+	srvErr, err = serviceErrFromXML([]byte("<Error></Error>"), 312, "")
+	c.Assert(err, IsNil)
+	c.Assert(srvErr.StatusCode, Equals, 312)
+
+	unexpect := UnexpectedStatusCodeError{[]int{200}, 202}
+	c.Assert(len(unexpect.Error()) > 0, Equals, true)
+	c.Assert(unexpect.Got(), Equals, 202)
+}

+ 79 - 0
oss/const.go

@@ -0,0 +1,79 @@
+package oss
+
+// ACLType Bucket/Object的访问控制
+type ACLType string
+
+const (
+	// ACLPrivate 私有读写
+	ACLPrivate ACLType = "private"
+
+	// ACLPublicRead 公共读私有写
+	ACLPublicRead ACLType = "public-read"
+
+	// ACLPublicReadWrite 公共读写
+	ACLPublicReadWrite ACLType = "public-read-write"
+
+	// ACLDefault Object默认权限,Bucket无此权限
+	ACLDefault ACLType = "default"
+)
+
+// MetadataDirectiveType 对象COPY时新对象是否使用原对象的Meta
+type MetadataDirectiveType string
+
+const (
+	// MetaCopy 目标对象使用源对象的META
+	MetaCopy MetadataDirectiveType = "COPY"
+
+	// MetaReplace 目标对象使用自定义的META
+	MetaReplace MetadataDirectiveType = "REPLACE"
+)
+
+// Http头标签
+const (
+	HTTPHeaderAcceptEncoding     string = "Accept-Encoding"
+	HTTPHeaderAuthorization             = "Authorization"
+	HTTPHeaderCacheControl              = "Cache-Control"
+	HTTPHeaderContentDisposition        = "Content-Disposition"
+	HTTPHeaderContentEncoding           = "Content-Encoding"
+	HTTPHeaderContentLength             = "Content-Length"
+	HTTPHeaderContentMD5                = "Content-MD5"
+	HTTPHeaderContentType               = "Content-Type"
+	HTTPHeaderContentLanguage           = "Content-Language"
+	HTTPHeaderDate                      = "Date"
+	HTTPHeaderEtag                      = "ETag"
+	HTTPHeaderExpires                   = "Expires"
+	HTTPHeaderHost                      = "Host"
+	HTTPHeaderLastModified              = "Last-Modified"
+	HTTPHeaderRange                     = "Range"
+	HTTPHeaderLocation                  = "Location"
+	HTTPHeaderOrigin                    = "Origin"
+	HTTPHeaderServer                    = "Server"
+	HTTPHeaderUserAgent                 = "User-Agent"
+	HTTPHeaderIfModifiedSince           = "If-Modified-Since"
+	HTTPHeaderIfUnmodifiedSince         = "If-Unmodified-Since"
+	HTTPHeaderIfMatch                   = "If-Match"
+	HTTPHeaderIfNoneMatch               = "If-None-Match"
+
+	HTTPHeaderOssACL                         = "X-Oss-Acl"
+	HTTPHeaderOssMetaPrefix                  = "X-Oss-Meta-"
+	HTTPHeaderOssObjectACL                   = "X-Oss-Object-Acl"
+	HTTPHeaderOssSecurityToken               = "X-Oss-Security-Token"
+	HTTPHeaderOssServerSideEncryption        = "X-Oss-Server-Side-Encryption"
+	HTTPHeaderOssCopySource                  = "X-Oss-Copy-Source"
+	HTTPHeaderOssCopySourceRange             = "X-Oss-Copy-Source-Range"
+	HTTPHeaderOssCopySourceIfMatch           = "X-Oss-Copy-Source-If-Match"
+	HTTPHeaderOssCopySourceIfNoneMatch       = "X-Oss-Copy-Source-If-None-Match"
+	HTTPHeaderOssCopySourceIfModifiedSince   = "X-Oss-Copy-Source-If-Modified-Since"
+	HTTPHeaderOssCopySourceIfUnmodifiedSince = "X-Oss-Copy-Source-If-Unmodified-Since"
+	HTTPHeaderOssMetadataDirective           = "X-Oss-Metadata-Directive"
+	HTTPHeaderOssNextAppendPosition          = "X-Oss-Next-Append-Position"
+	HTTPHeaderOssRequestID                   = "X-Oss-Request-Id"
+)
+
+// 其它常量
+const (
+	MaxPartSize = 5 * 1024 * 1024 * 1024 // 文件片最大值,5GB
+	MinPartSize = 100 * 1024             // 文件片最小值,100KB
+
+	Version = "0.1.1" // Go sdk版本
+)

+ 60 - 0
oss/error.go

@@ -0,0 +1,60 @@
+package oss
+
+import (
+	"encoding/xml"
+	"fmt"
+	"net/http"
+	"strings"
+)
+
+// ServiceError contains fields of the error response from Oss Service REST API.
+type ServiceError struct {
+	XMLName    xml.Name `xml:"Error"`
+	Code       string   `xml:"Code"`      // OSS返回给用户的错误码
+	Message    string   `xml:"Message"`   // OSS给出的详细错误信息
+	RequestID  string   `xml:"RequestId"` // 用于唯一标识该次请求的UUID
+	HostID     string   `xml:"HostId"`    // 用于标识访问的OSS集群
+	StatusCode int      // HTTP状态码
+}
+
+// Implement interface error
+func (e ServiceError) Error() string {
+	return fmt.Sprintf("oss: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s",
+		e.StatusCode, e.Code, e.Message, e.RequestID)
+}
+
+// UnexpectedStatusCodeError is returned when a storage service responds with neither an error
+// nor with an HTTP status code indicating success.
+type UnexpectedStatusCodeError struct {
+	allowed []int // 预期OSS返回HTTP状态码
+	got     int   // OSS实际返回HTTP状态码
+}
+
+// Implement interface error
+func (e UnexpectedStatusCodeError) Error() string {
+	s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) }
+
+	got := s(e.got)
+	expected := []string{}
+	for _, v := range e.allowed {
+		expected = append(expected, s(v))
+	}
+	return fmt.Sprintf("oss: status code from service response is %s; was expecting %s",
+		got, strings.Join(expected, " or "))
+}
+
+// Got is the actual status code returned by oss.
+func (e UnexpectedStatusCodeError) Got() int {
+	return e.got
+}
+
+// checkRespCode returns UnexpectedStatusError if the given response code is not
+// one of the allowed status codes; otherwise nil.
+func checkRespCode(respCode int, allowed []int) error {
+	for _, v := range allowed {
+		if respCode == v {
+			return nil
+		}
+	}
+	return UnexpectedStatusCodeError{allowed, respCode}
+}

+ 245 - 0
oss/mime.go

@@ -0,0 +1,245 @@
+package oss
+
+import (
+	"mime"
+	"path"
+	"strings"
+)
+
+var extToMimeType = map[string]string{
+	".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",
+	".hqx":     "application/mac-binhex40",
+	".cpt":     "application/mac-compactpro",
+	".doc":     "application/msword",
+	".ogg":     "application/ogg",
+	".pdf":     "application/pdf",
+	".rtf":     "text/rtf",
+	".mif":     "application/vnd.mif",
+	".xls":     "application/vnd.ms-excel",
+	".ppt":     "application/vnd.ms-powerpoint",
+	".odc":     "application/vnd.oasis.opendocument.chart",
+	".odb":     "application/vnd.oasis.opendocument.database",
+	".odf":     "application/vnd.oasis.opendocument.formula",
+	".odg":     "application/vnd.oasis.opendocument.graphics",
+	".otg":     "application/vnd.oasis.opendocument.graphics-template",
+	".odi":     "application/vnd.oasis.opendocument.image",
+	".odp":     "application/vnd.oasis.opendocument.presentation",
+	".otp":     "application/vnd.oasis.opendocument.presentation-template",
+	".ods":     "application/vnd.oasis.opendocument.spreadsheet",
+	".ots":     "application/vnd.oasis.opendocument.spreadsheet-template",
+	".odt":     "application/vnd.oasis.opendocument.text",
+	".odm":     "application/vnd.oasis.opendocument.text-master",
+	".ott":     "application/vnd.oasis.opendocument.text-template",
+	".oth":     "application/vnd.oasis.opendocument.text-web",
+	".sxw":     "application/vnd.sun.xml.writer",
+	".stw":     "application/vnd.sun.xml.writer.template",
+	".sxc":     "application/vnd.sun.xml.calc",
+	".stc":     "application/vnd.sun.xml.calc.template",
+	".sxd":     "application/vnd.sun.xml.draw",
+	".std":     "application/vnd.sun.xml.draw.template",
+	".sxi":     "application/vnd.sun.xml.impress",
+	".sti":     "application/vnd.sun.xml.impress.template",
+	".sxg":     "application/vnd.sun.xml.writer.global",
+	".sxm":     "application/vnd.sun.xml.math",
+	".sis":     "application/vnd.symbian.install",
+	".wbxml":   "application/vnd.wap.wbxml",
+	".wmlc":    "application/vnd.wap.wmlc",
+	".wmlsc":   "application/vnd.wap.wmlscriptc",
+	".bcpio":   "application/x-bcpio",
+	".torrent": "application/x-bittorrent",
+	".bz2":     "application/x-bzip2",
+	".vcd":     "application/x-cdlink",
+	".pgn":     "application/x-chess-pgn",
+	".cpio":    "application/x-cpio",
+	".csh":     "application/x-csh",
+	".dvi":     "application/x-dvi",
+	".spl":     "application/x-futuresplash",
+	".gtar":    "application/x-gtar",
+	".hdf":     "application/x-hdf",
+	".jar":     "application/x-java-archive",
+	".jnlp":    "application/x-java-jnlp-file",
+	".js":      "application/x-javascript",
+	".ksp":     "application/x-kspread",
+	".chrt":    "application/x-kchart",
+	".kil":     "application/x-killustrator",
+	".latex":   "application/x-latex",
+	".rpm":     "application/x-rpm",
+	".sh":      "application/x-sh",
+	".shar":    "application/x-shar",
+	".swf":     "application/x-shockwave-flash",
+	".sit":     "application/x-stuffit",
+	".sv4cpio": "application/x-sv4cpio",
+	".sv4crc":  "application/x-sv4crc",
+	".tar":     "application/x-tar",
+	".tcl":     "application/x-tcl",
+	".tex":     "application/x-tex",
+	".man":     "application/x-troff-man",
+	".me":      "application/x-troff-me",
+	".ms":      "application/x-troff-ms",
+	".ustar":   "application/x-ustar",
+	".src":     "application/x-wais-source",
+	".zip":     "application/zip",
+	".m3u":     "audio/x-mpegurl",
+	".ra":      "audio/x-pn-realaudio",
+	".wav":     "audio/x-wav",
+	".wma":     "audio/x-ms-wma",
+	".wax":     "audio/x-ms-wax",
+	".pdb":     "chemical/x-pdb",
+	".xyz":     "chemical/x-xyz",
+	".bmp":     "image/bmp",
+	".gif":     "image/gif",
+	".ief":     "image/ief",
+	".png":     "image/png",
+	".wbmp":    "image/vnd.wap.wbmp",
+	".ras":     "image/x-cmu-raster",
+	".pnm":     "image/x-portable-anymap",
+	".pbm":     "image/x-portable-bitmap",
+	".pgm":     "image/x-portable-graymap",
+	".ppm":     "image/x-portable-pixmap",
+	".rgb":     "image/x-rgb",
+	".xbm":     "image/x-xbitmap",
+	".xpm":     "image/x-xpixmap",
+	".xwd":     "image/x-xwindowdump",
+	".css":     "text/css",
+	".rtx":     "text/richtext",
+	".tsv":     "text/tab-separated-values",
+	".jad":     "text/vnd.sun.j2me.app-descriptor",
+	".wml":     "text/vnd.wap.wml",
+	".wmls":    "text/vnd.wap.wmlscript",
+	".etx":     "text/x-setext",
+	".mxu":     "video/vnd.mpegurl",
+	".flv":     "video/x-flv",
+	".wm":      "video/x-ms-wm",
+	".wmv":     "video/x-ms-wmv",
+	".wmx":     "video/x-ms-wmx",
+	".wvx":     "video/x-ms-wvx",
+	".avi":     "video/x-msvideo",
+	".movie":   "video/x-sgi-movie",
+	".ice":     "x-conference/x-cooltalk",
+	".3gp":     "video/3gpp",
+	".ai":      "application/postscript",
+	".aif":     "audio/x-aiff",
+	".aifc":    "audio/x-aiff",
+	".aiff":    "audio/x-aiff",
+	".asc":     "text/plain",
+	".atom":    "application/atom+xml",
+	".au":      "audio/basic",
+	".bin":     "application/octet-stream",
+	".cdf":     "application/x-netcdf",
+	".cgm":     "image/cgm",
+	".class":   "application/octet-stream",
+	".dcr":     "application/x-director",
+	".dif":     "video/x-dv",
+	".dir":     "application/x-director",
+	".djv":     "image/vnd.djvu",
+	".djvu":    "image/vnd.djvu",
+	".dll":     "application/octet-stream",
+	".dmg":     "application/octet-stream",
+	".dms":     "application/octet-stream",
+	".dtd":     "application/xml-dtd",
+	".dv":      "video/x-dv",
+	".dxr":     "application/x-director",
+	".eps":     "application/postscript",
+	".exe":     "application/octet-stream",
+	".ez":      "application/andrew-inset",
+	".gram":    "application/srgs",
+	".grxml":   "application/srgs+xml",
+	".gz":      "application/x-gzip",
+	".htm":     "text/html",
+	".html":    "text/html",
+	".ico":     "image/x-icon",
+	".ics":     "text/calendar",
+	".ifb":     "text/calendar",
+	".iges":    "model/iges",
+	".igs":     "model/iges",
+	".jp2":     "image/jp2",
+	".jpe":     "image/jpeg",
+	".jpeg":    "image/jpeg",
+	".jpg":     "image/jpeg",
+	".kar":     "audio/midi",
+	".lha":     "application/octet-stream",
+	".lzh":     "application/octet-stream",
+	".m4a":     "audio/mp4a-latm",
+	".m4p":     "audio/mp4a-latm",
+	".m4u":     "video/vnd.mpegurl",
+	".m4v":     "video/x-m4v",
+	".mac":     "image/x-macpaint",
+	".mathml":  "application/mathml+xml",
+	".mesh":    "model/mesh",
+	".mid":     "audio/midi",
+	".midi":    "audio/midi",
+	".mov":     "video/quicktime",
+	".mp2":     "audio/mpeg",
+	".mp3":     "audio/mpeg",
+	".mp4":     "video/mp4",
+	".mpe":     "video/mpeg",
+	".mpeg":    "video/mpeg",
+	".mpg":     "video/mpeg",
+	".mpga":    "audio/mpeg",
+	".msh":     "model/mesh",
+	".nc":      "application/x-netcdf",
+	".oda":     "application/oda",
+	".ogv":     "video/ogv",
+	".pct":     "image/pict",
+	".pic":     "image/pict",
+	".pict":    "image/pict",
+	".pnt":     "image/x-macpaint",
+	".pntg":    "image/x-macpaint",
+	".ps":      "application/postscript",
+	".qt":      "video/quicktime",
+	".qti":     "image/x-quicktime",
+	".qtif":    "image/x-quicktime",
+	".ram":     "audio/x-pn-realaudio",
+	".rdf":     "application/rdf+xml",
+	".rm":      "application/vnd.rn-realmedia",
+	".roff":    "application/x-troff",
+	".sgm":     "text/sgml",
+	".sgml":    "text/sgml",
+	".silo":    "model/mesh",
+	".skd":     "application/x-koan",
+	".skm":     "application/x-koan",
+	".skp":     "application/x-koan",
+	".skt":     "application/x-koan",
+	".smi":     "application/smil",
+	".smil":    "application/smil",
+	".snd":     "audio/basic",
+	".so":      "application/octet-stream",
+	".svg":     "image/svg+xml",
+	".t":       "application/x-troff",
+	".texi":    "application/x-texinfo",
+	".texinfo": "application/x-texinfo",
+	".tif":     "image/tiff",
+	".tiff":    "image/tiff",
+	".tr":      "application/x-troff",
+	".txt":     "text/plain",
+	".vrml":    "model/vrml",
+	".vxml":    "application/voicexml+xml",
+	".webm":    "video/webm",
+	".wrl":     "model/vrml",
+	".xht":     "application/xhtml+xml",
+	".xhtml":   "application/xhtml+xml",
+	".xml":     "application/xml",
+	".xsl":     "application/xml",
+	".xslt":    "application/xslt+xml",
+	".xul":     "application/vnd.mozilla.xul+xml",
+}
+
+// TypeByExtension returns the MIME type associated with the file extension ext.
+// 获取文件类型,选项ContentType使用
+func TypeByExtension(filePath string) string {
+	typ := mime.TypeByExtension(path.Ext(filePath))
+	if typ == "" {
+		typ = extToMimeType[strings.ToLower(path.Ext(filePath))]
+	}
+	return typ
+}

+ 337 - 0
oss/multipart.go

@@ -0,0 +1,337 @@
+package oss
+
+import (
+	"bytes"
+	"encoding/xml"
+	"errors"
+	"io"
+	"net/http"
+	"os"
+	"sort"
+	"strconv"
+)
+
+//
+// InitiateMultipartUpload 初始化分片上传任务。
+//
+// objectKey  Object名称。
+// options    上传时可以指定Object的属性,可选属性有CacheControl、ContentDisposition、ContentEncoding、Expires、
+// ServerSideEncryption、Meta,具体含义请参考
+// https://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/InitiateMultipartUpload.html
+//
+// InitiateMultipartUploadResult 初始化后操作成功的返回值,用于后面的UploadPartFromFile、UploadPartCopy等操作。error为nil时有效。
+// error  操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) InitiateMultipartUpload(objectKey string, options ...Option) (InitiateMultipartUploadResult, error) {
+	var imur InitiateMultipartUploadResult
+	opts := addContentType(options, objectKey)
+	resp, err := bucket.do("POST", objectKey, "uploads", "uploads", opts, nil)
+	if err != nil {
+		return imur, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &imur)
+	return imur, err
+}
+
+//
+// UploadPart 上传分片。
+//
+// 初始化一个Multipart Upload之后,可以根据指定的Object名和Upload ID来分片(Part)上传数据。
+// 每一个上传的Part都有一个标识它的号码(part number,范围是1~10000)。对于同一个Upload ID,
+// 该号码不但唯一标识这一片数据,也标识了这片数据在整个文件内的相对位置。如果您用同一个part号码,上传了新的数据,
+// 那么OSS上已有的这个号码的Part数据将被覆盖。除了最后一片Part以外,其他的part最小为100KB;
+// 最后一片Part没有大小限制。
+//
+// imur        InitiateMultipartUpload成功后的返回值。
+// reader      io.Reader 需要分片上传的reader。
+// size        本次上传片Part的大小。
+// partNumber  本次上传片(Part)的编号,范围是1~10000。如果超出范围,OSS将返回InvalidArgument错误。
+//
+// UploadPart 上传成功的返回值,两个成员PartNumber、ETag。PartNumber片编号,即传入参数partNumber;
+// ETag及上传数据的MD5。error为nil时有效。
+// error 操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) UploadPart(imur InitiateMultipartUploadResult, reader io.Reader,
+	size int64, partNumber int) (UploadPart, error) {
+	var part = UploadPart{}
+	params := "partNumber=" + strconv.Itoa(partNumber) + "&uploadId=" + imur.UploadID
+	opts := []Option{ContentLength(size)}
+	resp, err := bucket.do("PUT", imur.Key, params, params, opts, &io.LimitedReader{R: reader, N: size})
+	if err != nil {
+		return part, err
+	}
+	defer resp.body.Close()
+
+	part.ETag = resp.headers.Get(HTTPHeaderEtag)
+	part.PartNumber = partNumber
+	return part, nil
+}
+
+//
+// UploadPartFromFile 上传分片。
+//
+// imur           InitiateMultipartUpload成功后的返回值。
+// filePath       需要分片上传的本地文件。
+// startPosition  本次上传文件片的起始位置。
+// partSize       本次上传文件片的大小。
+// partNumber     本次上传文件片的编号,范围是1~10000。
+//
+// UploadPart 上传成功的返回值,两个成员PartNumber、ETag。PartNumber片编号,传入参数partNumber;
+// ETag上传数据的MD5。error为nil时有效。
+// error 操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) UploadPartFromFile(imur InitiateMultipartUploadResult, filePath string,
+	startPosition, partSize int64, partNumber int) (UploadPart, error) {
+	var part = UploadPart{}
+	fd, err := os.Open(filePath)
+	if err != nil {
+		return part, err
+	}
+	defer fd.Close()
+	fd.Seek(startPosition, os.SEEK_SET)
+
+	params := "partNumber=" + strconv.Itoa(partNumber) + "&uploadId=" + imur.UploadID
+	resp, err := bucket.do("PUT", imur.Key, params, params, nil, &io.LimitedReader{R: fd, N: partSize})
+	if err != nil {
+		return part, err
+	}
+	defer resp.body.Close()
+
+	part.ETag = resp.headers.Get(HTTPHeaderEtag)
+	part.PartNumber = partNumber
+	return part, nil
+}
+
+//
+// UploadPartCopy 拷贝分片。
+//
+// imur           InitiateMultipartUpload成功后的返回值。
+// copySrc        源Object名称。
+// startPosition  本次拷贝片(Part)在源Object的起始位置。
+// partSize       本次拷贝片的大小。
+// partNumber     本次拷贝片的编号,范围是1~10000。如果超出范围,OSS将返回InvalidArgument错误。
+// options        copy时源Object的限制条件,满足限制条件时copy,不满足时返回错误。可选条件有CopySourceIfMatch、
+// CopySourceIfNoneMatch、CopySourceIfModifiedSince  CopySourceIfUnmodifiedSince,具体含义请参看
+// https://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/UploadPartCopy.html
+//
+// UploadPart 上传成功的返回值,两个成员PartNumber、ETag。PartNumber片(Part)编号,即传入参数partNumber;
+// ETag及上传数据的MD5。error为nil时有效。
+// error 操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) UploadPartCopy(imur InitiateMultipartUploadResult, copySrc string, startPosition,
+	partSize int64, partNumber int, options ...Option) (UploadPart, error) {
+	var out UploadPartCopyResult
+	var part UploadPart
+
+	opts := []Option{CopySource(bucket.BucketName, copySrc),
+		CopySourceRange(startPosition, partSize)}
+	opts = append(opts, options...)
+	params := "partNumber=" + strconv.Itoa(partNumber) + "&uploadId=" + imur.UploadID
+	resp, err := bucket.do("PUT", imur.Key, params, params, opts, nil)
+	if err != nil {
+		return part, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	if err != nil {
+		return part, err
+	}
+	part.ETag = out.ETag
+	part.PartNumber = partNumber
+
+	return part, nil
+}
+
+//
+// CompleteMultipartUpload 提交分片上传任务。
+//
+// imur   InitiateMultipartUpload的返回值。
+// parts  UploadPart/UploadPartFromFile/UploadPartCopy返回值组成的数组。
+//
+// CompleteMultipartUploadResponse  操作成功后的返回值。error为nil时有效。
+// error  操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) CompleteMultipartUpload(imur InitiateMultipartUploadResult,
+	parts []UploadPart) (CompleteMultipartUploadResult, error) {
+	var out CompleteMultipartUploadResult
+
+	sort.Sort(uploadParts(parts))
+	cxml := completeMultipartUploadXML{}
+	cxml.Part = parts
+	bs, err := xml.Marshal(cxml)
+	if err != nil {
+		return out, err
+	}
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+
+	params := "uploadId=" + imur.UploadID
+	resp, err := bucket.do("POST", imur.Key, params, params, nil, buffer)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// AbortMultipartUpload 取消分片上传任务。
+//
+// imur  InitiateMultipartUpload的返回值。
+//
+// error  操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) AbortMultipartUpload(imur InitiateMultipartUploadResult) error {
+	params := "uploadId=" + imur.UploadID
+	resp, err := bucket.do("DELETE", imur.Key, params, params, nil, nil)
+	if err != nil {
+		return err
+	}
+	defer resp.body.Close()
+	return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
+
+//
+// ListUploadedParts 列出指定上传任务已经上传的分片。
+//
+// imur  InitiateMultipartUpload的返回值。
+//
+// ListUploadedPartsResponse  操作成功后的返回值,成员UploadedParts已经上传/拷贝的片。error为nil时该返回值有效。
+// error  操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) ListUploadedParts(imur InitiateMultipartUploadResult) (ListUploadedPartsResult, error) {
+	var out ListUploadedPartsResult
+	params := "uploadId=" + imur.UploadID
+	resp, err := bucket.do("GET", imur.Key, params, params, nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
+//
+// ListMultipartUploads 列出所有未上传完整的multipart任务列表。
+//
+// options  ListObject的筛选行为。Prefix返回object的前缀,KeyMarker返回object的起始位置,MaxUploads最大数目默认1000,
+// Delimiter用于对Object名字进行分组的字符,所有名字包含指定的前缀且第一次出现delimiter字符之间的object。
+//
+// ListMultipartUploadResponse  操作成功后的返回值,error为nil时该返回值有效。
+// error  操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) ListMultipartUploads(options ...Option) (ListMultipartUploadResult, error) {
+	var out ListMultipartUploadResult
+
+	options = append(options, EncodingType("url"))
+	params, err := handleParams(options)
+	if err != nil {
+		return out, err
+	}
+
+	resp, err := bucket.do("GET", "", "uploads&"+params, "uploads", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	if err != nil {
+		return out, err
+	}
+	err = decodeListMultipartUploadResult(&out)
+	return out, err
+}
+
+//
+// UploadFile 分块上传文件,适合加大文件
+//
+// objectKey  object名称。
+// filePath   本地文件。需要上传的文件。
+// partSize   本次上传文件片的大小,字节数。比如100 * 1024为每片100KB。
+// options    上传Object时可以指定Object的属性。详见InitiateMultipartUpload。
+//
+// error 操作成功为nil,非nil为错误信息。
+//
+func (bucket Bucket) UploadFile(objectKey, filePath string, partSize int64, options ...Option) error {
+	if partSize < MinPartSize || partSize > MaxPartSize {
+		return errors.New("oss: part size invalid range (1024KB, 5GB]")
+	}
+
+	chunks, err := SplitFileByPartSize(filePath, partSize)
+	if err != nil {
+		return err
+	}
+
+	imur, err := bucket.InitiateMultipartUpload(objectKey, options...)
+	if err != nil {
+		return err
+	}
+
+	parts := []UploadPart{}
+	for _, chunk := range chunks {
+		part, err := bucket.UploadPartFromFile(imur, filePath, chunk.Offset, chunk.Size,
+			chunk.Number)
+		if err != nil {
+			bucket.AbortMultipartUpload(imur)
+			return err
+		}
+		parts = append(parts, part)
+	}
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		bucket.AbortMultipartUpload(imur)
+		return err
+	}
+	return nil
+}
+
+//
+// DownloadFile 分块下载文件,适合加大Object
+//
+// objectKey  object key。
+// filePath   本地文件。objectKey下载到文件。
+// partSize   本次上传文件片的大小,字节数。比如100 * 1024为每片100KB。
+// options    Object的属性限制项。详见GetObject。
+//
+// error 操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) DownloadFile(objectKey, filePath string, partSize int64, options ...Option) error {
+	if partSize < 1 || partSize > MaxPartSize {
+		return errors.New("oss: part size invalid range (1, 5GB]")
+	}
+
+	meta, err := bucket.GetObjectDetailedMeta(objectKey)
+	if err != nil {
+		return err
+	}
+
+	fd, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0660)
+	if err != nil {
+		return err
+	}
+	defer fd.Close()
+
+	objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
+	for i := int64(0); i < objectSize; i += partSize {
+		option := Range(i, GetPartEnd(i, objectSize, partSize))
+		options = append(options, option)
+		r, err := bucket.GetObject(objectKey, options...)
+		if err != nil {
+			return err
+		}
+		defer r.Close()
+		_, err = io.Copy(fd, r)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 935 - 0
oss/multipart_test.go

@@ -0,0 +1,935 @@
+// multipart test
+
+package oss
+
+import (
+	"fmt"
+	"math/rand"
+	"net/http"
+	"os"
+	"strconv"
+
+	. "gopkg.in/check.v1"
+)
+
+type OssBucketMultipartSuite struct {
+	client *Client
+	bucket *Bucket
+}
+
+var _ = Suite(&OssBucketMultipartSuite{})
+
+// Run once when the suite starts running
+func (s *OssBucketMultipartSuite) SetUpSuite(c *C) {
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+	s.client = client
+
+	err = s.client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := s.client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+	s.bucket = bucket
+
+	fmt.Println("SetUpSuite")
+}
+
+// Run before each test or benchmark starts running
+func (s *OssBucketMultipartSuite) TearDownSuite(c *C) {
+	// Delete Part
+	lmur, err := s.bucket.ListMultipartUploads()
+	c.Assert(err, IsNil)
+
+	for _, upload := range lmur.Uploads {
+		var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName,
+			Key: upload.Key, UploadID: upload.UploadID}
+		err = s.bucket.AbortMultipartUpload(imur)
+		c.Assert(err, IsNil)
+	}
+
+	// Delete Objects
+	lor, err := s.bucket.ListObjects()
+	c.Assert(err, IsNil)
+
+	for _, object := range lor.Objects {
+		err = s.bucket.DeleteObject(object.Key)
+		c.Assert(err, IsNil)
+	}
+
+	// delete bucket
+	err = s.client.DeleteBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	fmt.Println("TearDownSuite")
+}
+
+// Run after each test or benchmark runs
+func (s *OssBucketMultipartSuite) SetUpTest(c *C) {
+	err := removeTempFiles("../oss", ".jpg")
+	c.Assert(err, IsNil)
+
+	fmt.Println("SetUpTest")
+}
+
+// Run once after all tests or benchmarks have finished running
+func (s *OssBucketMultipartSuite) TearDownTest(c *C) {
+	err := removeTempFiles("../oss", ".jpg")
+	c.Assert(err, IsNil)
+
+	err = removeTempFiles("../oss", ".txt1")
+	c.Assert(err, IsNil)
+
+	err = removeTempFiles("../oss", ".txt2")
+	c.Assert(err, IsNil)
+
+	fmt.Println("TearDownTest")
+}
+
+// TestMultipartUpload
+func (s *OssBucketMultipartSuite) TestMultipartUpload(c *C) {
+	objectName := objectNamePrefix + "tmu"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	chunks, err := SplitFileByPartNum(fileName, 3)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	options := []Option{
+		Expires(futureDate), Meta("my", "myprop"),
+	}
+
+	fd, err := os.Open(fileName)
+	c.Assert(err, IsNil)
+	defer fd.Close()
+
+	imur, err := s.bucket.InitiateMultipartUpload(objectName, options...)
+	c.Assert(err, IsNil)
+	var parts []UploadPart
+	for _, chunk := range chunks {
+		fd.Seek(chunk.Offset, os.SEEK_SET)
+		part, err := s.bucket.UploadPart(imur, fd, chunk.Size, chunk.Number)
+		c.Assert(err, IsNil)
+		parts = append(parts, part)
+	}
+
+	cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+	c.Assert(err, IsNil)
+	fmt.Println("cmur:", cmur)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+	c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+	c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+	c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+	err = s.bucket.GetObjectToFile(objectName, "newpic1.jpg")
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestMultipartUpload
+func (s *OssBucketMultipartSuite) TestMultipartUploadFromFile(c *C) {
+	objectName := objectNamePrefix + "tmuff"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	chunks, err := SplitFileByPartNum(fileName, 3)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	options := []Option{
+		Expires(futureDate), Meta("my", "myprop"),
+	}
+	imur, err := s.bucket.InitiateMultipartUpload(objectName, options...)
+	c.Assert(err, IsNil)
+	var parts []UploadPart
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number)
+		c.Assert(err, IsNil)
+		parts = append(parts, part)
+	}
+
+	cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+	c.Assert(err, IsNil)
+	fmt.Println("cmur:", cmur)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+	c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+	c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+	c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+	err = s.bucket.GetObjectToFile(objectName, "newpic1.jpg")
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestUploadPartCopy
+func (s *OssBucketMultipartSuite) TestUploadPartCopy(c *C) {
+	objectSrc := objectNamePrefix + "tupc" + "src"
+	objectDesc := objectNamePrefix + "tupc" + "desc"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	chunks, err := SplitFileByPartNum(fileName, 3)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+	c.Assert(err, IsNil)
+
+	options := []Option{
+		Expires(futureDate), Meta("my", "myprop"),
+	}
+	imur, err := s.bucket.InitiateMultipartUpload(objectDesc, options...)
+	c.Assert(err, IsNil)
+	var parts []UploadPart
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+		parts = append(parts, part)
+	}
+
+	cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+	c.Assert(err, IsNil)
+	fmt.Println("cmur:", cmur)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectDesc)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+	c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+	c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+	c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+	err = s.bucket.GetObjectToFile(objectDesc, "newpic2.jpg")
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectSrc)
+	c.Assert(err, IsNil)
+	err = s.bucket.DeleteObject(objectDesc)
+	c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestListUploadedParts(c *C) {
+	objectName := objectNamePrefix + "tlup"
+	objectSrc := objectNamePrefix + "tlup" + "src"
+	objectDesc := objectNamePrefix + "tlup" + "desc"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	chunks, err := SplitFileByPartSize(fileName, 100*1024)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+	c.Assert(err, IsNil)
+
+	// upload
+	imurUpload, err := s.bucket.InitiateMultipartUpload(objectName)
+	var partsUpload []UploadPart
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+		partsUpload = append(partsUpload, part)
+	}
+
+	// copy
+	imurCopy, err := s.bucket.InitiateMultipartUpload(objectDesc)
+	var partsCopy []UploadPart
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartCopy(imurCopy, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+		partsCopy = append(partsCopy, part)
+	}
+
+	// list
+	lupr, err := s.bucket.ListUploadedParts(imurUpload)
+	c.Assert(err, IsNil)
+	fmt.Println("lupr:", lupr)
+	c.Assert(len(lupr.UploadedParts), Equals, len(chunks))
+
+	lupr, err = s.bucket.ListUploadedParts(imurCopy)
+	c.Assert(err, IsNil)
+	fmt.Println("lupr:", lupr)
+	c.Assert(len(lupr.UploadedParts), Equals, len(chunks))
+
+	lmur, err := s.bucket.ListMultipartUploads()
+	c.Assert(err, IsNil)
+	fmt.Println("lmur:", lmur)
+	c.Assert(len(lmur.Uploads), Equals, 2)
+
+	// complete
+	_, err = s.bucket.CompleteMultipartUpload(imurUpload, partsUpload)
+	c.Assert(err, IsNil)
+	_, err = s.bucket.CompleteMultipartUpload(imurCopy, partsCopy)
+	c.Assert(err, IsNil)
+
+	// download
+	err = s.bucket.GetObjectToFile(objectDesc, "newpic3.jpg")
+	c.Assert(err, IsNil)
+	err = s.bucket.GetObjectToFile(objectName, "newpic4.jpg")
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+	err = s.bucket.DeleteObject(objectDesc)
+	c.Assert(err, IsNil)
+	err = s.bucket.DeleteObject(objectSrc)
+	c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestAbortMultipartUpload(c *C) {
+	objectName := objectNamePrefix + "tamu"
+	objectSrc := objectNamePrefix + "tamu" + "src"
+	objectDesc := objectNamePrefix + "tamu" + "desc"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	chunks, err := SplitFileByPartSize(fileName, 100*1024)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+	c.Assert(err, IsNil)
+
+	// upload
+	imurUpload, err := s.bucket.InitiateMultipartUpload(objectName)
+	var partsUpload []UploadPart
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+		partsUpload = append(partsUpload, part)
+	}
+
+	// copy
+	imurCopy, err := s.bucket.InitiateMultipartUpload(objectDesc)
+	var partsCopy []UploadPart
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartCopy(imurCopy, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+		partsCopy = append(partsCopy, part)
+	}
+
+	// list
+	lupr, err := s.bucket.ListUploadedParts(imurUpload)
+	c.Assert(err, IsNil)
+	fmt.Println("lupr:", lupr)
+	c.Assert(len(lupr.UploadedParts), Equals, len(chunks))
+
+	lupr, err = s.bucket.ListUploadedParts(imurCopy)
+	c.Assert(err, IsNil)
+	fmt.Println("lupr:", lupr)
+	c.Assert(len(lupr.UploadedParts), Equals, len(chunks))
+
+	lmur, err := s.bucket.ListMultipartUploads()
+	c.Assert(err, IsNil)
+	fmt.Println("lmur:", lmur)
+	c.Assert(len(lmur.Uploads), Equals, 2)
+
+	// abort
+	err = s.bucket.AbortMultipartUpload(imurUpload)
+	c.Assert(err, IsNil)
+	err = s.bucket.AbortMultipartUpload(imurCopy)
+	c.Assert(err, IsNil)
+
+	lmur, err = s.bucket.ListMultipartUploads()
+	c.Assert(err, IsNil)
+	fmt.Println("lmur:", lmur)
+	c.Assert(len(lmur.Uploads), Equals, 0)
+
+	// download
+	err = s.bucket.GetObjectToFile(objectDesc, "newpic3.jpg")
+	c.Assert(err, NotNil)
+	err = s.bucket.GetObjectToFile(objectName, "newpic4.jpg")
+	c.Assert(err, NotNil)
+}
+
+// TestUploadPartCopyWithConstraints
+func (s *OssBucketMultipartSuite) TestUploadPartCopyWithConstraints(c *C) {
+	objectSrc := objectNamePrefix + "tucwc" + "src"
+	objectDesc := objectNamePrefix + "tucwc" + "desc"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	chunks, err := SplitFileByPartNum(fileName, 3)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+	c.Assert(err, IsNil)
+
+	imur, err := s.bucket.InitiateMultipartUpload(objectDesc)
+	var parts []UploadPart
+	for _, chunk := range chunks {
+		_, err = s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+			CopySourceIfModifiedSince(futureDate))
+		c.Assert(err, NotNil)
+	}
+
+	for _, chunk := range chunks {
+		_, err = s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+			CopySourceIfUnmodifiedSince(futureDate))
+		c.Assert(err, IsNil)
+	}
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectSrc)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+
+	for _, chunk := range chunks {
+		_, err = s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+			CopySourceIfNoneMatch(meta.Get("Etag")))
+		c.Assert(err, NotNil)
+	}
+
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+			CopySourceIfMatch(meta.Get("Etag")))
+		c.Assert(err, IsNil)
+		parts = append(parts, part)
+	}
+
+	cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+	c.Assert(err, IsNil)
+	fmt.Println("cmur:", cmur)
+
+	err = s.bucket.GetObjectToFile(objectDesc, "newpic5.jpg")
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectSrc)
+	c.Assert(err, IsNil)
+	err = s.bucket.DeleteObject(objectDesc)
+	c.Assert(err, IsNil)
+}
+
+// TestMultipartUploadFromFileOutofOrder
+func (s *OssBucketMultipartSuite) TestMultipartUploadFromFileOutofOrder(c *C) {
+	objectName := objectNamePrefix + "tmuffoo"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	chunks, err := SplitFileByPartSize(fileName, 1024*100)
+	shuffleArray(chunks)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	imur, err := s.bucket.InitiateMultipartUpload(objectName)
+	var parts []UploadPart
+	for _, chunk := range chunks {
+		_, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+	}
+	// double upload
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+		parts = append(parts, part)
+	}
+
+	cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+	c.Assert(err, IsNil)
+	fmt.Println("cmur:", cmur)
+
+	err = s.bucket.GetObjectToFile(objectName, "newpic6.jpg")
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestUploadPartCopyOutofOrder
+func (s *OssBucketMultipartSuite) TestUploadPartCopyOutofOrder(c *C) {
+	objectSrc := objectNamePrefix + "tupcoo" + "src"
+	objectDesc := objectNamePrefix + "tupcoo" + "desc"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	chunks, err := SplitFileByPartSize(fileName, 1024*100)
+	shuffleArray(chunks)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	err = s.bucket.PutObjectFromFile(objectSrc, fileName)
+	c.Assert(err, IsNil)
+
+	imur, err := s.bucket.InitiateMultipartUpload(objectDesc)
+	var parts []UploadPart
+	for _, chunk := range chunks {
+		_, err := s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+	}
+	//double copy
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+		parts = append(parts, part)
+	}
+
+	cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+	c.Assert(err, IsNil)
+	fmt.Println("cmur:", cmur)
+
+	err = s.bucket.GetObjectToFile(objectDesc, "newpic7.jpg")
+	c.Assert(err, IsNil)
+
+	err = s.bucket.DeleteObject(objectSrc)
+	c.Assert(err, IsNil)
+	err = s.bucket.DeleteObject(objectDesc)
+	c.Assert(err, IsNil)
+}
+
+// TestMultipartUpload
+func (s *OssBucketMultipartSuite) TestMultipartUploadFromFileType(c *C) {
+	objectName := objectNamePrefix + "tmuffwm" + ".jpg"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	chunks, err := SplitFileByPartNum(fileName, 4)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	imur, err := s.bucket.InitiateMultipartUpload(objectName)
+	var parts []UploadPart
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number)
+		c.Assert(err, IsNil)
+		parts = append(parts, part)
+	}
+
+	fmt.Println("parts:", parts)
+	cmur, err := s.bucket.CompleteMultipartUpload(imur, parts)
+	c.Assert(err, IsNil)
+	fmt.Println("cmur:", cmur)
+
+	err = s.bucket.GetObjectToFile(objectName, "newpic8.jpg")
+	c.Assert(err, IsNil)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("Content-Type"), Equals, "image/jpeg")
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestListMultipartUploads(c *C) {
+	objectName := objectNamePrefix + "tlmu"
+
+	imurs := []InitiateMultipartUploadResult{}
+	for i := 0; i < 20; i++ {
+		imur, err := s.bucket.InitiateMultipartUpload(objectName + strconv.Itoa(i))
+		c.Assert(err, IsNil)
+		imurs = append(imurs, imur)
+	}
+
+	lmpu, err := s.bucket.ListMultipartUploads()
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 20)
+
+	lmpu, err = s.bucket.ListMultipartUploads(MaxUploads(3))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 3)
+
+	lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 20)
+
+	lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName + "1"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 11)
+
+	lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName + "22"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 0)
+
+	lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectName + "10"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 17)
+
+	lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectName+"10"), MaxUploads(3))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 3)
+
+	lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName), Delimiter("4"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 18)
+	c.Assert(len(lmpu.CommonPrefixes), Equals, 2)
+
+	// upload-id-marker
+	lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectName+"12"), UploadIDMarker("EEE"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 15)
+	//fmt.Println("UploadIDMarker", lmpu.Uploads)
+
+	for _, imur := range imurs {
+		err = s.bucket.AbortMultipartUpload(imur)
+		c.Assert(err, IsNil)
+	}
+}
+
+func (s *OssBucketMultipartSuite) TestListMultipartUploadsEncodingKey(c *C) {
+	objectName := objectNamePrefix + "让你任性让你狂" + "tlmuek"
+
+	imurs := []InitiateMultipartUploadResult{}
+	for i := 0; i < 3; i++ {
+		imur, err := s.bucket.InitiateMultipartUpload(objectName + strconv.Itoa(i))
+		c.Assert(err, IsNil)
+		imurs = append(imurs, imur)
+	}
+
+	lmpu, err := s.bucket.ListMultipartUploads()
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 3)
+
+	lmpu, err = s.bucket.ListMultipartUploads(Prefix("myobject让你任性让你狂tlmuek1"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 1)
+
+	lmpu, err = s.bucket.ListMultipartUploads(KeyMarker("myobject让你任性让你狂tlmuek1"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lmpu.Uploads), Equals, 1)
+
+	lmpu, err = s.bucket.ListMultipartUploads(EncodingType("url"))
+	c.Assert(err, IsNil)
+	for i, upload := range lmpu.Uploads {
+		c.Assert(upload.Key, Equals, "myobject让你任性让你狂tlmuek"+strconv.Itoa(i))
+	}
+
+	for _, imur := range imurs {
+		err = s.bucket.AbortMultipartUpload(imur)
+		c.Assert(err, IsNil)
+	}
+}
+
+func (s *OssBucketMultipartSuite) TestMultipartNegative(c *C) {
+	objectName := objectNamePrefix + "tmn"
+
+	// key tool long
+	data := make([]byte, 100*1024)
+	imur, err := s.bucket.InitiateMultipartUpload(string(data))
+	c.Assert(err, NotNil)
+
+	// imur invalid
+	fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+	fd, err := os.Open(fileName)
+	c.Assert(err, IsNil)
+	defer fd.Close()
+
+	_, err = s.bucket.UploadPart(imur, fd, 1024, 1)
+	c.Assert(err, NotNil)
+
+	_, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 1024, 1)
+	c.Assert(err, NotNil)
+
+	_, err = s.bucket.UploadPartCopy(imur, fileName, 0, 1024, 1)
+	c.Assert(err, NotNil)
+
+	err = s.bucket.AbortMultipartUpload(imur)
+	c.Assert(err, NotNil)
+
+	_, err = s.bucket.ListUploadedParts(imur)
+	c.Assert(err, NotNil)
+
+	// invalid exist
+	imur, err = s.bucket.InitiateMultipartUpload(objectName)
+	c.Assert(err, IsNil)
+
+	_, err = s.bucket.UploadPart(imur, fd, 1024, 1)
+	c.Assert(err, IsNil)
+
+	_, err = s.bucket.UploadPart(imur, fd, 102400, 10001)
+	c.Assert(err, NotNil)
+
+	//    _, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 1024, 1)
+	//    c.Assert(err, IsNil)
+
+	_, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 102400, 10001)
+	c.Assert(err, NotNil)
+
+	_, err = s.bucket.UploadPartCopy(imur, fileName, 0, 1024, 1)
+	c.Assert(err, NotNil)
+
+	_, err = s.bucket.UploadPartCopy(imur, fileName, 0, 1024, 1000)
+	c.Assert(err, NotNil)
+
+	err = s.bucket.AbortMultipartUpload(imur)
+	c.Assert(err, IsNil)
+
+	// option invalid
+	_, err = s.bucket.InitiateMultipartUpload(objectName, IfModifiedSince(futureDate))
+	c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestMultipartUploadFromFileBigFile(c *C) {
+	objectName := objectNamePrefix + "tmuffbf"
+	bigFile := "D:\\tmp\\bigfile.zip"
+	newFile := "D:\\tmp\\newbigfile.zip"
+
+	exist, err := isFileExist(bigFile)
+	c.Assert(err, IsNil)
+	if !exist {
+		return
+	}
+
+	chunks, err := SplitFileByPartNum(bigFile, 64)
+	c.Assert(err, IsNil)
+	fmt.Println("chunks:", chunks)
+
+	imur, err := s.bucket.InitiateMultipartUpload(objectName)
+	var parts []UploadPart
+	start := GetNowSec()
+	for _, chunk := range chunks {
+		part, err := s.bucket.UploadPartFromFile(imur, bigFile, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		c.Assert(err, IsNil)
+		parts = append(parts, part)
+	}
+	end := GetNowSec()
+	fmt.Println("Uplaod big file:", bigFile, "use sec:", end-start)
+
+	fmt.Println("parts:", parts)
+	_, err = s.bucket.CompleteMultipartUpload(imur, parts)
+	c.Assert(err, IsNil)
+
+	start = GetNowSec()
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+	end = GetNowSec()
+	fmt.Println("Download big file:", bigFile, "use sec:", end-start)
+
+	start = GetNowSec()
+	eq, err := compareFiles(bigFile, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+	end = GetNowSec()
+	fmt.Println("Compare big file:", bigFile, "use sec:", end-start)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+// TestUploadFile
+func (s *OssBucketMultipartSuite) TestUploadFile(c *C) {
+	objectName := objectNamePrefix + "tuff"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+	newFile := "newfiletuff.jpg"
+
+	// 有余数
+	err := s.bucket.UploadFile(objectName, fileName, 100*1024)
+	c.Assert(err, IsNil)
+
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err := compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// 整除
+	err = s.bucket.UploadFile(objectName, fileName, 482048/4)
+	c.Assert(err, IsNil)
+
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// 等于文件大小
+	err = s.bucket.UploadFile(objectName, fileName, 482048)
+	c.Assert(err, IsNil)
+
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// 大于文件大小
+	err = s.bucket.UploadFile(objectName, fileName, 482049)
+	c.Assert(err, IsNil)
+
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// option
+	options := []Option{
+		Expires(futureDate),
+		ObjectACL(ACLPublicRead),
+		Meta("myprop", "mypropval")}
+	err = s.bucket.UploadFile(objectName, fileName, 482049, options...)
+	c.Assert(err, IsNil)
+
+	// Check
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	acl, err := s.bucket.GetObjectACL(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectAcl:", acl)
+	c.Assert(acl.ACL, Equals, "default")
+
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+	c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+}
+
+func (s *OssBucketMultipartSuite) TestUploadFileNegative(c *C) {
+	objectName := objectNamePrefix + "tufn"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+
+	// 小于最小文件片
+	err := s.bucket.UploadFile(objectName, fileName, 100*1024-1)
+	c.Assert(err, NotNil)
+
+	// 大于最大文件片
+	err = s.bucket.UploadFile(objectName, fileName, 1024*1024*1024*5+1)
+	c.Assert(err, NotNil)
+
+	// 文件不存在
+	err = s.bucket.UploadFile(objectName, "/tmp/x", 1024*1024*1024)
+	c.Assert(err, NotNil)
+
+	// Key无效
+	err = s.bucket.UploadFile("", fileName, 100*1024)
+	c.Assert(err, NotNil)
+}
+
+// TestDownloadFile
+func (s *OssBucketMultipartSuite) TestDownloadFile(c *C) {
+	objectName := objectNamePrefix + "tdff"
+	var fileName = "../sample/BingWallpaper-2015-11-07.jpg"
+	newFile := "newfiletdff.jpg"
+
+	err := s.bucket.UploadFile(objectName, fileName, 100*1024)
+	c.Assert(err, IsNil)
+
+	// 有余数
+	err = s.bucket.DownloadFile(objectName, newFile, 100*1024)
+	c.Assert(err, IsNil)
+
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err := compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	// 整除
+	err = s.bucket.DownloadFile(objectName, newFile, 482048/4)
+	c.Assert(err, IsNil)
+
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	// 等于文件大小
+	err = s.bucket.DownloadFile(objectName, newFile, 482048)
+	c.Assert(err, IsNil)
+
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	// 大于文件大小
+	err = s.bucket.DownloadFile(objectName, newFile, 482049)
+	c.Assert(err, IsNil)
+
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	// option
+	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	fmt.Println("GetObjectDetailedMeta:", meta)
+
+	// If-Match
+	err = s.bucket.DownloadFile(objectName, newFile, 482048/4, IfMatch(meta.Get("Etag")))
+	c.Assert(err, IsNil)
+
+	os.Remove(newFile)
+	err = s.bucket.GetObjectToFile(objectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	// If-None-Match
+	err = s.bucket.DownloadFile(objectName, newFile, 482048, IfNoneMatch(meta.Get("Etag")))
+	c.Assert(err, NotNil)
+
+	os.Remove(newFile)
+	err = s.bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+}
+
+func (s *OssBucketMultipartSuite) TestDownloadFileNegative(c *C) {
+	objectName := objectNamePrefix + "tufn"
+	newFile := "newfiletudff.jpg"
+
+	// 小于最小文件片
+	err := s.bucket.DownloadFile(objectName, newFile, 100*1024-1)
+	c.Assert(err, NotNil)
+
+	// 大于最大文件片
+	err = s.bucket.DownloadFile(objectName, newFile, 1024*1024*1024+1)
+	c.Assert(err, NotNil)
+
+	// 文件不存在
+	err = s.bucket.DownloadFile(objectName, "D:\\work\\oss\\", 1024*1024*1024+1)
+	c.Assert(err, NotNil)
+
+	// Key不存在
+	err = s.bucket.DownloadFile(objectName, newFile, 100*1024)
+	c.Assert(err, NotNil)
+}
+
+// private
+func shuffleArray(chunks []FileChunk) []FileChunk {
+	for i := range chunks {
+		j := rand.Intn(i + 1)
+		chunks[i], chunks[j] = chunks[j], chunks[i]
+	}
+	return chunks
+}

+ 287 - 0
oss/option.go

@@ -0,0 +1,287 @@
+package oss
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"net/url"
+	"sort"
+	"strconv"
+	"time"
+)
+
+type optionType string
+
+const (
+	optionParam optionType = "parameter" // URL中的参数
+	optionHTTP  optionType = "http"      // HTTP头
+)
+
+type (
+	optionValue struct {
+		Value string
+		Type  optionType
+	}
+
+	// Option http option
+	Option func(map[string]optionValue) error
+)
+
+// ACL is an option to set X-Oss-Acl header
+func ACL(acl ACLType) Option {
+	return setHeader(HTTPHeaderOssACL, string(acl))
+}
+
+// ContentType is an option to set Content-Type header
+func ContentType(value string) Option {
+	return setHeader(HTTPHeaderContentType, value)
+}
+
+// ContentLength is an option to set Content-Length header
+func ContentLength(length int64) Option {
+	return setHeader(HTTPHeaderContentLength, strconv.FormatInt(length, 10))
+}
+
+// CacheControl is an option to set Cache-Control header
+func CacheControl(value string) Option {
+	return setHeader(HTTPHeaderCacheControl, value)
+}
+
+// ContentDisposition is an option to set Content-Disposition header
+func ContentDisposition(value string) Option {
+	return setHeader(HTTPHeaderContentDisposition, value)
+}
+
+// ContentEncoding is an option to set Content-Encoding header
+func ContentEncoding(value string) Option {
+	return setHeader(HTTPHeaderContentEncoding, value)
+}
+
+// Expires is an option to set Expires header
+func Expires(t time.Time) Option {
+	return setHeader(HTTPHeaderExpires, t.Format(http.TimeFormat))
+}
+
+// Meta is an option to set Meta header
+func Meta(key, value string) Option {
+	return setHeader(HTTPHeaderOssMetaPrefix+key, value)
+}
+
+// Range is an option to set Range header, [start, end]
+func Range(start, end int64) Option {
+	return setHeader(HTTPHeaderRange, fmt.Sprintf("bytes=%d-%d", start, end))
+}
+
+// AcceptEncoding is an option to set Accept-Encoding header
+func AcceptEncoding(value string) Option {
+	return setHeader(HTTPHeaderAcceptEncoding, value)
+}
+
+// IfModifiedSince is an option to set If-Modified-Since header
+func IfModifiedSince(t time.Time) Option {
+	return setHeader(HTTPHeaderIfModifiedSince, t.Format(http.TimeFormat))
+}
+
+// IfUnmodifiedSince is an option to set If-Unmodified-Since header
+func IfUnmodifiedSince(t time.Time) Option {
+	return setHeader(HTTPHeaderIfUnmodifiedSince, t.Format(http.TimeFormat))
+}
+
+// IfMatch is an option to set If-Match header
+func IfMatch(value string) Option {
+	return setHeader(HTTPHeaderIfMatch, value)
+}
+
+// IfNoneMatch is an option to set IfNoneMatch header
+func IfNoneMatch(value string) Option {
+	return setHeader(HTTPHeaderIfNoneMatch, value)
+}
+
+// CopySource is an option to set X-Oss-Copy-Source header
+func CopySource(sourceBucket, sourceObject string) Option {
+	return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject)
+}
+
+// CopySourceRange is an option to set X-Oss-Copy-Source header
+func CopySourceRange(startPosition, partSize int64) Option {
+	val := "bytes=" + strconv.FormatInt(startPosition, 10) + "-" +
+		strconv.FormatInt((startPosition+partSize-1), 10)
+	return setHeader(HTTPHeaderOssCopySourceRange, val)
+}
+
+// CopySourceIfMatch is an option to set X-Oss-Copy-Source-If-Match header
+func CopySourceIfMatch(value string) Option {
+	return setHeader(HTTPHeaderOssCopySourceIfMatch, value)
+}
+
+// CopySourceIfNoneMatch is an option to set X-Oss-Copy-Source-If-None-Match header
+func CopySourceIfNoneMatch(value string) Option {
+	return setHeader(HTTPHeaderOssCopySourceIfNoneMatch, value)
+}
+
+// CopySourceIfModifiedSince is an option to set X-Oss-CopySource-If-Modified-Since header
+func CopySourceIfModifiedSince(t time.Time) Option {
+	return setHeader(HTTPHeaderOssCopySourceIfModifiedSince, t.Format(http.TimeFormat))
+}
+
+// CopySourceIfUnmodifiedSince is an option to set X-Oss-Copy-Source-If-Unmodified-Since header
+func CopySourceIfUnmodifiedSince(t time.Time) Option {
+	return setHeader(HTTPHeaderOssCopySourceIfUnmodifiedSince, t.Format(http.TimeFormat))
+}
+
+// MetadataDirective is an option to set X-Oss-Metadata-Directive header
+func MetadataDirective(directive MetadataDirectiveType) Option {
+	return setHeader(HTTPHeaderOssMetadataDirective, string(directive))
+}
+
+// ServerSideEncryption is an option to set X-Oss-Server-Side-Encryption header
+func ServerSideEncryption(value string) Option {
+	return setHeader(HTTPHeaderOssServerSideEncryption, value)
+}
+
+// ObjectACL is an option to set X-Oss-Object-Acl header
+func ObjectACL(acl ACLType) Option {
+	return setHeader(HTTPHeaderOssObjectACL, string(acl))
+}
+
+// Origin is an option to set Origin header
+func Origin(value string) Option {
+	return setHeader(HTTPHeaderOrigin, value)
+}
+
+// Delimiter is an option to set delimiler parameter
+func Delimiter(value string) Option {
+	return addParam("delimiter", value)
+}
+
+// Marker is an option to set marker parameter
+func Marker(value string) Option {
+	return addParam("marker", value)
+}
+
+// MaxKeys is an option to set maxkeys parameter
+func MaxKeys(value int) Option {
+	return addParam("max-keys", strconv.Itoa(value))
+}
+
+// Prefix is an option to set prefix parameter
+func Prefix(value string) Option {
+	return addParam("prefix", value)
+}
+
+// EncodingType is an option to set encoding-type parameter
+func EncodingType(value string) Option {
+	return addParam("encoding-type", value)
+}
+
+// MaxUploads is an option to set max-uploads parameter
+func MaxUploads(value int) Option {
+	return addParam("max-uploads", strconv.Itoa(value))
+}
+
+// KeyMarker is an option to set key-marker parameter
+func KeyMarker(value string) Option {
+	return addParam("key-marker", value)
+}
+
+// UploadIDMarker is an option to set upload-id-marker parameter
+func UploadIDMarker(value string) Option {
+	return addParam("upload-id-marker", value)
+}
+
+const deleteObjectsQuiet = "delete-objects-quiet"
+
+// DeleteObjectsQuiet DeleteObjects详细(verbose)模式或简单(quiet)模式,默认是详细模式。
+func DeleteObjectsQuiet(isQuiet bool) Option {
+	return addParam(deleteObjectsQuiet, strconv.FormatBool(isQuiet))
+}
+
+func setHeader(key, value string) Option {
+	return func(args map[string]optionValue) error {
+		if value == "" {
+			return nil
+		}
+		args[key] = optionValue{value, optionHTTP}
+		return nil
+	}
+}
+
+func addParam(key, value string) Option {
+	return func(args map[string]optionValue) error {
+		if value == "" {
+			return nil
+		}
+		args[key] = optionValue{value, optionParam}
+		return nil
+	}
+}
+
+func handleOptions(headers map[string]string, options []Option) error {
+	args := map[string]optionValue{}
+	for _, option := range options {
+		if option != nil {
+			if err := option(args); err != nil {
+				return err
+			}
+		}
+	}
+
+	for k, v := range args {
+		if v.Type == optionHTTP {
+			headers[k] = v.Value
+		}
+	}
+	return nil
+}
+
+func handleParams(options []Option) (string, error) {
+	// option
+	args := map[string]optionValue{}
+	for _, option := range options {
+		if option != nil {
+			if err := option(args); err != nil {
+				return "", err
+			}
+		}
+	}
+
+	// sort
+	var buf bytes.Buffer
+	keys := make([]string, 0, len(args))
+	for k, v := range args {
+		if v.Type == optionParam {
+			keys = append(keys, k)
+		}
+	}
+	sort.Strings(keys)
+
+	// serialize
+	for _, k := range keys {
+		vs := args[k]
+		prefix := url.QueryEscape(k) + "="
+
+		if buf.Len() > 0 {
+			buf.WriteByte('&')
+		}
+		buf.WriteString(prefix)
+		buf.WriteString(url.QueryEscape(vs.Value))
+	}
+
+	return buf.String(), nil
+}
+
+func findOption(options []Option, param, defaultVal string) (string, error) {
+	args := map[string]optionValue{}
+	for _, option := range options {
+		if option != nil {
+			if err := option(args); err != nil {
+				return "", err
+			}
+		}
+	}
+
+	if val, ok := args[param]; ok {
+		return val.Value, nil
+	}
+	return defaultVal, nil
+}

+ 251 - 0
oss/option_test.go

@@ -0,0 +1,251 @@
+package oss
+
+import (
+	"net/http"
+
+	. "gopkg.in/check.v1"
+)
+
+type OssOptionSuite struct{}
+
+var _ = Suite(&OssOptionSuite{})
+
+type optionTestCase struct {
+	option Option
+	key    string
+	value  string
+}
+
+var headerTestcases = []optionTestCase{
+	{
+		option: Meta("User", "baymax"),
+		key:    "X-Oss-Meta-User",
+		value:  "baymax",
+	},
+	{
+		option: ACL(ACLPrivate),
+		key:    "X-Oss-Acl",
+		value:  "private",
+	},
+	{
+		option: ContentType("plain/text"),
+		key:    "Content-Type",
+		value:  "plain/text",
+	},
+	{
+		option: CacheControl("no-cache"),
+		key:    "Cache-Control",
+		value:  "no-cache",
+	},
+	{
+		option: ContentDisposition("Attachment; filename=example.txt"),
+		key:    "Content-Disposition",
+		value:  "Attachment; filename=example.txt",
+	},
+	{
+		option: ContentEncoding("gzip"),
+		key:    "Content-Encoding",
+		value:  "gzip",
+	},
+	{
+		option: Expires(pastDate),
+		key:    "Expires",
+		value:  pastDate.Format(http.TimeFormat),
+	},
+	{
+		option: Range(0, 9),
+		key:    "Range",
+		value:  "bytes=0-9",
+	},
+	{
+		option: Origin("localhost"),
+		key:    "Origin",
+		value:  "localhost",
+	},
+	{
+		option: CopySourceRange(0, 9),
+		key:    "X-Oss-Copy-Source-Range",
+		value:  "bytes=0-8",
+	},
+	{
+		option: IfModifiedSince(pastDate),
+		key:    "If-Modified-Since",
+		value:  pastDate.Format(http.TimeFormat),
+	},
+	{
+		option: IfUnmodifiedSince(futureDate),
+		key:    "If-Unmodified-Since",
+		value:  futureDate.Format(http.TimeFormat),
+	},
+	{
+		option: IfMatch("xyzzy"),
+		key:    "If-Match",
+		value:  "xyzzy",
+	},
+	{
+		option: IfNoneMatch("xyzzy"),
+		key:    "If-None-Match",
+		value:  "xyzzy",
+	},
+	{
+		option: CopySource("bucket_name", "object_name"),
+		key:    "X-Oss-Copy-Source",
+		value:  "/bucket_name/object_name",
+	},
+	{
+		option: CopySourceIfModifiedSince(pastDate),
+		key:    "X-Oss-Copy-Source-If-Modified-Since",
+		value:  pastDate.Format(http.TimeFormat),
+	},
+	{
+		option: CopySourceIfUnmodifiedSince(futureDate),
+		key:    "X-Oss-Copy-Source-If-Unmodified-Since",
+		value:  futureDate.Format(http.TimeFormat),
+	},
+	{
+		option: CopySourceIfMatch("xyzzy"),
+		key:    "X-Oss-Copy-Source-If-Match",
+		value:  "xyzzy",
+	},
+	{
+		option: CopySourceIfNoneMatch("xyzzy"),
+		key:    "X-Oss-Copy-Source-If-None-Match",
+		value:  "xyzzy",
+	},
+	{
+		option: MetadataDirective(MetaCopy),
+		key:    "X-Oss-Metadata-Directive",
+		value:  "COPY",
+	},
+	{
+		option: ServerSideEncryption("AES256"),
+		key:    "X-Oss-Server-Side-Encryption",
+		value:  "AES256",
+	},
+	{
+		option: ObjectACL(ACLPrivate),
+		key:    "X-Oss-Object-Acl",
+		value:  "private",
+	},
+}
+
+func (s *OssOptionSuite) TestHeaderOptions(c *C) {
+	for _, testcase := range headerTestcases {
+		headers := make(map[string]optionValue)
+		err := testcase.option(headers)
+		c.Assert(err, IsNil)
+
+		expected, actual := testcase.value, headers[testcase.key].Value
+		c.Assert(expected, Equals, actual)
+	}
+}
+
+var paramTestCases = []optionTestCase{
+	{
+		option: Delimiter("/"),
+		key:    "delimiter",
+		value:  "/",
+	},
+	{
+		option: Marker("abc"),
+		key:    "marker",
+		value:  "abc",
+	},
+	{
+		option: MaxKeys(150),
+		key:    "max-keys",
+		value:  "150",
+	},
+	{
+		option: Prefix("fun"),
+		key:    "prefix",
+		value:  "fun",
+	},
+	{
+		option: EncodingType("ascii"),
+		key:    "encoding-type",
+		value:  "ascii",
+	},
+	{
+		option: MaxUploads(100),
+		key:    "max-uploads",
+		value:  "100",
+	},
+	{
+		option: KeyMarker("abc"),
+		key:    "key-marker",
+		value:  "abc",
+	},
+	{
+		option: UploadIDMarker("xyz"),
+		key:    "upload-id-marker",
+		value:  "xyz",
+	},
+}
+
+func (s *OssOptionSuite) TestParamOptions(c *C) {
+	for _, testcase := range paramTestCases {
+		params := make(map[string]optionValue)
+		err := testcase.option(params)
+		c.Assert(err, IsNil)
+
+		expected, actual := testcase.value, params[testcase.key].Value
+		c.Assert(expected, Equals, actual)
+	}
+}
+
+func (s *OssOptionSuite) TestHandleOptions(c *C) {
+	headers := make(map[string]string)
+	options := []Option{}
+
+	for _, testcase := range headerTestcases {
+		options = append(options, testcase.option)
+	}
+
+	err := handleOptions(headers, options)
+	c.Assert(err, IsNil)
+
+	for _, testcase := range headerTestcases {
+		expected, actual := testcase.value, headers[testcase.key]
+		c.Assert(expected, Equals, actual)
+	}
+
+	options = []Option{IfMatch(""), nil}
+	headers = map[string]string{}
+	err = handleOptions(headers, options)
+	c.Assert(err, IsNil)
+	c.Assert(len(headers), Equals, 0)
+}
+
+func (s *OssOptionSuite) TestHandleParams(c *C) {
+	options := []Option{}
+
+	for _, testcase := range paramTestCases {
+		options = append(options, testcase.option)
+	}
+
+	out, err := handleParams(options)
+	c.Assert(err, IsNil)
+	c.Assert(len(out), Equals, 120)
+
+	options = []Option{KeyMarker(""), nil}
+	out, err = handleParams(options)
+	c.Assert(out, Equals, "")
+	c.Assert(err, IsNil)
+}
+
+func (s *OssOptionSuite) TestFindOption(c *C) {
+	options := []Option{}
+
+	for _, testcase := range headerTestcases {
+		options = append(options, testcase.option)
+	}
+
+	str, err := findOption(options, "X-Oss-Acl", "")
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, "private")
+
+	str, err = findOption(options, "MyProp", "")
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, "")
+}

+ 424 - 0
oss/type.go

@@ -0,0 +1,424 @@
+package oss
+
+import (
+	"encoding/xml"
+	"net/url"
+	"time"
+)
+
+// ListBucketsResult ListBuckets请求返回的结果
+type ListBucketsResult struct {
+	XMLName     xml.Name           `xml:"ListAllMyBucketsResult"`
+	Prefix      string             `xml:"Prefix"`         // 本次查询结果的前缀
+	Marker      string             `xml:"Marker"`         // 标明查询的起点,未全部返回时有此节点
+	MaxKeys     int                `xml:"MaxKeys"`        // 返回结果的最大数目,未全部返回时有此节点
+	IsTruncated bool               `xml:"IsTruncated"`    // 所有的结果是否已经全部返回
+	NextMarker  string             `xml:"NextMarker"`     // 表示下一次查询的起点
+	Owner       Owner              `xml:"Owner"`          // 拥有者信息
+	Buckets     []BucketProperties `xml:"Buckets>Bucket"` // Bucket列表
+}
+
+// BucketProperties Bucket信息
+type BucketProperties struct {
+	XMLName      xml.Name  `xml:"Bucket"`
+	Name         string    `xml:"Name"`         // Bucket名称
+	Location     string    `xml:"Location"`     // Bucket所在的数据中心
+	CreationDate time.Time `xml:"CreationDate"` // Bucket创建时间
+}
+
+// GetBucketACLResult GetBucketACL请求返回的结果
+type GetBucketACLResult struct {
+	XMLName xml.Name `xml:"AccessControlPolicy"`
+	ACL     string   `xml:"AccessControlList>Grant"` // Bucket权限
+	Owner   Owner    `xml:"Owner"`                   // Bucket拥有者信息
+}
+
+// LifecycleConfiguration Bucket的Lifecycle配置
+type LifecycleConfiguration struct {
+	XMLName xml.Name        `xml:"LifecycleConfiguration"`
+	Rules   []LifecycleRule `xml:"Rule"`
+}
+
+// LifecycleRule Lifecycle规则
+type LifecycleRule struct {
+	XMLName    xml.Name            `xml:"Rule"`
+	ID         string              `xml:"ID"`         // 规则唯一的ID
+	Prefix     string              `xml:"Prefix"`     // 规则所适用Object的前缀
+	Status     string              `xml:"Status"`     // 规则是否生效
+	Expiration LifecycleExpiration `xml:"Expiration"` // 规则的过期属性
+}
+
+// LifecycleExpiration 规则的过期属性
+type LifecycleExpiration struct {
+	XMLName xml.Name  `xml:"Expiration"`
+	Days    int       `xml:"Days,omitempty"` // 最后修改时间过后多少天生效
+	Date    time.Time `xml:"Date,omitempty"` // 指定规则何时生效
+}
+
+type lifecycleXML struct {
+	XMLName xml.Name        `xml:"LifecycleConfiguration"`
+	Rules   []lifecycleRule `xml:"Rule"`
+}
+
+type lifecycleRule struct {
+	XMLName    xml.Name            `xml:"Rule"`
+	ID         string              `xml:"ID"`
+	Prefix     string              `xml:"Prefix"`
+	Status     string              `xml:"Status"`
+	Expiration lifecycleExpiration `xml:"Expiration"`
+}
+
+type lifecycleExpiration struct {
+	XMLName xml.Name `xml:"Expiration"`
+	Days    int      `xml:"Days,omitempty"`
+	Date    string   `xml:"Date,omitempty"`
+}
+
+const expirationDateFormat = "2006-01-02T15:04:05.000Z"
+
+func convLifecycleRule(rules []LifecycleRule) []lifecycleRule {
+	rs := []lifecycleRule{}
+	for _, rule := range rules {
+		r := lifecycleRule{}
+		r.ID = rule.ID
+		r.Prefix = rule.Prefix
+		r.Status = rule.Status
+		if rule.Expiration.Date.IsZero() {
+			r.Expiration.Days = rule.Expiration.Days
+		} else {
+			r.Expiration.Date = rule.Expiration.Date.Format(expirationDateFormat)
+		}
+		rs = append(rs, r)
+	}
+	return rs
+}
+
+// BuildLifecycleRuleByDays 指定过期天数构建Lifecycle规则
+func BuildLifecycleRuleByDays(id, prefix string, status bool, days int) LifecycleRule {
+	var statusStr = "Enabled"
+	if !status {
+		statusStr = "Disabled"
+	}
+	return LifecycleRule{ID: id, Prefix: prefix, Status: statusStr,
+		Expiration: LifecycleExpiration{Days: days}}
+}
+
+// BuildLifecycleRuleByDate 指定过期时间构建Lifecycle规则
+func BuildLifecycleRuleByDate(id, prefix string, status bool, year, month, day int) LifecycleRule {
+	var statusStr = "Enabled"
+	if !status {
+		statusStr = "Disabled"
+	}
+	date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
+	return LifecycleRule{ID: id, Prefix: prefix, Status: statusStr,
+		Expiration: LifecycleExpiration{Date: date}}
+}
+
+// GetBucketLifecycleResult GetBucketLifecycle请求请求结果
+type GetBucketLifecycleResult LifecycleConfiguration
+
+// RefererXML Referer配置
+type RefererXML struct {
+	XMLName           xml.Name `xml:"RefererConfiguration"`
+	AllowEmptyReferer bool     `xml:"AllowEmptyReferer"`   // 是否允许referer字段为空的请求访问
+	RefererList       []string `xml:"RefererList>Referer"` // referer访问白名单
+}
+
+// GetBucketRefererResult GetBucketReferer请教返回结果
+type GetBucketRefererResult RefererXML
+
+// LoggingXML Logging配置
+type LoggingXML struct {
+	XMLName        xml.Name       `xml:"BucketLoggingStatus"`
+	LoggingEnabled LoggingEnabled `xml:"LoggingEnabled"` // 访问日志信息容器
+}
+
+type loggingXMLEmpty struct {
+	XMLName xml.Name `xml:"BucketLoggingStatus"`
+}
+
+// LoggingEnabled 访问日志信息容器
+type LoggingEnabled struct {
+	XMLName      xml.Name `xml:"LoggingEnabled"`
+	TargetBucket string   `xml:"TargetBucket"` //存放访问日志的Bucket
+	TargetPrefix string   `xml:"TargetPrefix"` //保存访问日志的文件前缀
+}
+
+// GetBucketLoggingResult GetBucketLogging请求返回结果
+type GetBucketLoggingResult LoggingXML
+
+// WebsiteXML Website配置
+type WebsiteXML struct {
+	XMLName       xml.Name      `xml:"WebsiteConfiguration"`
+	IndexDocument IndexDocument `xml:"IndexDocument"` // 目录URL时添加的索引文件
+	ErrorDocument ErrorDocument `xml:"ErrorDocument"` // 404错误时使用的文件
+}
+
+// IndexDocument 目录URL时添加的索引文件
+type IndexDocument struct {
+	XMLName xml.Name `xml:"IndexDocument"`
+	Suffix  string   `xml:"Suffix"` // 目录URL时添加的索引文件名
+}
+
+// ErrorDocument 404错误时使用的文件
+type ErrorDocument struct {
+	XMLName xml.Name `xml:"ErrorDocument"`
+	Key     string   `xml:"Key"` // 404错误时使用的文件名
+}
+
+// GetBucketWebsiteResult GetBucketWebsite请求返回结果
+type GetBucketWebsiteResult WebsiteXML
+
+// CORSXML CORS配置
+type CORSXML struct {
+	XMLName   xml.Name   `xml:"CORSConfiguration"`
+	CORSRules []CORSRule `xml:"CORSRule"` // CORS规则列表
+}
+
+// CORSRule CORS规则
+type CORSRule struct {
+	XMLName       xml.Name `xml:"CORSRule"`
+	AllowedOrigin []string `xml:"AllowedOrigin"` // 允许的来源,默认通配符"*"
+	AllowedMethod []string `xml:"AllowedMethod"` // 允许的方法
+	AllowedHeader []string `xml:"AllowedHeader"` // 允许的请求头
+	ExposeHeader  []string `xml:"ExposeHeader"`  // 允许的响应头
+	MaxAgeSeconds int      `xml:"MaxAgeSeconds"` // 最大的缓存时间
+}
+
+// GetBucketCORSResult GetBucketCORS请求返回的结果
+type GetBucketCORSResult CORSXML
+
+// ListObjectsResult ListObjects请求返回结果
+type ListObjectsResult struct {
+	XMLName        xml.Name           `xml:"ListBucketResult"`
+	Prefix         string             `xml:"Prefix"`                // 本次查询结果的开始前缀
+	Marker         string             `xml:"Marker"`                // 这次查询的起点
+	MaxKeys        int                `xml:"MaxKeys"`               // 请求返回结果的最大数目
+	Delimiter      string             `xml:"Delimiter"`             // 对Object名字进行分组的字符
+	IsTruncated    bool               `xml:"IsTruncated"`           // 是否所有的结果都已经返回
+	NextMarker     string             `xml:"NextMarker"`            // 下一次查询的起点
+	Objects        []ObjectProperties `xml:"Contents"`              // Object类别
+	CommonPrefixes []string           `xml:"CommonPrefixes>Prefix"` // 以delimiter结尾并有共同前缀的Object的集合
+}
+
+// ObjectProperties Objecct属性
+type ObjectProperties struct {
+	XMLName      xml.Name  `xml:"Contents"`
+	Key          string    `xml:"Key"`          // Object的Key
+	Type         string    `xml:"Type"`         // Object Type
+	Size         uint      `xml:"Size"`         // Object的长度字节数
+	ETag         string    `xml:"ETag"`         // 标示Object的内容
+	Owner        Owner     `xml:"Owner"`        // 保存Object拥有者信息的容器
+	LastModified time.Time `xml:"LastModified"` // Object最后修改时间
+	StorageClass string    `xml:"StorageClass"` // Object的存储类型,目前只能是Standard
+}
+
+// Owner Bucket/Object的owner
+type Owner struct {
+	XMLName     xml.Name `xml:"Owner"`
+	ID          string   `xml:"ID"`          // 用户ID
+	DisplayName string   `xml:"DisplayName"` // Owner名字
+}
+
+// CopyObjectResult CopyObject请求返回的结果
+type CopyObjectResult struct {
+	XMLName      xml.Name  `xml:"CopyObjectResult"`
+	LastModified time.Time `xml:"LastModified"` // 新Object最后更新时间
+	ETag         string    `xml:"ETag"`         // 新Object的ETag值
+}
+
+// GetObjectACLResult GetObjectACL请求返回的结果
+type GetObjectACLResult GetBucketACLResult
+
+type deleteXML struct {
+	XMLName xml.Name       `xml:"Delete"`
+	Objects []DeleteObject `xml:"Object"` // 删除的所有Object
+	Quiet   bool           `xml:"Quiet"`  // 安静响应模式
+}
+
+// DeleteObject 删除的Object
+type DeleteObject struct {
+	XMLName xml.Name `xml:"Object"`
+	Key     string   `xml:"Key"` // Object名称
+}
+
+// DeleteObjectsResult DeleteObjects请求返回结果
+type DeleteObjectsResult struct {
+	XMLName        xml.Name `xml:"DeleteResult"`
+	DeletedObjects []string `xml:"Deleted>Key"` // 删除的Object列表
+}
+
+// InitiateMultipartUploadResult InitiateMultipartUpload请求返回结果
+type InitiateMultipartUploadResult struct {
+	XMLName  xml.Name `xml:"InitiateMultipartUploadResult"`
+	Bucket   string   `xml:"Bucket"`   // Bucket名称
+	Key      string   `xml:"Key"`      // 上传Object名称
+	UploadID string   `xml:"UploadId"` // 生成的UploadId
+}
+
+// UploadPart 上传/拷贝的分片
+type UploadPart struct {
+	XMLName    xml.Name `xml:"Part"`
+	PartNumber int      `xml:"PartNumber"` // Part编号
+	ETag       string   `xml:"ETag"`       // ETag缓存码
+}
+
+type uploadParts []UploadPart
+
+func (slice uploadParts) Len() int {
+	return len(slice)
+}
+
+func (slice uploadParts) Less(i, j int) bool {
+	return slice[i].PartNumber < slice[j].PartNumber
+}
+
+func (slice uploadParts) Swap(i, j int) {
+	slice[i], slice[j] = slice[j], slice[i]
+}
+
+// UploadPartCopyResult 拷贝分片请求返回的结果
+type UploadPartCopyResult struct {
+	XMLName      xml.Name  `xml:"CopyPartResult"`
+	LastModified time.Time `xml:"LastModified"` // 最后修改时间
+	ETag         string    `xml:"ETag"`         // ETag
+}
+
+type completeMultipartUploadXML struct {
+	XMLName xml.Name     `xml:"CompleteMultipartUpload"`
+	Part    []UploadPart `xml:"Part"`
+}
+
+// CompleteMultipartUploadResult 提交分片上传任务返回结果
+type CompleteMultipartUploadResult struct {
+	XMLName  xml.Name `xml:"CompleteMultipartUploadResult"`
+	Location string   `xml:"Location"` // Object的URL
+	Bucket   string   `xml:"Bucket"`   // Bucket名称
+	ETag     string   `xml:"ETag"`     // Object的ETag
+	Key      string   `xml:"Key"`      // Object的名字
+}
+
+// ListUploadedPartsResult ListUploadedParts请求返回结果
+type ListUploadedPartsResult struct {
+	XMLName              xml.Name       `xml:"ListPartsResult"`
+	Bucket               string         `xml:"Bucket"`               // Bucket名称
+	Key                  string         `xml:"Key"`                  // Object名称
+	UploadID             string         `xml:"UploadId"`             // 上传Id
+	NextPartNumberMarker string         `xml:"NextPartNumberMarker"` // 下一个Part的位置
+	MaxParts             int            `xml:"MaxParts"`             // 最大Part个数
+	IsTruncated          bool           `xml:"IsTruncated"`          // 是否完全上传完成
+	UploadedParts        []UploadedPart `xml:"Part"`                 // 已完成的Part
+}
+
+// UploadedPart 该任务已经上传的分片
+type UploadedPart struct {
+	XMLName      xml.Name  `xml:"Part"`
+	PartNumber   int       `xml:"PartNumber"`   // Part编号
+	LastModified time.Time `xml:"LastModified"` // 最后一次修改时间
+	ETag         string    `xml:"ETag"`         // ETag缓存码
+	Size         int       `xml:"Size"`         // Part大小
+}
+
+// ListMultipartUploadResult ListMultipartUpload请求返回结果
+type ListMultipartUploadResult struct {
+	XMLName            xml.Name            `xml:"ListMultipartUploadsResult"`
+	Bucket             string              `xml:"Bucket"`                // Bucket名称
+	Delimiter          string              `xml:"Delimiter"`             // 分组分割符
+	Prefix             string              `xml:"Prefix"`                // 筛选前缀
+	KeyMarker          string              `xml:"KeyMarker"`             // 起始Object位置
+	UploadIDMarker     string              `xml:"UploadIdMarker"`        // 起始UploadId位置
+	NextKeyMarker      string              `xml:"NextKeyMarker"`         // 如果没有全部返回,标明接下去的KeyMarker位置
+	NextUploadIDMarker string              `xml:"NextUploadIdMarker"`    // 如果没有全部返回,标明接下去的UploadId位置
+	MaxUploads         int                 `xml:"MaxUploads"`            // 返回最大Upload数目
+	IsTruncated        bool                `xml:"IsTruncated"`           // 是否完全返回
+	Uploads            []UncompletedUpload `xml:"Upload"`                // 未完成上传的MultipartUpload
+	CommonPrefixes     []string            `xml:"CommonPrefixes>Prefix"` // 所有名字包含指定的前缀且第一次出现delimiter字符之间的object作为一组的分组结果
+}
+
+// UncompletedUpload 未完成的Upload任务
+type UncompletedUpload struct {
+	XMLName   xml.Name  `xml:"Upload"`
+	Key       string    `xml:"Key"`       // Object名称
+	UploadID  string    `xml:"UploadId"`  // 对应UploadId
+	Initiated time.Time `xml:"Initiated"` // 初始化时间,格式2012-02-23T04:18:23.000Z
+}
+
+// 解析URL编码
+func decodeDeleteObjectsResult(result *DeleteObjectsResult) error {
+	var err error
+	for i := 0; i < len(result.DeletedObjects); i++ {
+		result.DeletedObjects[i], err = url.QueryUnescape(result.DeletedObjects[i])
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// 解析URL编码
+func decodeListObjectsResult(result *ListObjectsResult) error {
+	var err error
+	result.Prefix, err = url.QueryUnescape(result.Prefix)
+	if err != nil {
+		return err
+	}
+	result.Marker, err = url.QueryUnescape(result.Marker)
+	if err != nil {
+		return err
+	}
+	result.Delimiter, err = url.QueryUnescape(result.Delimiter)
+	if err != nil {
+		return err
+	}
+	result.NextMarker, err = url.QueryUnescape(result.NextMarker)
+	if err != nil {
+		return err
+	}
+	for i := 0; i < len(result.Objects); i++ {
+		result.Objects[i].Key, err = url.QueryUnescape(result.Objects[i].Key)
+		if err != nil {
+			return err
+		}
+	}
+	for i := 0; i < len(result.CommonPrefixes); i++ {
+		result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// 解析URL编码
+func decodeListMultipartUploadResult(result *ListMultipartUploadResult) error {
+	var err error
+	result.Prefix, err = url.QueryUnescape(result.Prefix)
+	if err != nil {
+		return err
+	}
+	result.Delimiter, err = url.QueryUnescape(result.Delimiter)
+	if err != nil {
+		return err
+	}
+	result.KeyMarker, err = url.QueryUnescape(result.KeyMarker)
+	if err != nil {
+		return err
+	}
+	result.NextKeyMarker, err = url.QueryUnescape(result.NextKeyMarker)
+	if err != nil {
+		return err
+	}
+	for i := 0; i < len(result.Uploads); i++ {
+		result.Uploads[i].Key, err = url.QueryUnescape(result.Uploads[i].Key)
+		if err != nil {
+			return err
+		}
+	}
+	for i := 0; i < len(result.CommonPrefixes); i++ {
+		result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 127 - 0
oss/type_test.go

@@ -0,0 +1,127 @@
+package oss
+
+import (
+	"net/url"
+	"sort"
+
+	. "gopkg.in/check.v1"
+)
+
+type OssTypeSuite struct{}
+
+var _ = Suite(&OssTypeSuite{})
+
+var (
+	goStr     = "go go + go <> go"
+	chnStr    = "试问闲情几许"
+	goURLStr  = url.QueryEscape(goStr)
+	chnURLStr = url.QueryEscape(chnStr)
+)
+
+func (s *OssTypeSuite) TestConvLifecycleRule(c *C) {
+	r1 := BuildLifecycleRuleByDate("id1", "one", true, 2015, 11, 11)
+	r2 := BuildLifecycleRuleByDays("id2", "two", false, 3)
+
+	rs := convLifecycleRule([]LifecycleRule{r1})
+	c.Assert(rs[0].ID, Equals, "id1")
+	c.Assert(rs[0].Prefix, Equals, "one")
+	c.Assert(rs[0].Status, Equals, "Enabled")
+	c.Assert(rs[0].Expiration.Date, Equals, "2015-11-11T00:00:00.000Z")
+	c.Assert(rs[0].Expiration.Days, Equals, 0)
+
+	rs = convLifecycleRule([]LifecycleRule{r2})
+	c.Assert(rs[0].ID, Equals, "id2")
+	c.Assert(rs[0].Prefix, Equals, "two")
+	c.Assert(rs[0].Status, Equals, "Disabled")
+	c.Assert(rs[0].Expiration.Date, Equals, "")
+	c.Assert(rs[0].Expiration.Days, Equals, 3)
+}
+
+func (s *OssTypeSuite) TestDecodeDeleteObjectsResult(c *C) {
+	var res DeleteObjectsResult
+	err := decodeDeleteObjectsResult(&res)
+	c.Assert(err, IsNil)
+
+	res.DeletedObjects = []string{""}
+	err = decodeDeleteObjectsResult(&res)
+	c.Assert(err, IsNil)
+	c.Assert(res.DeletedObjects[0], Equals, "")
+
+	res.DeletedObjects = []string{goURLStr, chnURLStr}
+	err = decodeDeleteObjectsResult(&res)
+	c.Assert(err, IsNil)
+	c.Assert(res.DeletedObjects[0], Equals, goStr)
+	c.Assert(res.DeletedObjects[1], Equals, chnStr)
+}
+
+func (s *OssTypeSuite) TestDecodeListObjectsResult(c *C) {
+	var res ListObjectsResult
+	err := decodeListObjectsResult(&res)
+	c.Assert(err, IsNil)
+
+	res = ListObjectsResult{}
+	err = decodeListObjectsResult(&res)
+	c.Assert(err, IsNil)
+
+	res = ListObjectsResult{Prefix: goURLStr, Marker: goURLStr,
+		Delimiter: goURLStr, NextMarker: goURLStr,
+		Objects:        []ObjectProperties{{Key: chnURLStr}},
+		CommonPrefixes: []string{chnURLStr}}
+
+	err = decodeListObjectsResult(&res)
+	c.Assert(err, IsNil)
+
+	c.Assert(res.Prefix, Equals, goStr)
+	c.Assert(res.Marker, Equals, goStr)
+	c.Assert(res.Delimiter, Equals, goStr)
+	c.Assert(res.NextMarker, Equals, goStr)
+	c.Assert(res.Objects[0].Key, Equals, chnStr)
+	c.Assert(res.CommonPrefixes[0], Equals, chnStr)
+}
+
+func (s *OssTypeSuite) TestDecodeListMultipartUploadResult(c *C) {
+	res := ListMultipartUploadResult{}
+	err := decodeListMultipartUploadResult(&res)
+	c.Assert(err, IsNil)
+
+	res = ListMultipartUploadResult{Prefix: goURLStr, KeyMarker: goURLStr,
+		Delimiter: goURLStr, NextKeyMarker: goURLStr,
+		Uploads: []UncompletedUpload{{Key: chnURLStr}}}
+
+	err = decodeListMultipartUploadResult(&res)
+	c.Assert(err, IsNil)
+
+	c.Assert(res.Prefix, Equals, goStr)
+	c.Assert(res.KeyMarker, Equals, goStr)
+	c.Assert(res.Delimiter, Equals, goStr)
+	c.Assert(res.NextKeyMarker, Equals, goStr)
+	c.Assert(res.Uploads[0].Key, Equals, chnStr)
+}
+
+func (s *OssTypeSuite) TestSortUploadPart(c *C) {
+	parts := []UploadPart{}
+
+	sort.Sort(uploadParts(parts))
+	c.Assert(len(parts), Equals, 0)
+
+	parts = []UploadPart{
+		{PartNumber: 5, ETag: "E5"},
+		{PartNumber: 1, ETag: "E1"},
+		{PartNumber: 4, ETag: "E4"},
+		{PartNumber: 2, ETag: "E2"},
+		{PartNumber: 3, ETag: "E3"},
+	}
+
+	sort.Sort(uploadParts(parts))
+
+	c.Assert(parts[0].PartNumber, Equals, 1)
+	c.Assert(parts[0].ETag, Equals, "E1")
+	c.Assert(parts[1].PartNumber, Equals, 2)
+	c.Assert(parts[1].ETag, Equals, "E2")
+	c.Assert(parts[2].PartNumber, Equals, 3)
+	c.Assert(parts[2].ETag, Equals, "E3")
+	c.Assert(parts[3].PartNumber, Equals, 4)
+	c.Assert(parts[3].ETag, Equals, "E4")
+	c.Assert(parts[4].PartNumber, Equals, 5)
+	c.Assert(parts[4].ETag, Equals, "E5")
+}

+ 159 - 0
oss/utils.go

@@ -0,0 +1,159 @@
+package oss
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"net/http"
+	"os"
+	"os/exec"
+	"runtime"
+	"time"
+)
+
+// Get User Agent
+// Go sdk相关信息,包括sdk版本,操作系统类型,GO版本
+var userAgent = func() string {
+	sys := getSysInfo()
+	return fmt.Sprintf("aliyun-sdk-go/%s (%s/%s/%s;%s)", Version, sys.name,
+		sys.release, sys.machine, runtime.Version())
+}()
+
+type sysInfo struct {
+	name    string // 操作系统名称windows/Linux
+	release string // 操作系统版本 2.6.32-220.23.2.ali1089.el5.x86_64等
+	machine string // 机器类型amd64/x86_64
+}
+
+// Get system info
+// 获取操作系统信息、机器类型
+func getSysInfo() sysInfo {
+	name := runtime.GOOS
+	release := "-"
+	machine := runtime.GOARCH
+	if out, err := exec.Command("uname", "-s").CombinedOutput(); err == nil {
+		name = string(bytes.TrimSpace(out))
+	}
+	if out, err := exec.Command("uname", "-r").CombinedOutput(); err == nil {
+		release = string(bytes.TrimSpace(out))
+	}
+	if out, err := exec.Command("uname", "-m").CombinedOutput(); err == nil {
+		machine = string(bytes.TrimSpace(out))
+	}
+	return sysInfo{name: name, release: release, machine: machine}
+}
+
+// GetNowSec returns Unix time, the number of seconds elapsed since January 1, 1970 UTC.
+// 获取当前时间,从UTC开始的秒数。
+func GetNowSec() int64 {
+	return time.Now().Unix()
+}
+
+// GetNowNanoSec returns t as a Unix time, the number of nanoseconds elapsed
+// since January 1, 1970 UTC. The result is undefined if the Unix time
+// in nanoseconds cannot be represented by an int64. Note that this
+// means the result of calling UnixNano on the zero Time is undefined.
+// 获取当前时间,从UTC开始的纳秒。
+func GetNowNanoSec() int64 {
+	return time.Now().UnixNano()
+}
+
+// GetNowGMT 获取当前时间,格式形如"Mon, 02 Jan 2006 15:04:05 GMT",HTTP中使用的时间格式
+func GetNowGMT() string {
+	return time.Now().UTC().Format(http.TimeFormat)
+}
+
+// FileChunk 文件片定义
+type FileChunk struct {
+	Number int   // 块序号
+	Offset int64 // 块在文件中的偏移量
+	Size   int64 // 块大小
+}
+
+// SplitFileByPartNum Split big file to part by the num of part
+// 按指定的块数分割文件。返回值FileChunk为分割结果,error为nil时有效。
+func SplitFileByPartNum(fileName string, chunkNum int) ([]FileChunk, error) {
+	if chunkNum <= 0 || chunkNum > 10000 {
+		return nil, errors.New("chunkNum invalid")
+	}
+
+	file, err := os.Open(fileName)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+
+	stat, err := file.Stat()
+	if err != nil {
+		return nil, err
+	}
+
+	if int64(chunkNum) > stat.Size() {
+		return nil, errors.New("oss: chunkNum invalid")
+	}
+
+	var chunks []FileChunk
+	var chunk = FileChunk{}
+	var chunkN = (int64)(chunkNum)
+	for i := int64(0); i < chunkN; i++ {
+		chunk.Number = int(i + 1)
+		chunk.Offset = i * (stat.Size() / chunkN)
+		if i == chunkN-1 {
+			chunk.Size = stat.Size()/chunkN + stat.Size()%chunkN
+		} else {
+			chunk.Size = stat.Size() / chunkN
+		}
+		chunks = append(chunks, chunk)
+	}
+
+	return chunks, nil
+}
+
+// SplitFileByPartSize Split big file to part by the size of part
+// 按块大小分割文件。返回值FileChunk为分割结果,error为nil时有效。
+func SplitFileByPartSize(fileName string, chunkSize int64) ([]FileChunk, error) {
+	if chunkSize <= 0 {
+		return nil, errors.New("chunkSize invalid")
+	}
+
+	file, err := os.Open(fileName)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+
+	stat, err := file.Stat()
+	if err != nil {
+		return nil, err
+	}
+	var chunkN = stat.Size() / chunkSize
+	if chunkN >= 10000 {
+		return nil, errors.New("Too many parts, please increase part size.")
+	}
+
+	var chunks []FileChunk
+	var chunk = FileChunk{}
+	for i := int64(0); i < chunkN; i++ {
+		chunk.Number = int(i + 1)
+		chunk.Offset = i * chunkSize
+		chunk.Size = chunkSize
+		chunks = append(chunks, chunk)
+	}
+
+	if stat.Size()%chunkSize > 0 {
+		chunk.Number = len(chunks) + 1
+		chunk.Offset = int64(len(chunks)) * chunkSize
+		chunk.Size = stat.Size() % chunkSize
+		chunks = append(chunks, chunk)
+	}
+
+	return chunks, nil
+}
+
+// GetPartEnd 计算结束位置
+func GetPartEnd(begin int64, total int64, per int64) int64 {
+	if begin+per > total {
+		return total - 1
+	}
+	return begin + per - 1
+}

+ 106 - 0
oss/utils_test.go

@@ -0,0 +1,106 @@
+package oss
+
+import (
+	"fmt"
+	. "gopkg.in/check.v1"
+)
+
+type OssUtilsSuite struct{}
+
+var _ = Suite(&OssUtilsSuite{})
+
+func (s *OssUtilsSuite) TestUtilsTime(c *C) {
+	c.Assert(GetNowSec() > 1448597674, Equals, true)
+	c.Assert(GetNowNanoSec() > 1448597674000000000, Equals, true)
+	c.Assert(len(GetNowGMT()), Equals, len("Fri, 27 Nov 2015 04:14:34 GMT"))
+}
+
+func (s *OssUtilsSuite) TestUtilsSplitFile(c *C) {
+	localFile := "../sample/BingWallpaper-2015-11-07.jpg"
+
+	// Num
+	parts, err := SplitFileByPartNum(localFile, 4)
+	c.Assert(err, IsNil)
+	c.Assert(len(parts), Equals, 4)
+	fmt.Println("parts 4:", parts)
+	for i, part := range parts {
+		c.Assert(part.Number, Equals, i+1)
+		c.Assert(part.Offset, Equals, int64(i*120512))
+		c.Assert(part.Size, Equals, int64(120512))
+	}
+
+	parts, err = SplitFileByPartNum(localFile, 5)
+	c.Assert(err, IsNil)
+	c.Assert(len(parts), Equals, 5)
+	fmt.Println("parts 5:", parts)
+	for i, part := range parts {
+		c.Assert(part.Number, Equals, i+1)
+		c.Assert(part.Offset, Equals, int64(i*96409))
+	}
+
+	_, err = SplitFileByPartNum(localFile, 10001)
+	c.Assert(err, NotNil)
+
+	_, err = SplitFileByPartNum(localFile, 0)
+	c.Assert(err, NotNil)
+
+	_, err = SplitFileByPartNum(localFile, -1)
+	c.Assert(err, NotNil)
+
+	_, err = SplitFileByPartNum("notexist", 1024)
+	c.Assert(err, NotNil)
+
+	// Size
+	parts, err = SplitFileByPartSize(localFile, 120512)
+	c.Assert(err, IsNil)
+	c.Assert(len(parts), Equals, 4)
+	fmt.Println("parts 4:", parts)
+	for i, part := range parts {
+		c.Assert(part.Number, Equals, i+1)
+		c.Assert(part.Offset, Equals, int64(i*120512))
+		c.Assert(part.Size, Equals, int64(120512))
+	}
+
+	parts, err = SplitFileByPartSize(localFile, 96409)
+	c.Assert(err, IsNil)
+	c.Assert(len(parts), Equals, 6)
+	fmt.Println("parts 6:", parts)
+	for i, part := range parts {
+		c.Assert(part.Number, Equals, i+1)
+		c.Assert(part.Offset, Equals, int64(i*96409))
+	}
+
+	_, err = SplitFileByPartSize(localFile, 0)
+	c.Assert(err, NotNil)
+
+	_, err = SplitFileByPartSize(localFile, -1)
+	c.Assert(err, NotNil)
+
+	_, err = SplitFileByPartSize(localFile, 10)
+	c.Assert(err, NotNil)
+
+	_, err = SplitFileByPartSize("noexist", 120512)
+	c.Assert(err, NotNil)
+}
+
+func (s *OssUtilsSuite) TestUtilsFileExt(c *C) {
+	c.Assert(TypeByExtension("test.txt"), Equals, "text/plain; charset=utf-8")
+	c.Assert(TypeByExtension("test.jpg"), Equals, "image/jpeg")
+	c.Assert(TypeByExtension("test.pdf"), Equals, "application/pdf")
+	c.Assert(TypeByExtension("test"), Equals, "")
+	c.Assert(TypeByExtension("/root/dir/test.txt"), Equals, "text/plain; charset=utf-8")
+	c.Assert(TypeByExtension("root/dir/test.txt"), Equals, "text/plain; charset=utf-8")
+	c.Assert(TypeByExtension("root\\dir\\test.txt"), Equals, "text/plain; charset=utf-8")
+	c.Assert(TypeByExtension("D:\\work\\dir\\test.txt"), Equals, "text/plain; charset=utf-8")
+}
+
+func (s *OssUtilsSuite) TestGetPartEnd(c *C) {
+	end := GetPartEnd(3, 10, 3)
+	c.Assert(end, Equals, int64(5))
+
+	end = GetPartEnd(9, 10, 3)
+	c.Assert(end, Equals, int64(9))
+
+	end = GetPartEnd(7, 10, 3)
+	c.Assert(end, Equals, int64(9))
+}

+ 35 - 0
sample.go

@@ -0,0 +1,35 @@
+// main of samples
+
+package main
+
+import (
+	"fmt"
+	"sample"
+)
+
+func main() {
+	sample.CreateBucketSample()
+	sample.NewBucketSample()
+	sample.ListBucketsSample()
+	sample.BucketACLSample()
+	sample.BucketLifecycleSample()
+	sample.BucketRefererSample()
+	sample.BucketLoggingSample()
+	sample.BucketCORSSample()
+
+	sample.ObjectACLSample()
+	sample.ObjectMetaSample()
+	sample.ListObjectsSample()
+	sample.DeleteObjectSample()
+	sample.AppendObjectSample()
+	sample.CopyObjectSample()
+	sample.PutObjectSample()
+	sample.GetObjectSample()
+
+	sample.MultipartUploadSample()
+	sample.MultipartCopySample()
+
+	sample.CnameSample()
+
+	fmt.Println("All samples completed")
+}

BIN
sample/BingWallpaper-2015-11-07.jpg


+ 3663 - 0
sample/The Go Programming Language.html

@@ -0,0 +1,3663 @@
+<!DOCTYPE html>
+<!-- saved from url=(0032)https://golang.org/pkg/net/http/ -->
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="theme-color" content="#375EAB">
+
+  <title>http - The Go Programming Language</title>
+
+<link type="text/css" rel="stylesheet" href="./http - The Go Programming Language_files/style.css">
+
+<link rel="search" type="application/opensearchdescription+xml" title="godoc" href="https://golang.org/opensearch.xml">
+
+<link rel="stylesheet" href="./http - The Go Programming Language_files/jquery.treeview.css">
+<script type="text/javascript" async="" src="./http - The Go Programming Language_files/ga.js"></script><script type="text/javascript">window.initFuncs = [];</script>
+<script type="text/javascript">
+var _gaq = _gaq || [];
+_gaq.push(["_setAccount", "UA-11222381-2"]);
+_gaq.push(["b._setAccount", "UA-49880327-6"]);
+window.trackPageview = function() {
+  _gaq.push(["_trackPageview", location.pathname+location.hash]);
+  _gaq.push(["b._trackPageview", location.pathname+location.hash]);
+};
+window.trackPageview();
+window.trackEvent = function(category, action, opt_label, opt_value, opt_noninteraction) {
+  _gaq.push(["_trackEvent", category, action, opt_label, opt_value, opt_noninteraction]);
+  _gaq.push(["b._trackEvent", category, action, opt_label, opt_value, opt_noninteraction]);
+};
+</script>
+</head>
+<body>
+
+<div id="lowframe" style="position: fixed; bottom: 0; left: 0; height: 0; width: 100%; border-top: thin solid grey; background-color: white; overflow: auto;">
+...
+</div><!-- #lowframe -->
+
+<div id="topbar" class="wide"><div class="container">
+<div class="top-heading" id="heading-wide"><a href="https://golang.org/">The Go Programming Language</a></div>
+<div class="top-heading" id="heading-narrow"><a href="https://golang.org/">Go</a></div>
+<a href="https://golang.org/pkg/net/http/#" id="menu-button"><span id="menu-button-arrow">▽</span></a>
+<form method="GET" action="https://golang.org/search">
+<div id="menu" style="min-width: 60px;">
+<a href="https://golang.org/doc/">Documents</a>
+<a href="https://golang.org/pkg/">Packages</a>
+<a href="https://golang.org/project/">The Project</a>
+<a href="https://golang.org/help/">Help</a>
+<a href="https://golang.org/blog/">Blog</a>
+
+<a id="playgroundButton" href="http://play.golang.org/" title="Show Go Playground" style="display: inline;">Play</a>
+
+<input type="text" id="search" name="q" class="inactive" value="Search" placeholder="Search">
+</div>
+</form>
+
+</div></div>
+
+
+<div id="playground" class="play">
+	<div class="input"><textarea class="code">package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hello, 世界")
+}</textarea></div>
+	<div class="output"></div>
+	<div class="buttons">
+		<a class="run" title="Run this code [shift-enter]">Run</a>
+		<a class="fmt" title="Format this code">Format</a>
+		
+	</div>
+</div>
+
+
+<div id="page" class="wide" tabindex="-1" style="outline: 0px;">
+<div class="container">
+
+
+  <h1>Package http</h1>
+
+
+
+
+<div id="nav"></div>
+
+
+<!--
+	Copyright 2009 The Go Authors. All rights reserved.
+	Use of this source code is governed by a BSD-style
+	license that can be found in the LICENSE file.
+-->
+<!--
+	Note: Static (i.e., not template-generated) href and id
+	attributes start with "pkg-" to make it impossible for
+	them to conflict with generated attributes (some of which
+	correspond to Go identifiers).
+-->
+
+	<script type="text/javascript">
+	document.ANALYSIS_DATA = null;
+	document.CALLGRAPH = null;
+	</script>
+
+	
+		
+		<div id="short-nav">
+			<dl>
+			<dd><code>import "net/http"</code></dd>
+			</dl>
+			<dl>
+			<dd><a href="https://golang.org/pkg/net/http/#pkg-overview" class="overviewLink">Overview</a></dd>
+			<dd><a href="https://golang.org/pkg/net/http/#pkg-index" class="indexLink">Index</a></dd>
+			
+				<dd><a href="https://golang.org/pkg/net/http/#pkg-examples" class="examplesLink">Examples</a></dd>
+			
+			
+				<dd><a href="https://golang.org/pkg/net/http/#pkg-subdirectories">Subdirectories</a></dd>
+			
+			</dl>
+		</div>
+		<!-- The package's Name is printed as title by the top-level template -->
+		<div id="pkg-overview" class="toggleVisible">
+			<div class="collapsed">
+				<h2 class="toggleButton" title="Click to show Overview section">Overview ▹</h2>
+			</div>
+			<div class="expanded">
+				<h2 class="toggleButton" title="Click to hide Overview section">Overview ▾</h2>
+				<p>
+Package http provides HTTP client and server implementations.
+</p>
+<p>
+Get, Head, Post, and PostForm make HTTP (or HTTPS) requests:
+</p>
+<pre>resp, err := http.Get("<a href="http://example.com/">http://example.com/</a>")
+...
+resp, err := http.Post("<a href="http://example.com/upload">http://example.com/upload</a>", "image/jpeg", &amp;buf)
+...
+resp, err := http.PostForm("<a href="http://example.com/form">http://example.com/form</a>",
+	url.Values{"key": {"Value"}, "id": {"123"}})
+</pre>
+<p>
+The client must close the response body when finished with it:
+</p>
+<pre>resp, err := http.Get("<a href="http://example.com/">http://example.com/</a>")
+if err != nil {
+	// handle error
+}
+defer resp.Body.Close()
+body, err := ioutil.ReadAll(resp.Body)
+// ...
+</pre>
+<p>
+For control over HTTP client headers, redirect policy, and other
+settings, create a Client:
+</p>
+<pre>client := &amp;http.Client{
+	CheckRedirect: redirectPolicyFunc,
+}
+
+resp, err := client.Get("<a href="http://example.com/">http://example.com</a>")
+// ...
+
+req, err := http.NewRequest("GET", "<a href="http://example.com/">http://example.com</a>", nil)
+// ...
+req.Header.Add("If-None-Match", `W/"wyzzy"`)
+resp, err := client.Do(req)
+// ...
+</pre>
+<p>
+For control over proxies, TLS configuration, keep-alives,
+compression, and other settings, create a Transport:
+</p>
+<pre>tr := &amp;http.Transport{
+	TLSClientConfig:    &amp;tls.Config{RootCAs: pool},
+	DisableCompression: true,
+}
+client := &amp;http.Client{Transport: tr}
+resp, err := client.Get("<a href="https://example.com/">https://example.com</a>")
+</pre>
+<p>
+Clients and Transports are safe for concurrent use by multiple
+goroutines and for efficiency should only be created once and re-used.
+</p>
+<p>
+ListenAndServe starts an HTTP server with a given address and handler.
+The handler is usually nil, which means to use DefaultServeMux.
+Handle and HandleFunc add handlers to DefaultServeMux:
+</p>
+<pre>http.Handle("/foo", fooHandler)
+
+http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
+	fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
+})
+
+log.Fatal(http.ListenAndServe(":8080", nil))
+</pre>
+<p>
+More control over the server's behavior is available by creating a
+custom Server:
+</p>
+<pre>s := &amp;http.Server{
+	Addr:           ":8080",
+	Handler:        myHandler,
+	ReadTimeout:    10 * time.Second,
+	WriteTimeout:   10 * time.Second,
+	MaxHeaderBytes: 1 &lt;&lt; 20,
+}
+log.Fatal(s.ListenAndServe())
+</pre>
+
+			</div>
+		</div>
+		
+
+		<div id="pkg-index" class="toggleVisible">
+		<div class="collapsed">
+			<h2 class="toggleButton" title="Click to show Index section">Index ▹</h2>
+		</div>
+		<div class="expanded">
+			<h2 class="toggleButton" title="Click to hide Index section">Index ▾</h2>
+
+		<!-- Table of contents for API; must be named manual-nav to turn off auto nav. -->
+			<div id="manual-nav">
+			<dl>
+			
+				<dd><a href="https://golang.org/pkg/net/http/#pkg-constants">Constants</a></dd>
+			
+			
+				<dd><a href="https://golang.org/pkg/net/http/#pkg-variables">Variables</a></dd>
+			
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#CanonicalHeaderKey">func CanonicalHeaderKey(s string) string</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#DetectContentType">func DetectContentType(data []byte) string</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Error">func Error(w ResponseWriter, error string, code int)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Handle">func Handle(pattern string, handler Handler)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#HandleFunc">func HandleFunc(pattern string, handler func(ResponseWriter, *Request))</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ListenAndServe">func ListenAndServe(addr string, handler Handler) error</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ListenAndServeTLS">func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#MaxBytesReader">func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#NotFound">func NotFound(w ResponseWriter, r *Request)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ParseHTTPVersion">func ParseHTTPVersion(vers string) (major, minor int, ok bool)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ParseTime">func ParseTime(text string) (t time.Time, err error)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ProxyFromEnvironment">func ProxyFromEnvironment(req *Request) (*url.URL, error)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ProxyURL">func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Redirect">func Redirect(w ResponseWriter, r *Request, urlStr string, code int)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Serve">func Serve(l net.Listener, handler Handler) error</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ServeContent">func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ServeFile">func ServeFile(w ResponseWriter, r *Request, name string)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#SetCookie">func SetCookie(w ResponseWriter, cookie *Cookie)</a></dd>
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#StatusText">func StatusText(code int) string</a></dd>
+			
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Client">type Client</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.Do">func (c *Client) Do(req *Request) (resp *Response, err error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.Get">func (c *Client) Get(url string) (resp *Response, err error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.Head">func (c *Client) Head(url string) (resp *Response, err error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.Post">func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Client.PostForm">func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#CloseNotifier">type CloseNotifier</a></dd>
+				
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ConnState">type ConnState</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ConnState.String">func (c ConnState) String() string</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Cookie">type Cookie</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Cookie.String">func (c *Cookie) String() string</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#CookieJar">type CookieJar</a></dd>
+				
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Dir">type Dir</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Dir.Open">func (d Dir) Open(name string) (File, error)</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#File">type File</a></dd>
+				
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#FileSystem">type FileSystem</a></dd>
+				
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Flusher">type Flusher</a></dd>
+				
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Handler">type Handler</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#FileServer">func FileServer(root FileSystem) Handler</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#NotFoundHandler">func NotFoundHandler() Handler</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#RedirectHandler">func RedirectHandler(url string, code int) Handler</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#StripPrefix">func StripPrefix(prefix string, h Handler) Handler</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#TimeoutHandler">func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler</a></dd>
+				
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#HandlerFunc">type HandlerFunc</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#HandlerFunc.ServeHTTP">func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Header">type Header</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Add">func (h Header) Add(key, value string)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Del">func (h Header) Del(key string)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Get">func (h Header) Get(key string) string</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Set">func (h Header) Set(key, value string)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.Write">func (h Header) Write(w io.Writer) error</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Header.WriteSubset">func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Hijacker">type Hijacker</a></dd>
+				
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ProtocolError">type ProtocolError</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ProtocolError.Error">func (err *ProtocolError) Error() string</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Request">type Request</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#NewRequest">func NewRequest(method, urlStr string, body io.Reader) (*Request, error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ReadRequest">func ReadRequest(b *bufio.Reader) (req *Request, err error)</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.AddCookie">func (r *Request) AddCookie(c *Cookie)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.BasicAuth">func (r *Request) BasicAuth() (username, password string, ok bool)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.Cookie">func (r *Request) Cookie(name string) (*Cookie, error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.Cookies">func (r *Request) Cookies() []*Cookie</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.FormFile">func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.FormValue">func (r *Request) FormValue(key string) string</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.MultipartReader">func (r *Request) MultipartReader() (*multipart.Reader, error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.ParseForm">func (r *Request) ParseForm() error</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.ParseMultipartForm">func (r *Request) ParseMultipartForm(maxMemory int64) error</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.PostFormValue">func (r *Request) PostFormValue(key string) string</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.ProtoAtLeast">func (r *Request) ProtoAtLeast(major, minor int) bool</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.Referer">func (r *Request) Referer() string</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.SetBasicAuth">func (r *Request) SetBasicAuth(username, password string)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.UserAgent">func (r *Request) UserAgent() string</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.Write">func (r *Request) Write(w io.Writer) error</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Request.WriteProxy">func (r *Request) WriteProxy(w io.Writer) error</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Response">type Response</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Get">func Get(url string) (resp *Response, err error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Head">func Head(url string) (resp *Response, err error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Post">func Post(url string, bodyType string, body io.Reader) (resp *Response, err error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#PostForm">func PostForm(url string, data url.Values) (resp *Response, err error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ReadResponse">func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Response.Cookies">func (r *Response) Cookies() []*Cookie</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Response.Location">func (r *Response) Location() (*url.URL, error)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Response.ProtoAtLeast">func (r *Response) ProtoAtLeast(major, minor int) bool</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Response.Write">func (r *Response) Write(w io.Writer) error</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ResponseWriter">type ResponseWriter</a></dd>
+				
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#RoundTripper">type RoundTripper</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#NewFileTransport">func NewFileTransport(fs FileSystem) RoundTripper</a></dd>
+				
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#ServeMux">type ServeMux</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#NewServeMux">func NewServeMux() *ServeMux</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ServeMux.Handle">func (mux *ServeMux) Handle(pattern string, handler Handler)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ServeMux.HandleFunc">func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ServeMux.Handler">func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#ServeMux.ServeHTTP">func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Server">type Server</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Server.ListenAndServe">func (srv *Server) ListenAndServe() error</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Server.ListenAndServeTLS">func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Server.Serve">func (srv *Server) Serve(l net.Listener) error</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Server.SetKeepAlivesEnabled">func (srv *Server) SetKeepAlivesEnabled(v bool)</a></dd>
+				
+			
+				
+				<dd><a href="https://golang.org/pkg/net/http/#Transport">type Transport</a></dd>
+				
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Transport.CancelRequest">func (t *Transport) CancelRequest(req *Request)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Transport.CloseIdleConnections">func (t *Transport) CloseIdleConnections()</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Transport.RegisterProtocol">func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper)</a></dd>
+				
+					
+					<dd>&nbsp; &nbsp; <a href="https://golang.org/pkg/net/http/#Transport.RoundTrip">func (t *Transport) RoundTrip(req *Request) (resp *Response, err error)</a></dd>
+				
+			
+			
+			</dl>
+			</div><!-- #manual-nav -->
+
+		
+		<div id="pkg-examples">
+			<h4>Examples</h4>
+			<dl>
+			
+			<dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_FileServer">FileServer</a></dd>
+			
+			<dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_FileServer_stripPrefix">FileServer (StripPrefix)</a></dd>
+			
+			<dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_Get">Get</a></dd>
+			
+			<dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_Hijacker">Hijacker</a></dd>
+			
+			<dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_ResponseWriter_trailers">ResponseWriter (Trailers)</a></dd>
+			
+			<dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_ServeMux_Handle">ServeMux.Handle</a></dd>
+			
+			<dd><a class="exampleLink" href="https://golang.org/pkg/net/http/#example_StripPrefix">StripPrefix</a></dd>
+			
+			</dl>
+		</div>
+		
+
+		
+			<h4>Package files</h4>
+			<p>
+			<span style="font-size:90%">
+			
+				<a href="https://golang.org/src/net/http/client.go">client.go</a>
+			
+				<a href="https://golang.org/src/net/http/cookie.go">cookie.go</a>
+			
+				<a href="https://golang.org/src/net/http/doc.go">doc.go</a>
+			
+				<a href="https://golang.org/src/net/http/filetransport.go">filetransport.go</a>
+			
+				<a href="https://golang.org/src/net/http/fs.go">fs.go</a>
+			
+				<a href="https://golang.org/src/net/http/header.go">header.go</a>
+			
+				<a href="https://golang.org/src/net/http/jar.go">jar.go</a>
+			
+				<a href="https://golang.org/src/net/http/lex.go">lex.go</a>
+			
+				<a href="https://golang.org/src/net/http/request.go">request.go</a>
+			
+				<a href="https://golang.org/src/net/http/response.go">response.go</a>
+			
+				<a href="https://golang.org/src/net/http/server.go">server.go</a>
+			
+				<a href="https://golang.org/src/net/http/sniff.go">sniff.go</a>
+			
+				<a href="https://golang.org/src/net/http/status.go">status.go</a>
+			
+				<a href="https://golang.org/src/net/http/transfer.go">transfer.go</a>
+			
+				<a href="https://golang.org/src/net/http/transport.go">transport.go</a>
+			
+			</span>
+			</p>
+		
+		</div><!-- .expanded -->
+		</div><!-- #pkg-index -->
+
+		<div id="pkg-callgraph" class="toggle" style="display: none">
+		<div class="collapsed">
+			<h2 class="toggleButton" title="Click to show Internal Call Graph section">Internal call graph ▹</h2>
+		</div> <!-- .expanded -->
+		<div class="expanded">
+			<h2 class="toggleButton" title="Click to hide Internal Call Graph section">Internal call graph ▾</h2>
+			<p>
+			  In the call graph viewer below, each node
+			  is a function belonging to this package
+			  and its children are the functions it
+			  calls—perhaps dynamically.
+			</p>
+			<p>
+			  The root nodes are the entry points of the
+			  package: functions that may be called from
+			  outside the package.
+			  There may be non-exported or anonymous
+			  functions among them if they are called
+			  dynamically from another package.
+			</p>
+			<p>
+			  Click a node to visit that function's source code.
+			  From there you can visit its callers by
+			  clicking its declaring <code>func</code>
+			  token.
+			</p>
+			<p>
+			  Functions may be omitted if they were
+			  determined to be unreachable in the
+			  particular programs or tests that were
+			  analyzed.
+			</p>
+			<!-- Zero means show all package entry points. -->
+			<ul style="margin-left: 0.5in" id="callgraph-0" class="treeview"></ul>
+		</div>
+		</div> <!-- #pkg-callgraph -->
+
+		
+			<h2 id="pkg-constants">Constants</h2>
+			
+				<pre>const (
+        <span id="StatusContinue">StatusContinue</span>           = 100
+        <span id="StatusSwitchingProtocols">StatusSwitchingProtocols</span> = 101
+
+        <span id="StatusOK">StatusOK</span>                   = 200
+        <span id="StatusCreated">StatusCreated</span>              = 201
+        <span id="StatusAccepted">StatusAccepted</span>             = 202
+        <span id="StatusNonAuthoritativeInfo">StatusNonAuthoritativeInfo</span> = 203
+        <span id="StatusNoContent">StatusNoContent</span>            = 204
+        <span id="StatusResetContent">StatusResetContent</span>         = 205
+        <span id="StatusPartialContent">StatusPartialContent</span>       = 206
+
+        <span id="StatusMultipleChoices">StatusMultipleChoices</span>   = 300
+        <span id="StatusMovedPermanently">StatusMovedPermanently</span>  = 301
+        <span id="StatusFound">StatusFound</span>             = 302
+        <span id="StatusSeeOther">StatusSeeOther</span>          = 303
+        <span id="StatusNotModified">StatusNotModified</span>       = 304
+        <span id="StatusUseProxy">StatusUseProxy</span>          = 305
+        <span id="StatusTemporaryRedirect">StatusTemporaryRedirect</span> = 307
+
+        <span id="StatusBadRequest">StatusBadRequest</span>                   = 400
+        <span id="StatusUnauthorized">StatusUnauthorized</span>                 = 401
+        <span id="StatusPaymentRequired">StatusPaymentRequired</span>              = 402
+        <span id="StatusForbidden">StatusForbidden</span>                    = 403
+        <span id="StatusNotFound">StatusNotFound</span>                     = 404
+        <span id="StatusMethodNotAllowed">StatusMethodNotAllowed</span>             = 405
+        <span id="StatusNotAcceptable">StatusNotAcceptable</span>                = 406
+        <span id="StatusProxyAuthRequired">StatusProxyAuthRequired</span>            = 407
+        <span id="StatusRequestTimeout">StatusRequestTimeout</span>               = 408
+        <span id="StatusConflict">StatusConflict</span>                     = 409
+        <span id="StatusGone">StatusGone</span>                         = 410
+        <span id="StatusLengthRequired">StatusLengthRequired</span>               = 411
+        <span id="StatusPreconditionFailed">StatusPreconditionFailed</span>           = 412
+        <span id="StatusRequestEntityTooLarge">StatusRequestEntityTooLarge</span>        = 413
+        <span id="StatusRequestURITooLong">StatusRequestURITooLong</span>            = 414
+        <span id="StatusUnsupportedMediaType">StatusUnsupportedMediaType</span>         = 415
+        <span id="StatusRequestedRangeNotSatisfiable">StatusRequestedRangeNotSatisfiable</span> = 416
+        <span id="StatusExpectationFailed">StatusExpectationFailed</span>            = 417
+        <span id="StatusTeapot">StatusTeapot</span>                       = 418
+
+        <span id="StatusInternalServerError">StatusInternalServerError</span>     = 500
+        <span id="StatusNotImplemented">StatusNotImplemented</span>          = 501
+        <span id="StatusBadGateway">StatusBadGateway</span>              = 502
+        <span id="StatusServiceUnavailable">StatusServiceUnavailable</span>      = 503
+        <span id="StatusGatewayTimeout">StatusGatewayTimeout</span>          = 504
+        <span id="StatusHTTPVersionNotSupported">StatusHTTPVersionNotSupported</span> = 505
+)</pre>
+				<p>
+HTTP status codes, defined in RFC 2616.
+</p>
+
+			
+				<pre>const <span id="DefaultMaxHeaderBytes">DefaultMaxHeaderBytes</span> = 1 &lt;&lt; 20 <span class="comment">// 1 MB</span>
+</pre>
+				<p>
+DefaultMaxHeaderBytes is the maximum permitted size of the headers
+in an HTTP request.
+This can be overridden by setting Server.MaxHeaderBytes.
+</p>
+
+			
+				<pre>const <span id="DefaultMaxIdleConnsPerHost">DefaultMaxIdleConnsPerHost</span> = 2</pre>
+				<p>
+DefaultMaxIdleConnsPerHost is the default value of Transport's
+MaxIdleConnsPerHost.
+</p>
+
+			
+				<pre>const <span id="TimeFormat">TimeFormat</span> = "Mon, 02 Jan 2006 15:04:05 GMT"</pre>
+				<p>
+TimeFormat is the time format to use with
+time.Parse and time.Time.Format when parsing
+or generating times in HTTP headers.
+It is like time.RFC1123 but hard codes GMT as the time zone.
+</p>
+
+			
+		
+		
+			<h2 id="pkg-variables">Variables</h2>
+			
+				<pre>var (
+        <span id="ErrHeaderTooLong">ErrHeaderTooLong</span>        = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"header too long"}
+        <span id="ErrShortBody">ErrShortBody</span>            = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"entity body too short"}
+        <span id="ErrNotSupported">ErrNotSupported</span>         = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"feature not supported"}
+        <span id="ErrUnexpectedTrailer">ErrUnexpectedTrailer</span>    = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"trailer header without chunked transfer encoding"}
+        <span id="ErrMissingContentLength">ErrMissingContentLength</span> = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"missing ContentLength in HEAD response"}
+        <span id="ErrNotMultipart">ErrNotMultipart</span>         = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"request Content-Type isn't multipart/form-data"}
+        <span id="ErrMissingBoundary">ErrMissingBoundary</span>      = &amp;<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>{"no multipart boundary param in Content-Type"}
+)</pre>
+				
+			
+				<pre>var (
+        <span id="ErrWriteAfterFlush">ErrWriteAfterFlush</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("Conn.Write called after Flush")
+        <span id="ErrBodyNotAllowed">ErrBodyNotAllowed</span>  = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: request method or response status code does not allow body")
+        <span id="ErrHijacked">ErrHijacked</span>        = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("Conn has been hijacked")
+        <span id="ErrContentLength">ErrContentLength</span>   = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("Conn.Write wrote more than the declared Content-Length")
+)</pre>
+				<p>
+Errors introduced by the HTTP server.
+</p>
+
+			
+				<pre>var <span id="DefaultClient">DefaultClient</span> = &amp;<a href="https://golang.org/pkg/net/http/#Client">Client</a>{}</pre>
+				<p>
+DefaultClient is the default Client and is used by Get, Head, and Post.
+</p>
+
+			
+				<pre>var <span id="DefaultServeMux">DefaultServeMux</span> = <a href="https://golang.org/pkg/net/http/#NewServeMux">NewServeMux</a>()</pre>
+				<p>
+DefaultServeMux is the default ServeMux used by Serve.
+</p>
+
+			
+				<pre>var <span id="ErrBodyReadAfterClose">ErrBodyReadAfterClose</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: invalid Read on closed Body")</pre>
+				<p>
+ErrBodyReadAfterClose is returned when reading a Request or Response
+Body after the body has been closed. This typically happens when the body is
+read after an HTTP Handler calls WriteHeader or Write on its
+ResponseWriter.
+</p>
+
+			
+				<pre>var <span id="ErrHandlerTimeout">ErrHandlerTimeout</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: Handler timeout")</pre>
+				<p>
+ErrHandlerTimeout is returned on ResponseWriter Write calls
+in handlers which have timed out.
+</p>
+
+			
+				<pre>var <span id="ErrLineTooLong">ErrLineTooLong</span> = <a href="https://golang.org/pkg/net/http/internal/">internal</a>.<a href="https://golang.org/pkg/net/http/internal/#ErrLineTooLong">ErrLineTooLong</a></pre>
+				<p>
+ErrLineTooLong is returned when reading request or response bodies
+with malformed chunked encoding.
+</p>
+
+			
+				<pre>var <span id="ErrMissingFile">ErrMissingFile</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: no such file")</pre>
+				<p>
+ErrMissingFile is returned by FormFile when the provided file field name
+is either not present in the request or not a file field.
+</p>
+
+			
+				<pre>var <span id="ErrNoCookie">ErrNoCookie</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: named cookie not present")</pre>
+				<p>
+ErrNoCookie is returned by Request's Cookie method when a cookie is not found.
+</p>
+
+			
+				<pre>var <span id="ErrNoLocation">ErrNoLocation</span> = <a href="https://golang.org/pkg/errors/">errors</a>.<a href="https://golang.org/pkg/errors/#New">New</a>("http: no Location header in response")</pre>
+				<p>
+ErrNoLocation is returned by Response's Location method
+when no Location header is present.
+</p>
+
+			
+		
+		
+			
+			
+			<h2 id="CanonicalHeaderKey">func <a href="https://golang.org/src/net/http/header.go?s=4562:4602#L163">CanonicalHeaderKey</a></h2>
+			<pre>func CanonicalHeaderKey(s <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+			<p>
+CanonicalHeaderKey returns the canonical format of the
+header key s.  The canonicalization converts the first
+letter and any letter following a hyphen to upper case;
+the rest are converted to lowercase.  For example, the
+canonical key for "accept-encoding" is "Accept-Encoding".
+If s contains a space or invalid header field bytes, it is
+returned without modifications.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="DetectContentType">func <a href="https://golang.org/src/net/http/sniff.go?s=648:690#L11">DetectContentType</a></h2>
+			<pre>func DetectContentType(data []<a href="https://golang.org/pkg/builtin/#byte">byte</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+			<p>
+DetectContentType implements the algorithm described
+at <a href="http://mimesniff.spec.whatwg.org/">http://mimesniff.spec.whatwg.org/</a> to determine the
+Content-Type of the given data.  It considers at most the
+first 512 bytes of data.  DetectContentType always returns
+a valid MIME type: if it cannot determine a more specific one, it
+returns "application/octet-stream".
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="Error">func <a href="https://golang.org/src/net/http/server.go?s=41562:41614#L1419">Error</a></h2>
+			<pre>func Error(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, error <a href="https://golang.org/pkg/builtin/#string">string</a>, code <a href="https://golang.org/pkg/builtin/#int">int</a>)</pre>
+			<p>
+Error replies to the request with the specified error message and HTTP code.
+The error message should be plain text.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="Handle">func <a href="https://golang.org/src/net/http/server.go?s=51434:51478#L1740">Handle</a></h2>
+			<pre>func Handle(pattern <a href="https://golang.org/pkg/builtin/#string">string</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>)</pre>
+			<p>
+Handle registers the handler for the given pattern
+in the DefaultServeMux.
+The documentation for ServeMux explains how patterns are matched.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="HandleFunc">func <a href="https://golang.org/src/net/http/server.go?s=51688:51759#L1745">HandleFunc</a></h2>
+			<pre>func HandleFunc(pattern <a href="https://golang.org/pkg/builtin/#string">string</a>, handler func(<a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, *<a href="https://golang.org/pkg/net/http/#Request">Request</a>))</pre>
+			<p>
+HandleFunc registers the handler function for the given pattern
+in the DefaultServeMux.
+The documentation for ServeMux explains how patterns are matched.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="ListenAndServe">func <a href="https://golang.org/src/net/http/server.go?s=58314:58369#L1955">ListenAndServe</a></h2>
+			<pre>func ListenAndServe(addr <a href="https://golang.org/pkg/builtin/#string">string</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+			<p>
+ListenAndServe listens on the TCP network address addr
+and then calls Serve with handler to handle requests
+on incoming connections.  Handler is typically nil,
+in which case the DefaultServeMux is used.
+</p>
+<p>
+A trivial example server is:
+</p>
+<pre>package main
+
+import (
+	"io"
+	"net/http"
+	"log"
+)
+
+// hello world, the web server
+func HelloServer(w http.ResponseWriter, req *http.Request) {
+	io.WriteString(w, "hello, world!\n")
+}
+
+func main() {
+	http.HandleFunc("/hello", HelloServer)
+	err := http.ListenAndServe(":12345", nil)
+	if err != nil {
+		log.Fatal("ListenAndServe: ", err)
+	}
+}
+</pre>
+
+			
+			
+
+		
+			
+			
+			<h2 id="ListenAndServeTLS">func <a href="https://golang.org/src/net/http/server.go?s=59426:59517#L1988">ListenAndServeTLS</a></h2>
+			<pre>func ListenAndServeTLS(addr <a href="https://golang.org/pkg/builtin/#string">string</a>, certFile <a href="https://golang.org/pkg/builtin/#string">string</a>, keyFile <a href="https://golang.org/pkg/builtin/#string">string</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+			<p>
+ListenAndServeTLS acts identically to ListenAndServe, except that it
+expects HTTPS connections. Additionally, files containing a certificate and
+matching private key for the server must be provided. If the certificate
+is signed by a certificate authority, the certFile should be the concatenation
+of the server's certificate, any intermediates, and the CA's certificate.
+</p>
+<p>
+A trivial example server is:
+</p>
+<pre>import (
+	"log"
+	"net/http"
+)
+
+func handler(w http.ResponseWriter, req *http.Request) {
+	w.Header().Set("Content-Type", "text/plain")
+	w.Write([]byte("This is an example server.\n"))
+}
+
+func main() {
+	http.HandleFunc("/", handler)
+	log.Printf("About to listen on 10443. Go to <a href="https://127.0.0.1:10443/">https://127.0.0.1:10443/</a>")
+	err := http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+</pre>
+<p>
+One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="MaxBytesReader">func <a href="https://golang.org/src/net/http/request.go?s=22939:23016#L726">MaxBytesReader</a></h2>
+			<pre>func MaxBytesReader(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadCloser">ReadCloser</a>, n <a href="https://golang.org/pkg/builtin/#int64">int64</a>) <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadCloser">ReadCloser</a></pre>
+			<p>
+MaxBytesReader is similar to io.LimitReader but is intended for
+limiting the size of incoming request bodies. In contrast to
+io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a
+non-EOF error for a Read beyond the limit, and closes the
+underlying reader when its Close method is called.
+</p>
+<p>
+MaxBytesReader prevents clients from accidentally or maliciously
+sending a large request and wasting server resources.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="NotFound">func <a href="https://golang.org/src/net/http/server.go?s=41848:41891#L1427">NotFound</a></h2>
+			<pre>func NotFound(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+			<p>
+NotFound replies to the request with an HTTP 404 not found error.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="ParseHTTPVersion">func <a href="https://golang.org/src/net/http/request.go?s=16539:16601#L498">ParseHTTPVersion</a></h2>
+			<pre>func ParseHTTPVersion(vers <a href="https://golang.org/pkg/builtin/#string">string</a>) (major, minor <a href="https://golang.org/pkg/builtin/#int">int</a>, ok <a href="https://golang.org/pkg/builtin/#bool">bool</a>)</pre>
+			<p>
+ParseHTTPVersion parses a HTTP version string.
+"HTTP/1.0" returns (1, 0, true).
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="ParseTime">func <a href="https://golang.org/src/net/http/header.go?s=1908:1960#L69">ParseTime</a></h2>
+			<pre>func ParseTime(text <a href="https://golang.org/pkg/builtin/#string">string</a>) (t <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Time">Time</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+			<p>
+ParseTime parses a time header (such as the Date: header),
+trying each of the three formats allowed by HTTP/1.1:
+TimeFormat, time.RFC850, and time.ANSIC.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="ProxyFromEnvironment">func <a href="https://golang.org/src/net/http/transport.go?s=4805:4862#L126">ProxyFromEnvironment</a></h2>
+			<pre>func ProxyFromEnvironment(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+			<p>
+ProxyFromEnvironment returns the URL of the proxy to use for a
+given request, as indicated by the environment variables
+HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions
+thereof). HTTPS_PROXY takes precedence over HTTP_PROXY for https
+requests.
+</p>
+<p>
+The environment values may be either a complete URL or a
+"host[:port]", in which case the "http" scheme is assumed.
+An error is returned if the value is a different form.
+</p>
+<p>
+A nil URL and nil error are returned if no proxy is defined in the
+environment, or a proxy should not be used for the given request,
+as defined by NO_PROXY.
+</p>
+<p>
+As a special case, if req.URL.Host is "localhost" (with or without
+a port number), then a nil URL and nil error will be returned.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="ProxyURL">func <a href="https://golang.org/src/net/http/transport.go?s=5664:5729#L157">ProxyURL</a></h2>
+			<pre>func ProxyURL(fixedURL *<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>) func(*<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+			<p>
+ProxyURL returns a proxy function (for use in a Transport)
+that always returns the same URL.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="Redirect">func <a href="https://golang.org/src/net/http/server.go?s=42815:42883#L1454">Redirect</a></h2>
+			<pre>func Redirect(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, urlStr <a href="https://golang.org/pkg/builtin/#string">string</a>, code <a href="https://golang.org/pkg/builtin/#int">int</a>)</pre>
+			<p>
+Redirect replies to the request with a redirect to url,
+which may be a path relative to the request path.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="Serve">func <a href="https://golang.org/src/net/http/server.go?s=52072:52121#L1753">Serve</a></h2>
+			<pre>func Serve(l <a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Listener">Listener</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+			<p>
+Serve accepts incoming HTTP connections on the listener l,
+creating a new service goroutine for each.  The service goroutines
+read requests and then call handler to reply to them.
+Handler is typically nil, in which case the DefaultServeMux is used.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="ServeContent">func <a href="https://golang.org/src/net/http/fs.go?s=3535:3639#L107">ServeContent</a></h2>
+			<pre>func ServeContent(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, name <a href="https://golang.org/pkg/builtin/#string">string</a>, modtime <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Time">Time</a>, content <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadSeeker">ReadSeeker</a>)</pre>
+			<p>
+ServeContent replies to the request using the content in the
+provided ReadSeeker.  The main benefit of ServeContent over io.Copy
+is that it handles Range requests properly, sets the MIME type, and
+handles If-Modified-Since requests.
+</p>
+<p>
+If the response's Content-Type header is not set, ServeContent
+first tries to deduce the type from name's file extension and,
+if that fails, falls back to reading the first block of the content
+and passing it to DetectContentType.
+The name is otherwise unused; in particular it can be empty and is
+never sent in the response.
+</p>
+<p>
+If modtime is not the zero time or Unix epoch, ServeContent
+includes it in a Last-Modified header in the response.  If the
+request includes an If-Modified-Since header, ServeContent uses
+modtime to decide whether the content needs to be sent at all.
+</p>
+<p>
+The content's Seek method must work: ServeContent uses
+a seek to the end of the content to determine its size.
+</p>
+<p>
+If the caller has set w's ETag header, ServeContent uses it to
+handle requests using If-Range and If-None-Match.
+</p>
+<p>
+Note that *os.File implements the io.ReadSeeker interface.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="ServeFile">func <a href="https://golang.org/src/net/http/fs.go?s=13941:13998#L443">ServeFile</a></h2>
+			<pre>func ServeFile(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, name <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+			<p>
+ServeFile replies to the request with the contents of the named
+file or directory.
+</p>
+<p>
+As a special case, ServeFile redirects any request where r.URL.Path
+ends in "/index.html" to the same path, without the final
+"index.html". To avoid such redirects either modify the path or
+use ServeContent.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="SetCookie">func <a href="https://golang.org/src/net/http/cookie.go?s=3059:3107#L120">SetCookie</a></h2>
+			<pre>func SetCookie(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, cookie *<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>)</pre>
+			<p>
+SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
+The provided cookie must have a valid Name. Invalid cookies may be
+silently dropped.
+</p>
+
+			
+			
+
+		
+			
+			
+			<h2 id="StatusText">func <a href="https://golang.org/src/net/http/status.go?s=4602:4634#L108">StatusText</a></h2>
+			<pre>func StatusText(code <a href="https://golang.org/pkg/builtin/#int">int</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+			<p>
+StatusText returns a text for the HTTP status code. It returns the empty
+string if the code is unknown.
+</p>
+
+			
+			
+
+		
+		
+			
+			
+			<h2 id="Client">type <a href="https://golang.org/src/net/http/client.go?s=897:2420#L26">Client</a></h2>
+			<pre>type Client struct {
+        <span class="comment">// Transport specifies the mechanism by which individual</span>
+        <span class="comment">// HTTP requests are made.</span>
+        <span class="comment">// If nil, DefaultTransport is used.</span>
+        Transport <a href="https://golang.org/pkg/net/http/#RoundTripper">RoundTripper</a>
+
+        <span class="comment">// CheckRedirect specifies the policy for handling redirects.</span>
+        <span class="comment">// If CheckRedirect is not nil, the client calls it before</span>
+        <span class="comment">// following an HTTP redirect. The arguments req and via are</span>
+        <span class="comment">// the upcoming request and the requests made already, oldest</span>
+        <span class="comment">// first. If CheckRedirect returns an error, the Client's Get</span>
+        <span class="comment">// method returns both the previous Response and</span>
+        <span class="comment">// CheckRedirect's error (wrapped in a url.Error) instead of</span>
+        <span class="comment">// issuing the Request req.</span>
+        <span class="comment">//</span>
+        <span class="comment">// If CheckRedirect is nil, the Client uses its default policy,</span>
+        <span class="comment">// which is to stop after 10 consecutive requests.</span>
+        CheckRedirect func(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, via []*<a href="https://golang.org/pkg/net/http/#Request">Request</a>) <a href="https://golang.org/pkg/builtin/#error">error</a>
+
+        <span class="comment">// Jar specifies the cookie jar.</span>
+        <span class="comment">// If Jar is nil, cookies are not sent in requests and ignored</span>
+        <span class="comment">// in responses.</span>
+        Jar <a href="https://golang.org/pkg/net/http/#CookieJar">CookieJar</a>
+
+        <span class="comment">// Timeout specifies a time limit for requests made by this</span>
+        <span class="comment">// Client. The timeout includes connection time, any</span>
+        <span class="comment">// redirects, and reading the response body. The timer remains</span>
+        <span class="comment">// running after Get, Head, Post, or Do return and will</span>
+        <span class="comment">// interrupt reading of the Response.Body.</span>
+        <span class="comment">//</span>
+        <span class="comment">// A Timeout of zero means no timeout.</span>
+        <span class="comment">//</span>
+        <span class="comment">// The Client's Transport must support the CancelRequest</span>
+        <span class="comment">// method or Client will return errors when attempting to make</span>
+        <span class="comment">// a request with Get, Head, Post, or Do. Client's default</span>
+        <span class="comment">// Transport (DefaultTransport) supports CancelRequest.</span>
+        Timeout <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a>
+}</pre>
+			<p>
+A Client is an HTTP client. Its zero value (DefaultClient) is a
+usable client that uses DefaultTransport.
+</p>
+<p>
+The Client's Transport typically has internal state (cached TCP
+connections), so Clients should be reused instead of created as
+needed. Clients are safe for concurrent use by multiple goroutines.
+</p>
+<p>
+A Client is higher-level than a RoundTripper (such as Transport)
+and additionally handles HTTP details such as cookies and
+redirects.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+				
+				<h3 id="Client.Do">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=6009:6070#L163">Do</a></h3>
+				<pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) Do(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+Do sends an HTTP request and returns an HTTP response, following
+policy (e.g. redirects, cookies, auth) as configured on the client.
+</p>
+<p>
+An error is returned if caused by client policy (such as
+CheckRedirect), or if there was an HTTP protocol error.
+A non-2xx response doesn't cause an error.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+</p>
+<p>
+Callers should close resp.Body when done reading from it. If
+resp.Body is not closed, the Client's underlying RoundTripper
+(typically Transport) may not be able to re-use a persistent TCP
+connection to the server for a subsequent "keep-alive" request.
+</p>
+<p>
+The request Body, if non-nil, will be closed by the underlying
+Transport, even on errors.
+</p>
+<p>
+Generally Get, Post, or PostForm will be used instead of Do.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Client.Get">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=9928:9988#L291">Get</a></h3>
+				<pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) Get(url <a href="https://golang.org/pkg/builtin/#string">string</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+Get issues a GET to the specified URL. If the response is one of the
+following redirect codes, Get follows the redirect after calling the
+Client's CheckRedirect function:
+</p>
+<pre>301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+</pre>
+<p>
+An error is returned if the Client's CheckRedirect function fails
+or if there was an HTTP protocol error. A non-2xx response doesn't
+cause an error.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+To make a request with custom headers, use NewRequest and Client.Do.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Client.Head">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=16276:16337#L512">Head</a></h3>
+				<pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) Head(url <a href="https://golang.org/pkg/builtin/#string">string</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+Head issues a HEAD to the specified URL.  If the response is one of the
+following redirect codes, Head follows the redirect after calling the
+Client's CheckRedirect function:
+</p>
+<pre>301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+</pre>
+
+				
+				
+				
+			
+				
+				<h3 id="Client.Post">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=14197:14291#L455">Post</a></h3>
+				<pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) Post(url <a href="https://golang.org/pkg/builtin/#string">string</a>, bodyType <a href="https://golang.org/pkg/builtin/#string">string</a>, body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Reader">Reader</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+Post issues a POST to the specified URL.
+</p>
+<p>
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+If the provided body is an io.Closer, it is closed after the
+request.
+</p>
+<p>
+To set custom headers, use NewRequest and Client.Do.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Client.PostForm">func (*Client) <a href="https://golang.org/src/net/http/client.go?s=15401:15483#L486">PostForm</a></h3>
+				<pre>func (c *<a href="https://golang.org/pkg/net/http/#Client">Client</a>) PostForm(url <a href="https://golang.org/pkg/builtin/#string">string</a>, data <a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#Values">Values</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+PostForm issues a POST to the specified URL,
+with data's keys and values URL-encoded as the request body.
+</p>
+<p>
+The Content-Type header is set to application/x-www-form-urlencoded.
+To set other headers, use NewRequest and DefaultClient.Do.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+Caller should close resp.Body when done reading from it.
+</p>
+
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="CloseNotifier">type <a href="https://golang.org/src/net/http/server.go?s=3931:4099#L106">CloseNotifier</a></h2>
+			<pre>type CloseNotifier interface {
+        <span class="comment">// CloseNotify returns a channel that receives a single value</span>
+        <span class="comment">// when the client connection has gone away.</span>
+        CloseNotify() &lt;-chan <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+}</pre>
+			<p>
+The CloseNotifier interface is implemented by ResponseWriters which
+allow detecting when the underlying connection has gone away.
+</p>
+<p>
+This mechanism can be used to cancel long operations on the server
+if the client has disconnected before the response is ready.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="ConnState">type <a href="https://golang.org/src/net/http/server.go?s=53915:53933#L1793">ConnState</a></h2>
+			<pre>type ConnState <a href="https://golang.org/pkg/builtin/#int">int</a></pre>
+			<p>
+A ConnState represents the state of a client connection to a server.
+It's used by the optional Server.ConnState hook.
+</p>
+
+
+			
+				<pre>const (
+        <span class="comment">// StateNew represents a new connection that is expected to</span>
+        <span class="comment">// send a request immediately. Connections begin at this</span>
+        <span class="comment">// state and then transition to either StateActive or</span>
+        <span class="comment">// StateClosed.</span>
+        <span id="StateNew">StateNew</span> <a href="https://golang.org/pkg/net/http/#ConnState">ConnState</a> = <a href="https://golang.org/pkg/builtin/#iota">iota</a>
+
+        <span class="comment">// StateActive represents a connection that has read 1 or more</span>
+        <span class="comment">// bytes of a request. The Server.ConnState hook for</span>
+        <span class="comment">// StateActive fires before the request has entered a handler</span>
+        <span class="comment">// and doesn't fire again until the request has been</span>
+        <span class="comment">// handled. After the request is handled, the state</span>
+        <span class="comment">// transitions to StateClosed, StateHijacked, or StateIdle.</span>
+        <span id="StateActive">StateActive</span>
+
+        <span class="comment">// StateIdle represents a connection that has finished</span>
+        <span class="comment">// handling a request and is in the keep-alive state, waiting</span>
+        <span class="comment">// for a new request. Connections transition from StateIdle</span>
+        <span class="comment">// to either StateActive or StateClosed.</span>
+        <span id="StateIdle">StateIdle</span>
+
+        <span class="comment">// StateHijacked represents a hijacked connection.</span>
+        <span class="comment">// This is a terminal state. It does not transition to StateClosed.</span>
+        <span id="StateHijacked">StateHijacked</span>
+
+        <span class="comment">// StateClosed represents a closed connection.</span>
+        <span class="comment">// This is a terminal state. Hijacked connections do not</span>
+        <span class="comment">// transition to StateClosed.</span>
+        <span id="StateClosed">StateClosed</span>
+)</pre>
+				
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+				
+				<h3 id="ConnState.String">func (ConnState) <a href="https://golang.org/src/net/http/server.go?s=55217:55251#L1834">String</a></h3>
+				<pre>func (c <a href="https://golang.org/pkg/net/http/#ConnState">ConnState</a>) String() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+				
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="Cookie">type <a href="https://golang.org/src/net/http/cookie.go?s=439:952#L11">Cookie</a></h2>
+			<pre>type Cookie struct {
+        Name  <a href="https://golang.org/pkg/builtin/#string">string</a>
+        Value <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        Path       <a href="https://golang.org/pkg/builtin/#string">string</a>    <span class="comment">// optional</span>
+        Domain     <a href="https://golang.org/pkg/builtin/#string">string</a>    <span class="comment">// optional</span>
+        Expires    <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Time">Time</a> <span class="comment">// optional</span>
+        RawExpires <a href="https://golang.org/pkg/builtin/#string">string</a>    <span class="comment">// for reading cookies only</span>
+
+        <span class="comment">// MaxAge=0 means no 'Max-Age' attribute specified.</span>
+        <span class="comment">// MaxAge&lt;0 means delete cookie now, equivalently 'Max-Age: 0'</span>
+        <span class="comment">// MaxAge&gt;0 means Max-Age attribute present and given in seconds</span>
+        MaxAge   <a href="https://golang.org/pkg/builtin/#int">int</a>
+        Secure   <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+        HttpOnly <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+        Raw      <a href="https://golang.org/pkg/builtin/#string">string</a>
+        Unparsed []<a href="https://golang.org/pkg/builtin/#string">string</a> <span class="comment">// Raw text of unparsed attribute-value pairs</span>
+}</pre>
+			<p>
+A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
+HTTP response or the Cookie header of an HTTP request.
+</p>
+<p>
+See <a href="http://tools.ietf.org/html/rfc6265">http://tools.ietf.org/html/rfc6265</a> for details.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+				
+				<h3 id="Cookie.String">func (*Cookie) <a href="https://golang.org/src/net/http/cookie.go?s=3428:3460#L130">String</a></h3>
+				<pre>func (c *<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>) String() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+				<p>
+String returns the serialization of the cookie for use in a Cookie
+header (if only Name and Value are set) or a Set-Cookie response
+header (if other fields are set).
+If c is nil or c.Name is invalid, the empty string is returned.
+</p>
+
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="CookieJar">type <a href="https://golang.org/src/net/http/jar.go?s=433:899#L7">CookieJar</a></h2>
+			<pre>type CookieJar interface {
+        <span class="comment">// SetCookies handles the receipt of the cookies in a reply for the</span>
+        <span class="comment">// given URL.  It may or may not choose to save the cookies, depending</span>
+        <span class="comment">// on the jar's policy and implementation.</span>
+        SetCookies(u *<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, cookies []*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>)
+
+        <span class="comment">// Cookies returns the cookies to send in a request for the given URL.</span>
+        <span class="comment">// It is up to the implementation to honor the standard cookie use</span>
+        <span class="comment">// restrictions such as in RFC 6265.</span>
+        Cookies(u *<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>) []*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>
+}</pre>
+			<p>
+A CookieJar manages storage and use of cookies in HTTP requests.
+</p>
+<p>
+Implementations of CookieJar must be safe for concurrent use by multiple
+goroutines.
+</p>
+<p>
+The net/http/cookiejar package provides a CookieJar implementation.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="Dir">type <a href="https://golang.org/src/net/http/fs.go?s=719:734#L23">Dir</a></h2>
+			<pre>type Dir <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+			<p>
+A Dir implements FileSystem using the native file system restricted to a
+specific directory tree.
+</p>
+<p>
+While the FileSystem.Open method takes '/'-separated paths, a Dir's string
+value is a filename on the native file system, not a URL, so it is separated
+by filepath.Separator, which isn't necessarily '/'.
+</p>
+<p>
+An empty Dir is treated as ".".
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+				
+				<h3 id="Dir.Open">func (Dir) <a href="https://golang.org/src/net/http/fs.go?s=736:780#L25">Open</a></h3>
+				<pre>func (d <a href="https://golang.org/pkg/net/http/#Dir">Dir</a>) Open(name <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/net/http/#File">File</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="File">type <a href="https://golang.org/src/net/http/fs.go?s=1591:1755#L52">File</a></h2>
+			<pre>type File interface {
+        <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Closer">Closer</a>
+        <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Reader">Reader</a>
+        Readdir(count <a href="https://golang.org/pkg/builtin/#int">int</a>) ([]<a href="https://golang.org/pkg/os/">os</a>.<a href="https://golang.org/pkg/os/#FileInfo">FileInfo</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+        Seek(offset <a href="https://golang.org/pkg/builtin/#int64">int64</a>, whence <a href="https://golang.org/pkg/builtin/#int">int</a>) (<a href="https://golang.org/pkg/builtin/#int64">int64</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+        Stat() (<a href="https://golang.org/pkg/os/">os</a>.<a href="https://golang.org/pkg/os/#FileInfo">FileInfo</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+}</pre>
+			<p>
+A File is returned by a FileSystem's Open method and can be
+served by the FileServer implementation.
+</p>
+<p>
+The methods should behave the same as those on an *os.File.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="FileSystem">type <a href="https://golang.org/src/net/http/fs.go?s=1354:1416#L44">FileSystem</a></h2>
+			<pre>type FileSystem interface {
+        Open(name <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/net/http/#File">File</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+}</pre>
+			<p>
+A FileSystem implements access to a collection of named files.
+The elements in a file path are separated by slash ('/', U+002F)
+characters, regardless of host operating system convention.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="Flusher">type <a href="https://golang.org/src/net/http/server.go?s=2921:3005#L79">Flusher</a></h2>
+			<pre>type Flusher interface {
+        <span class="comment">// Flush sends any buffered data to the client.</span>
+        Flush()
+}</pre>
+			<p>
+The Flusher interface is implemented by ResponseWriters that allow
+an HTTP handler to flush buffered data to the client.
+</p>
+<p>
+Note that even for ResponseWriters that support Flush,
+if the client is connected through an HTTP proxy,
+the buffered data may not reach the client until the response
+completes.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="Handler">type <a href="https://golang.org/src/net/http/server.go?s=1361:1424#L42">Handler</a></h2>
+			<pre>type Handler interface {
+        ServeHTTP(<a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)
+}</pre>
+			<p>
+Objects implementing the Handler interface can be
+registered to serve a particular path or subtree
+in the HTTP server.
+</p>
+<p>
+ServeHTTP should write reply headers and data to the ResponseWriter
+and then return.  Returning signals that the request is finished
+and that the HTTP server can move on to the next request on
+the connection.
+</p>
+<p>
+If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
+that the effect of the panic was isolated to the active request.
+It recovers the panic, logs a stack trace to the server error log,
+and hangs up the connection.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+				
+				<h3 id="FileServer">func <a href="https://golang.org/src/net/http/fs.go?s=14534:14574#L463">FileServer</a></h3>
+				<pre>func FileServer(root <a href="https://golang.org/pkg/net/http/#FileSystem">FileSystem</a>) <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+				<p>
+FileServer returns a handler that serves HTTP requests
+with the contents of the file system rooted at root.
+</p>
+<p>
+To use the operating system's file system implementation,
+use http.Dir:
+</p>
+<pre>http.Handle("/", http.FileServer(http.Dir("/tmp")))
+</pre>
+<p>
+As a special case, the returned file server redirects any request
+ending in "/index.html" to the same path, without the final
+"index.html".
+</p>
+
+				<div id="example_FileServer" class="toggle">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+		
+		
+		
+			<div class="play">
+				<div class="input"><textarea class="code">package main
+
+import (
+	"log"
+	"net/http"
+)
+
+func main() {
+	// Simple static webserver:
+	log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
+}
+</textarea></div>
+				<div class="output"><pre></pre></div>
+				<div class="buttons">
+					<a class="run" title="Run this code [shift-enter]">Run</a>
+					<a class="fmt" title="Format this code">Format</a>
+					
+				</div>
+			</div>
+		
+	</div>
+</div>
+<div id="example_FileServer_stripPrefix" class="toggle">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Example (StripPrefix)</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Example (StripPrefix)</span></p>
+		
+		
+		
+			<div class="play">
+				<div class="input"><textarea class="code">package main
+
+import (
+	"net/http"
+)
+
+func main() {
+	// To serve a directory on disk (/tmp) under an alternate URL
+	// path (/tmpfiles/), use StripPrefix to modify the request
+	// URL's path before the FileServer sees it:
+	http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
+}
+</textarea></div>
+				<div class="output"><pre></pre></div>
+				<div class="buttons">
+					<a class="run" title="Run this code [shift-enter]">Run</a>
+					<a class="fmt" title="Format this code">Format</a>
+					
+				</div>
+			</div>
+		
+	</div>
+</div>
+
+				
+			
+				
+				<h3 id="NotFoundHandler">func <a href="https://golang.org/src/net/http/server.go?s=42065:42095#L1431">NotFoundHandler</a></h3>
+				<pre>func NotFoundHandler() <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+				<p>
+NotFoundHandler returns a simple request handler
+that replies to each request with a “404 page not found” reply.
+</p>
+
+				
+				
+			
+				
+				<h3 id="RedirectHandler">func <a href="https://golang.org/src/net/http/server.go?s=45249:45299#L1538">RedirectHandler</a></h3>
+				<pre>func RedirectHandler(url <a href="https://golang.org/pkg/builtin/#string">string</a>, code <a href="https://golang.org/pkg/builtin/#int">int</a>) <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+				<p>
+RedirectHandler returns a request handler that redirects
+each request it receives to the given url using the given
+status code.
+</p>
+
+				
+				
+			
+				
+				<h3 id="StripPrefix">func <a href="https://golang.org/src/net/http/server.go?s=42404:42454#L1438">StripPrefix</a></h3>
+				<pre>func StripPrefix(prefix <a href="https://golang.org/pkg/builtin/#string">string</a>, h <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>) <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+				<p>
+StripPrefix returns a handler that serves HTTP requests
+by removing the given prefix from the request URL's Path
+and invoking the handler h. StripPrefix handles a
+request for a path that doesn't begin with prefix by
+replying with an HTTP 404 not found error.
+</p>
+
+				<div id="example_StripPrefix" class="toggle">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+		
+		
+		
+			<div class="play">
+				<div class="input"><textarea class="code">package main
+
+import (
+	"net/http"
+)
+
+func main() {
+	// To serve a directory on disk (/tmp) under an alternate URL
+	// path (/tmpfiles/), use StripPrefix to modify the request
+	// URL's path before the FileServer sees it:
+	http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
+}
+</textarea></div>
+				<div class="output"><pre></pre></div>
+				<div class="buttons">
+					<a class="run" title="Run this code [shift-enter]">Run</a>
+					<a class="fmt" title="Format this code">Format</a>
+					
+				</div>
+			</div>
+		
+	</div>
+</div>
+
+				
+			
+				
+				<h3 id="TimeoutHandler">func <a href="https://golang.org/src/net/http/server.go?s=61275:61343#L2039">TimeoutHandler</a></h3>
+				<pre>func TimeoutHandler(h <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>, dt <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a>, msg <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/net/http/#Handler">Handler</a></pre>
+				<p>
+TimeoutHandler returns a Handler that runs h with the given time limit.
+</p>
+<p>
+The new Handler calls h.ServeHTTP to handle each request, but if a
+call runs for longer than its time limit, the handler responds with
+a 503 Service Unavailable error and the given message in its body.
+(If msg is empty, a suitable default message will be sent.)
+After such a timeout, writes by h to its ResponseWriter will return
+ErrHandlerTimeout.
+</p>
+
+				
+				
+			
+
+			
+		
+			
+			
+			<h2 id="HandlerFunc">type <a href="https://golang.org/src/net/http/server.go?s=41267:41314#L1408">HandlerFunc</a></h2>
+			<pre>type HandlerFunc func(<a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+			<p>
+The HandlerFunc type is an adapter to allow the use of
+ordinary functions as HTTP handlers.  If f is a function
+with the appropriate signature, HandlerFunc(f) is a
+Handler object that calls f.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+				
+				<h3 id="HandlerFunc.ServeHTTP">func (HandlerFunc) <a href="https://golang.org/src/net/http/server.go?s=41344:41404#L1411">ServeHTTP</a></h3>
+				<pre>func (f <a href="https://golang.org/pkg/net/http/#HandlerFunc">HandlerFunc</a>) ServeHTTP(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+				<p>
+ServeHTTP calls f(w, r).
+</p>
+
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="Header">type <a href="https://golang.org/src/net/http/header.go?s=350:381#L9">Header</a></h2>
+			<pre>type Header map[<a href="https://golang.org/pkg/builtin/#string">string</a>][]<a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+			<p>
+A Header represents the key-value pairs in an HTTP header.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+				
+				<h3 id="Header.Add">func (Header) <a href="https://golang.org/src/net/http/header.go?s=488:526#L13">Add</a></h3>
+				<pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Add(key, value <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+				<p>
+Add adds the key, value pair to the header.
+It appends to any existing values associated with key.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Header.Del">func (Header) <a href="https://golang.org/src/net/http/header.go?s=1321:1352#L41">Del</a></h3>
+				<pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Del(key <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+				<p>
+Del deletes the values associated with key.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Header.Get">func (Header) <a href="https://golang.org/src/net/http/header.go?s=1015:1053#L28">Get</a></h3>
+				<pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Get(key <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+				<p>
+Get gets the first value associated with the given key.
+If there are no values associated with the key, Get returns "".
+To access multiple values of a key, access the map directly
+with CanonicalHeaderKey.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Header.Set">func (Header) <a href="https://golang.org/src/net/http/header.go?s=713:751#L20">Set</a></h3>
+				<pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Set(key, value <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+				<p>
+Set sets the header entries associated with key to
+the single element value.  It replaces any existing
+values associated with key.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Header.Write">func (Header) <a href="https://golang.org/src/net/http/header.go?s=1433:1473#L46">Write</a></h3>
+				<pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) Write(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+Write writes a header in wire format.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Header.WriteSubset">func (Header) <a href="https://golang.org/src/net/http/header.go?s=3676:3747#L135">WriteSubset</a></h3>
+				<pre>func (h <a href="https://golang.org/pkg/net/http/#Header">Header</a>) WriteSubset(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>, exclude map[<a href="https://golang.org/pkg/builtin/#string">string</a>]<a href="https://golang.org/pkg/builtin/#bool">bool</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+WriteSubset writes a header in wire format.
+If exclude is not nil, keys where exclude[key] == true are not written.
+</p>
+
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="Hijacker">type <a href="https://golang.org/src/net/http/server.go?s=3126:3656#L86">Hijacker</a></h2>
+			<pre>type Hijacker interface {
+        <span class="comment">// Hijack lets the caller take over the connection.</span>
+        <span class="comment">// After a call to Hijack(), the HTTP server library</span>
+        <span class="comment">// will not do anything else with the connection.</span>
+        <span class="comment">//</span>
+        <span class="comment">// It becomes the caller's responsibility to manage</span>
+        <span class="comment">// and close the connection.</span>
+        <span class="comment">//</span>
+        <span class="comment">// The returned net.Conn may have read or write deadlines</span>
+        <span class="comment">// already set, depending on the configuration of the</span>
+        <span class="comment">// Server. It is the caller's responsibility to set</span>
+        <span class="comment">// or clear those deadlines as needed.</span>
+        Hijack() (<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Conn">Conn</a>, *<a href="https://golang.org/pkg/bufio/">bufio</a>.<a href="https://golang.org/pkg/bufio/#ReadWriter">ReadWriter</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+}</pre>
+			<p>
+The Hijacker interface is implemented by ResponseWriters that allow
+an HTTP handler to take over the connection.
+</p>
+
+
+			
+
+			
+
+			<div id="example_Hijacker" class="toggle">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+		
+		
+		
+			<div class="play">
+				<div class="input"><textarea class="code">package main
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+)
+
+func main() {
+	http.HandleFunc("/hijack", func(w http.ResponseWriter, r *http.Request) {
+		hj, ok := w.(http.Hijacker)
+		if !ok {
+			http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
+			return
+		}
+		conn, bufrw, err := hj.Hijack()
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		// Don't forget to close the connection:
+		defer conn.Close()
+		bufrw.WriteString("Now we're speaking raw TCP. Say hi: ")
+		bufrw.Flush()
+		s, err := bufrw.ReadString('\n')
+		if err != nil {
+			log.Printf("error reading string: %v", err)
+			return
+		}
+		fmt.Fprintf(bufrw, "You said: %q\nBye.\n", s)
+		bufrw.Flush()
+	})
+}
+</textarea></div>
+				<div class="output"><pre></pre></div>
+				<div class="buttons">
+					<a class="run" title="Run this code [shift-enter]">Run</a>
+					<a class="fmt" title="Format this code">Format</a>
+					
+				</div>
+			</div>
+		
+	</div>
+</div>
+
+			
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="ProtocolError">type <a href="https://golang.org/src/net/http/request.go?s=668:717#L26">ProtocolError</a></h2>
+			<pre>type ProtocolError struct {
+        ErrorString <a href="https://golang.org/pkg/builtin/#string">string</a>
+}</pre>
+			<p>
+HTTP request parsing errors.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+				
+				<h3 id="ProtocolError.Error">func (*ProtocolError) <a href="https://golang.org/src/net/http/request.go?s=719:759#L30">Error</a></h3>
+				<pre>func (err *<a href="https://golang.org/pkg/net/http/#ProtocolError">ProtocolError</a>) Error() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+				
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="Request">type <a href="https://golang.org/src/net/http/request.go?s=2057:8270#L64">Request</a></h2>
+			<pre>type Request struct {
+        <span class="comment">// Method specifies the HTTP method (GET, POST, PUT, etc.).</span>
+        <span class="comment">// For client requests an empty string means GET.</span>
+        Method <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// URL specifies either the URI being requested (for server</span>
+        <span class="comment">// requests) or the URL to access (for client requests).</span>
+        <span class="comment">//</span>
+        <span class="comment">// For server requests the URL is parsed from the URI</span>
+        <span class="comment">// supplied on the Request-Line as stored in RequestURI.  For</span>
+        <span class="comment">// most requests, fields other than Path and RawQuery will be</span>
+        <span class="comment">// empty. (See RFC 2616, Section 5.1.2)</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests, the URL's Host specifies the server to</span>
+        <span class="comment">// connect to, while the Request's Host field optionally</span>
+        <span class="comment">// specifies the Host header value to send in the HTTP</span>
+        <span class="comment">// request.</span>
+        URL *<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>
+
+        <span class="comment">// The protocol version for incoming requests.</span>
+        <span class="comment">// Client requests always use HTTP/1.1.</span>
+        Proto      <a href="https://golang.org/pkg/builtin/#string">string</a> <span class="comment">// "HTTP/1.0"</span>
+        ProtoMajor <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// 1</span>
+        ProtoMinor <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// 0</span>
+
+        <span class="comment">// A header maps request lines to their values.</span>
+        <span class="comment">// If the header says</span>
+        <span class="comment">//</span>
+        <span class="comment">//	accept-encoding: gzip, deflate</span>
+        <span class="comment">//	Accept-Language: en-us</span>
+        <span class="comment">//	Connection: keep-alive</span>
+        <span class="comment">//</span>
+        <span class="comment">// then</span>
+        <span class="comment">//</span>
+        <span class="comment">//	Header = map[string][]string{</span>
+        <span class="comment">//		"Accept-Encoding": {"gzip, deflate"},</span>
+        <span class="comment">//		"Accept-Language": {"en-us"},</span>
+        <span class="comment">//		"Connection": {"keep-alive"},</span>
+        <span class="comment">//	}</span>
+        <span class="comment">//</span>
+        <span class="comment">// HTTP defines that header names are case-insensitive.</span>
+        <span class="comment">// The request parser implements this by canonicalizing the</span>
+        <span class="comment">// name, making the first character and any characters</span>
+        <span class="comment">// following a hyphen uppercase and the rest lowercase.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests certain headers are automatically</span>
+        <span class="comment">// added and may override values in Header.</span>
+        <span class="comment">//</span>
+        <span class="comment">// See the documentation for the Request.Write method.</span>
+        Header <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// Body is the request's body.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests a nil body means the request has no</span>
+        <span class="comment">// body, such as a GET request. The HTTP Client's Transport</span>
+        <span class="comment">// is responsible for calling the Close method.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For server requests the Request Body is always non-nil</span>
+        <span class="comment">// but will return EOF immediately when no body is present.</span>
+        <span class="comment">// The Server will close the request body. The ServeHTTP</span>
+        <span class="comment">// Handler does not need to.</span>
+        Body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadCloser">ReadCloser</a>
+
+        <span class="comment">// ContentLength records the length of the associated content.</span>
+        <span class="comment">// The value -1 indicates that the length is unknown.</span>
+        <span class="comment">// Values &gt;= 0 indicate that the given number of bytes may</span>
+        <span class="comment">// be read from Body.</span>
+        <span class="comment">// For client requests, a value of 0 means unknown if Body is not nil.</span>
+        ContentLength <a href="https://golang.org/pkg/builtin/#int64">int64</a>
+
+        <span class="comment">// TransferEncoding lists the transfer encodings from outermost to</span>
+        <span class="comment">// innermost. An empty list denotes the "identity" encoding.</span>
+        <span class="comment">// TransferEncoding can usually be ignored; chunked encoding is</span>
+        <span class="comment">// automatically added and removed as necessary when sending and</span>
+        <span class="comment">// receiving requests.</span>
+        TransferEncoding []<a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// Close indicates whether to close the connection after</span>
+        <span class="comment">// replying to this request (for servers) or after sending</span>
+        <span class="comment">// the request (for clients).</span>
+        Close <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+
+        <span class="comment">// For server requests Host specifies the host on which the</span>
+        <span class="comment">// URL is sought. Per RFC 2616, this is either the value of</span>
+        <span class="comment">// the "Host" header or the host name given in the URL itself.</span>
+        <span class="comment">// It may be of the form "host:port".</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests Host optionally overrides the Host</span>
+        <span class="comment">// header to send. If empty, the Request.Write method uses</span>
+        <span class="comment">// the value of URL.Host.</span>
+        Host <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// Form contains the parsed form data, including both the URL</span>
+        <span class="comment">// field's query parameters and the POST or PUT form data.</span>
+        <span class="comment">// This field is only available after ParseForm is called.</span>
+        <span class="comment">// The HTTP client ignores Form and uses Body instead.</span>
+        Form <a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#Values">Values</a>
+
+        <span class="comment">// PostForm contains the parsed form data from POST, PATCH,</span>
+        <span class="comment">// or PUT body parameters.</span>
+        <span class="comment">//</span>
+        <span class="comment">// This field is only available after ParseForm is called.</span>
+        <span class="comment">// The HTTP client ignores PostForm and uses Body instead.</span>
+        PostForm <a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#Values">Values</a>
+
+        <span class="comment">// MultipartForm is the parsed multipart form, including file uploads.</span>
+        <span class="comment">// This field is only available after ParseMultipartForm is called.</span>
+        <span class="comment">// The HTTP client ignores MultipartForm and uses Body instead.</span>
+        MultipartForm *<a href="https://golang.org/pkg/mime/multipart/">multipart</a>.<a href="https://golang.org/pkg/mime/multipart/#Form">Form</a>
+
+        <span class="comment">// Trailer specifies additional headers that are sent after the request</span>
+        <span class="comment">// body.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For server requests the Trailer map initially contains only the</span>
+        <span class="comment">// trailer keys, with nil values. (The client declares which trailers it</span>
+        <span class="comment">// will later send.)  While the handler is reading from Body, it must</span>
+        <span class="comment">// not reference Trailer. After reading from Body returns EOF, Trailer</span>
+        <span class="comment">// can be read again and will contain non-nil values, if they were sent</span>
+        <span class="comment">// by the client.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For client requests Trailer must be initialized to a map containing</span>
+        <span class="comment">// the trailer keys to later send. The values may be nil or their final</span>
+        <span class="comment">// values. The ContentLength must be 0 or -1, to send a chunked request.</span>
+        <span class="comment">// After the HTTP request is sent the map values can be updated while</span>
+        <span class="comment">// the request body is read. Once the body returns EOF, the caller must</span>
+        <span class="comment">// not mutate Trailer.</span>
+        <span class="comment">//</span>
+        <span class="comment">// Few HTTP clients, servers, or proxies support HTTP trailers.</span>
+        Trailer <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// RemoteAddr allows HTTP servers and other software to record</span>
+        <span class="comment">// the network address that sent the request, usually for</span>
+        <span class="comment">// logging. This field is not filled in by ReadRequest and</span>
+        <span class="comment">// has no defined format. The HTTP server in this package</span>
+        <span class="comment">// sets RemoteAddr to an "IP:port" address before invoking a</span>
+        <span class="comment">// handler.</span>
+        <span class="comment">// This field is ignored by the HTTP client.</span>
+        RemoteAddr <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// RequestURI is the unmodified Request-URI of the</span>
+        <span class="comment">// Request-Line (RFC 2616, Section 5.1) as sent by the client</span>
+        <span class="comment">// to a server. Usually the URL field should be used instead.</span>
+        <span class="comment">// It is an error to set this field in an HTTP client request.</span>
+        RequestURI <a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// TLS allows HTTP servers and other software to record</span>
+        <span class="comment">// information about the TLS connection on which the request</span>
+        <span class="comment">// was received. This field is not filled in by ReadRequest.</span>
+        <span class="comment">// The HTTP server in this package sets the field for</span>
+        <span class="comment">// TLS-enabled connections before invoking a handler;</span>
+        <span class="comment">// otherwise it leaves the field nil.</span>
+        <span class="comment">// This field is ignored by the HTTP client.</span>
+        TLS *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#ConnectionState">ConnectionState</a>
+
+        <span class="comment">// Cancel is an optional channel whose closure indicates that the client</span>
+        <span class="comment">// request should be regarded as canceled. Not all implementations of</span>
+        <span class="comment">// RoundTripper may support Cancel.</span>
+        <span class="comment">//</span>
+        <span class="comment">// For server requests, this field is not applicable.</span>
+        Cancel &lt;-chan struct{}
+}</pre>
+			<p>
+A Request represents an HTTP request received by a server
+or to be sent by a client.
+</p>
+<p>
+The field semantics differ slightly between client and server
+usage. In addition to the notes on the fields below, see the
+documentation for Request.Write and RoundTripper.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+				
+				<h3 id="NewRequest">func <a href="https://golang.org/src/net/http/request.go?s=17718:17790#L536">NewRequest</a></h3>
+				<pre>func NewRequest(method, urlStr <a href="https://golang.org/pkg/builtin/#string">string</a>, body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Reader">Reader</a>) (*<a href="https://golang.org/pkg/net/http/#Request">Request</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+NewRequest returns a new Request given a method, URL, and optional body.
+</p>
+<p>
+If the provided body is also an io.Closer, the returned
+Request.Body is set to body and will be closed by the Client
+methods Do, Post, and PostForm, and Transport.RoundTrip.
+</p>
+<p>
+NewRequest returns a Request suitable for use with Client.Do or
+Transport.RoundTrip.
+To create a request for use with testing a Server Handler use either
+ReadRequest or manually update the Request fields. See the Request
+type's documentation for the difference between inbound and outbound
+request fields.
+</p>
+
+				
+				
+			
+				
+				<h3 id="ReadRequest">func <a href="https://golang.org/src/net/http/request.go?s=20301:20360#L636">ReadRequest</a></h3>
+				<pre>func ReadRequest(b *<a href="https://golang.org/pkg/bufio/">bufio</a>.<a href="https://golang.org/pkg/bufio/#Reader">Reader</a>) (req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+ReadRequest reads and parses an incoming request from b.
+</p>
+
+				
+				
+			
+
+			
+				
+				<h3 id="Request.AddCookie">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=9423:9461#L259">AddCookie</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) AddCookie(c *<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>)</pre>
+				<p>
+AddCookie adds a cookie to the request.  Per RFC 6265 section 5.4,
+AddCookie does not attach more than one Cookie header field.  That
+means all cookies, if any, are written into the same line,
+separated by semicolon.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.BasicAuth">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=18567:18633#L572">BasicAuth</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) BasicAuth() (username, password <a href="https://golang.org/pkg/builtin/#string">string</a>, ok <a href="https://golang.org/pkg/builtin/#bool">bool</a>)</pre>
+				<p>
+BasicAuth returns the username and password provided in the request's
+Authorization header, if the request uses HTTP Basic Authentication.
+See RFC 2617, Section 2.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.Cookie">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=9041:9095#L248">Cookie</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) Cookie(name <a href="https://golang.org/pkg/builtin/#string">string</a>) (*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+Cookie returns the named cookie provided in the request or
+ErrNoCookie if not found.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.Cookies">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=8727:8764#L239">Cookies</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) Cookies() []*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a></pre>
+				<p>
+Cookies parses and returns the HTTP cookies sent with the request.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.FormFile">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=29076:29161#L960">FormFile</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) FormFile(key <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/mime/multipart/">multipart</a>.<a href="https://golang.org/pkg/mime/multipart/#File">File</a>, *<a href="https://golang.org/pkg/mime/multipart/">multipart</a>.<a href="https://golang.org/pkg/mime/multipart/#FileHeader">FileHeader</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+FormFile returns the first file for the provided form key.
+FormFile calls ParseMultipartForm and ParseForm if necessary.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.FormValue">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=28248:28294#L933">FormValue</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) FormValue(key <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+				<p>
+FormValue returns the first value for the named component of the query.
+POST and PUT body parameters take precedence over URL query string values.
+FormValue calls ParseMultipartForm and ParseForm if necessary and ignores
+any errors returned by these functions.
+If key is not present, FormValue returns the empty string.
+To access multiple values of the same key, call ParseForm and
+then inspect Request.Form directly.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.MultipartReader">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=10760:10822#L292">MultipartReader</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) MultipartReader() (*<a href="https://golang.org/pkg/mime/multipart/">multipart</a>.<a href="https://golang.org/pkg/mime/multipart/#Reader">Reader</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+MultipartReader returns a MIME multipart reader if this is a
+multipart/form-data POST request, else returns nil and an error.
+Use this function instead of ParseMultipartForm to
+process the request body as a stream.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.ParseForm">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=26212:26247#L854">ParseForm</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) ParseForm() <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+ParseForm parses the raw query from the URL and updates r.Form.
+</p>
+<p>
+For POST or PUT requests, it also parses the request body as a form and
+put the results into both r.PostForm and r.Form.
+POST and PUT body parameters take precedence over URL query string values
+in r.Form.
+</p>
+<p>
+If the request Body's size has not already been limited by MaxBytesReader,
+the size is capped at 10MB.
+</p>
+<p>
+ParseMultipartForm calls ParseForm automatically.
+It is idempotent.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.ParseMultipartForm">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=27267:27326#L895">ParseMultipartForm</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) ParseMultipartForm(maxMemory <a href="https://golang.org/pkg/builtin/#int64">int64</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+ParseMultipartForm parses a request body as multipart/form-data.
+The whole request body is parsed and up to a total of maxMemory bytes of
+its file parts are stored in memory, with the remainder stored on
+disk in temporary files.
+ParseMultipartForm calls ParseForm if necessary.
+After one call to ParseMultipartForm, subsequent calls have no effect.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.PostFormValue">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=28755:28805#L948">PostFormValue</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) PostFormValue(key <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+				<p>
+PostFormValue returns the first value for the named component of the POST
+or PUT request body. URL query parameters are ignored.
+PostFormValue calls ParseMultipartForm and ParseForm if necessary and ignores
+any errors returned by these functions.
+If key is not present, PostFormValue returns the empty string.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.ProtoAtLeast">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=8370:8423#L228">ProtoAtLeast</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) ProtoAtLeast(major, minor <a href="https://golang.org/pkg/builtin/#int">int</a>) <a href="https://golang.org/pkg/builtin/#bool">bool</a></pre>
+				<p>
+ProtoAtLeast reports whether the HTTP protocol used
+in the request is at least major.minor.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.Referer">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=10131:10165#L276">Referer</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) Referer() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+				<p>
+Referer returns the referring URL, if sent in the request.
+</p>
+<p>
+Referer is misspelled as in the request itself, a mistake from the
+earliest days of HTTP.  This value can also be fetched from the
+Header map as Header["Referer"]; the benefit of making it available
+as a method is that the compiler can diagnose programs that use the
+alternate (correct English) spelling req.Referrer() but cannot
+diagnose programs that use Header["Referrer"].
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.SetBasicAuth">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=19455:19512#L604">SetBasicAuth</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) SetBasicAuth(username, password <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+				<p>
+SetBasicAuth sets the request's Authorization header to use HTTP
+Basic Authentication with the provided username and password.
+</p>
+<p>
+With HTTP Basic Authentication the provided username and password
+are not encrypted.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.UserAgent">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=8580:8616#L234">UserAgent</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) UserAgent() <a href="https://golang.org/pkg/builtin/#string">string</a></pre>
+				<p>
+UserAgent returns the client's User-Agent, if sent in the request.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.Write">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=12411:12453#L346">Write</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) Write(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+Write writes an HTTP/1.1 request, which is the header and body, in wire format.
+This method consults the following fields of the request:
+</p>
+<pre>Host
+URL
+Method (defaults to "GET")
+Header
+ContentLength
+TransferEncoding
+Body
+</pre>
+<p>
+If Body is present, Content-Length is &lt;= 0 and TransferEncoding
+hasn't been set to "identity", Write adds "Transfer-Encoding:
+chunked" to the header. Body is closed after it is sent.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Request.WriteProxy">func (*Request) <a href="https://golang.org/src/net/http/request.go?s=12846:12893#L356">WriteProxy</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) WriteProxy(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+WriteProxy is like Write but writes the request in the form
+expected by an HTTP proxy.  In particular, WriteProxy writes the
+initial Request-URI line of the request with an absolute URI, per
+section 5.1.2 of RFC 2616, including the scheme and host.
+In either case, WriteProxy also writes a Host header, using
+either r.Host or r.URL.Host.
+</p>
+
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="Response">type <a href="https://golang.org/src/net/http/response.go?s=512:2849#L19">Response</a></h2>
+			<pre>type Response struct {
+        Status     <a href="https://golang.org/pkg/builtin/#string">string</a> <span class="comment">// e.g. "200 OK"</span>
+        StatusCode <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// e.g. 200</span>
+        Proto      <a href="https://golang.org/pkg/builtin/#string">string</a> <span class="comment">// e.g. "HTTP/1.0"</span>
+        ProtoMajor <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// e.g. 1</span>
+        ProtoMinor <a href="https://golang.org/pkg/builtin/#int">int</a>    <span class="comment">// e.g. 0</span>
+
+        <span class="comment">// Header maps header keys to values.  If the response had multiple</span>
+        <span class="comment">// headers with the same key, they may be concatenated, with comma</span>
+        <span class="comment">// delimiters.  (Section 4.2 of RFC 2616 requires that multiple headers</span>
+        <span class="comment">// be semantically equivalent to a comma-delimited sequence.) Values</span>
+        <span class="comment">// duplicated by other fields in this struct (e.g., ContentLength) are</span>
+        <span class="comment">// omitted from Header.</span>
+        <span class="comment">//</span>
+        <span class="comment">// Keys in the map are canonicalized (see CanonicalHeaderKey).</span>
+        Header <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// Body represents the response body.</span>
+        <span class="comment">//</span>
+        <span class="comment">// The http Client and Transport guarantee that Body is always</span>
+        <span class="comment">// non-nil, even on responses without a body or responses with</span>
+        <span class="comment">// a zero-length body. It is the caller's responsibility to</span>
+        <span class="comment">// close Body. The default HTTP client's Transport does not</span>
+        <span class="comment">// attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections</span>
+        <span class="comment">// ("keep-alive") unless the Body is read to completion and is</span>
+        <span class="comment">// closed.</span>
+        <span class="comment">//</span>
+        <span class="comment">// The Body is automatically dechunked if the server replied</span>
+        <span class="comment">// with a "chunked" Transfer-Encoding.</span>
+        Body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#ReadCloser">ReadCloser</a>
+
+        <span class="comment">// ContentLength records the length of the associated content.  The</span>
+        <span class="comment">// value -1 indicates that the length is unknown.  Unless Request.Method</span>
+        <span class="comment">// is "HEAD", values &gt;= 0 indicate that the given number of bytes may</span>
+        <span class="comment">// be read from Body.</span>
+        ContentLength <a href="https://golang.org/pkg/builtin/#int64">int64</a>
+
+        <span class="comment">// Contains transfer encodings from outer-most to inner-most. Value is</span>
+        <span class="comment">// nil, means that "identity" encoding is used.</span>
+        TransferEncoding []<a href="https://golang.org/pkg/builtin/#string">string</a>
+
+        <span class="comment">// Close records whether the header directed that the connection be</span>
+        <span class="comment">// closed after reading Body.  The value is advice for clients: neither</span>
+        <span class="comment">// ReadResponse nor Response.Write ever closes a connection.</span>
+        Close <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+
+        <span class="comment">// Trailer maps trailer keys to values, in the same</span>
+        <span class="comment">// format as the header.</span>
+        Trailer <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// The Request that was sent to obtain this Response.</span>
+        <span class="comment">// Request's Body is nil (having already been consumed).</span>
+        <span class="comment">// This is only populated for Client requests.</span>
+        Request *<a href="https://golang.org/pkg/net/http/#Request">Request</a>
+
+        <span class="comment">// TLS contains information about the TLS connection on which the</span>
+        <span class="comment">// response was received. It is nil for unencrypted responses.</span>
+        <span class="comment">// The pointer is shared between responses and should not be</span>
+        <span class="comment">// modified.</span>
+        TLS *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#ConnectionState">ConnectionState</a>
+}</pre>
+			<p>
+Response represents the response from an HTTP request.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+				
+				<h3 id="Get">func <a href="https://golang.org/src/net/http/client.go?s=9198:9246#L270">Get</a></h3>
+				<pre>func Get(url <a href="https://golang.org/pkg/builtin/#string">string</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+Get issues a GET to the specified URL. If the response is one of
+the following redirect codes, Get follows the redirect, up to a
+maximum of 10 redirects:
+</p>
+<pre>301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+</pre>
+<p>
+An error is returned if there were too many redirects or if there
+was an HTTP protocol error. A non-2xx response doesn't cause an
+error.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+Get is a wrapper around DefaultClient.Get.
+</p>
+<p>
+To make a request with custom headers, use NewRequest and
+DefaultClient.Do.
+</p>
+
+				<div id="example_Get" class="toggle">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+		
+		
+		
+			<div class="play">
+				<div class="input"><textarea class="code">package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+)
+
+func main() {
+	res, err := http.Get("http://www.google.com/robots.txt")
+	if err != nil {
+		log.Fatal(err)
+	}
+	robots, err := ioutil.ReadAll(res.Body)
+	res.Body.Close()
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Printf("%s", robots)
+}
+</textarea></div>
+				<div class="output"><pre></pre></div>
+				<div class="buttons">
+					<a class="run" title="Run this code [shift-enter]">Run</a>
+					<a class="fmt" title="Format this code">Format</a>
+					
+				</div>
+			</div>
+		
+	</div>
+</div>
+
+				
+			
+				
+				<h3 id="Head">func <a href="https://golang.org/src/net/http/client.go?s=15901:15950#L500">Head</a></h3>
+				<pre>func Head(url <a href="https://golang.org/pkg/builtin/#string">string</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+Head issues a HEAD to the specified URL.  If the response is one of
+the following redirect codes, Head follows the redirect, up to a
+maximum of 10 redirects:
+</p>
+<pre>301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+</pre>
+<p>
+Head is a wrapper around DefaultClient.Head
+</p>
+
+				
+				
+			
+				
+				<h3 id="Post">func <a href="https://golang.org/src/net/http/client.go?s=13816:13898#L443">Post</a></h3>
+				<pre>func Post(url <a href="https://golang.org/pkg/builtin/#string">string</a>, bodyType <a href="https://golang.org/pkg/builtin/#string">string</a>, body <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Reader">Reader</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+Post issues a POST to the specified URL.
+</p>
+<p>
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+If the provided body is an io.Closer, it is closed after the
+request.
+</p>
+<p>
+Post is a wrapper around DefaultClient.Post.
+</p>
+<p>
+To set custom headers, use NewRequest and DefaultClient.Do.
+</p>
+
+				
+				
+			
+				
+				<h3 id="PostForm">func <a href="https://golang.org/src/net/http/client.go?s=14909:14979#L474">PostForm</a></h3>
+				<pre>func PostForm(url <a href="https://golang.org/pkg/builtin/#string">string</a>, data <a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#Values">Values</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+PostForm issues a POST to the specified URL, with data's keys and
+values URL-encoded as the request body.
+</p>
+<p>
+The Content-Type header is set to application/x-www-form-urlencoded.
+To set other headers, use NewRequest and DefaultClient.Do.
+</p>
+<p>
+When err is nil, resp always contains a non-nil resp.Body.
+Caller should close resp.Body when done reading from it.
+</p>
+<p>
+PostForm is a wrapper around DefaultClient.PostForm.
+</p>
+
+				
+				
+			
+				
+				<h3 id="ReadResponse">func <a href="https://golang.org/src/net/http/response.go?s=3992:4059#L111">ReadResponse</a></h3>
+				<pre>func ReadResponse(r *<a href="https://golang.org/pkg/bufio/">bufio</a>.<a href="https://golang.org/pkg/bufio/#Reader">Reader</a>, req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/http/#Response">Response</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+ReadResponse reads and returns an HTTP response from r.
+The req parameter optionally specifies the Request that corresponds
+to this Response. If nil, a GET request is assumed.
+Clients must call resp.Body.Close when finished reading resp.Body.
+After that call, clients can inspect resp.Trailer to find key/value
+pairs included in the response trailer.
+</p>
+
+				
+				
+			
+
+			
+				
+				<h3 id="Response.Cookies">func (*Response) <a href="https://golang.org/src/net/http/response.go?s=2924:2962#L82">Cookies</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Response">Response</a>) Cookies() []*<a href="https://golang.org/pkg/net/http/#Cookie">Cookie</a></pre>
+				<p>
+Cookies parses and returns the cookies set in the Set-Cookie headers.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Response.Location">func (*Response) <a href="https://golang.org/src/net/http/response.go?s=3387:3434#L94">Location</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Response">Response</a>) Location() (*<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+Location returns the URL of the response's "Location" header,
+if present.  Relative redirects are resolved relative to
+the Response's Request.  ErrNoLocation is returned if no
+Location header is present.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Response.ProtoAtLeast">func (*Response) <a href="https://golang.org/src/net/http/response.go?s=5570:5624#L179">ProtoAtLeast</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Response">Response</a>) ProtoAtLeast(major, minor <a href="https://golang.org/pkg/builtin/#int">int</a>) <a href="https://golang.org/pkg/builtin/#bool">bool</a></pre>
+				<p>
+ProtoAtLeast reports whether the HTTP protocol used
+in the response is at least major.minor.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Response.Write">func (*Response) <a href="https://golang.org/src/net/http/response.go?s=6162:6205#L200">Write</a></h3>
+				<pre>func (r *<a href="https://golang.org/pkg/net/http/#Response">Response</a>) Write(w <a href="https://golang.org/pkg/io/">io</a>.<a href="https://golang.org/pkg/io/#Writer">Writer</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+Write writes r to w in the HTTP/1.n server response format,
+including the status line, headers, body, and optional trailer.
+</p>
+<p>
+This method consults the following fields of the response r:
+</p>
+<pre>StatusCode
+ProtoMajor
+ProtoMinor
+Request.Method
+TransferEncoding
+Trailer
+Body
+ContentLength
+Header, values for non-canonical keys will have unpredictable behavior
+</pre>
+<p>
+The Response Body is closed after it is sent.
+</p>
+
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="ResponseWriter">type <a href="https://golang.org/src/net/http/server.go?s=1517:2599#L48">ResponseWriter</a></h2>
+			<pre>type ResponseWriter interface {
+        <span class="comment">// Header returns the header map that will be sent by</span>
+        <span class="comment">// WriteHeader. Changing the header after a call to</span>
+        <span class="comment">// WriteHeader (or Write) has no effect unless the modified</span>
+        <span class="comment">// headers were declared as trailers by setting the</span>
+        <span class="comment">// "Trailer" header before the call to WriteHeader (see example).</span>
+        <span class="comment">// To suppress implicit response headers, set their value to nil.</span>
+        Header() <a href="https://golang.org/pkg/net/http/#Header">Header</a>
+
+        <span class="comment">// Write writes the data to the connection as part of an HTTP reply.</span>
+        <span class="comment">// If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK)</span>
+        <span class="comment">// before writing the data.  If the Header does not contain a</span>
+        <span class="comment">// Content-Type line, Write adds a Content-Type set to the result of passing</span>
+        <span class="comment">// the initial 512 bytes of written data to DetectContentType.</span>
+        Write([]<a href="https://golang.org/pkg/builtin/#byte">byte</a>) (<a href="https://golang.org/pkg/builtin/#int">int</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+
+        <span class="comment">// WriteHeader sends an HTTP response header with status code.</span>
+        <span class="comment">// If WriteHeader is not called explicitly, the first call to Write</span>
+        <span class="comment">// will trigger an implicit WriteHeader(http.StatusOK).</span>
+        <span class="comment">// Thus explicit calls to WriteHeader are mainly used to</span>
+        <span class="comment">// send error codes.</span>
+        WriteHeader(<a href="https://golang.org/pkg/builtin/#int">int</a>)
+}</pre>
+			<p>
+A ResponseWriter interface is used by an HTTP handler to
+construct an HTTP response.
+</p>
+
+
+			
+
+			
+
+			<div id="example_ResponseWriter_trailers" class="toggle">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Example (Trailers)</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Example (Trailers)</span></p>
+		<p>HTTP Trailers are a set of key/value pairs like headers that come
+after the HTTP response, instead of before.
+</p>
+		
+		
+			<div class="play">
+				<div class="input"><textarea class="code">package main
+
+import (
+	"io"
+	"net/http"
+)
+
+func main() {
+	mux := http.NewServeMux()
+	mux.HandleFunc("/sendstrailers", func(w http.ResponseWriter, req *http.Request) {
+		// Before any call to WriteHeader or Write, declare
+		// the trailers you will set during the HTTP
+		// response. These three headers are actually sent in
+		// the trailer.
+		w.Header().Set("Trailer", "AtEnd1, AtEnd2")
+		w.Header().Add("Trailer", "AtEnd3")
+
+		w.Header().Set("Content-Type", "text/plain; charset=utf-8") // normal header
+		w.WriteHeader(http.StatusOK)
+
+		w.Header().Set("AtEnd1", "value 1")
+		io.WriteString(w, "This HTTP response has both headers before this text and trailers at the end.\n")
+		w.Header().Set("AtEnd2", "value 2")
+		w.Header().Set("AtEnd3", "value 3") // These will appear as trailers.
+	})
+}
+</textarea></div>
+				<div class="output"><pre></pre></div>
+				<div class="buttons">
+					<a class="run" title="Run this code [shift-enter]">Run</a>
+					<a class="fmt" title="Format this code">Format</a>
+					
+				</div>
+			</div>
+		
+	</div>
+</div>
+
+			
+			
+
+			
+
+			
+		
+			
+			
+			<h2 id="RoundTripper">type <a href="https://golang.org/src/net/http/client.go?s=2750:3517#L73">RoundTripper</a></h2>
+			<pre>type RoundTripper interface {
+        <span class="comment">// RoundTrip executes a single HTTP transaction, returning</span>
+        <span class="comment">// the Response for the request req.  RoundTrip should not</span>
+        <span class="comment">// attempt to interpret the response.  In particular,</span>
+        <span class="comment">// RoundTrip must return err == nil if it obtained a response,</span>
+        <span class="comment">// regardless of the response's HTTP status code.  A non-nil</span>
+        <span class="comment">// err should be reserved for failure to obtain a response.</span>
+        <span class="comment">// Similarly, RoundTrip should not attempt to handle</span>
+        <span class="comment">// higher-level protocol details such as redirects,</span>
+        <span class="comment">// authentication, or cookies.</span>
+        <span class="comment">//</span>
+        <span class="comment">// RoundTrip should not modify the request, except for</span>
+        <span class="comment">// consuming and closing the Body, including on errors. The</span>
+        <span class="comment">// request's URL and Header fields are guaranteed to be</span>
+        <span class="comment">// initialized.</span>
+        RoundTrip(*<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/http/#Response">Response</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+}</pre>
+			<p>
+RoundTripper is an interface representing the ability to execute a
+single HTTP transaction, obtaining the Response for a given Request.
+</p>
+<p>
+A RoundTripper must be safe for concurrent use by multiple
+goroutines.
+</p>
+
+
+			
+
+			
+				<pre>var <span id="DefaultTransport">DefaultTransport</span> <a href="https://golang.org/pkg/net/http/#RoundTripper">RoundTripper</a> = &amp;<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>{
+        <a href="https://golang.org/pkg/net/http/#Proxy">Proxy</a>: <a href="https://golang.org/pkg/net/http/#ProxyFromEnvironment">ProxyFromEnvironment</a>,
+        <a href="https://golang.org/pkg/net/http/#Dial">Dial</a>: (&amp;<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Dialer">Dialer</a>{
+                <a href="https://golang.org/pkg/net/http/#Timeout">Timeout</a>:   30 * <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Second">Second</a>,
+                <a href="https://golang.org/pkg/net/http/#KeepAlive">KeepAlive</a>: 30 * <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Second">Second</a>,
+        }).<a href="https://golang.org/pkg/net/http/#Dial">Dial</a>,
+        <a href="https://golang.org/pkg/net/http/#TLSHandshakeTimeout">TLSHandshakeTimeout</a>: 10 * <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Second">Second</a>,
+}</pre>
+				<p>
+DefaultTransport is the default implementation of Transport and is
+used by DefaultClient. It establishes network connections as needed
+and caches them for reuse by subsequent calls. It uses HTTP proxies
+as directed by the $HTTP_PROXY and $NO_PROXY (or $http_proxy and
+$no_proxy) environment variables.
+</p>
+
+			
+
+			
+			
+			
+
+			
+				
+				<h3 id="NewFileTransport">func <a href="https://golang.org/src/net/http/filetransport.go?s=827:876#L20">NewFileTransport</a></h3>
+				<pre>func NewFileTransport(fs <a href="https://golang.org/pkg/net/http/#FileSystem">FileSystem</a>) <a href="https://golang.org/pkg/net/http/#RoundTripper">RoundTripper</a></pre>
+				<p>
+NewFileTransport returns a new RoundTripper, serving the provided
+FileSystem. The returned RoundTripper ignores the URL host in its
+incoming requests, as well as most other properties of the
+request.
+</p>
+<p>
+The typical use case for NewFileTransport is to register the "file"
+protocol with a Transport, as in:
+</p>
+<pre>t := &amp;http.Transport{}
+t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
+c := &amp;http.Client{Transport: t}
+res, err := c.Get("file:///etc/passwd")
+...
+</pre>
+
+				
+				
+			
+
+			
+		
+			
+			
+			<h2 id="ServeMux">type <a href="https://golang.org/src/net/http/server.go?s=46684:46809#L1569">ServeMux</a></h2>
+			<pre>type ServeMux struct {
+        <span class="comment">// contains filtered or unexported fields</span>
+}</pre>
+			<p>
+ServeMux is an HTTP request multiplexer.
+It matches the URL of each incoming request against a list of registered
+patterns and calls the handler for the pattern that
+most closely matches the URL.
+</p>
+<p>
+Patterns name fixed, rooted paths, like "/favicon.ico",
+or rooted subtrees, like "/images/" (note the trailing slash).
+Longer patterns take precedence over shorter ones, so that
+if there are handlers registered for both "/images/"
+and "/images/thumbnails/", the latter handler will be
+called for paths beginning "/images/thumbnails/" and the
+former will receive requests for any other paths in the
+"/images/" subtree.
+</p>
+<p>
+Note that since a pattern ending in a slash names a rooted subtree,
+the pattern "/" matches all paths not matched by other registered
+patterns, not just the URL with Path == "/".
+</p>
+<p>
+Patterns may optionally begin with a host name, restricting matches to
+URLs on that host only.  Host-specific patterns take precedence over
+general patterns, so that a handler might register for the two patterns
+"/codesearch" and "codesearch.google.com/" without also taking over
+requests for "<a href="http://www.google.com/">http://www.google.com/</a>".
+</p>
+<p>
+ServeMux also takes care of sanitizing the URL request path,
+redirecting any request containing . or .. elements to an
+equivalent .- and ..-free URL.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+				
+				<h3 id="NewServeMux">func <a href="https://golang.org/src/net/http/server.go?s=46940:46968#L1582">NewServeMux</a></h3>
+				<pre>func NewServeMux() *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a></pre>
+				<p>
+NewServeMux allocates and returns a new ServeMux.
+</p>
+
+				
+				
+			
+
+			
+				
+				<h3 id="ServeMux.Handle">func (*ServeMux) <a href="https://golang.org/src/net/http/server.go?s=49985:50045#L1694">Handle</a></h3>
+				<pre>func (mux *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a>) Handle(pattern <a href="https://golang.org/pkg/builtin/#string">string</a>, handler <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>)</pre>
+				<p>
+Handle registers the handler for the given pattern.
+If a handler already exists for pattern, Handle panics.
+</p>
+
+				
+				<div id="example_ServeMux_Handle" class="toggle">
+	<div class="collapsed">
+		<p class="exampleHeading toggleButton">▹ <span class="text">Example</span></p>
+	</div>
+	<div class="expanded">
+		<p class="exampleHeading toggleButton">▾ <span class="text">Example</span></p>
+		
+		
+		
+			<p>Code:</p>
+			<pre class="code">    mux := http.NewServeMux()
+    mux.Handle("/api/", apiHandler{})
+    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+            <span class="comment">// The "/" pattern matches everything, so we need to check</span>
+            <span class="comment">// that we're at the root here.</span>
+            if req.URL.Path != "/" {
+                    http.NotFound(w, req)
+                    return
+            }
+            fmt.Fprintf(w, "Welcome to the home page!")
+    })
+</pre>
+			
+		
+	</div>
+</div>
+
+				
+			
+				
+				<h3 id="ServeMux.HandleFunc">func (*ServeMux) <a href="https://golang.org/src/net/http/server.go?s=51148:51235#L1733">HandleFunc</a></h3>
+				<pre>func (mux *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a>) HandleFunc(pattern <a href="https://golang.org/pkg/builtin/#string">string</a>, handler func(<a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, *<a href="https://golang.org/pkg/net/http/#Request">Request</a>))</pre>
+				<p>
+HandleFunc registers the handler function for the given pattern.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="ServeMux.Handler">func (*ServeMux) <a href="https://golang.org/src/net/http/server.go?s=48689:48757#L1646">Handler</a></h3>
+				<pre>func (mux *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a>) Handler(r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (h <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>, pattern <a href="https://golang.org/pkg/builtin/#string">string</a>)</pre>
+				<p>
+Handler returns the handler to use for the given request,
+consulting r.Method, r.Host, and r.URL.Path. It always returns
+a non-nil handler. If the path is not in its canonical form, the
+handler will be an internally-generated handler that redirects
+to the canonical path.
+</p>
+<p>
+Handler also returns the registered pattern that matches the
+request or, in the case of internally-generated redirects,
+the pattern that will match after following the redirect.
+</p>
+<p>
+If there is no registered handler that applies to the request,
+Handler returns a “page not found” handler and an empty pattern.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="ServeMux.ServeHTTP">func (*ServeMux) <a href="https://golang.org/src/net/http/server.go?s=49617:49677#L1680">ServeHTTP</a></h3>
+				<pre>func (mux *<a href="https://golang.org/pkg/net/http/#ServeMux">ServeMux</a>) ServeHTTP(w <a href="https://golang.org/pkg/net/http/#ResponseWriter">ResponseWriter</a>, r *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+				<p>
+ServeHTTP dispatches the request to the handler whose
+pattern most closely matches the request URL.
+</p>
+
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="Server">type <a href="https://golang.org/src/net/http/server.go?s=52296:53789#L1760">Server</a></h2>
+			<pre>type Server struct {
+        Addr           <a href="https://golang.org/pkg/builtin/#string">string</a>        <span class="comment">// TCP address to listen on, ":http" if empty</span>
+        Handler        <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>       <span class="comment">// handler to invoke, http.DefaultServeMux if nil</span>
+        ReadTimeout    <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a> <span class="comment">// maximum duration before timing out read of the request</span>
+        WriteTimeout   <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a> <span class="comment">// maximum duration before timing out write of the response</span>
+        MaxHeaderBytes <a href="https://golang.org/pkg/builtin/#int">int</a>           <span class="comment">// maximum size of request headers, DefaultMaxHeaderBytes if 0</span>
+        TLSConfig      *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#Config">Config</a>   <span class="comment">// optional TLS config, used by ListenAndServeTLS</span>
+
+        <span class="comment">// TLSNextProto optionally specifies a function to take over</span>
+        <span class="comment">// ownership of the provided TLS connection when an NPN</span>
+        <span class="comment">// protocol upgrade has occurred.  The map key is the protocol</span>
+        <span class="comment">// name negotiated. The Handler argument should be used to</span>
+        <span class="comment">// handle HTTP requests and will initialize the Request's TLS</span>
+        <span class="comment">// and RemoteAddr if not already set.  The connection is</span>
+        <span class="comment">// automatically closed when the function returns.</span>
+        TLSNextProto map[<a href="https://golang.org/pkg/builtin/#string">string</a>]func(*<a href="https://golang.org/pkg/net/http/#Server">Server</a>, *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#Conn">Conn</a>, <a href="https://golang.org/pkg/net/http/#Handler">Handler</a>)
+
+        <span class="comment">// ConnState specifies an optional callback function that is</span>
+        <span class="comment">// called when a client connection changes state. See the</span>
+        <span class="comment">// ConnState type and associated constants for details.</span>
+        ConnState func(<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Conn">Conn</a>, <a href="https://golang.org/pkg/net/http/#ConnState">ConnState</a>)
+
+        <span class="comment">// ErrorLog specifies an optional logger for errors accepting</span>
+        <span class="comment">// connections and unexpected behavior from handlers.</span>
+        <span class="comment">// If nil, logging goes to os.Stderr via the log package's</span>
+        <span class="comment">// standard logger.</span>
+        ErrorLog *<a href="https://golang.org/pkg/log/">log</a>.<a href="https://golang.org/pkg/log/#Logger">Logger</a>
+        <span class="comment">// contains filtered or unexported fields</span>
+}</pre>
+			<p>
+A Server defines parameters for running an HTTP server.
+The zero value for Server is a valid configuration.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+				
+				<h3 id="Server.ListenAndServe">func (*Server) <a href="https://golang.org/src/net/http/server.go?s=55886:55927#L1858">ListenAndServe</a></h3>
+				<pre>func (srv *<a href="https://golang.org/pkg/net/http/#Server">Server</a>) ListenAndServe() <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+ListenAndServe listens on the TCP network address srv.Addr and then
+calls Serve to handle requests on incoming connections.  If
+srv.Addr is blank, ":http" is used.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Server.ListenAndServeTLS">func (*Server) <a href="https://golang.org/src/net/http/server.go?s=60146:60214#L2003">ListenAndServeTLS</a></h3>
+				<pre>func (srv *<a href="https://golang.org/pkg/net/http/#Server">Server</a>) ListenAndServeTLS(certFile, keyFile <a href="https://golang.org/pkg/builtin/#string">string</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+ListenAndServeTLS listens on the TCP network address srv.Addr and
+then calls Serve to handle requests on incoming TLS connections.
+</p>
+<p>
+Filenames containing a certificate and matching private key for the
+server must be provided if the Server's TLSConfig.Certificates is
+not populated. If the certificate is signed by a certificate
+authority, the certFile should be the concatenation of the server's
+certificate, any intermediates, and the CA's certificate.
+</p>
+<p>
+If srv.Addr is blank, ":https" is used.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Server.Serve">func (*Server) <a href="https://golang.org/src/net/http/server.go?s=56308:56354#L1873">Serve</a></h3>
+				<pre>func (srv *<a href="https://golang.org/pkg/net/http/#Server">Server</a>) Serve(l <a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Listener">Listener</a>) <a href="https://golang.org/pkg/builtin/#error">error</a></pre>
+				<p>
+Serve accepts incoming connections on the Listener l, creating a
+new service goroutine for each.  The service goroutines read requests and
+then call srv.Handler to reply to them.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Server.SetKeepAlivesEnabled">func (*Server) <a href="https://golang.org/src/net/http/server.go?s=57329:57376#L1912">SetKeepAlivesEnabled</a></h3>
+				<pre>func (srv *<a href="https://golang.org/pkg/net/http/#Server">Server</a>) SetKeepAlivesEnabled(v <a href="https://golang.org/pkg/builtin/#bool">bool</a>)</pre>
+				<p>
+SetKeepAlivesEnabled controls whether HTTP keep-alives are enabled.
+By default, keep-alives are always enabled. Only very
+resource-constrained environments or servers in the process of
+shutting down should disable them.
+</p>
+
+				
+				
+				
+			
+		
+			
+			
+			<h2 id="Transport">type <a href="https://golang.org/src/net/http/transport.go?s=1324:4038#L39">Transport</a></h2>
+			<pre>type Transport struct {
+
+        <span class="comment">// Proxy specifies a function to return a proxy for a given</span>
+        <span class="comment">// Request. If the function returns a non-nil error, the</span>
+        <span class="comment">// request is aborted with the provided error.</span>
+        <span class="comment">// If Proxy is nil or returns a nil *URL, no proxy is used.</span>
+        Proxy func(*<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (*<a href="https://golang.org/pkg/net/url/">url</a>.<a href="https://golang.org/pkg/net/url/#URL">URL</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+
+        <span class="comment">// Dial specifies the dial function for creating unencrypted</span>
+        <span class="comment">// TCP connections.</span>
+        <span class="comment">// If Dial is nil, net.Dial is used.</span>
+        Dial func(network, addr <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Conn">Conn</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+
+        <span class="comment">// DialTLS specifies an optional dial function for creating</span>
+        <span class="comment">// TLS connections for non-proxied HTTPS requests.</span>
+        <span class="comment">//</span>
+        <span class="comment">// If DialTLS is nil, Dial and TLSClientConfig are used.</span>
+        <span class="comment">//</span>
+        <span class="comment">// If DialTLS is set, the Dial hook is not used for HTTPS</span>
+        <span class="comment">// requests and the TLSClientConfig and TLSHandshakeTimeout</span>
+        <span class="comment">// are ignored. The returned net.Conn is assumed to already be</span>
+        <span class="comment">// past the TLS handshake.</span>
+        DialTLS func(network, addr <a href="https://golang.org/pkg/builtin/#string">string</a>) (<a href="https://golang.org/pkg/net/">net</a>.<a href="https://golang.org/pkg/net/#Conn">Conn</a>, <a href="https://golang.org/pkg/builtin/#error">error</a>)
+
+        <span class="comment">// TLSClientConfig specifies the TLS configuration to use with</span>
+        <span class="comment">// tls.Client. If nil, the default configuration is used.</span>
+        TLSClientConfig *<a href="https://golang.org/pkg/crypto/tls/">tls</a>.<a href="https://golang.org/pkg/crypto/tls/#Config">Config</a>
+
+        <span class="comment">// TLSHandshakeTimeout specifies the maximum amount of time waiting to</span>
+        <span class="comment">// wait for a TLS handshake. Zero means no timeout.</span>
+        TLSHandshakeTimeout <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a>
+
+        <span class="comment">// DisableKeepAlives, if true, prevents re-use of TCP connections</span>
+        <span class="comment">// between different HTTP requests.</span>
+        DisableKeepAlives <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+
+        <span class="comment">// DisableCompression, if true, prevents the Transport from</span>
+        <span class="comment">// requesting compression with an "Accept-Encoding: gzip"</span>
+        <span class="comment">// request header when the Request contains no existing</span>
+        <span class="comment">// Accept-Encoding value. If the Transport requests gzip on</span>
+        <span class="comment">// its own and gets a gzipped response, it's transparently</span>
+        <span class="comment">// decoded in the Response.Body. However, if the user</span>
+        <span class="comment">// explicitly requested gzip it is not automatically</span>
+        <span class="comment">// uncompressed.</span>
+        DisableCompression <a href="https://golang.org/pkg/builtin/#bool">bool</a>
+
+        <span class="comment">// MaxIdleConnsPerHost, if non-zero, controls the maximum idle</span>
+        <span class="comment">// (keep-alive) to keep per-host.  If zero,</span>
+        <span class="comment">// DefaultMaxIdleConnsPerHost is used.</span>
+        MaxIdleConnsPerHost <a href="https://golang.org/pkg/builtin/#int">int</a>
+
+        <span class="comment">// ResponseHeaderTimeout, if non-zero, specifies the amount of</span>
+        <span class="comment">// time to wait for a server's response headers after fully</span>
+        <span class="comment">// writing the request (including its body, if any). This</span>
+        <span class="comment">// time does not include the time to read the response body.</span>
+        ResponseHeaderTimeout <a href="https://golang.org/pkg/time/">time</a>.<a href="https://golang.org/pkg/time/#Duration">Duration</a>
+        <span class="comment">// contains filtered or unexported fields</span>
+}</pre>
+			<p>
+Transport is an implementation of RoundTripper that supports HTTP,
+HTTPS, and HTTP proxies (for either HTTP or HTTPS with CONNECT).
+Transport can also cache connections for future re-use.
+</p>
+
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+				
+				<h3 id="Transport.CancelRequest">func (*Transport) <a href="https://golang.org/src/net/http/transport.go?s=8881:8928#L269">CancelRequest</a></h3>
+				<pre>func (t *<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>) CancelRequest(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>)</pre>
+				<p>
+CancelRequest cancels an in-flight request by closing its connection.
+CancelRequest should only be called after RoundTrip has returned.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Transport.CloseIdleConnections">func (*Transport) <a href="https://golang.org/src/net/http/transport.go?s=8498:8540#L253">CloseIdleConnections</a></h3>
+				<pre>func (t *<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>) CloseIdleConnections()</pre>
+				<p>
+CloseIdleConnections closes any connections which were previously
+connected from previous requests but are now sitting idle in
+a "keep-alive" state. It does not interrupt any connections currently
+in use.
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Transport.RegisterProtocol">func (*Transport) <a href="https://golang.org/src/net/http/transport.go?s=7866:7934#L234">RegisterProtocol</a></h3>
+				<pre>func (t *<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>) RegisterProtocol(scheme <a href="https://golang.org/pkg/builtin/#string">string</a>, rt <a href="https://golang.org/pkg/net/http/#RoundTripper">RoundTripper</a>)</pre>
+				<p>
+RegisterProtocol registers a new protocol with scheme.
+The Transport will pass requests using the given scheme to rt.
+It is rt's responsibility to simulate HTTP request semantics.
+</p>
+<p>
+RegisterProtocol can be used by other packages to provide
+implementations of protocol schemes like "ftp" or "file".
+</p>
+
+				
+				
+				
+			
+				
+				<h3 id="Transport.RoundTrip">func (*Transport) <a href="https://golang.org/src/net/http/transport.go?s=6344:6415#L181">RoundTrip</a></h3>
+				<pre>func (t *<a href="https://golang.org/pkg/net/http/#Transport">Transport</a>) RoundTrip(req *<a href="https://golang.org/pkg/net/http/#Request">Request</a>) (resp *<a href="https://golang.org/pkg/net/http/#Response">Response</a>, err <a href="https://golang.org/pkg/builtin/#error">error</a>)</pre>
+				<p>
+RoundTrip implements the RoundTripper interface.
+</p>
+<p>
+For higher-level HTTP client support (such as handling of cookies
+and redirects), see Get, Post, and the Client type.
+</p>
+
+				
+				
+				
+			
+		
+	
+
+	
+
+
+
+
+
+	
+	
+		<h2 id="pkg-subdirectories">Subdirectories</h2>
+	
+	
+
+
+	<div class="pkg-dir">
+		<table>
+			<tbody><tr>
+				<th class="pkg-name">Name</th>
+				<th class="pkg-synopsis">Synopsis</th>
+			</tr>
+
+			
+			<tr>
+				<td colspan="2"><a href="https://golang.org/pkg/net/">..</a></td>
+			</tr>
+			
+
+			
+				
+					<tr>
+						<td class="pkg-name" style="padding-left: 0px;">
+							<a href="https://golang.org/pkg/net/http/cgi/">cgi</a>
+						</td>
+						<td class="pkg-synopsis">
+							Package cgi implements CGI (Common Gateway Interface) as specified in RFC 3875.
+						</td>
+					</tr>
+				
+			
+				
+					<tr>
+						<td class="pkg-name" style="padding-left: 0px;">
+							<a href="https://golang.org/pkg/net/http/cookiejar/">cookiejar</a>
+						</td>
+						<td class="pkg-synopsis">
+							Package cookiejar implements an in-memory RFC 6265-compliant http.CookieJar.
+						</td>
+					</tr>
+				
+			
+				
+					<tr>
+						<td class="pkg-name" style="padding-left: 0px;">
+							<a href="https://golang.org/pkg/net/http/fcgi/">fcgi</a>
+						</td>
+						<td class="pkg-synopsis">
+							Package fcgi implements the FastCGI protocol.
+						</td>
+					</tr>
+				
+			
+				
+					<tr>
+						<td class="pkg-name" style="padding-left: 0px;">
+							<a href="https://golang.org/pkg/net/http/httptest/">httptest</a>
+						</td>
+						<td class="pkg-synopsis">
+							Package httptest provides utilities for HTTP testing.
+						</td>
+					</tr>
+				
+			
+				
+					<tr>
+						<td class="pkg-name" style="padding-left: 0px;">
+							<a href="https://golang.org/pkg/net/http/httputil/">httputil</a>
+						</td>
+						<td class="pkg-synopsis">
+							Package httputil provides HTTP utility functions, complementing the more common ones in the net/http package.
+						</td>
+					</tr>
+				
+			
+				
+					<tr>
+						<td class="pkg-name" style="padding-left: 0px;">
+							<a href="https://golang.org/pkg/net/http/pprof/">pprof</a>
+						</td>
+						<td class="pkg-synopsis">
+							Package pprof serves via its HTTP server runtime profiling data in the format expected by the pprof visualization tool.
+						</td>
+					</tr>
+				
+			
+		</tbody></table>
+	</div>
+
+
+	
+
+
+
+<div id="footer">
+Build version go1.5.1.<br>
+Except as <a href="https://developers.google.com/site-policies#restrictions">noted</a>,
+the content of this page is licensed under the
+Creative Commons Attribution 3.0 License,
+and code is licensed under a <a href="https://golang.org/LICENSE">BSD license</a>.<br>
+<a href="https://golang.org/doc/tos.html">Terms of Service</a> | 
+<a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
+</div>
+
+</div><!-- .container -->
+</div><!-- #page -->
+
+<!-- TODO(adonovan): load these from <head> using "defer" attribute? -->
+<script type="text/javascript" src="./http - The Go Programming Language_files/jquery.min.js"></script>
+<script type="text/javascript" src="./http - The Go Programming Language_files/jquery.treeview.js"></script>
+<script type="text/javascript" src="./http - The Go Programming Language_files/jquery.treeview.edit.js"></script>
+
+
+<script type="text/javascript" src="./http - The Go Programming Language_files/playground.js"></script>
+
+<script type="text/javascript" src="./http - The Go Programming Language_files/godocs.js"></script>
+
+<script type="text/javascript">
+(function() {
+  var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
+  ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
+  var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
+})();
+</script>
+
+
+
+</body></html>

+ 156 - 0
sample/append_object.go

@@ -0,0 +1,156 @@
+// Package sample examples
+package sample
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// AppendObjectSample Append Object Sample
+func AppendObjectSample() {
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectKey)
+
+	var str = "弃我去者,昨日之日不可留。 乱我心者,今日之日多烦忧!"
+	var nextPos int64
+
+	// 场景1:追加字符串到object
+	// 第一次追加的位置是0,返回值为下一次追加的位置
+	nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 第二次追加
+	nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 下载
+	body, err := bucket.GetObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	data, err := ioutil.ReadAll(body)
+	body.Close()
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println(objectKey, ":", string(data))
+
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:追加[]byte到object
+	nextPos = 0
+	// 第一次追加的位置是0,返回值为下一次追加的位置
+	nextPos, err = bucket.AppendObject(objectKey, bytes.NewReader([]byte(str)), nextPos)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 第二次追加
+	nextPos, err = bucket.AppendObject(objectKey, bytes.NewReader([]byte(str)), nextPos)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 下载
+	body, err = bucket.GetObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	data, err = ioutil.ReadAll(body)
+	body.Close()
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println(objectKey, ":", string(data))
+
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	//场景3:本地文件追加到Object
+	fd, err := os.Open(localFile)
+	if err != nil {
+		HandleError(err)
+	}
+	defer fd.Close()
+
+	nextPos = 0
+	nextPos, err = bucket.AppendObject(objectKey, fd, nextPos)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景4,您可以通过GetObjectDetailedMeta获取下次追加的位置
+	props, err := bucket.GetObjectDetailedMeta(objectKey)
+	nextPos, err = strconv.ParseInt(props.Get(oss.HTTPHeaderOssNextAppendPosition), 10, 0)
+	if err != nil {
+		HandleError(err)
+	}
+
+	nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景5:第一次追加操作时,可以指定Object的Properties,包括以"x-oss-meta-my"为前缀的用户自定义属性
+	options := []oss.Option{
+		oss.Expires(futureDate),
+		oss.ObjectACL(oss.ACLPublicRead),
+		oss.Meta("myprop", "mypropval")}
+	nextPos = 0
+	fd.Seek(0, os.SEEK_SET)
+	nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos, options...)
+	if err != nil {
+		HandleError(err)
+	}
+	// 第二次追加
+	fd.Seek(0, os.SEEK_SET)
+	nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos)
+	if err != nil {
+		HandleError(err)
+	}
+
+	props, err = bucket.GetObjectDetailedMeta(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("myprop:", props.Get("x-oss-meta-myprop"))
+
+	goar, err := bucket.GetObjectACL(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Object ACL:", goar.ACL)
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("AppendObjectSample completed")
+}

+ 43 - 0
sample/bucket_acl.go

@@ -0,0 +1,43 @@
+package sample
+
+import (
+	"fmt"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketACLSample Set/Get Bucket ACL Sample
+func BucketACLSample() {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 使用默认参数创建bucket
+	err = client.CreateBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景:设置Bucket ACL,可选权限有ACLPrivate、ACLPublicRead、ACLPublicReadWrite
+	err = client.SetBucketACL(bucketName, oss.ACLPublicRead)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 查看Bucket ACL
+	gbar, err := client.GetBucketACL(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Bucket ACL:", gbar.ACL)
+
+	// 删除bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("BucketACLSample completed")
+}

+ 71 - 0
sample/bucket_cors.go

@@ -0,0 +1,71 @@
+package sample
+
+import (
+	"fmt"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketCORSSample Set/Get/Delete Bucket CORS Sample
+func BucketCORSSample() {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 使用默认参数创建bucket
+	err = client.CreateBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	rule1 := oss.CORSRule{
+		AllowedOrigin: []string{"*"},
+		AllowedMethod: []string{"PUT", "GET", "POST"},
+		AllowedHeader: []string{},
+		ExposeHeader:  []string{},
+		MaxAgeSeconds: 100,
+	}
+
+	rule2 := oss.CORSRule{
+		AllowedOrigin: []string{"http://www.a.com", "http://www.b.com"},
+		AllowedMethod: []string{"GET"},
+		AllowedHeader: []string{"Authorization"},
+		ExposeHeader:  []string{"x-oss-test", "x-oss-test1"},
+		MaxAgeSeconds: 100,
+	}
+
+	// 场景1:设置Bucket的CORS规则
+	err = client.SetBucketCORS(bucketName, []oss.CORSRule{rule1})
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:设置Bucket的CORS规则,如果该Bucket上已经设置了CORS规则,则会覆盖。
+	err = client.SetBucketCORS(bucketName, []oss.CORSRule{rule1, rule2})
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 获取Bucket上设置的CORS
+	gbl, err := client.GetBucketCORS(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Bucket CORS:", gbl.CORSRules)
+
+	// 删除Bucket上的CORS设置
+	err = client.DeleteBucketCORS(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("BucketCORSSample completed")
+}

+ 68 - 0
sample/bucket_lifecycle.go

@@ -0,0 +1,68 @@
+package sample
+
+import (
+	"fmt"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketLifecycleSample Set/Get/Delete Bucket Lifecycle Sample
+func BucketLifecycleSample() {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 使用默认参数创建bucket
+	err = client.CreateBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1:设置Lifecycle,其中规则的id是id1,规则生效的object前缀是one,符合的Object绝对过期时间2015/11/11
+	var rule1 = oss.BuildLifecycleRuleByDate("id1", "one", true, 2015, 11, 11)
+	var rules = []oss.LifecycleRule{rule1}
+	err = client.SetBucketLifecycle(bucketName, rules)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:设置Lifecycle,其中规则的id是id2,规则生效的object前缀是two,符合的Object相对过期时间是3天后
+	var rule2 = oss.BuildLifecycleRuleByDays("id2", "two", true, 3)
+	rules = []oss.LifecycleRule{rule2}
+	err = client.SetBucketLifecycle(bucketName, rules)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景3:在Bucket上同时设置两条规格,两个规则分别作用与不同的对象。规则id相同是会覆盖老的规则。
+	var rule3 = oss.BuildLifecycleRuleByDays("id1", "two", true, 365)
+	var rule4 = oss.BuildLifecycleRuleByDate("id2", "one", true, 2016, 11, 11)
+	rules = []oss.LifecycleRule{rule3, rule4}
+	err = client.SetBucketLifecycle(bucketName, rules)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 获取Bucket上设置的Lifecycle
+	gbl, err := client.GetBucketLifecycle(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Bucket Lifecycle:", gbl.Rules)
+
+	// 删除Bucket上的Lifecycle设置
+	err = client.DeleteBucketLifecycle(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("BucketLifecycleSample completed")
+}

+ 90 - 0
sample/bucket_logging.go

@@ -0,0 +1,90 @@
+package sample
+
+import (
+	"fmt"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketLoggingSample Set/Get/Delete Bucket Logging Sample
+func BucketLoggingSample() {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 创建bucket
+	err = client.CreateBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+	// 创建Target bucket,存储访问日志
+	var targetBucketName = "target-bucket"
+	err = client.CreateBucket(targetBucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1:设置Logging,bucketName中以"prefix"为前缀的object的访问日志将被记录到targetBucketName
+	err = client.SetBucketLogging(bucketName, targetBucketName, "prefix-1", true)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:设置Logging,bucketName中以"prefix"为前缀的object的访问日志将被记录到bucketName
+	// 注意:相同bucket,相同prefix,多次设置后者会覆盖前者
+	err = client.SetBucketLogging(bucketName, bucketName, "prefix-2", true)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除Bucket上的Logging设置
+	err = client.DeleteBucketLogging(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景3:设置但不生效
+	err = client.SetBucketLogging(bucketName, targetBucketName, "prefix-3", false)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 获取Bucket上设置的Logging
+	gbl, err := client.GetBucketLogging(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Bucket Logging:", gbl.LoggingEnabled)
+
+	err = client.SetBucketLogging(bucketName, bucketName, "prefix2", true)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 获取Bucket上设置的Logging
+	gbl, err = client.GetBucketLogging(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Bucket Logging:", gbl.LoggingEnabled)
+
+	// 删除Bucket上的Logging设置
+	err = client.DeleteBucketLogging(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+	err = client.DeleteBucket(targetBucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("BucketLoggingSample completed")
+}

+ 57 - 0
sample/bucket_referer.go

@@ -0,0 +1,57 @@
+package sample
+
+import (
+	"fmt"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// BucketRefererSample Set/Get Bucket Referer Sample
+func BucketRefererSample() {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 使用默认参数创建bucket
+	err = client.CreateBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	var referers = []string{
+		"http://www.aliyun.com",
+		"http://www.???.aliyuncs.com",
+		"http://www.*.com",
+	}
+
+	// 场景1:设置referers,referer中支持?和*,分布代替一个或多个字符
+	err = client.SetBucketReferer(bucketName, referers, false)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:清空referers
+	referers = []string{}
+	err = client.SetBucketReferer(bucketName, referers, true)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 获取Bucket上设置的Lifecycle
+	gbr, err := client.GetBucketReferer(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Bucket Referers:", gbr.RefererList,
+		"AllowEmptyReferer:", gbr.AllowEmptyReferer)
+
+	// 删除bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("BucketRefererSample completed")
+}

+ 96 - 0
sample/cname_sample.go

@@ -0,0 +1,96 @@
+package sample
+
+import (
+	"fmt"
+	"io/ioutil"
+	"strings"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CnameSample Cname Sample
+func CnameSample() {
+	// NewClient
+	client, err := oss.New(endpoint4Cname, accessID4Cname, accessKey4Cname,
+		oss.UseCname(true))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// CreateBucket
+	err = client.CreateBucket(bucketName4Cname)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// SetBucketACL
+	err = client.SetBucketACL(bucketName4Cname, oss.ACLPrivate)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 查看Bucket ACL
+	gbar, err := client.GetBucketACL(bucketName4Cname)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Bucket ACL:", gbar.ACL)
+
+	// ListBuckets, cname用户不能使用该操作
+	_, err = client.ListBuckets()
+	if err == nil {
+		HandleError(err)
+	}
+
+	bucket, err := client.Bucket(bucketName4Cname)
+	if err != nil {
+		HandleError(err)
+	}
+
+	objectValue := "长忆观潮,满郭人争江上望。来疑沧海尽成空,万面鼓声中。弄潮儿向涛头立,手把红旗旗不湿。别来几向梦中看,梦觉尚心寒。"
+
+	// PutObject
+	err = bucket.PutObject(objectKey, strings.NewReader(objectValue))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// GetObject
+	body, err := bucket.GetObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	data, err := ioutil.ReadAll(body)
+	body.Close()
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println(objectKey, ":", string(data))
+
+	// PutObjectFromFile
+	err = bucket.PutObjectFromFile(objectKey, localFile)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// GetObjectToFile
+	err = bucket.GetObjectToFile(objectKey, newPicName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// ListObjects
+	lor, err := bucket.ListObjects()
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("objects:", lor.Objects)
+
+	// DeleteObject
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("CnameSample completed")
+}

+ 123 - 0
sample/comm.go

@@ -0,0 +1,123 @@
+package sample
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"time"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+var (
+	pastDate   = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+	futureDate = time.Date(2049, time.January, 10, 23, 0, 0, 0, time.UTC)
+)
+
+// HandleError sample中的错误处理
+func HandleError(err error) {
+	fmt.Println("occurred error:", err)
+	os.Exit(-1)
+}
+
+// GetTestBucket 创建sample的Bucket并返回OssBucket对象,该函数为了简化sample,让sample代码更明了
+func GetTestBucket(bucketName string) (*oss.Bucket, error) {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		return nil, err
+	}
+
+	// Create Bucket
+	err = client.CreateBucket(bucketName)
+	if err != nil {
+		return nil, err
+	}
+
+	// Get Bucket
+	bucket, err := client.Bucket(bucketName)
+	if err != nil {
+		return nil, err
+	}
+
+	return bucket, nil
+}
+
+// DeleteTestBucketAndObject 删除sample的object和bucket,该函数为了简化sample,让sample代码更明了
+func DeleteTestBucketAndObject(bucketName string) error {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		return err
+	}
+
+	// Get Bucket
+	bucket, err := client.Bucket(bucketName)
+	if err != nil {
+		return err
+	}
+
+	// Delete Part
+	lmur, err := bucket.ListMultipartUploads()
+	if err != nil {
+		return err
+	}
+
+	for _, upload := range lmur.Uploads {
+		var imur = oss.InitiateMultipartUploadResult{Bucket: bucket.BucketName,
+			Key: upload.Key, UploadID: upload.UploadID}
+		err = bucket.AbortMultipartUpload(imur)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Delete Objects
+	lor, err := bucket.ListObjects()
+	if err != nil {
+		return err
+	}
+
+	for _, object := range lor.Objects {
+		err = bucket.DeleteObject(object.Key)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Delete Bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Object pair of key and value
+type Object struct {
+	Key   string
+	Value string
+}
+
+// CreateObjects 创建一组对象,该函数为了简化sample,让sample代码更明了
+func CreateObjects(bucket *oss.Bucket, objects []Object) error {
+	for _, object := range objects {
+		err := bucket.PutObject(object.Key, strings.NewReader(object.Value))
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// DeleteObjects 删除sample的object和bucket,该函数为了简化sample,让sample代码更明了
+func DeleteObjects(bucket *oss.Bucket, objects []Object) error {
+	for _, object := range objects {
+		err := bucket.DeleteObject(object.Key)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 25 - 0
sample/config.go

@@ -0,0 +1,25 @@
+package sample
+
+const (
+	// sample运行的环境配置。如果您需要运行sample,请先修成您的配置。
+	endpoint   string = "<endpoint>"
+	accessID   string = "<AccessKeyId>"
+	accessKey  string = "<AccessKeySecret>"
+	bucketName string = "<my-bucket>"
+
+	// 运行cname的示例程序sample/cname_sample的示例程序的配置。
+	// 如果您需要运行sample/cname_sample,请先修成您的配置。
+	endpoint4Cname   string = "<endpoint>"
+	accessID4Cname   string = "<AccessKeyId>"
+	accessKey4Cname  string = "<AccessKeySecret>"
+	bucketName4Cname string = "<my-cname-bucket>"
+
+	// 运行sample时的Object名称
+	objectKey string = "my-object"
+
+	// 运行sample需要的资源,即sample目录目录下的BingWallpaper-2015-11-07.jpg
+	// 和The Go Programming Language.html,请根据实际情况修改
+	localFile     string = "src/sample/BingWallpaper-2015-11-07.jpg"
+	htmlLocalFile string = "src/sample/The Go Programming Language.html"
+	newPicName    string = "src/sample/NewBingWallpaper-2015-11-07.jpg"
+)

+ 89 - 0
sample/copy_object.go

@@ -0,0 +1,89 @@
+package sample
+
+import (
+	"fmt"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CopyObjectSample Copy Object Sample
+func CopyObjectSample() {
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 创建一个Object
+	err = bucket.PutObjectFromFile(objectKey, localFile)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1:把已经存在的对象copy成一个新对象
+	var descObjectKey = "descobject"
+	_, err = bucket.CopyObject(objectKey, descObjectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:把已经存在的对象copy成一个新对象,目标对象存在时,会覆盖
+	_, err = bucket.CopyObject(objectKey, descObjectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(descObjectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景3:对象copy时对源对象执行约束条件,满足时候copy,不满足时返回错误,不执行copy
+	// 约束条件不满足,copy没有执行
+	_, err = bucket.CopyObject(objectKey, descObjectKey, oss.CopySourceIfModifiedSince(futureDate))
+	if err == nil {
+		HandleError(err)
+	}
+	fmt.Println("CopyObjectError:", err)
+	// 约束条件满足,copy执行
+	_, err = bucket.CopyObject(objectKey, descObjectKey, oss.CopySourceIfUnmodifiedSince(futureDate))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景4:对象copy时,可以指定目标对象的Properties,同时一定要指定MetadataDirective为MetaReplace
+	options := []oss.Option{
+		oss.Expires(futureDate),
+		oss.Meta("myprop", "mypropval"),
+		oss.MetadataDirective(oss.MetaReplace)}
+	_, err = bucket.CopyObject(objectKey, descObjectKey, options...)
+	if err != nil {
+		HandleError(err)
+	}
+
+	meta, err := bucket.GetObjectDetailedMeta(descObjectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("meta:", meta)
+
+	// 场景5:当源对象和目标对象相同时,目的是用来修改源对象的meta
+	options = []oss.Option{
+		oss.Expires(futureDate),
+		oss.Meta("myprop", "mypropval"),
+		oss.MetadataDirective(oss.MetaReplace)}
+
+	_, err = bucket.CopyObject(objectKey, objectKey, options...)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("meta:", meta)
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("CopyObjectSample completed")
+}

+ 50 - 0
sample/create_bucket.go

@@ -0,0 +1,50 @@
+package sample
+
+import (
+	"fmt"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// CreateBucketSample Create Bucket Sample
+func CreateBucketSample() {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	DeleteTestBucketAndObject(bucketName)
+
+	// 场景1:使用默认参数创建bucket
+	err = client.CreateBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:创建bucket时指定其权限
+	err = client.CreateBucket(bucketName, oss.ACL(oss.ACLPublicRead))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景3:重复创建OSS不会报错,但是不做任何操作,指定的ACL无效
+	err = client.CreateBucket(bucketName, oss.ACL(oss.ACLPublicReadWrite))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("CreateBucketSample completed")
+}

+ 108 - 0
sample/delete_object.go

@@ -0,0 +1,108 @@
+package sample
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// DeleteObjectSample Delete Object Sample
+func DeleteObjectSample() {
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	var val = "抽刀断水水更流,举杯销愁愁更愁。 人生在世不称意,明朝散发弄扁舟。"
+
+	// 场景1:删除Object
+	err = bucket.PutObject(objectKey, strings.NewReader(val))
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:删除多个Object
+	err = bucket.PutObject(objectKey+"1", strings.NewReader(val))
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.PutObject(objectKey+"2", strings.NewReader(val))
+	if err != nil {
+		HandleError(err)
+	}
+
+	delRes, err := bucket.DeleteObjects([]string{objectKey + "1", objectKey + "2"})
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Del Res:", delRes)
+
+	lsRes, err := bucket.ListObjects()
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Objects:", getObjectsFormResponse(lsRes))
+
+	// 场景3:删除多个Object,详细模式时返回的结果中会包含成功删除的Object,默认该模式
+	err = bucket.PutObject(objectKey+"1", strings.NewReader(val))
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.PutObject(objectKey+"2", strings.NewReader(val))
+	if err != nil {
+		HandleError(err)
+	}
+
+	delRes, err = bucket.DeleteObjects([]string{objectKey + "1", objectKey + "2"},
+		oss.DeleteObjectsQuiet(false))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Detail Del Res:", delRes)
+
+	lsRes, err = bucket.ListObjects()
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Objects:", getObjectsFormResponse(lsRes))
+
+	// 场景4:删除多个Object,简单模式返回的消息体中只包含删除出错的Object结果
+	err = bucket.PutObject(objectKey+"1", strings.NewReader(val))
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.PutObject(objectKey+"2", strings.NewReader(val))
+	if err != nil {
+		HandleError(err)
+	}
+
+	delRes, err = bucket.DeleteObjects([]string{objectKey + "1", objectKey + "2"}, oss.DeleteObjectsQuiet(true))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Sample Del Res:", delRes)
+
+	lsRes, err = bucket.ListObjects()
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Objects:", getObjectsFormResponse(lsRes))
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("DeleteObjectSample completed")
+}

+ 143 - 0
sample/get_object.go

@@ -0,0 +1,143 @@
+package sample
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"strconv"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// GetObjectSample Get Object Sample
+func GetObjectSample() {
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 上传对象
+	err = bucket.PutObjectFromFile(objectKey, localFile)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1:下载object存储到ReadCloser,注意需要Close
+	body, err := bucket.GetObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	data, err := ioutil.ReadAll(body)
+	body.Close()
+	if err != nil {
+		HandleError(err)
+	}
+	data = data // use data
+
+	// 场景2:下载object存储到bytes数组,适合小对象
+	buf := new(bytes.Buffer)
+	body, err = bucket.GetObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	io.Copy(buf, body)
+	body.Close()
+
+	// 场景3:下载object存储到本地文件,用户打开文件
+	fd, err := os.OpenFile("mynewfile-1.jpg", os.O_WRONLY|os.O_CREATE, 0660)
+	if err != nil {
+		HandleError(err)
+	}
+	defer fd.Close()
+
+	body, err = bucket.GetObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	io.Copy(fd, body)
+	body.Close()
+
+	// 场景4:下载object存储到本地文件
+	err = bucket.GetObjectToFile(objectKey, "mynewfile-2.jpg")
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景5:满足约束条件下载,否则返回错误。GetObjectToFile具有相同功能。
+	// 修改时间,约束条件满足,执行下载
+	body, err = bucket.GetObject(objectKey, oss.IfModifiedSince(pastDate))
+	if err != nil {
+		HandleError(err)
+	}
+	body.Close()
+	// 修改时间,约束条件不满足,不执行下载
+	_, err = bucket.GetObject(objectKey, oss.IfUnmodifiedSince(pastDate))
+	if err == nil {
+		HandleError(err)
+	}
+
+	meta, err := bucket.GetObjectDetailedMeta(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	md5 := meta.Get(oss.HTTPHeaderEtag)
+	// 校验内容,约束条件满足,执行下载
+	body, err = bucket.GetObject(objectKey, oss.IfMatch(md5))
+	if err != nil {
+		HandleError(err)
+	}
+	body.Close()
+
+	// 校验内容,约束条件不满足,不执行下载
+	body, err = bucket.GetObject(objectKey, oss.IfNoneMatch(md5))
+	if err == nil {
+		HandleError(err)
+	}
+
+	// 场景6:指定value的开始结束位置下载object,可以实现断点下载。GetObjectToFile具有相同功能。
+	meta, err = bucket.GetObjectDetailedMeta(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Object Meta:", meta[oss.HTTPHeaderContentLength])
+
+	var partSize int64 = 100 * 1024
+	objectSize, err := strconv.ParseInt(meta.Get(oss.HTTPHeaderContentLength), 10, 0)
+	fd, err = os.OpenFile("myfile.jpg", os.O_WRONLY|os.O_CREATE, 0660)
+	if err != nil {
+		HandleError(err)
+	}
+	defer fd.Close()
+
+	for i := int64(0); i < objectSize; i += partSize {
+		option := oss.Range(i, oss.GetPartEnd(i, objectSize, partSize))
+		body, err := bucket.GetObject(objectKey, option)
+		if err != nil {
+			HandleError(err)
+		}
+		io.Copy(fd, body)
+		body.Close()
+	}
+
+	// 场景7:内容进行 GZIP压缩传输的用户。GetObject/GetObjectToWriter具有相同功能。
+	err = bucket.PutObjectFromFile(objectKey, htmlLocalFile)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.GetObjectToFile(objectKey, "myhtml.gzip", oss.AcceptEncoding("gzip"))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("GetObjectSample completed")
+}

+ 128 - 0
sample/list_buckets.go

@@ -0,0 +1,128 @@
+package sample
+
+import (
+	"fmt"
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ListBucketsSample List Buckets Sample
+func ListBucketsSample() {
+	var myBuckets = []string{
+		"my-bucket-1",
+		"my-bucket-11",
+		"my-bucket-2",
+		"my-bucket-21",
+		"my-bucket-22",
+		"my-bucket-3",
+		"my-bucket-31",
+		"my-bucket-32"}
+
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// remove other bucket
+	lbr, err := client.ListBuckets()
+	if err != nil {
+		HandleError(err)
+	}
+
+	for _, bucket := range lbr.Buckets {
+		err = client.DeleteBucket(bucket.Name)
+		if err != nil {
+			//HandleError(err)
+		}
+	}
+
+	// 创建bucket
+	for _, bucketName := range myBuckets {
+		err = client.CreateBucket(bucketName)
+		if err != nil {
+			HandleError(err)
+		}
+	}
+
+	// 场景1:使用默认参数参数
+	lbr, err = client.ListBuckets()
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("my buckets:", lbr.Buckets)
+
+	// 场景2:指定最大返回数量
+	lbr, err = client.ListBuckets(oss.MaxKeys(3))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("my buckets max num:", lbr.Buckets)
+
+	// 场景3:返回指定前缀的Bucket
+	lbr, err = client.ListBuckets(oss.Prefix("my-bucket-2"))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("my buckets prefix :", lbr.Buckets)
+
+	// 场景4:指定从某个之后返回
+	lbr, err = client.ListBuckets(oss.Marker("my-bucket-22"))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("my buckets marker :", lbr.Buckets)
+
+	// 场景5:分页获取所有bucket,每次返回3个
+	marker := oss.Marker("")
+	for {
+		lbr, err = client.ListBuckets(oss.MaxKeys(3), marker)
+		if err != nil {
+			HandleError(err)
+		}
+		marker = oss.Marker(lbr.NextMarker)
+		fmt.Println("my buckets page :", lbr.Buckets)
+		if !lbr.IsTruncated {
+			break
+		}
+	}
+
+	// 场景6:分页所有获取从某个之后的bucket,每次返回3个
+	marker = oss.Marker("my-bucket-22")
+	for {
+		lbr, err = client.ListBuckets(oss.MaxKeys(3), marker)
+		if err != nil {
+			HandleError(err)
+		}
+		marker = oss.Marker(lbr.NextMarker)
+		fmt.Println("my buckets marker&page :", lbr.Buckets)
+		if !lbr.IsTruncated {
+			break
+		}
+	}
+
+	// 场景7:分页所有获取前缀的bucket,每次返回3个
+	pre := oss.Prefix("my-bucket-2")
+	marker = oss.Marker("")
+	for {
+		lbr, err = client.ListBuckets(oss.MaxKeys(3), pre, marker)
+		if err != nil {
+			HandleError(err)
+		}
+		pre = oss.Prefix(lbr.Prefix)
+		marker = oss.Marker(lbr.NextMarker)
+		fmt.Println("my buckets prefix&page :", lbr.Buckets)
+		if !lbr.IsTruncated {
+			break
+		}
+	}
+
+	// 删除bucket
+	for _, bucketName := range myBuckets {
+		err = client.DeleteBucket(bucketName)
+		if err != nil {
+			HandleError(err)
+		}
+	}
+
+	fmt.Println("ListsBucketSample completed")
+}

+ 147 - 0
sample/list_objects.go

@@ -0,0 +1,147 @@
+package sample
+
+import (
+	"fmt"
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ListObjectsSample List Objects Sample
+func ListObjectsSample() {
+	var myObjects = []Object{
+		{"my-object-1", ""},
+		{"my-object-11", ""},
+		{"my-object-2", ""},
+		{"my-object-21", ""},
+		{"my-object-22", ""},
+		{"my-object-3", ""},
+		{"my-object-31", ""},
+		{"my-object-32", ""}}
+
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 创建object
+	err = CreateObjects(bucket, myObjects)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1:使用默认参数参数
+	lor, err := bucket.ListObjects()
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("my objects:", getObjectsFormResponse(lor))
+
+	// 场景2:指定最大返回数量
+	lor, err = bucket.ListObjects(oss.MaxKeys(3))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("my objects max num:", getObjectsFormResponse(lor))
+
+	// 场景3:返回指定前缀的Bucket
+	lor, err = bucket.ListObjects(oss.Prefix("my-object-2"))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("my objects prefix :", getObjectsFormResponse(lor))
+
+	// 场景4:指定从某个之后返回
+	lor, err = bucket.ListObjects(oss.Marker("my-object-22"))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("my objects marker :", getObjectsFormResponse(lor))
+
+	// 场景5:分页获取所有object,每次返回3个
+	marker := oss.Marker("")
+	for {
+		lor, err = bucket.ListObjects(oss.MaxKeys(3), marker)
+		if err != nil {
+			HandleError(err)
+		}
+		marker = oss.Marker(lor.NextMarker)
+		fmt.Println("my objects page :", getObjectsFormResponse(lor))
+		if !lor.IsTruncated {
+			break
+		}
+	}
+
+	// 场景6:分页所有获取从某个之后的object,每次返回3个
+	marker = oss.Marker("my-object-22")
+	for {
+		lor, err = bucket.ListObjects(oss.MaxKeys(3), marker)
+		if err != nil {
+			HandleError(err)
+		}
+		marker = oss.Marker(lor.NextMarker)
+		fmt.Println("my objects marker&page :", getObjectsFormResponse(lor))
+		if !lor.IsTruncated {
+			break
+		}
+	}
+
+	// 场景7:分页所有获取前缀的object,每次返回2个
+	pre := oss.Prefix("my-object-2")
+	marker = oss.Marker("")
+	for {
+		lor, err = bucket.ListObjects(oss.MaxKeys(2), marker, pre)
+		if err != nil {
+			HandleError(err)
+		}
+		pre = oss.Prefix(lor.Prefix)
+		marker = oss.Marker(lor.NextMarker)
+		fmt.Println("my objects prefix&page :", getObjectsFormResponse(lor))
+		if !lor.IsTruncated {
+			break
+		}
+	}
+
+	err = DeleteObjects(bucket, myObjects)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景8:prefix和delimiter结合,完成分组功能,ListObjectsResponse.Objects表示不再组中,
+	// ListObjectsResponse.CommonPrefixes分组结果
+	myObjects = []Object{
+		{"fun/test.txt", ""},
+		{"fun/test.jpg", ""},
+		{"fun/movie/001.avi", ""},
+		{"fun/movie/007.avi", ""},
+		{"fun/music/001.mp3", ""},
+		{"fun/music/001.mp3", ""}}
+
+	// 创建object
+	err = CreateObjects(bucket, myObjects)
+	if err != nil {
+		HandleError(err)
+	}
+
+	lor, err = bucket.ListObjects(oss.Prefix("fun/"), oss.Delimiter("/"))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("my objects prefix :", getObjectsFormResponse(lor),
+		"common prefixes:", lor.CommonPrefixes)
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("ListObjectsSample completed")
+}
+
+func getObjectsFormResponse(lor oss.ListObjectsResult) string {
+	var output string
+	for _, object := range lor.Objects {
+		output += object.Key + "  "
+	}
+	return output
+}

+ 223 - 0
sample/multipart_copy.go

@@ -0,0 +1,223 @@
+package sample
+
+import (
+	"fmt"
+	"sync"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// MultipartCopySample Multipart Copy Sample
+func MultipartCopySample() {
+	var objectSrc = "my-object-src"
+	var objectDesc = "my-object-desc"
+
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.PutObjectFromFile(objectSrc, localFile)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1:大文件分片拷贝,按照文件片大小分片
+	chunks, err := oss.SplitFileByPartNum(localFile, 3)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err := bucket.InitiateMultipartUpload(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts := []oss.UploadPart{}
+	for _, chunk := range chunks {
+		part, err := bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size,
+			chunk.Number)
+		if err != nil {
+			HandleError(err)
+		}
+		parts = append(parts, part)
+	}
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:大文件分片拷贝,按照指定文件片数
+	chunks, err = oss.SplitFileByPartSize(localFile, 1024*100)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err = bucket.InitiateMultipartUpload(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts = []oss.UploadPart{}
+	for _, chunk := range chunks {
+		part, err := bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size,
+			chunk.Number)
+		if err != nil {
+			HandleError(err)
+		}
+		parts = append(parts, part)
+	}
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景3:大文件分片拷贝,初始化时指定对象属性
+	chunks, err = oss.SplitFileByPartNum(localFile, 3)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err = bucket.InitiateMultipartUpload(objectDesc, oss.Meta("myprop", "mypropval"))
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts = []oss.UploadPart{}
+	for _, chunk := range chunks {
+		part, err := bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size,
+			chunk.Number)
+		if err != nil {
+			HandleError(err)
+		}
+		parts = append(parts, part)
+	}
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景4:大文件分片拷贝,每个分片可以有线程/进程/机器独立完成,下面示例是每个线程拷贝一个分片
+	partNum := 4
+	chunks, err = oss.SplitFileByPartNum(localFile, partNum)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err = bucket.InitiateMultipartUpload(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 并发拷贝分片上传
+	var waitgroup sync.WaitGroup
+	var ps = make([]oss.UploadPart, partNum)
+	for _, chunk := range chunks {
+		waitgroup.Add(1)
+		go func(chunk oss.FileChunk) {
+			part, err := bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size,
+				chunk.Number)
+			if err != nil {
+				HandleError(err)
+			}
+			ps[chunk.Number-1] = part
+			waitgroup.Done()
+		}(chunk)
+	}
+
+	// 等待拷贝完成
+	waitgroup.Wait()
+
+	// 通知完成
+	_, err = bucket.CompleteMultipartUpload(imur, ps)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景5:大文件分片拷贝,对拷贝有约束条件,满足时候拷贝,不满足时报错
+	chunks, err = oss.SplitFileByPartNum(localFile, 3)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err = bucket.InitiateMultipartUpload(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts = []oss.UploadPart{}
+	for _, chunk := range chunks {
+		constraint := oss.CopySourceIfMatch("InvalidETag")
+		_, err := bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size,
+			chunk.Number, constraint)
+		fmt.Println(err)
+	}
+
+	err = bucket.AbortMultipartUpload(imur)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景6:大文件分片拷贝一部分后,中止上传,上传的数据将丢弃,UploadId也将无效
+	chunks, err = oss.SplitFileByPartNum(localFile, 3)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err = bucket.InitiateMultipartUpload(objectDesc)
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts = []oss.UploadPart{}
+	for _, chunk := range chunks {
+		part, err := bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size,
+			chunk.Number)
+		if err != nil {
+			HandleError(err)
+		}
+		parts = append(parts, part)
+	}
+
+	err = bucket.AbortMultipartUpload(imur)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("MultipartCopySample completed")
+}

+ 230 - 0
sample/multipart_upload.go

@@ -0,0 +1,230 @@
+package sample
+
+import (
+	"fmt"
+	"os"
+	"sync"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// MultipartUploadSample Multipart Upload Sample
+func MultipartUploadSample() {
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1:大文件分片上传,按照文件片大小分片
+	chunks, err := oss.SplitFileByPartNum(localFile, 3)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err := bucket.InitiateMultipartUpload(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts := []oss.UploadPart{}
+	for _, chunk := range chunks {
+		part, err := bucket.UploadPartFromFile(imur, localFile, chunk.Offset,
+			chunk.Size, chunk.Number)
+		if err != nil {
+			HandleError(err)
+		}
+		parts = append(parts, part)
+	}
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:大文件分片上传,按照指定文件片数
+	chunks, err = oss.SplitFileByPartSize(localFile, 1024*100)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err = bucket.InitiateMultipartUpload(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts = []oss.UploadPart{}
+	for _, chunk := range chunks {
+		part, err := bucket.UploadPartFromFile(imur, localFile, chunk.Offset,
+			chunk.Size, chunk.Number)
+		if err != nil {
+			HandleError(err)
+		}
+		parts = append(parts, part)
+	}
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	chunks = []oss.FileChunk{
+		{Number: 1, Offset: 0 * 1024 * 1024, Size: 1024 * 1024},
+		{Number: 2, Offset: 1 * 1024 * 1024, Size: 1024 * 1024},
+		{Number: 3, Offset: 2 * 1024 * 1024, Size: 1024 * 1024},
+	}
+
+	// 创建3:大文件上传,您自己打开文件,传入句柄
+	chunks, err = oss.SplitFileByPartNum(localFile, 3)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fd, err := os.Open(localFile)
+	if err != nil {
+		HandleError(err)
+	}
+	defer fd.Close()
+
+	imur, err = bucket.InitiateMultipartUpload(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts = []oss.UploadPart{}
+	for _, chunk := range chunks {
+		fd.Seek(chunk.Offset, os.SEEK_SET)
+		part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number)
+		if err != nil {
+			HandleError(err)
+		}
+		parts = append(parts, part)
+	}
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景4:大文件分片上传,初始化时指定对象属性
+	chunks, err = oss.SplitFileByPartNum(localFile, 3)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err = bucket.InitiateMultipartUpload(objectKey, oss.Meta("myprop", "mypropval"))
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts = []oss.UploadPart{}
+	for _, chunk := range chunks {
+		part, err := bucket.UploadPartFromFile(imur, localFile, chunk.Offset,
+			chunk.Size, chunk.Number)
+		if err != nil {
+			HandleError(err)
+		}
+		parts = append(parts, part)
+	}
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景5:大文件分片上传,每个分片可以有线程/进程/机器独立完成,下面示例是每个线程上传一个分片
+	partNum := 4
+	chunks, err = oss.SplitFileByPartNum(localFile, partNum)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err = bucket.InitiateMultipartUpload(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 并发上传分片
+	var waitgroup sync.WaitGroup
+	var ps = make([]oss.UploadPart, partNum)
+	for _, chunk := range chunks {
+		waitgroup.Add(1)
+		go func(chunk oss.FileChunk) {
+			part, err := bucket.UploadPartFromFile(imur, localFile, chunk.Offset,
+				chunk.Size, chunk.Number)
+			if err != nil {
+				HandleError(err)
+			}
+			ps[chunk.Number-1] = part
+			waitgroup.Done()
+		}(chunk)
+	}
+
+	// 等待上传完成
+	waitgroup.Wait()
+
+	// 通知完成
+	_, err = bucket.CompleteMultipartUpload(imur, ps)
+	if err != nil {
+		HandleError(err)
+	}
+
+	err = bucket.DeleteObject(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景6:大文件分片上传一部分后,中止上传,上传的数据将丢弃,UploadId也将无效
+	chunks, err = oss.SplitFileByPartNum(localFile, 3)
+	if err != nil {
+		HandleError(err)
+	}
+
+	imur, err = bucket.InitiateMultipartUpload(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	parts = []oss.UploadPart{}
+	for _, chunk := range chunks {
+		part, err := bucket.UploadPartFromFile(imur, localFile, chunk.Offset,
+			chunk.Size, chunk.Number)
+		if err != nil {
+			HandleError(err)
+		}
+		parts = append(parts, part)
+	}
+
+	err = bucket.AbortMultipartUpload(imur)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("MultipartUploadSample completed")
+}

+ 50 - 0
sample/new_bucket.go

@@ -0,0 +1,50 @@
+package sample
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// NewBucketSample New Bucket Sample
+func NewBucketSample() {
+	// New Client
+	client, err := oss.New(endpoint, accessID, accessKey)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// Create Bucket
+	err = client.CreateBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// New Bucket
+	bucket, err := client.Bucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// Put Object,上传一个Object
+	var objectName = "myobject"
+	err = bucket.PutObject(objectName, strings.NewReader("MyObjectValue"))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// Delete Object,删除Object
+	err = bucket.DeleteObject(objectName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 删除bucket
+	err = client.DeleteBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("NewBucketSample completed")
+}

+ 44 - 0
sample/object_acl.go

@@ -0,0 +1,44 @@
+package sample
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ObjectACLSample Set/Get Object ACL Sample
+func ObjectACLSample() {
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 创建object
+	err = bucket.PutObject(objectKey, strings.NewReader("YoursObjectValue"))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景:设置Bucket ACL,可选权限有ACLPrivate、ACLPublicRead、ACLPublicReadWrite
+	err = bucket.SetObjectACL(objectKey, oss.ACLPrivate)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 查看Object ACL,返回的权限标识为private、public-read、public-read-write其中之一
+	goar, err := bucket.GetObjectACL(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Object ACL:", goar.ACL)
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("ObjectACLSample completed")
+}

+ 73 - 0
sample/object_meta.go

@@ -0,0 +1,73 @@
+package sample
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// ObjectMetaSample Set/Get Object Meta Sample
+func ObjectMetaSample() {
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 创建object
+	err = bucket.PutObject(objectKey, strings.NewReader("YoursObjectValue"))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景:设置Bucket Meta,可以设置一个或多个属性。
+	// 注意:Meta不区分大小写
+	options := []oss.Option{
+		oss.Expires(futureDate),
+		oss.Meta("myprop", "mypropval")}
+	err = bucket.SetObjectMeta(objectKey, options...)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景1:查看Object的meta,只返回少量基本meta信息,如ETag、Size、LastModified。
+	props, err := bucket.GetObjectMeta(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Object Meta:", props)
+
+	// 场景2:查看Object的所有Meta,包括自定义的meta。
+	props, err = bucket.GetObjectDetailedMeta(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Expires:", props.Get("Expires"))
+
+	// 场景3:查看Object的所有Meta,符合约束条件返回,不符合约束条件保存,包括自定义的meta。
+	props, err = bucket.GetObjectDetailedMeta(objectKey, oss.IfUnmodifiedSince(futureDate))
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("MyProp:", props.Get("X-Oss-Meta-Myprop"))
+
+	_, err = bucket.GetObjectDetailedMeta(objectKey, oss.IfModifiedSince(futureDate))
+	if err == nil {
+		HandleError(err)
+	}
+
+	goar, err := bucket.GetObjectACL(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Object ACL:", goar.ACL)
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("ObjectMetaSample completed")
+}

+ 93 - 0
sample/put_object.go

@@ -0,0 +1,93 @@
+package sample
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+)
+
+// PutObjectSample Put Object Sample
+func PutObjectSample() {
+	// 创建Bucket
+	bucket, err := GetTestBucket(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	var val = "花间一壶酒,独酌无相亲。 举杯邀明月,对影成三人。"
+
+	// 场景1:上传object,value是字符串
+	err = bucket.PutObject(objectKey, strings.NewReader(val))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景2:上传object,value是[]byte
+	err = bucket.PutObject(objectKey, bytes.NewReader([]byte(val)))
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景3:上传本地文件,您自己打开文件,传入句柄
+	fd, err := os.Open(localFile)
+	if err != nil {
+		HandleError(err)
+	}
+	defer fd.Close()
+
+	err = bucket.PutObject(objectKey, fd)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景4:上传object,上传时指定对象属性
+	options := []oss.Option{
+		oss.Expires(futureDate),
+		oss.ObjectACL(oss.ACLPublicRead),
+		oss.Meta("myprop", "mypropval"),
+	}
+	err = bucket.PutObject(objectKey, strings.NewReader(val), options...)
+	if err != nil {
+		HandleError(err)
+	}
+
+	props, err := bucket.GetObjectDetailedMeta(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Object Meta:", props)
+
+	// 场景5:上传本地文件
+	err = bucket.PutObjectFromFile(objectKey, localFile)
+	if err != nil {
+		HandleError(err)
+	}
+
+	// 场景6:上传本地文件,上传时指定对象属性
+	options = []oss.Option{
+		oss.Expires(futureDate),
+		oss.ObjectACL(oss.ACLPublicRead),
+		oss.Meta("myprop", "mypropval"),
+	}
+	err = bucket.PutObjectFromFile(objectKey, localFile, options...)
+	if err != nil {
+		HandleError(err)
+	}
+
+	props, err = bucket.GetObjectDetailedMeta(objectKey)
+	if err != nil {
+		HandleError(err)
+	}
+	fmt.Println("Object Meta:", props)
+
+	// 删除object和bucket
+	err = DeleteTestBucketAndObject(bucketName)
+	if err != nil {
+		HandleError(err)
+	}
+
+	fmt.Println("PutObjectSample completed")
+}