我正在尝试在Indy中编写代理服务器以接收来自外部客户端的HTTPS调用,并将它们在HTTP中转发到同一台机器上的另一个服务器应用程序。原因是其他应用程序不支持SSL,因此我希望将其流量包装在SSL层中以实现外部安全。
我目前的方法是使用带有SSL IOHandler的TIdHTTP服务器,并在其OnCommandGet处理程序中动态创建一个TIdHTTP客户端,从内部应用程序获取TFileStream ContentStream,并将该流作为Response.ContentStream返回给外部呼叫者。
此方法的问题是在外部流开始发送之前必须等待内部内容流完全接收所导致的延迟。例如,它不适用于流媒体。
我的问题是:是否有更好的方法将HTTPS代理到可用于流的HTTP?即,无需使用中间文件流。
答案 0 :(得分:1)
如果请求客户端支持HTTP 1.1分块(请参阅RFC 2616 Section 3.6.1),那么您将可以从目标服务器读取数据并立即将其发送到客户端。
如果您使用的是相当新版本的Indy,则TIdHTTP
会在OnChunkReceived
属性中生成hoNoReadChunked
个事件和HTTPOptions
标记:
New TIdHTTP flags and OnChunkReceived event
在TIdHTTPServer.OnCommand...
事件处理程序中,您可以根据需要填充AResponseInfo
。请务必:
离开AResponseInfo.ContentText
和AResponseInfo.ContentStream
取消分配
将AResponseInfo.ContentLength
设为0
将AResponseInfo.TransferEncoding
设置为'chunked'
然后直接调用AResponseInfo.WriteHeader()
方法,例如在TIdHTTP.OnHeadersRecceived
事件中,将响应标头发送给客户端。
然后,您可以使用OnChunkedReceived
或hoNoReadChunked
读取目标服务器的响应正文,并使用AContext.Connection.IOHandler
直接将每个收到的块写入客户端。
然而,有一些警告:
如果您使用TIdHTTP.OnChunkReceived
事件,则仍需要向TStream
提供输出TIdHTTP
,否则不会触发事件(此限制可能会在未来发布)。但是,您可以使用TIdEventStream
而不为其分配OnWrite
事件处理程序。或者编写一个自定义TStream
类来覆盖虚拟Write()
方法,以便不执行任何操作。或者,只需使用您想要的任何TStream
,并让OnChunkReceived
事件处理程序清除收到的Chunk
,这样就无法写入TStream
。
如果您使用hoNoReadChunked
标记,则可以在TIdHTTP.IOHandler
退出后直接从TIdHTTP
手动读取HTTP块。只需确保启用HTTP keep-alives,否则TIdHTTP
将关闭与服务器的连接,然后才有机会读取服务器的响应正文。
如果您使用的是旧版本的Indy,或者目标服务器不支持分块,则不会丢失所有内容。您应该能够编写一个自定义TStream
类来覆盖虚拟Write()
方法,以便将提供的数据块作为HTTP块写入客户端。然后,您可以将该类用作TStream
的输出TIdHTTP
。
如果客户端不支持HTTP分块,或者这些方法对您不起作用,那么您可能不得不直接使用TIdTCPServer
而不是TIdHTTPServer
来实现整个HTTP协议从头开始,您可以根据需要处理自己的流媒体。看看TIdHTTPProxyServer
的一些想法的源代码(TIdHTTPProxyServer
本身并不适合您的特定情况,但它会告诉您如何近乎实时地在连接之间传递HTTP请求/响应一般而言。
答案 1 :(得分:0)
感谢您提供非常全面的答复。我最终解决它的方法是创建一个TStream后代,用作服务器响应的ContentStream。 TStream是TIdTcpClient的包装器,它包含一个基本的HTTP实现,其TStream.Read函数获取Tcp连接的HTTP内容。
type
TTcpSocketStream = class(TStream)
private
FAuthorization: string;
FBuffer: TBytes;
FBytesRead: Int64;
FCommand: string;
FContentLength: Int64;
FContentType: string;
FDocument: string;
FHeaders: TIdHeaderList;
FHost: string;
FIntercept: TServerLogEvent;
FPort: Integer;
FResponseCode: Integer;
FQueryParams: string;
FTcpClient: TIdTCPClient;
FWwwAuthenticate: string;
public
constructor Create;
destructor Destroy; override;
procedure Initialize;
function Read(var Buffer; Count: Longint): Longint; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
property Authorization: string read FAuthorization write FAuthorization;
property Command: string read FCommand write FCommand;
property ContentType: string read FContentType;
property ContentLength: Int64 read FContentLength;
property Document: string read FDocument write FDocument;
property Host: string read fHost write FHost;
property Intercept: TServerLogEvent read FIntercept write FIntercept;
property Port: Integer read FPort write FPort;
property QueryParams: string read FQueryParams write FQueryParams;
property ResponseCode: Integer read FResponseCode;
property WWWAuthenticate: string read FWwwAuthenticate
write FWwwAuthenticate;
end;
const
crlf = #13#10;
cContentSeparator = crlf+crlf;
implementation
{ TTcpSocketStream }
constructor TTcpSocketStream.Create;
begin
inherited;
FHeaders := TIdHeaderList.Create(QuoteHTTP);
FTcpClient := TIdTcpClient.Create(nil);
FTcpClient.ConnectTimeout := 5000;
FTcpClient.ReadTimeout := 5000;
FCommand := 'GET';
FPort := 443;
FResponseCode := 404;
end;
destructor TTcpSocketStream.Destroy;
begin
if FTcpClient.Connected then
FTcpClient.Disconnect;
if FTcpClient.Intercept <> nil then
begin
FTcpClient.Intercept.Free;
FTcpClient.Intercept := nil;
end;
FTcpClient.Free;
FHeaders.Free;
SetLength(FBuffer, 0);
inherited;
end;
procedure TTcpSocketStream.Initialize;
var
s: string;
LLog: TClientLogEvent;
LRespText: string;
begin
try
if FQueryParams <> '' then
FQueryParams := '?' + FQueryParams;
FTcpClient.Port := FPort;
FTcpClient.Host := FHost;
if FIntercept <> nil then
begin
LLog := TClientLogEvent.Create;
LLog.OnLog := FIntercept.OnLog;
FTcpClient.Intercept := LLog;
end;
FTcpClient.Connect;
if FTcpClient.Connected then
begin
FTcpClient.IOHandler.Writeln(Format('%s %s%s HTTP/1.1',
[FCommand, FDocument, FQueryParams]));
FTcpClient.IOHandler.Writeln('Accept: */*');
if FAuthorization <> '' then
FTcpClient.IOHandler.Writeln(Format('Authorization: %s',
[FAuthorization]));
FTcpClient.IOHandler.Writeln('Connection: Close');
FTcpClient.IOHandler.Writeln(Format('Host: %s:%d', [FHost, FPort]));
FTcpClient.IOHandler.Writeln('User-Agent: Whitebear SSL Proxy');
FTcpClient.IOHandler.Writeln('');
LRespText := FTcpClient.IOHandler.ReadLn;
s := LRespText;
Fetch(s);
s := Trim(s);
FResponseCode := StrToIntDef(Fetch(s, ' ', False), -1);
repeat
try
s := FTcpClient.IOHandler.ReadLn;
except
on Exception do
break;
end;
if s <> '' then
FHeaders.Add(s);
until s = '';
FContentLength := StrToInt64Def(FHeaders.Values['Content-Length'], -1);
FContentType := FHeaders.Values['Content-Type'];
FWwwAuthenticate := FHeaders.Values['WWW-Authenticate'];
end;
except
on E:Exception do ;
end;
end;
function TTcpSocketStream.Read(var Buffer; Count: Integer): Longint;
begin
Result := 0;
try
if FTcpClient.Connected then
begin
if Length(FBuffer) < Count then
SetLength(FBuffer, Count);
FTcpClient.IOHandler.ReadBytes(FBuffer, Count, False);
Move(FBuffer[0], PChar(Buffer), Count);
Inc(FBytesRead, Count);
Result := Count;
end;
except
on Exception do ;
end;
end;
function TTcpSocketStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
Result := 0;
case Origin of
soBeginning: Result := Offset;
soCurrent: Result := FBytesRead + Offset;
soEnd: Result := FContentLength + Offset;
end;
end;