Delphi - IndyHttpServer接收上传文件
我使用delphi12CE+Indy中的idhttpserver接收post上来的图片,代码如下:
// 处理 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 可运行的示例代码:
function ExtractFileFromRequest(ARequestInfo: TIdHTTPRequestInfo;out AFileName: string): TMemoryStream;
varLBoundary, LBoundaryMarker: string;LContent: TBytes;LContentStr, HeaderStr: string;PartStart, PartEnd, HeaderEnd: Integer;
beginResult := nil;AFileName := '';if not Assigned(ARequestInfo.PostStream) thenExit;// boundaryLBoundary := 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);// 遍历所有 partPartStart := Pos(LBoundaryMarker, LContentStr);while PartStart > 0 dobegin// 找 header 结束HeaderEnd := Pos(#13#10#13#10, LContentStr, PartStart);if HeaderEnd = 0 then Break;HeaderStr := Copy(LContentStr, PartStart, HeaderEnd - PartStart);if ContainsText(HeaderStr, 'filename="') thenbegin// 文件名AFileName := Copy(HeaderStr, Pos('filename="', HeaderStr) + 10, MaxInt);AFileName := Copy(AFileName, 1, Pos('"', AFileName) - 1);// 文件起点Inc(HeaderEnd, 4);// 找下一个 boundaryPartEnd := Pos(LBoundaryMarker, LContentStr, HeaderEnd);if PartEnd = 0 then Break;// ⚠️ 精确剔除 boundary 前的换行while (PartEnd > HeaderEnd) and((LContent[PartEnd - 2] = 13) or (LContent[PartEnd - 2] = 10)) doDec(PartEnd);// 复制二进制Result := TMemoryStream.Create;Result.WriteBuffer(LContent[HeaderEnd - 1], PartEnd - HeaderEnd + 1);Result.Position := 0;Exit;end;// 下一个 partPartStart := Pos(LBoundaryMarker, LContentStr, HeaderEnd);end;
end;
我把经过测试可以正常运行的代码封装到了一个单元中,下面是下载地址:
源代码下载