Indy代理服务器HTTPS到HTTP

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

我正在尝试用Indy编写一个代理服务器,以接收来自外部客户端的HTTPS调用,并将它们以HTTP转发到同一台机器上的另一个服务器应用程序。原因是另一个应用程序不支持SSL,因此我希望将其流量包装在SSL层中以实现外部安全性

我当前的方法是将TIdHTTPserver与SSL IOHandler一起使用,并在其OnCommandGet处理程序中动态创建一个TIdHTTP客户端,该客户端从内部应用程序获取一个TFileStream ContentStream,并将该流作为Response.ContentStream返回给外部调用方

这种方法的问题是,在外部流开始发送之前,必须等待内部内容流被完全接收,从而导致延迟。例如,它不能用于流媒体


我的问题是:有没有更好的方式将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
    后直接从
    TIdHTTP.IOHandler
    手动读取HTTP块。只需确保启用HTTP keep alives,否则在您有机会读取服务器的响应正文之前,HTTP将关闭与服务器的连接

如果您使用的是较旧版本的Indy,或者如果目标服务器不支持分块,则不会丢失所有内容。您应该能够编写一个自定义的
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;
财产