Delphi - IndyHttpServer接收上传文件

我使用delphi12CE+Indy中的idhttpserver接收post上来的图片,代码如下:

Delphi 复制代码
// 处理 POST 请求 
if SameText(ARequestInfo.Command, 'POST') and SameText(ARequestInfo.Document, '/upload') then 
begin 
    if ARequestInfo.PostStream <> nil then 
        begin 
            outPath := TPath.Combine(FUploadDir, FormatDateTime('yyyymmdd_hhnnss_zzz',    Now).Replace('.', '') + '.jpg'); 
            FileStream := TFileStream.Create(outPath, fmCreate); 
            ARequestInfo.PostStream.Position := 0; 
            FileStream.CopyFrom(ARequestInfo.PostStream, ARequestInfo.PostStream.Size); { Copy 流 } FileStream.Free; 
        end; 
end;

可以接收,但是接收的图片文件都无法打开,使用NotePad++打开发现图片文件是这样的:

------WebKitFormBoundarybBNrZRN79bc6VhED Content-Disposition: form-data; name="name" all ------WebKitFormBoundarybBNrZRN79bc6VhED Content-Disposition: form-data; name="file"; filename="th_d6bee6de9e633c8618fab537b2ae3b3chd_042427.jpg" Content-Type: image/jpeg ??JFIF ?C

这是文件头,文件尾部还有:

------WebKitFormBoundarybBNrZRN79bc6VhED--

似乎是除了图片文件数据外头部和尾部还有其他的东西,如何解决这个问题呢?

经过分析,现在的 POST 请求是 multipart/form-data ,而上面代码直接把 ARequestInfo.PostStream 整个保存成文件了,导致文件里面不仅有图片二进制,还包含了表单的边界(boundary)、Content-Disposition、Content-Type 等文本信息。

所以图片打不开是因为数据污染了。

Indy 的 TIdHTTPServer 在处理 multipart/form-data 时,并不会自动帮你分离文件,需要你手动解析表单。

解决方法是直接用 Indy 的 TIdMultiPartFormDataStream + ARequestInfo.RawHeaders 手动提取,下面是一个 Delphi12 CE 可运行的示例代码:

Delphi 复制代码
function ExtractFileFromRequest(ARequestInfo: TIdHTTPRequestInfo;
  out AFileName: string): TMemoryStream;
var
  LBoundary, LBoundaryMarker: string;
  LContent: TBytes;
  LContentStr, HeaderStr: string;
  PartStart, PartEnd, HeaderEnd: Integer;
begin
  Result := nil;
  AFileName := '';

  if not Assigned(ARequestInfo.PostStream) then
    Exit;

  // boundary
  LBoundary := ARequestInfo.RawHeaders.Values['Content-Type'];
  if LBoundary = '' then Exit;
  LBoundary := Copy(LBoundary, Pos('boundary=', LBoundary) + 9, MaxInt);
  if LBoundary = '' then Exit;

  LBoundaryMarker := '--' + LBoundary;

  // 读取全部内容
  SetLength(LContent, ARequestInfo.PostStream.Size);
  ARequestInfo.PostStream.Position := 0;
  ARequestInfo.PostStream.ReadBuffer(LContent[0], Length(LContent));
  LContentStr := TEncoding.ANSI.GetString(LContent);

  // 遍历所有 part
  PartStart := Pos(LBoundaryMarker, LContentStr);
  while PartStart > 0 do
  begin
    // 找 header 结束
    HeaderEnd := Pos(#13#10#13#10, LContentStr, PartStart);
    if HeaderEnd = 0 then Break;

    HeaderStr := Copy(LContentStr, PartStart, HeaderEnd - PartStart);

    if ContainsText(HeaderStr, 'filename="') then
    begin
      // 文件名
      AFileName := Copy(HeaderStr, Pos('filename="', HeaderStr) + 10, MaxInt);
      AFileName := Copy(AFileName, 1, Pos('"', AFileName) - 1);

      // 文件起点
      Inc(HeaderEnd, 4);

      // 找下一个 boundary
      PartEnd := Pos(LBoundaryMarker, LContentStr, HeaderEnd);
      if PartEnd = 0 then Break;

      // ⚠️ 精确剔除 boundary 前的换行
      while (PartEnd > HeaderEnd) and
        ((LContent[PartEnd - 2] = 13) or (LContent[PartEnd - 2] = 10)) do
        Dec(PartEnd);

      // 复制二进制
      Result := TMemoryStream.Create;
      Result.WriteBuffer(LContent[HeaderEnd - 1], PartEnd - HeaderEnd + 1);
      Result.Position := 0;
      Exit;
    end;

    // 下一个 part
    PartStart := Pos(LBoundaryMarker, LContentStr, HeaderEnd);
  end;
end;

我把经过测试可以正常运行的代码封装到了一个单元中,下面是下载地址:

源代码下载

相关推荐
zzzsde13 小时前
【Linux】进程(2):进程概念与操作理解
linux·运维·服务器
郝学胜-神的一滴13 小时前
Linux Socket模型创建流程详解
linux·服务器·开发语言·网络·c++·程序人生
天才奇男子13 小时前
《深度解析HAProxy七层代理:原理、配置与最佳实践》
linux·运维·微服务·云原生
Morantkk13 小时前
股票复盘11.20-11.27
服务器
交换机路由器测试之路13 小时前
交换机专题:什么是ALS(激光器自动关断)
运维·网络·以太网·交换机·节能
浪客灿心13 小时前
Linux的Ext系列文件系统
linux·运维·服务器·c语言
速易达网络14 小时前
linux命令大全
linux·运维·excel
·云扬·14 小时前
Redis运维实战:大key与热key排查优化、监控指标及内存策略全解析
运维·数据库·redis
落笔映浮华丶14 小时前
linux项目自动构建工具 -make/makefile
linux·运维·服务器
王者鳜錸14 小时前
企业微信自动化发消息-从0到1开发实践
运维·自动化·企业微信