I have a winCGI executable script returning a image. I am using Intraweb as server.
I have a custom web server made in delphi and I create a function to run the cgi and return the image.
The cgi is from a third party and I can't change your code. I have the next code returning from CGI when I query a image:
'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#$10#$10#$10#$11#$11#$11#$12#$12#$12#$13#$13#$13#$14#$14#$14#$15#$15#$15#$16#$16#$16#$17#$17#$17#$18#$18#$18#$19#........
I need to send the image to a front end app in the browser.
<div>
<img src="getImage(1)">
</div>
Here, getImage function takes the image from server, but not is showing, because I think the format I am returning the image from server to front end has something wrong. How could I fix the content text of the image on server to be a valid image in the front end?
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;
Your CGI module generates raw HTTP response that should be transmitted as-is by your IW content handler. The response message consists of:
IntraWeb's THTTPReply
doesn't seem to give you control over raw HTTP response. It provides dedicated interface to separately manipulate with status, headers and body. That's why you need to pre-process the stream returned from CGI and split headers apart from body by the empty line. Then you can transmit them via THTTPReply
. Maybe you'd also like to whitelist only some headers and ignore the rest.
In your code snippet you use ReadStringFromStream
which is pointless, because you discard the returned value anyway. It moves the position of Stream
to the end of the stream, but that doesn't matter, because aReply.SendStream(Stream)
sends the whole stream content from the beginning.
Another point is that you use IndyTextEncoding_OSDefault
as the last parameter to ReadStringFromStream
, which is probably wrong, because HTTP header is encoded in ASCII and therefore you should use IndyTextEncoding_ASCII
.
Try this code instead:
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;
Also please note that the CGI output you enclosed doesn't contain status line and starts straight with headers (Content-type: ...). If that's what you get from CGI then you should update the code to handle such a case.