3

I'm recoding an old Delphi XE program using Delphi 10.3 Rio. It uses the TIdHTTPProxyServer Indy component listening on 127.0.0.1:80.

  with IdHTTPProxyServer.Bindings.Add do begin

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

  end;

  IdHTTPProxyServer.Active := True;

For testing, I added 127.0.0.1 localtest123.com and 127.0.0.1 www.localtest123.com to the hosts file and disabled the DNS cache service. Then in multiple browers I requested http://localtest123.com/ and http://www.localtest123.com/. With OutputDebugString() I can see the connections accepted but then raises an "Unknown Protocol" error.

I debugged the exception in the TIdHTTPProxyServer.CommandPassThrough procedure in IdHTTPProxyServer.pas. It seems LURI.Protocol is an empty string which is why the RSHTTPUnknownProtocol is raised.

  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;

I'm probably missing something but TIdHTTPProxyServer just works without much code so I have to ask for help on this exception. Thanks in advance!

Jack
  • 33
  • 3

1 Answers1

1

You can't just redirect the domains in your HOSTS file and expect things to magically work. That is not how proxying works.

You must explicitly configure web browsers to make HTTP requests through an HTTP proxy so that they format the proper requests that a proxy would understand. Sending an HTTP request directly to a target web server is handled differently than sending the same HTTP request through a proxy.

You are getting the exception because the browser requests are not targeting your proxy properly.

For example, when a browser sends an HTTP GET request directly to a target web server, it connects directly to that server and then sends a request that looks something like this:

GET /path HTTP/1.1
Host: server.com

But, when it sends the same request through an HTTP proxy, it connects to the proxy and sends a request that looks more like this instead:

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

That extra path information in the GET line is missing in your browser requests, because you do not have your browsers configured for proxying, thus the exception when TIdHTTPProxyServer is trying to determine the info it needs to make a connection to the target web server and forward the current request to it.

This is fundamentally to how HTTP works, and how TIdHTTPProxyServer is designed to work.

Things are a bit more complicated when HTTPS is involved, but I'm leaving that detail out for now, as it is not relevant to your question about the exception.

UPDATE: in comments, you say:

In the XE version it never raised an exception when checking for the protocol which would still work today because I manually set the host and port in DoHTTPBeforeCommand.

In that old version, there was no exception raised, because TIdHTTPProxyServer did not check the protocol yet to differentiate between HTTP and HTTPS. You were able to manually fill in missing info when a request was received that was not specifically targeting your proxy. That is why things worked for you before.

In a later version, TIdHTTPProxyServer was updated to differentiate between HTTP and HTTPS when no port is explicitly specified in the request, so a default port is set based on protocol requested. That check happens before DoHTTPBeforeCommand() is called.

To get the old behavior back, you would have to alter TIdHTTPProxyServer's source code to delay the raising of the exception until after DoHTTPBeforeCommand() returns, so you have a chance to fill in missing values again.

If you file a feature request for that, I might consider adding it to Indy's official code.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Ok, when I made this program in Delphi XE the TIdHTTPProxyServer behaved differently. I needed to monitor a *single domain* and substitute different files when certain files were requested and it worked great with very little code which was amazing. It looks like you rewrote CommandPassThrough a bunch of years ago which won't work for my program now. Should I look into TIdMappedPortTCP or a TIdHTTPServer and handle everything myself? I can't set the browser to use a proxy because these GET requests will come from different apps too, hence why I went with the hosts file way. Thanks! – Jack May 27 '19 at 04:31
  • Thank you very much for the detailed explanation Remy! It looks like the TIdHTTPProxyServer is now a true proxy server. I'm actually re-thinking my project to use it without modifying the hosts file which has a couple positives. If not, I'll code my own TIdCmdTCPServer or just go with a TIdHTTPServer. Thanks for your help and Indy is an amazing socket library which is why I'm still with Delphi! – Jack May 27 '19 at 04:54
  • @Jack "*when I made this program in Delphi XE the TIdHTTPProxyServer behaved differently*" - no, it didn't. "*It looks like you rewrote CommandPassThrough a bunch of years ago*" - the internal implemention has changed through the years, but the *semantics* of HTTP proxying haven't changed. `TIdHTTPProxyServer` has always required a client to specify an absolute URL in the request line, even if it didn't always validate the URL's specified protocol. Web browsers don't request absolute URLs unless they know know they are connecting through an HTTP proxy. – Remy Lebeau May 27 '19 at 05:42
  • @Jack "*Should I look into TIdMappedPortTCP or a TIdHTTPServer and handle everything myself?*" - based on your description, I would have to say yes. – Remy Lebeau May 27 '19 at 05:45
  • I have a 8+ year old program that is in use every single day and the TIdHTTPProxyServer in XE does behave different than today's TIdHTTPProxyServer. In the XE version it never raised an exception when checking for the protocol which would still work today because I manually set the host and port in DoHTTPBeforeCommand. So TIdHTTPProxyServer must behave differently now. Thanks again for the help! – Jack May 27 '19 at 06:40
  • @Jack "*it never raised an exception when checking for the protocol*" - because that version didnt check the protocol, it didnt support HTTPS yet, but still required an absolute URL to get the host and port. Now, it does differentiate between HTTP and HTTPS, hence the exception. "*I manually set the host and port in DoHTTPBeforeCommand*" - ah, that explains it. You left out that important detail before. – Remy Lebeau May 27 '19 at 17:56