【问题标题】:Indy Proxy Server HTTPS to HTTPIndy 代理服务器 HTTPS 到 HTTP
【发布时间】:2017-05-21 03:07:48
【问题描述】:

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

我当前的方法是使用带有 SSL IOHandler 的 TIdHTTPserver,在其 OnCommandGet 处理程序中,我动态创建了一个 TIdHTTP 客户端,它从内部应用程序获取 TFileStream ContentStream 并将该流作为 Response.ContentStream 返回到外部来电者。

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

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

【问题讨论】:

    标签: http ssl https proxy indy


    【解决方案1】:

    如果请求客户端支持 HTTP 1.1 分块(请参阅 RFC 2616 Section 3.6.1),这将允许您从目标服务器读取数据并立即将其实时发送到客户端。

    如果您使用的是相当新的 Indy 版本,TIdHTTP 有一个 OnChunkReceived 事件,并且在其 HTTPOptions 属性中有一个 hoNoReadChunked 标志:

    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 事件,您仍需要向TIdHTTP 提供输出TStream,否则不会触发该事件(此限制可能会在未来的版本中删除)。但是,您可以使用 TIdEventStream 而无需为其分配 OnWrite 事件处理程序。或者编写一个自定义的TStream 类,覆盖虚拟Write() 方法以什么都不做。或者,只需使用您想要的任何TStream,并让OnChunkReceived 事件处理程序清除接收到的Chunk,这样就无法写入TStream

    • 如果您使用hoNoReadChunked 标志,这允许您在TIdHTTP 退出后直接从TIdHTTP.IOHandler 手动读取HTTP 块。只需确保启用 HTTP keep-alives,否则 TIdHTTP 将在您有机会读取服务器的响应正文之前关闭与服务器的连接。

    如果您使用的是旧版本的 Indy,或者如果目标服务器不支持分块,则不会丢失所有内容。您应该能够编写一个自定义的TStream 类来覆盖虚拟Write() 方法,以将提供的数据块作为HTTP 块写入客户端。然后您可以将该类用作TStream 的输出TIdHTTP

    如果客户端不支持 HTTP 分块,或者这些方法不适合您,那么您可能不得不直接使用 TIdTCPServer 而不是 TIdHTTPServer 并自己从头实现整个 HTTP 协议,然后您可以根据需要处理自己的流媒体。查看TIdHTTPProxyServer 的源代码以获得一些想法(TIdHTTPProxyServer 本身并不适合您的特定情况,但它会向您展示如何在连接之间几乎实时地传递 HTTP 请求/响应)。

    【讨论】:

      【解决方案2】:

      感谢您提供非常全面的答案。我最终解决它的方法是创建一个 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;
      

      【讨论】:

        猜你喜欢
        • 2020-03-23
        • 1970-01-01
        • 2020-05-03
        • 2015-08-23
        • 2011-05-07
        • 1970-01-01
        • 1970-01-01
        • 2019-12-13
        相关资源
        最近更新 更多