将文件上传到 Google 云端硬盘时多部分正文格式不正确



尝试使用其/upload 端点将文件上传到 Google 云端硬盘的问题时遇到一些麻烦。即使我尝试将简单的纯文本作为文件上传Malformed multipart body.我也不断收到错误。

以下 .net c# 代码用于创建请求:

string fileName = "test.txt";
string fileContent = "The quick brown fox jumps over the lazy dog";
var fileStream = GenerateStreamFromString(fileContent); // simple text string to Stream conversion
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
var multiPartFormDataContent = new MultipartFormDataContent("not_so_random_boundary");
// rfc2387 headers with boundary
multiPartFormDataContent.Headers.Remove("Content-Type");
multiPartFormDataContent.Headers.TryAddWithoutValidation("Content-Type", "multipart/related; boundary=" + "not_so_random_boundary");
// metadata part
multiPartFormDataContent.Add(new StringContent("{"name":"" + fileName + "","mimeType":"text/plain","parents":["" + folder.id + ""]}", Encoding.UTF8, "application/json"));
// media part (file)
multiPartFormDataContent.Add(streamContent);
var response_UploadFile = await httpClient.PostAsync(string.Format("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"), multiPartFormDataContent);

我记录以下请求:

Method: POST,
RequestUri: 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart',
Version: 1.1,
Content: System.Net.Http.MultipartFormDataContent,
Headers: { Authorization: Bearer /*snip*/ Content-Type: multipart/related; boundary=not_so_random_boundary }

具有以下请求内容(已修饰(:

--not_so_random_boundary
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data
{"name":"test.txt","mimeType":"text/plain","parents":["/*snip*/"]}
--not_so_random_boundary
Content-Type: text/plain
Content-Disposition: form-data
The quick brown fox jumps over the lazy dog
--not_so_random_boundary--

我花了一整天的时间,它让我走到了这一步。我有一种预感,这个问题是愚蠢的,但我就是想不通。

有人可以把目光投向这个,也许你可以发现我犯了一个非常有帮助的错误?

#

##ref:发送分段上传请求

RFC 2387

多亏@Tanaike建议,我们发现了我的代码的问题。

事实证明,虽然文档(或任何代码示例(中没有特别提及它,但将Content-Disposition: form-data; name="metadata"添加到请求正文的StringContent部分会使一切变得不同。

最终请求可以重写如下:

// sample file (controlled test example)
string fileName = "test.txt";
string fileType = "text/plain";
string fileContent = "The quick brown fox jumps over the lazy dog";
var fileStream = GenerateStreamFromString(fileContent); // test file
// media part (file)
//var fileStream = File.Open(path_to_file, FileMode.Open, FileAccess.Read); // you should read file from disk
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
streamContent.Headers.ContentDisposition.Name = ""file"";
// metadata part
var stringContent = new StringContent("{"name":"" + fileName + "","mimeType":"" + fileType + "","parents":["" + folder.id + ""]}", Encoding.UTF8, "application/json");
stringContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
stringContent.Headers.ContentDisposition.Name = ""metadata"";
var boundary = DataTime.Now.Ticks.ToString(); // or hard code a string like in my previous code
var multiPartFormDataContent = new MultipartFormDataContent(boundary);
// rfc2387 headers with boundary
multiPartFormDataContent.Headers.Remove("Content-Type");
multiPartFormDataContent.Headers.TryAddWithoutValidation("Content-Type", "multipart/related; boundary=" + boundary);
// request body
multiPartFormDataContent.Add(stringContent); // metadata part - must be first part in request body
multiPartFormDataContent.Add(streamContent); // media part - must follow metadata part
var response_UploadFile = await httpClient.PostAsync(string.Format("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"), multiPartFormDataContent);

请注意,通常会将文件名和内容类型添加为StreamContent但这些标头会被 Google Drive API 忽略。这是故意完成的,因为 API 希望接收具有相关属性的元数据对象。(以下标头已从上面的代码示例中删除,但将保留在此处以供将来参考(

streamContent.Headers.ContentDisposition.FileName = """ + fileName + """;
streamContent.Headers.ContentType = new MediaTypeHeaderValue(fileType);

请注意,如果您想将文件上传到 Google 云端硬盘中的子文件夹,则只需指定"parents":["{folder_id}"]属性。

希望这将在未来对其他人有所帮助。

我认为multipart/related的请求正文的结构可能不正确。那么修改如下怎么样?

修改的请求正文:

--not_so_random_boundary
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name="metadata"
{"name":"test.txt","mimeType":"text/plain","parents":["/*snip*/"]}
--not_so_random_boundary
Content-Type: text/plain
Content-Disposition: form-data; name="file"
The quick brown fox jumps over the lazy dog
--not_so_random_boundary--
  • 请注意请求正文的换行符。
  • 请为Content-Disposition的每个部分添加name

注意:

  • 现在我可以确认,当上述修改后的请求正文用于https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart作为 POST 方法的端点时,将创建一个具有 The quick brown fox jumps over the lazy dog 内容的test.txt文本文件。

引用:

  • 内容类型
  • 内容处置

如果这不起作用,我深表歉意。

另一种选择是使用Google .net客户端库,让它为您处理上传。

// Upload file Metadata
var fileMetadata = new Google.Apis.Drive.v3.Data.File()
    {
    Name = "Test hello uploaded.txt",
    Parents = new List() {"10krlloIS2i_2u_ewkdv3_1NqcpmWSL1w"}
    };
string uploadedFileId;
// Create a new file on Google Drive
await using (var fsSource = new FileStream(UploadFileName, FileMode.Open, FileAccess.Read))
      {
      // Create a new file, with metadata and stream.
      var request = service.Files.Create(fileMetadata, fsSource, "text/plain");
      request.Fields = "*";
      var results = await request.UploadAsync(CancellationToken.None);
      if (results.Status == UploadStatus.Failed)
         {
         Console.WriteLine($"Error uploading file: {results.Exception.Message}");
         }
          // the file id of the new file we created
          uploadedFileId = request.ResponseBody?.Id;
      }

将文件上传到谷歌云端硬盘

最新更新