Home > Blockchain >  Advanced look at HTTP headers
Advanced look at HTTP headers

Time:09-23

Is it possible to read the HTTP headers (specifically the GET header) in the Indy TIdHTTPServer.OnConnect event without interfering with the subsequent OnCommandGet event?

If I try to pull them with a loop of ReadLn's then OnCommandGet never fires. I need an advanced peek at them without pulling them from the input buffer.

CodePudding user response:

Is it possible to read the HTTP headers (specifically the GET header) in the Indy TIdHTTPServer.OnConnect event without interfering with the subsequent OnCommandGet event?

It it possible, in that you could use the TIdIOHandler.WaitFor() method to wait for the header terminator to arrive in the TIdIOHandler.InputBuffer, returning everything received before it without removing anything from the buffer, eg:

procedure TMyForm.IdHTTPServer1Connect(AContext: TIdContext);
var
  headers: String;
begin
  header := AContext.Connection.IOHandler.WaitFor(EOL EOL, False);
  ...
end;

However, this has some limitations:

  • it assumes each line is terminated by the byte sequence $0D $0A and thus the header is terminated by the byte sequence $0D $0A $0D $0A. This is technically true per the HTTP standards, and will usually be the case. However, some clients do terminate lines with just $0A and thus the header would be terminated by $0A $0A. TIdHTTPServer would normally handle that just fine, but using WaitFor() would not.

    A more robust solution would involve using TIdIOHandler.CheckForDataOnSource() in a loop, manually scanning the TIdIOHandler.InputBuffer until either $0D $0A $0D $0A or $0A $0A is found in the buffer.

  • this won't work if there are multiple HTTP requests on the same connection, which can happen if HTTP keep-alives or HTTP pipelining are used. You would be "peeking" the header of only the 1st HTTP request on the connection.

If I try to pull them with a loop of ReadLn's then OnCommandGet never fires.

Correct, because TIdHTTPServer expects to be the one to read them off the InputBuffer. If you read them yourself beforehand, there won't be anything left for TIdHTTPServer to read, so it won't know what each HTTP request even looks like.

I need an advanced peek at them without pulling them from the input buffer.

Why? What do you want to do with them, if you could get them?

You should check if the TIdHTTPServer.OnHeadersAvailable event suits your needs. It is fired at the beginning of every HTTP request, after the headers have been read from the InputBuffer but before the request body has been read.

CodePudding user response:

I got it working by peeking at the Inputbuffer per Remy's suggestion:

procedure TForm1.IdHTTPServer1Connect(AContext: TIdContext);
var
  s: string;
  Done: boolean;
begin
  Done := False;
  repeat
    Sleep(10);
    if AContext.Connection.IOHandler.CheckForDataOnSource then
    begin
      s := AContext.Connection.IOHandler.InputBuffer.AsString;
      if (Pos(#13#10#13#10, s) > 0) or (Pos(#10#10, s) > 0) then Done := True;
    end;
  until Done;
...
end;

One issue I could see happening is a bot making a TCP connection on my port and that loop never ending since no headers are coming. I would need to add some kind of timeout check.

The other suggestion of using OnHeadersAvailable wouldn't work for me because it's called right before OnCommandGet each time (i.e., multiple times per connection when KeepAlive is True) so I might as well just put tests in OnCommandGet if I went that route.

EDIT:

I also just tried doing this in the OnConnect handler:

s := AContext.Connection.IOHandler.WaitFor(#10, False, True, nil, 1000);

Since I only need the GET line and it will always be first (right?) if it's included at all, I just need to find the first line feed character. That solves the line terminator problem and there's a timeout parameter that solves the bot issue. While this does read the first line header, it also causes an immediate disconnect and CommandGet never gets called. What am I doing wrong?

  • Related