Indy代理服务器HTTPS到HTTP
我正在尝试用Indy编写一个代理服务器,以接收来自外部客户端的HTTPS调用,并将它们以HTTP转发到同一台机器上的另一个服务器应用程序。原因是另一个应用程序不支持SSL,因此我希望将其流量包装在SSL层中以实现外部安全性 我当前的方法是将TIdHTTPserver与SSL IOHandler一起使用,并在其OnCommandGet处理程序中动态创建一个TIdHTTP客户端,该客户端从内部应用程序获取一个TFileStream ContentStream,并将该流作为Response.ContentStream返回给外部调用方 这种方法的问题是,在外部流开始发送之前,必须等待内部内容流被完全接收,从而导致延迟。例如,它不能用于流媒体Indy代理服务器HTTPS到HTTP,http,ssl,https,proxy,indy,Http,Ssl,Https,Proxy,Indy,我正在尝试用Indy编写一个代理服务器,以接收来自外部客户端的HTTPS调用,并将它们以HTTP转发到同一台机器上的另一个服务器应用程序。原因是另一个应用程序不支持SSL,因此我希望将其流量包装在SSL层中以实现外部安全性 我当前的方法是将TIdHTTPserver与SSL IOHandler一起使用,并在其OnCommandGet处理程序中动态创建一个TIdHTTP客户端,该客户端从内部应用程序获取一个TFileStream ContentStream,并将该流作为Response.Conte
我的问题是:有没有更好的方式将HTTPS代理到HTTP,从而适用于流?也就是说,不必使用中间文件流。如果请求客户端支持HTTP 1.1分块(请参阅),则允许您从目标服务器读取数据,并立即实时发送到客户端 如果您使用的是Indy的最新版本,
TIdHTTP
在其HTTPOptions
属性中有一个OnChunkReceived
事件和一个OnoredChunked
标志:
在您的TIdHTTPServer.OnCommand…
事件处理程序中,您可以根据需要填充AresOnSeInfo
。确保:
- 保持
和AResponseInfo.ContentText
未分配AResponseInfo.ContentStream
- 将AresOnSeInfo.ContentLength设置为0
- 将
设置为AResponseInfo.TransferEncoding
'chunked'
AResponseInfo.WriteHeader()
方法,例如在TIdHTTP.onheadersreceived
事件中,将响应头发送到客户端
然后,您可以使用OnChunkedReceived
或onnoreadchunked
读取目标服务器的响应正文,并使用AContext.Connection.IOHandler
直接将每个接收到的区块写入客户端
但是,有一些注意事项:
- 如果使用
事件,您仍然需要向TIdHTTP.OnChunkReceived
提供输出TIdHTTP
,否则不会触发该事件(此限制可能在将来的版本中删除)。但是,您可以使用TStream
,而无需为其分配TIdEventStream
事件处理程序。或者编写一个自定义的OnWrite
类,该类重写虚拟TStream
方法以不执行任何操作。或者,只需使用您想要的任何write()
,并让TStream
事件处理程序清除接收到的OnChunkReceived
,这样就没有任何东西可以写入块
TStream
- 如果您使用
标志,这允许您在退出honoredchunked
后直接从TIdHTTP
手动读取HTTP块。只需确保启用HTTP keep alives,否则在您有机会读取服务器的响应正文之前,HTTP将关闭与服务器的连接TIdHTTP.IOHandler
TStream
类,该类覆盖虚拟write()
方法,将提供的数据块作为HTTP块写入客户端。然后您可以使用该类作为TIdHTTP
的输出TStream
如果客户端不支持HTTP分块,或者这些方法不适用于您,那么您可能必须直接使用
TIdTCPServer
,而不是tidttpserver
,并且自己从头开始实现整个HTTP协议,然后您可以根据需要处理自己的流。查看TIdHTTPProxyServer
的源代码,了解一些想法(TIdHTTPProxyServer
本身不适合您的特定情况,但它将向您展示如何在连接之间以近乎实时的方式传递HTTP请求/响应)。感谢您提供了非常全面的答案。我最终解决这个问题的方法是创建一个TStream子代,用作服务器响应的内容流。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;
类型
TTcpSocketStream=类(TStream)
私有的
错误授权:字符串;
FBuffer:t字节;
FBytesRead:Int64;
FCommand:字符串;
FContentLength:Int64;
FContentType:字符串;
FDocument:字符串;
领队:领队名单;
FHost:字符串;
FIntercept:TServerLogEvent;
FPort:整数;
FResponseCode:整数;
FQueryParams:字符串;
FTcpClient:TIdTCPClient;
fwww:字符串;
平民的
构造函数创建;
毁灭者毁灭;推翻
程序初始化;
函数读取(变量缓冲区;计数:Longint):Longint;推翻
函数寻道(常数偏移量:Int64;原点:TSeekOrigin):Int64;推翻
属性授权:字符串读取错误授权写入错误授权;
属性命令:字符串读取FCommand和写入FCommand;
属性ContentType:字符串读取FContentType;
属性ContentLength:Int64读取FContentLength;
属性文档:字符串读取FDocument写入FDocument;
属性主机:字符串读取fHost写入fHost;
属性拦截:TServerLogEvent读取FIntercept写入FIntercept;
属性端口:整数读FPort写FPort;
属性QueryParams:字符串读取FQueryParams写入FQueryParams;
属性响应代码:整数读取FResponseCode;
财产