Google Drive API v3分块上传文件遇400错误及连接重置问题求助
解决Google Drive分块上传的400错误与Connection Reset问题
你的核心问题是未遵循Google Drive可恢复上传(Resumable Upload)的规范,同时代码中存在请求格式、流处理、SSL配置的错误,导致请求被服务器拒绝或中断。以下是具体问题分析和修复方案:
问题分析
- 错误的请求体格式:Google Drive分块上传的分块请求不需要使用
TIdMultiPartFormDataStream,multipart格式仅用于普通单文件上传,分块请求应直接发送二进制分块数据。 - 手动设置Content-Length冲突:你手动指定的分块大小与
TIdMultiPartFormDataStream实际生成的流长度不匹配,导致服务器校验失败。 - 文件流未正确截取分块:你的代码直接上传了整个文件,而不是指定的
0-8388607字节范围,分块范围与实际上传数据不符。 - SSL模式配置错误:
sslmUnassigned未明确客户端身份,应设置为sslmClient以符合TLS客户端请求规范。 - 缺少可恢复会话创建步骤: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
相关产品推荐
相关产品推荐

