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;

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

源代码下载

相关推荐
利刃大大30 分钟前
【高并发服务器】十三、TcpServer服务器管理模块
服务器·高并发·项目·cpp
盼哥PyAI实验室44 分钟前
纯前端打造个人成长网站:零后端、零部署、零服务器的实践分享
运维·服务器·前端·javascript·echarts·个人开发
信看1 小时前
树莓派 ADS1263 各种库程序
linux·运维·服务器
鹓于1 小时前
Excel图片批量插入与文件瘦身
java·服务器·数据库
馨谙1 小时前
Linux 安全文件传输完全指南:sftp 与 scp 的深度解析引言
linux·运维·服务器
姓蔡小朋友1 小时前
Linux网络操作
linux·运维·服务器
qq_479875432 小时前
TcpConnection
运维·服务器·网络
linmengmeng_13142 小时前
【Centos】服务器硬盘扩容之新加硬盘扩容到现有路径下
linux·服务器·centos
边疆.2 小时前
【Linux】版本控制器Git和调试器—gdb/cgdb的使用
linux·服务器·git·gdb调试·cgdb
明天…ling2 小时前
Linux+Apache+MySQL+PHP 架构下搭建 Discuz 社区论坛
linux·服务器·apache