You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Google Drive API v3分块上传文件遇400错误及连接重置问题求助

解决Google Drive分块上传的400错误与Connection Reset问题

你的核心问题是未遵循Google Drive可恢复上传(Resumable Upload)的规范,同时代码中存在请求格式、流处理、SSL配置的错误,导致请求被服务器拒绝或中断。以下是具体问题分析和修复方案:

问题分析

  1. 错误的请求体格式:Google Drive分块上传的分块请求不需要使用TIdMultiPartFormDataStream,multipart格式仅用于普通单文件上传,分块请求应直接发送二进制分块数据。
  2. 手动设置Content-Length冲突:你手动指定的分块大小与TIdMultiPartFormDataStream实际生成的流长度不匹配,导致服务器校验失败。
  3. 文件流未正确截取分块:你的代码直接上传了整个文件,而不是指定的0-8388607字节范围,分块范围与实际上传数据不符。
  4. SSL模式配置错误:sslmUnassigned未明确客户端身份,应设置为sslmClient以符合TLS客户端请求规范。
  5. 缺少可恢复会话创建步骤:Google Drive分块上传必须先创建上传会话,获取专用的会话URL后再上传分块,不能直接向普通文件URL发送分块请求。

修复方案

步骤1:创建可恢复上传会话

首先需要发送POST请求创建上传会话,获取Google返回的会话URL,后续所有分块上传都使用这个URL。

function CreateResumableSession(const AAccessToken, AFileMimeType, AFileName: string): string;
var
  zIdHTTP: TIdHTTP;
  zIOHandler: TIdSSLIOHandlerSocketOpenSSL;
  zRequestBody: TStringStream;
begin
  zIdHTTP := TIdHTTP.Create(nil);
  zIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  zRequestBody := TStringStream.Create('', TEncoding.UTF8);
  try
    // 配置SSL为客户端模式,强制TLS 1.2
    zIOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2];
    zIOHandler.SSLOptions.Mode := sslmClient;
    zIOHandler.SSLOptions.VerifyMode := [];
    zIdHTTP.IOHandler := zIOHandler;
    zIdHTTP.HandleRedirects := True;

    // 设置认证和上传元数据头
    zIdHTTP.Request.CustomHeaders.AddValue('Authorization', 'Bearer ' + AAccessToken);
    zIdHTTP.Request.ContentType := 'application/json';
    zIdHTTP.Request.CustomHeaders.AddValue('X-Upload-Content-Type', AFileMimeType);
    zIdHTTP.Request.CustomHeaders.AddValue('X-Upload-Content-Length', IntToStr(TFile.Size(AFileName)));

    // 构建文件元数据(如文件名)
    zRequestBody.WriteString(Format('{"name": "%s"}', [ExtractFileName(AFileName)]));
    zRequestBody.Position := 0;

    try
      // 发送创建会话请求
      zIdHTTP.Post('https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable', zRequestBody);
      // 从响应头提取会话URL
      Result := zIdHTTP.Response.CustomHeaders.Values['Location'];
    except
      on E: Exception do
        raise Exception.Create('会话创建失败: ' + E.Message);
    end;
  finally
    zRequestBody.Free;
    zIOHandler.Free;
    zIdHTTP.Free;
  end;
end;

步骤2:上传单个分块

使用获取到的会话URL,上传指定范围的文件分块,注意正确截取文件流并设置Content-Range头。

procedure UploadChunk(const ASessionURL, AFilePath: string; const AStartOffset, AChunkSize, AFileTotalSize: Int64);
var
  zIdHTTP: TIdHTTP;
  zIOHandler: TIdSSLIOHandlerSocketOpenSSL;
  zFileStream: TFileStream;
  zChunkStream: TMemoryStream;
  EndOffset: Int64;
begin
  zIdHTTP := TIdHTTP.Create(nil);
  zIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  zFileStream := TFileStream.Create(AFilePath, fmOpenRead);
  zChunkStream := TMemoryStream.Create;
  try
    // 配置SSL
    zIOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2];
    zIOHandler.SSLOptions.Mode := sslmClient;
    zIOHandler.SSLOptions.VerifyMode := [];
    zIdHTTP.IOHandler := zIOHandler;
    zIdHTTP.HandleRedirects := True;

    // 计算分块结束偏移(避免超过文件总大小)
    EndOffset := AStartOffset + AChunkSize - 1;
    if EndOffset >= AFileTotalSize then
      EndOffset := AFileTotalSize - 1;

    // 从文件中读取指定范围的分块数据
    zFileStream.Position := AStartOffset;
    zChunkStream.CopyFrom(zFileStream, EndOffset - AStartOffset + 1);
    zChunkStream.Position := 0;

    // 设置分块范围头
    zIdHTTP.Request.CustomHeaders.AddValue('Content-Range', Format('bytes %d-%d/%d', [AStartOffset, EndOffset, AFileTotalSize]));
    zIdHTTP.Request.ContentType := 'application/octet-stream';

    try
      // 上传分块
      zIdHTTP.Put(ASessionURL, zChunkStream);
      ShowMessage('分块上传成功');
    except
      on E: EIdHTTPProtocolException do
        ShowMessage('协议错误: ' + IntToStr(zIdHTTP.ResponseCode) + ' - ' + E.Message);
      on E: Exception do
        ShowMessage('上传错误: ' + E.Message);
    end;
  finally
    zChunkStream.Free;
    zFileStream.Free;
    zIOHandler.Free;
    zIdHTTP.Free;
  end;
end;

步骤3:调用示例

在按钮点击事件中整合会话创建和分块上传逻辑:

procedure TMainForm.btnTesteClick(Sender: TObject);
const
  cFilePath = 'path of File';
  cAccessToken = '你的Google Access Token';
  cFileMimeType = 'application/octet-stream'; // 根据文件类型调整
var
  SessionURL: string;
  FileTotalSize: Int64;
  ChunkSize: Int64;
begin
  FileTotalSize := TFile.Size(cFilePath);
  ChunkSize := 8 * 1024 * 1024; // 8MB分块(Google建议范围256KB-5TB)

  try
    // 创建上传会话
    SessionURL := CreateResumableSession(cAccessToken, cFileMimeType, cFilePath);
    if SessionURL.IsEmpty then
    begin
      ShowMessage('创建上传会话失败');
      Exit;
    end;

    // 上传第一个分块
    UploadChunk(SessionURL, cFilePath, 0, ChunkSize, FileTotalSize);
  except
    on E: Exception do
      ShowMessage('操作失败: ' + E.Message);
  end;
end;

额外注意事项

  • 权限验证:确保你的Google API已启用Drive API,且Access Token拥有https://www.googleapis.com/auth/drive.file或更高权限。
  • 分块续传:如果收到308 Resume Incomplete响应,需从响应头的Range字段获取已上传的字节范围,继续上传剩余分块。
  • OpenSSL版本:使用最新版的OpenSSL库(1.1.1及以上),避免因TLS版本不兼容导致连接中断。
  • 不要手动设置Content-Length:TIdHTTP会自动根据流的大小设置正确的Content-Length,手动设置会导致校验失败。

内容的提问来源于stack exchange,提问作者Leonardo Ferreira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 05:50:20