我使用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;
我把经过测试可以正常运行的代码封装到了一个单元中,下面是下载地址: