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 IndyTIdHTTPServer.OnConnect
event without interfering with the subsequentOnCommandGet
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 usingWaitFor()
would not.A more robust solution would involve using
TIdIOHandler.CheckForDataOnSource()
in a loop, manually scanning theTIdIOHandler.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 thenOnCommandGet
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?