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;

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

源代码下载

相关推荐
上海蓝色星球5 小时前
迈向智慧电网新纪元:上海蓝色星球数字孪生变电主子站系统
运维·数据库
爬山算法7 小时前
Netty(10)Netty的粘包和拆包问题是什么?如何解决它们?
服务器·网络·tcp/ip
Sleepy MargulisItG7 小时前
【Linux网络编程】应用层协议:HTTP协议
linux·服务器·网络·http
logic_57 小时前
静态路由配置
运维·服务器·网络
zhuzewennamoamtf8 小时前
Linux内核platform抽象、数据结构、内核匹配机制
linux·运维·数据结构
门思科技8 小时前
企业级 LoRaWAN 网关远程运维方案对比:VPN 与 NPS FRP 的技术与安全差异分析
运维·网络·安全
云和数据.ChenGuang8 小时前
Deepseek 持续迭代的模型
运维·运维技术·数据库运维工程师·运维教程
物联网软硬件开发-轨物科技8 小时前
【轨物方案】聚焦锯床设备智能化升级,打造工业互联网新范式
运维·科技·物联网
suzhou_speeder9 小时前
企业数字化网络稳定运行与智能化管理解决方案
运维·服务器·网络·交换机·poe·poe交换机
RisunJan9 小时前
Linux命令-grpck命令(验证和修复组配置文件(`/etc/group` 和 `/etc/gshadow`)完整性的工具)
linux·运维·服务器