Indy代理服务器HTTPS到HTTP

时间:2017-01-06 11:00:56

标签: http ssl https proxy indy

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

我目前的方法是使用带有SSL IOHandler的TIdHTTP服务器,并在其OnCommandGet处理程序中动态创建一个TIdHTTP客户端,从内部应用程序获取TFileStream ContentStream,并将该流作为Response.ContentStream返回给外部呼叫者。

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

我的问题是:是否有更好的方法将HTTPS代理到可用于流的HTTP?即,无需使用中间文件流。

2 个答案:

答案 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.ContentTextAResponseInfo.ContentStream取消分配

  • AResponseInfo.ContentLength设为0

  • AResponseInfo.TransferEncoding设置为'chunked'

然后直接调用AResponseInfo.WriteHeader()方法,例如在TIdHTTP.OnHeadersRecceived事件中,将响应标头发送给客户端。

然后,您可以使用OnChunkedReceivedhoNoReadChunked读取目标服务器的响应正文,并使用AContext.Connection.IOHandler直接将每个收到的块写入客户端。

然而,有一些警告:

  • 如果您使用TIdHTTP.OnChunkReceived事件,则仍需要向TStream提供输出TIdHTTP,否则不会触发事件(此限制可能会在未来发布)。但是,您可以使用TIdEventStream而不为其分配OnWrite事件处理程序。或者编写一个自定义TStream类来覆盖虚拟Write()方法,以便不执行任何操作。或者,只需使用您想要的任何TStream,并让OnChunkReceived事件处理程序清除收到的Chunk,这样就无法写入TStream

    < / LI>
  • 如果您使用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;