运行 带有 CGI 的 Delphi 应用程序并将结果发送到前端
Running a Delphi app with CGI and sent result to front end
我有一个 winCGI 可执行脚本 return 图像。我正在使用 Intraweb 作为服务器。
我有一个在 delphi 中制作的自定义 Web 服务器,我为 运行 cgi 和 return 图像创建了一个函数。
cgi 来自第三方,我无法更改您的代码。
当我查询图像时,我有下一个代码 returning 来自 CGI:
'Content-type: image/gif'#$A'Access-Control-Allow-Origin: *'#$A#$A'GIF89a@'#1'@'#1#0#0#0'!ÿ'#$B'NETSCAPE2.0'#3#1'ÿÿ'#0'!ù'#4#0'!'#0#0#0','#0#0#0#0'@'#1'@'#1'‡'#0#0#0#1#1#1#2#2#2#3#3#3#4#4#4#5#5#5#6#6#6#7#7#7#8#8#8#9#9#9#$A#$A#$A#$B#$B#$B#$C#$C#$C#$D#$D#$D#$E#$E#$E#$F#$F#$F#############################........
我需要将图像发送到浏览器中的前端应用程序。
<div>
<img src="getImage(1)">
</div>
在这里,getImage 函数从服务器获取图像,但没有显示,因为我认为我 return 从服务器到前端的图像格式有问题。
如何将服务器上图像的内容文本修复为前端的有效图像?
function RunCGIOutput(CommandLine: string; Stream:TStream;Folder: string = ''): string;
const
CReadBuffer = 2400;
var
saSecurity: TSecurityAttributes;
hRead: THandle;
hWrite: THandle;
suiStartup: TStartupInfo;
piProcess: TProcessInformation;
dRead: DWORD;
Handle,WasOK:Boolean;
Buffer: array[0..CReadBuffer] of AnsiChar;
BytesRead: Cardinal;
WorkingDirP,EnvBlock:PChar;
begin
saSecurity.nLength := SizeOf(TSecurityAttributes);
saSecurity.bInheritHandle := true;
saSecurity.lpSecurityDescriptor := nil;
EnvBlock := BuildEnvBlock(True);
if Folder <> '' then WorkingDirP := PChar(Folder)
else WorkingDirP := nil;
if CreatePipe(hRead, hWrite, @saSecurity, 0) then
try
FillChar(suiStartup, SizeOf(TStartupInfo), #0);
suiStartup.cb := SizeOf(TStartupInfo);
suiStartup.hStdInput := hRead;
suiStartup.hStdOutput := hWrite;
suiStartup.hStdError := hWrite;
suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
suiStartup.wShowWindow := SW_HIDE;
Handle:=CreateProcess(nil, PChar(CommandLine), @saSecurity, @saSecurity, true, CREATE_UNICODE_ENVIRONMENT, EnvBlock, WorkingDirP, suiStartup,
piProcess);
CloseHandle(hWrite);
if Handle then
try
repeat
WasOK := ReadFile(hRead, Buffer, CReadBuffer, BytesRead, nil) and (BytesRead>0);
if WasOK then
Stream.WriteBuffer(Buffer, BytesRead);
until not WasOK;
WaitForSingleObject(piProcess.hProcess, INFINITE);
finally
CloseHandle(piProcess.hThread);
CloseHandle(piProcess.hProcess);
end;
finally
CloseHandle(hRead);
StrDispose(EnvBlock);
end;
end;
//Run CGI, take image and send to browser
function TContentImaqge.Execute(aRequest: THttpRequest; aReply: THttpReply;
const aPathname: string; aSession: TIWApplication;
aParams: TStrings): boolean;
var
s,wresult,saida,LocalDoc:string;
i:integer;
Stream:TMemoryStream;
begin
Result:=True;
LocalDoc:=TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\dgate.exe';
Stream:=TMemoryStream.Create;
saida:=RunCGIOutput(LocalDoc,Stream,TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\');
Stream.Position:=0;
saida:=ReadStringFromStream(Stream, -1, IndyTextEncoding_OSDefault);
with aReply do
begin
ResetReplyType;
Code := 200;
ContentType := MIME_GIF; // MIME_HTML;
SendStream(Stream);
end;
end;
您的 CGI module generates raw HTTP response 应该由您的 IW 内容处理程序传输 as-is。响应消息包括:
- 状态行
- HTTP header 行
- 空行分隔 headers 和 body
- Body(您的实际图像数据)
IntraWeb 的 THTTPReply
似乎无法让您控制原始 HTTP 响应。它提供了专用接口来单独操作状态、headers 和 body。这就是为什么您需要 pre-process 从 CGI 返回的流并通过空行将 header 与 body 分开。然后你可以通过THTTPReply
传送它们。也许您还想仅将某些 header 列入白名单并忽略其余的。
在您的代码片段中,您使用了 ReadStringFromStream
这是毫无意义的,因为您无论如何都会丢弃返回值。它将 Stream
的位置移动到流的末尾,但这并不重要,因为 aReply.SendStream(Stream)
从头开始发送整个流内容。
另一点是你使用IndyTextEncoding_OSDefault
作为ReadStringFromStream
的最后一个参数,这可能是错误的,因为HTTP header is encoded in ASCII所以你应该使用IndyTextEncoding_ASCII
。
试试这个代码:
function TContentImaqge.Execute(aRequest: THttpRequest; aReply: THttpReply;
const aPathname: string; aSession: TIWApplication;
aParams: TStrings): Boolean;
var
CGIOutput, ContentStream: TMemoryStream;
LocalDoc, Line: string;
CharPos: Integer;
begin
Result := True;
CGIOutput := TMemoryStream.Create;
try
LocalDoc := TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\dgate.exe';
RunCGIOutput(LocalDoc, CGIOutput, TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\');
if ReadLnFromStream(CGIOutput, Line, -1, IndyTextEncoding_ASCII) then
begin
{ process status line }
CharPos := Pos(' ', Line);
if CharPos > 0 then
begin
aReply.Code := StrToInt(Copy(Line, CharPos + 1, 3));
CharPos := Pos(' ', Line, CharPos);
if CharPos > 0 then
aReply.CodeText := Copy(Line, CharPos + 1);
end;
{ process headers (copy headers as they are) }
while ReadLnFromStream(CGIOutput, Line, -1, IndyTextEncoding_ASCII) and (Line <> '') do
aReply.Headers.Add(Line);
{ at this point CGIOutput.Position is at the beginning of body, so let's just copy
the content to a separate memory stream }
ContentStream := TMemoryStream.Create;
try
ContentStream.CopyFrom(CGIOutput, CGIOutput.Size - CGIOutput.Position);
except
ContentStream.Free;
raise;
end;
aReply.SendStream(ContentStream);
end
else
begin
aReply.Code := 500;
aReply.CodeText := RSHTTPInternalServerError;
aReply.WriteString('CGI module returned malformed response.');
end;
finally
CGIOutput.Free;
end;
end;
另请注意,您附上的 CGI 输出不包含状态行,而是直接以 headers (Content-type: ...) 开始。如果那是您从 CGI 得到的结果,那么您应该更新代码以处理这种情况。
我有一个 winCGI 可执行脚本 return 图像。我正在使用 Intraweb 作为服务器。
我有一个在 delphi 中制作的自定义 Web 服务器,我为 运行 cgi 和 return 图像创建了一个函数。
cgi 来自第三方,我无法更改您的代码。 当我查询图像时,我有下一个代码 returning 来自 CGI:
'Content-type: image/gif'#$A'Access-Control-Allow-Origin: *'#$A#$A'GIF89a@'#1'@'#1#0#0#0'!ÿ'#$B'NETSCAPE2.0'#3#1'ÿÿ'#0'!ù'#4#0'!'#0#0#0','#0#0#0#0'@'#1'@'#1'‡'#0#0#0#1#1#1#2#2#2#3#3#3#4#4#4#5#5#5#6#6#6#7#7#7#8#8#8#9#9#9#$A#$A#$A#$B#$B#$B#$C#$C#$C#$D#$D#$D#$E#$E#$E#$F#$F#$F#############################........
我需要将图像发送到浏览器中的前端应用程序。
<div>
<img src="getImage(1)">
</div>
在这里,getImage 函数从服务器获取图像,但没有显示,因为我认为我 return 从服务器到前端的图像格式有问题。 如何将服务器上图像的内容文本修复为前端的有效图像?
function RunCGIOutput(CommandLine: string; Stream:TStream;Folder: string = ''): string;
const
CReadBuffer = 2400;
var
saSecurity: TSecurityAttributes;
hRead: THandle;
hWrite: THandle;
suiStartup: TStartupInfo;
piProcess: TProcessInformation;
dRead: DWORD;
Handle,WasOK:Boolean;
Buffer: array[0..CReadBuffer] of AnsiChar;
BytesRead: Cardinal;
WorkingDirP,EnvBlock:PChar;
begin
saSecurity.nLength := SizeOf(TSecurityAttributes);
saSecurity.bInheritHandle := true;
saSecurity.lpSecurityDescriptor := nil;
EnvBlock := BuildEnvBlock(True);
if Folder <> '' then WorkingDirP := PChar(Folder)
else WorkingDirP := nil;
if CreatePipe(hRead, hWrite, @saSecurity, 0) then
try
FillChar(suiStartup, SizeOf(TStartupInfo), #0);
suiStartup.cb := SizeOf(TStartupInfo);
suiStartup.hStdInput := hRead;
suiStartup.hStdOutput := hWrite;
suiStartup.hStdError := hWrite;
suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
suiStartup.wShowWindow := SW_HIDE;
Handle:=CreateProcess(nil, PChar(CommandLine), @saSecurity, @saSecurity, true, CREATE_UNICODE_ENVIRONMENT, EnvBlock, WorkingDirP, suiStartup,
piProcess);
CloseHandle(hWrite);
if Handle then
try
repeat
WasOK := ReadFile(hRead, Buffer, CReadBuffer, BytesRead, nil) and (BytesRead>0);
if WasOK then
Stream.WriteBuffer(Buffer, BytesRead);
until not WasOK;
WaitForSingleObject(piProcess.hProcess, INFINITE);
finally
CloseHandle(piProcess.hThread);
CloseHandle(piProcess.hProcess);
end;
finally
CloseHandle(hRead);
StrDispose(EnvBlock);
end;
end;
//Run CGI, take image and send to browser
function TContentImaqge.Execute(aRequest: THttpRequest; aReply: THttpReply;
const aPathname: string; aSession: TIWApplication;
aParams: TStrings): boolean;
var
s,wresult,saida,LocalDoc:string;
i:integer;
Stream:TMemoryStream;
begin
Result:=True;
LocalDoc:=TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\dgate.exe';
Stream:=TMemoryStream.Create;
saida:=RunCGIOutput(LocalDoc,Stream,TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\');
Stream.Position:=0;
saida:=ReadStringFromStream(Stream, -1, IndyTextEncoding_OSDefault);
with aReply do
begin
ResetReplyType;
Code := 200;
ContentType := MIME_GIF; // MIME_HTML;
SendStream(Stream);
end;
end;
您的 CGI module generates raw HTTP response 应该由您的 IW 内容处理程序传输 as-is。响应消息包括:
- 状态行
- HTTP header 行
- 空行分隔 headers 和 body
- Body(您的实际图像数据)
IntraWeb 的 THTTPReply
似乎无法让您控制原始 HTTP 响应。它提供了专用接口来单独操作状态、headers 和 body。这就是为什么您需要 pre-process 从 CGI 返回的流并通过空行将 header 与 body 分开。然后你可以通过THTTPReply
传送它们。也许您还想仅将某些 header 列入白名单并忽略其余的。
在您的代码片段中,您使用了 ReadStringFromStream
这是毫无意义的,因为您无论如何都会丢弃返回值。它将 Stream
的位置移动到流的末尾,但这并不重要,因为 aReply.SendStream(Stream)
从头开始发送整个流内容。
另一点是你使用IndyTextEncoding_OSDefault
作为ReadStringFromStream
的最后一个参数,这可能是错误的,因为HTTP header is encoded in ASCII所以你应该使用IndyTextEncoding_ASCII
。
试试这个代码:
function TContentImaqge.Execute(aRequest: THttpRequest; aReply: THttpReply;
const aPathname: string; aSession: TIWApplication;
aParams: TStrings): Boolean;
var
CGIOutput, ContentStream: TMemoryStream;
LocalDoc, Line: string;
CharPos: Integer;
begin
Result := True;
CGIOutput := TMemoryStream.Create;
try
LocalDoc := TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\dgate.exe';
RunCGIOutput(LocalDoc, CGIOutput, TIWAppInfo.GetAppPath + 'wwwroot\cgi-bin\newweb\');
if ReadLnFromStream(CGIOutput, Line, -1, IndyTextEncoding_ASCII) then
begin
{ process status line }
CharPos := Pos(' ', Line);
if CharPos > 0 then
begin
aReply.Code := StrToInt(Copy(Line, CharPos + 1, 3));
CharPos := Pos(' ', Line, CharPos);
if CharPos > 0 then
aReply.CodeText := Copy(Line, CharPos + 1);
end;
{ process headers (copy headers as they are) }
while ReadLnFromStream(CGIOutput, Line, -1, IndyTextEncoding_ASCII) and (Line <> '') do
aReply.Headers.Add(Line);
{ at this point CGIOutput.Position is at the beginning of body, so let's just copy
the content to a separate memory stream }
ContentStream := TMemoryStream.Create;
try
ContentStream.CopyFrom(CGIOutput, CGIOutput.Size - CGIOutput.Position);
except
ContentStream.Free;
raise;
end;
aReply.SendStream(ContentStream);
end
else
begin
aReply.Code := 500;
aReply.CodeText := RSHTTPInternalServerError;
aReply.WriteString('CGI module returned malformed response.');
end;
finally
CGIOutput.Free;
end;
end;
另请注意,您附上的 CGI 输出不包含状态行,而是直接以 headers (Content-type: ...) 开始。如果那是您从 CGI 得到的结果,那么您应该更新代码以处理这种情况。