.NET SDK 手册

文件上传

在 NOS 中用户的基本操作单元是对象,亦可以理解为文件,NOS C# SDK 提供了丰富的上传接口,可以通过以下的方式上传文件:

  • 流式上传
  • 单块上传
  • 分块上传

流式上传、单块上传最大为 100 M;分块上传除最后一块外,分块不能小于 16k,每一个分块不能大于 100M,最多能上传 10000 个分块,即分块上传的文件最大支持 1 T。

流式上传

您可以使用 NosClient.PutObject 上传一个一个 Stream 中的内容,具体实现如下:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

/// <summary>
/// 流式上传
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
/// <param name="content">需要上传的流</param>
public void PutObject(string bucket, string key, Stream content)
{
  try
  {
    nosClient.PutObject(bucket, key, content);
    Console.WriteLine("Put object:{0} succeeded", key);
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
}

Attention

上传的流内容不超过 100M

单块上传

您可以使用 NosClient.PutObject 上传文件内容,具体实现如下:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

/// <summary>
/// 上传文件
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
/// <param name="fileToUpload">上传的文件</param>
public void PutObject(string bucket, string key, string fileToUpload)
{
  try
  {
    nosClient.PutObject(bucket, key, fileToUpload);
    Console.WriteLine("Put object:{0} succeeded", key);
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
}

Attention

上传的文件内容不超过 100M

分块上传

除了通过 putObject 接口上传文件到 NOS 之外,NOS 还提供了另外一种上传模式-分块上传,用户可以在如下应用场景内(但不限于此),使用分块上传模式,如:

  • 需支持断点上传
  • 上传超过 100M 的文件
  • 网络条件较差,经常和 NOS 服务器断开连接
  • 上传文件之前无法确定文件的大小

分块上传一般流程如下所示:

  • 初始化一个分块上传任务 (InitiateMultipartUpload)
  • 上传分块 (UploadPart)
  • 完成分块上传 (CompleteMultipartUpload) 或者取消分块上传 (AbortMultipartUpload)

初始化分块上传

您可以使用 NosClient.InitiateMultipartUpload 初始化分块上传,具体实现如下:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

/// <summary>
/// 初始化分块上传
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
public string InitiateMultipartUpload(String bucket, String key)
{
  string uploadId = "";
  try
  {
    var request = new InitiateMultipartUploadRequest(bucket, key);
    var result = nosClient.InitiateMultipartUpload(request);
    uploadId = result.UploadId;
    Console.WriteLine("Init multipart upload succeeded");
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }

  return uploadId;
}

上传分块

您可以使用 NosClient.UploadPart 上传分块,具体实现如下:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

/// <summary>
/// 上传分块
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
/// <param name="fileToUpload">上传的文件</param>
/// <param name="uploadId">分块上传的 ID</param>
/// <param name="partSize">分块大小</param>
public List<PartETag> UploadParts(String bucket, String key, String fileToUpload,
String uploadId, int partSize)
{
  // 计算片个数
  var fi = new FileInfo(fileToUpload);
  var fileSize = fi.Length;
  var partCount = fileSize / partSize;
  if (fileSize % partSize != 0)
  {
    partCount++;
  }

  // 开始分片上传
  var partETags = new List<PartETag>();
  try
  {
    using (var fs = File.Open(fileToUpload, FileMode.Open))
    {
      for (var i = 0; i < partCount; i++)
      {
        var skipBytes = (long)partSize * i;
        //定位到本次上传片应该开始的位置
        fs.Seek(skipBytes, 0);
        var size = (partSize < fileSize - skipBytes) ? partSize : (fileSize - skipBytes);
        var request = new UploadPartRequest(bucket, objectName, uploadId)
        {
          Content = fs,
          PartSize = size,
          PartNumber = i + 1
        };

        var result = nosClient.UploadPart(request);

        partETags.Add(result.PartETag);
      }
    }
      Console.WriteLine("Put multipart upload succeeded");
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
  return partETags;
}

完成分块上传

您可以使用 NosClient.CompleteMultipartUpload 完成上传分块,具体实现如下:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

/// <summary>
/// 完成分块上传
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
/// <param name="uploadId">分块上传的 ID</param>
/// <param name="partETags">PartETag 对象,它是上传块的 ETag 与块编号(PartNumber)的组合</param>
public void CompleteUploadPart(String bucket, String key,
String uploadId, List<PartETag> partETags)
{
  try
  {
    var completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucket, key, uploadId);
    foreach (var partETag in partETags)
    {
    completeMultipartUploadRequest.PartETags.Add(partETag);
    }

    nosClient.CompleteMultipartUpload(completeMultipartUploadRequest);
    Console.WriteLine("Complete Upload Part succeeded");
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
}

分块上传示例

下面通过一个完整的示例说明了如何进行分片上传操作,可以参考下面代码:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

/// <summary>
/// 分块上传示例
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
/// <param name="fileToUpload">上传的文件</param>
public void UploadPart(string bucket, string key, string fileToUpload)
{
  try
  {
    /*
    * 初始化一个分块上传事件
    * */
    var initRequest = new InitiateMultipartUploadRequest(bucket, key);
    var initResult = nosClient.InitiateMultipartUpload(initRequest);

    /*
    * 上传分块
    * */

    // 设置每块为 1M
    const int partSize = 1024 * 1024 * 1;

    var partFile = new FileInfo(fileToUpload);
    var fileSize = partFile.Length;
    // 计算分块数目
    var partCount = fileSize / partSize;
    if (fileSize % partSize != 0)
    {
      partCount++;
    }

    // 新建一个 List 保存每个分块上传后的 ETag 和 PartNumber
    var partETags = new List<PartETag>();
    //upload the file
    using (var fs = new FileStream(partFile.FullName, FileMode.Open))
    {
      for (var i = 0; i < partCount; i++)
      {
        // 跳到每个分块的开头
        long skipBytes = partSize * i;
        fs.Position = skipBytes;

        // 计算每个分块的大小
        var size = partSize < partFile.Length - skipBytes? partSize : partFile.Length - skipBytes;

        // 创建 UploadPartRequest,上传分块
        var uploadPartRequest = new UploadPartRequest(bucket, key, initResult.UploadId)
        {
          Content = fs,
          PartSize = size,
          PartNumber = (i + 1)
        };

        var uploadPartResult = nosClient.UploadPart(uploadPartRequest);

        // 将返回的 PartETag 保存到 List 中。
        partETags.Add(uploadPartResult.PartETag);
      }

          /*
     * 完成分块上传事件
     * */
     var completeRequest = new CompleteMultipartUploadRequest(bucket, key, initResult.UploadId);
     foreach (var partETag in partETags)
     {
       completeRequest.PartETags.Add(partETag);
     }
     nosClient.CompleteMultipartUpload(completeRequest);
         fs.Close();
     }
     Console.WriteLine("Upload Part succeeded");
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
}

Note

上面程序一共分为三个步骤:

  1. initiate
  2. uploadPart
  3. complete

UploadPart 方法要求除最后一个 Part 以外,其他的 Part 大小都要大于或等于 16K。但是 Upload Part 接口并不会立即校验上传 Part 的大小(因为不知道是否为最后一块);只有当 Complete Multipart Upload 的时候才会校验。 Part 号码的范围是 1~10000。如果超出这个范围,NOS 将返回 InvalidArgument 的错误码。 分块上传除最后一块外,分块不能小于 16k,每一个分块不能大于 100M。 每次上传 Part 时都要把流定位到此次上传块开头所对应的位置。 分片上传任务初始化或上传部分分片后,可以使用 abortMultipartUpload 接口中止分片上传事件。当分片上传事件被中止后,就不能再使用这个 Upload ID 做任何操作,已经上传的分片数据也会被删除。 每次上传 Part 之后,NOS 的返回结果会包含一个 PartETag 对象,它是上传块的 ETag 与块编号(PartNumber)的组合。在后续完成分片上传的步骤中会用到它,因此我们需要将其保存起来,然后在第三步 complete 的时候使用,具体操作参考上面代码。

取消分块上传

您可以使用 NosClient.AbortMultipartUpload 取消上传事件,具体实现如下:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

/// <summary>
/// 取消分块上传
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
/// <param name="uploadId">分块上传的 ID</param>
public void AbortMultipartUpload(string bucket, string key, string uploadId)
{
  try
  {
    var abortRequest = new AbortMultipartUploadRequest(bucket, key, uploadId);
    nosClient.AbortMultipartUpload(abortRequest);
    Console.WriteLine("Abort Multipart Upload succeeded");
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
}

查看已经上传的分片

查看已经上传的分片可以罗列出指定 Upload ID(InitiateMultipartUpload 时获取) 所属的所有已经上传成功的分片,您可以通过 NosClient.ListParts 接口获取已经上传的分片,可以参考以下代码:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

// <summary>
/// 列出已上传的分块
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
/// <param name="uploadId">分块上传的 ID</param>
public void ListParts(string bucket, string key, string uploadId)
{
  try
  {
    var listPartsRequest = new ListPartsRequest(bucket, key, uploadId);
    var result = nosClient.ListParts(listPartsRequest);
    Console.WriteLine("List parts succeeded");

    foreach (var part in result.Parts)
    {
      Console.WriteLine(part.ToString());
    }
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
}

查看已经上传的分片可以指定以下参数:

参数描述
MaxParts响应中的 limit 个数 类型:整型
PartMumberMarker分块号的界限,只有更大的分块号会被列出来。类型:字符串

查看当前正在进行的分片上传任务

查看正在进行的分片上传任务可以罗列出正在进行,还未完成的分片上传任务,您可以通过 NosClient.ListMultipartUploads 接口当前的上传任务,可以参考以下代码:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

// <summary>
/// 查看当前正在进行的分片上传任务
/// </summary>
/// <param name="bucket">桶名</param>
public void ListMultipartUploads(string bucket)
{
  try
  {
    var listMultipartUploadsRequest = new ListMultipartUploadsRequest(bucket){
      KeyMarker = "",
      MaxUploads = 10
    };
    var result = nosClient.ListMultipartUploads(listMultipartUploadsRequest);

    Console.WriteLine("List multi Uploads succeeded");
    foreach (var mu in result.MultipartUploads)
    {
      Console.WriteLine("Key:{0}, UploadId:{1}", mu.Key , mu.UploadId);
    }
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
}

上述代码中使用的 options 参数如下:

参数值描述
KeyMarker指定某一 uploads key,只有大于该 KeyMarker 的才会被列出
MaxUploads最多返回 MaxUploads 条记录,默认 1000

设置文件元信息

文件元数据 (object meta),是上传到 NOS 的文件属性描述信息,分为 http 标准属性和用户自定义元数据。文件元信息可以在各种上传方式 (流式上传、单块上传、分块上传) 时进行设置。

设定 http header

NOS 允许用户自定义 http Header。http header 相关信息请参考 RFC2616,几个常用的 header 说明如下:

名称描述默认值
Content-MD5文件数据校验,设置了该值后 NOS 会启用文件内容 MD5 校验,把您提供的 MD5 与文件的 MD5 比较,不一致会抛出错误
Content-Type文件的 MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据 Key 或文件名的扩展名生成,如果没有扩展名则填默认值application/octet-stream
Content-Disposition指示 MINME 用户代理如何显示附加的文件,打开或下载,及文件名称
Content-Length上传的文件的长度,超过流/文件的长度会截断,不足为实际值流/文件实际长度
Expires缓存过期时间,NOS 未使用,格式是格林威治时间(GMT)
Cache-Control指定该 Object 被下载时的网页的缓存行为

下面的源代码实现了上传文件时设置 Http header:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

/// <summary>
/// 上传文件
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
/// <param name="fileToUpload">上传的文件</param>
public void putObject(string bucket, string key, string fileToUpload)
{
  try
  {
    using (var fs = File.Open(fileToUpload, FileMode.Open))
    {
      var metadata = new ObjectMetadata
      {
        CacheControl = "no-cache",
        ContentDisposition = "abc.zip",
        ContentEncoding = "gzip",
      };
    //user metadata
    metadata.UserMetadata.Add("x-nos-meta-my-metaMyKey1", "MyValue1");
    metadata.UserMetadata.Add("x-nos-meta-my-metaMyKey2", "MyValue2");
    nosClient.PutObject(bucket, key, fs, metadata);

    Console.WriteLine("Put object succeeded");
    }
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
}

Attention

各种上传方式(包括流式上传、单块上传、分块上传)都可以设置元数据信息。流式上传设置元数据的方式与单块上传相同,分块上传在构造 ListMultipartUploadsRequest 对象时传入 ObjectMetadata 参数,如:ListMultipartUploadsRequest.ObjectMetadata; 文件的元信息可以通过 NosClient.GetObjectMetadata 获取;

user meta 的名称以”x-nos-meta-“开头,例如设置为:’x-nos-meta-name’,读取名字为 x-nos-meta-name 的参数即可;

上传时校验 MD5

下面的代码实现了上传时校验 MD5:

// 初始化 NosClient
var nosClient = new NosClient(endpoint, accessKeyId, accessKeySecret);

/// <summary>
/// 上传文件
/// </summary>
/// <param name="bucket">桶名</param>
/// <param name="key">对象名</param>
/// <param name="fileToUpload">上传的文件</param>
public void putObject(string bucket, string key, string fileToUpload)
{
  try
  {
    string md5 = "";
    using (var fs = File.Open(fileToUpload, FileMode.Open))
    {
      md5 = NosUtils.GetMD5FromStream(fs);
      var metadata = new ObjectMetadata
      {
        ContentMD5 = md5
      };
      nosClient.PutObject(bucket, key, fs, metadata);

      Console.WriteLine("Put object succeeded");
    }
  }
  catch (NosException ex)
  {
    Console.WriteLine("Failed with HTTPStatus: {0}; \nErrorCode: {1}; \nErrorMessage: {2}; \nRequestID:{3}; \nResource:{4}",
    ex.StatusCode, ex.ErrorCode, ex.Message, ex.RequestId, ex.Resource);
  }
  catch (Exception ex)
  {
    Console.WriteLine("Failed with error info: {0}", ex.Message);
  }
}

Attention

校验 MD5 会有一定的性能损失