【问题标题】:TIdHTTPProxyServer raising "Unknown Protocol" RSHTTPUnknownProtocol exceptionTIdHTTPProxyServer 引发“未知协议”RSHTTPUnknownProtocol 异常
【发布时间】:2019-10-12 14:57:06
【问题描述】:

我正在使用 Delphi 10.3 Rio 重新编写旧的 Delphi XE 程序。它使用 TIdHTTPProxyServer Indy 组件监听 127.0.0.1:80。

  with IdHTTPProxyServer.Bindings.Add do begin

    IP := '127.0.0.1';
    Port := 80;

  end;

  IdHTTPProxyServer.Active := True;

为了测试,我在 hosts 文件中添加了 127.0.0.1 localtest123.com 和 127.0.0.1 www.localtest123.com 并禁用了 DNS 缓存服务。然后在多个浏览器中我请求了http://localtest123.com/http://www.localtest123.com/。使用 OutputDebugString() 我可以看到连接被接受,但随后引发“未知协议”错误。

我在 IdHTTPProxyServer.pas 中的 TIdHTTPProxyServer.CommandPassThrough 过程中调试了异常。似乎 LURI.Protocol 是一个空字符串,这就是引发 RSHTTPUnknownProtocol 的原因。

  LContext := TIdHTTPProxyServerContext(ASender.Context);
  LContext.FCommand := ASender.CommandHandler.Command; //<-'GET'
  LContext.FTarget := ASender.Params.Strings[0]; //<-'/'

  LContext.FOutboundClient := TIdTCPClient.Create(nil);
  try
    LURI := TIdURI.Create(LContext.Target); //<-'/'
    try
      TIdTCPClient(LContext.FOutboundClient).Host := LURI.Host; //<-''

      if LURI.Port <> '' then begin //<-''
        TIdTCPClient(LContext.FOutboundClient).Port := IndyStrToInt(LURI.Port, 80);
      end
      else if TextIsSame(LURI.Protocol, 'http') then begin //<-''    {do not localize}
        TIdTCPClient(LContext.FOutboundClient).Port := IdPORT_HTTP;
      end
      else if TextIsSame(LURI.Protocol, 'https') then begin //<-'' {do not localize}
        TIdTCPClient(LContext.FOutboundClient).Port := IdPORT_https;
      end else begin
        raise EIdException.Create(RSHTTPUnknownProtocol);
      end;

我可能遗漏了一些东西,但 TIdHTTPProxyServer 无需太多代码即可工作,因此我必须就这个异常寻求帮助。提前致谢!

【问题讨论】:

    标签: delphi indy indy10 delphi-10.3-rio


    【解决方案1】:

    您不能只重定向 HOSTS 文件中的域并期望事情神奇地起作用。这不是代理的工作原理。

    您必须明确配置 Web 浏览器以通过 HTTP 代理发出 HTTP 请求,以便它们格式化代理可以理解的正确请求。将 HTTP 请求直接发送到目标 Web 服务器的处理方式与通过代理发送相同的 HTTP 请求不同。

    您收到异常是因为浏览器请求未正确定位您的代理。

    例如,当浏览器直接向目标 Web 服务器发送 HTTP GET 请求时,它会直接连接到该服务器,然后发送如下所示的请求:

    GET /path HTTP/1.1
    Host: server.com
    

    但是,当它通过 HTTP 代理发送相同的请求时,它会连接到代理并发送一个看起来更像这样的请求:

    GET http://server.com/path HTTP/1.1
    

    您的浏览器请求中缺少GET 行中的额外路径信息,因为您没有为浏览器配置代理,因此当TIdHTTPProxyServer 尝试确定建立连接所需的信息时出现异常到目标 Web 服务器并将当前请求转发给它。

    这从根本上说明了 HTTP 的工作原理,以及 TIdHTTPProxyServer 的工作原理。

    当涉及到 HTTPS 时,事情会稍微复杂一些,但我暂时忽略了这个细节,因为它与您关于异常的问题无关。

    更新:在 cmets 中,你说:

    在 XE 版本中,它在检查今天仍然有效的协议时从未引发异常,因为我在 DoHTTPBeforeCommand 中手动设置了主机和端口。

    在那个旧版本中,没有引发异常,因为TIdHTTPProxyServer 尚未检查协议以区分 HTTP 和 HTTPS。当收到并非专门针对您的代理的请求时,您可以手动填写缺失的信息。这就是为什么以前对你有用的原因。

    在以后的版本中,TIdHTTPProxyServer 已更新为在请求中未明确指定端口时区分 HTTP 和 HTTPS,因此根据请求的协议设置默认端口。该检查发生在调用 DoHTTPBeforeCommand() 之前。

    要恢复旧行为,您必须更改 TIdHTTPProxyServer 的源代码以延迟引发异常,直到 DoHTTPBeforeCommand() 返回之后,这样您就有机会再次填写缺失值。

    如果您为此 file a feature request,我可能会考虑将其添加到 Indy 的官方代码中。

    【讨论】:

    • 好的,当我在 Delphi XE 中制作这个程序时,TIdHTTPProxyServer 的行为有所不同。我需要监视一个单个域,并在请求某些文件时替换不同的文件,并且它在非常少的代码的情况下工作得很好,这太棒了。看起来你在几年前重写了 CommandPassThrough,现在对我的程序不起作用。我应该查看 TIdMappedPortTCP 或 TIdHTTPServer 并自己处理所有事情吗?我无法将浏览器设置为使用代理,因为这些 GET 请求也将来自不同的应用程序,因此我使用主机文件方式。谢谢!
    • 非常感谢雷米的详细解释!看起来 TIdHTTPProxyServer 现在是一个真正的代理服务器。实际上,我正在重新考虑我的项目以在不修改主机文件的情况下使用它,这有几个好处。如果没有,我将编写自己的 TIdCmdTCPServer 或直接使用 TIdHTTPServer。感谢您的帮助,Indy 是一个了不起的套接字库,这就是我仍在使用 Delphi 的原因!
    • @Jack "当我在 Delphi XE 中制作这个程序时,TIdHTTPProxyServer 的行为有所不同" - 不,它没有。 “看起来你在很多年前重写了 CommandPassThrough” - 多年来,内部实现发生了变化,但 HTTP 代理的 语义 并没有改变。 TIdHTTPProxyServer 始终要求客户端在请求行中指定绝对 URL,即使它并不总是验证 URL 的指定协议。 Web 浏览器不会请求绝对 URL,除非它们知道自己通过 HTTP 代理进行连接。
    • @Jack "我应该查看 TIdMappedPortTCP 还是 TIdHTTPServer 并自己处理所有事情?" - 根据您的描述,我不得不说是的。
    • 我有一个 8 年以上的程序,每天都在使用,XE 中的 TIdHTTPProxyServer 的行为确实与今天的 TIdHTTPProxyServer 不同。在 XE 版本中,它在检查今天仍然有效的协议时从未引发异常,因为我在 DoHTTPBeforeCommand 中手动设置了主机和端口。所以 TIdHTTPProxyServer 现在必须表现不同。再次感谢您的帮助!
    猜你喜欢
    • 2015-04-10
    • 1970-01-01
    • 1970-01-01
    • 2011-12-02
    • 1970-01-01
    • 2014-03-02
    • 1970-01-01
    • 1970-01-01
    • 2011-02-21
    相关资源
    最近更新 更多