深入了解 HTTP headers

Advanced look at HTTP headers

是否可以在 Indy TIdHTTPServer.OnConnect 事件中读取 HTTP headers(特别是 GET header)而不干扰后续 OnCommandGet 事件?

如果我尝试用 ReadLn 的循环拉动它们,那么 OnCommandGet 永远不会触发。我需要在不将它们从输入缓冲区中拉出的情况下深入了解它们。

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?

可能 ,因为您可以使用 TIdIOHandler.WaitFor() 方法等待 header 终止符到达 TIdIOHandler.InputBuffer ,返回之前收到的所有内容而不从缓冲区中删除任何内容,例如:

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

但是,这有一些限制:

  • 它假定每一行都以字节序列 [=16=]D [=16=]A 结束,因此 header 以字节序列 [=17=]D [=17=]A [=17=]D [=17=]A 结束。根据 HTTP 标准,这在技术上是正确的,通常 会是这种情况。但是,一些客户端 do 仅以 [=18=]A 终止行,因此 header 将由 [=19=]A [=19=]A 终止。 TIdHTTPServer 通常会处理得很好,但使用 WaitFor() 就不行。

    更强大的解决方案是在循环中使用 TIdIOHandler.CheckForDataOnSource(),手动扫描 TIdIOHandler.InputBuffer,直到在缓冲区中找到 [=17=]D [=17=]A [=17=]D [=17=]A[=19=]A [=19=]A

  • 如果同一连接上有多个 HTTP 请求,这将不起作用,如果使用 HTTP keep-alives 或 HTTP 流水线,则可能发生这种情况。您将“偷看”连接上的第一个 HTTP 请求的 header。

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

正确,因为 TIdHTTPServer 期望是从 InputBuffer 读取它们的人。如果您事先自己阅读它们,TIdHTTPServer 将没有任何内容可供阅读,因此它甚至不知道每个 HTTP 请求是什么样的。

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

为什么?如果你能得到它们,你想用它们做什么?

您应该检查 TIdHTTPServer.OnHeadersAvailable 活动是否符合您的需要。它在每个 HTTP 请求开始时触发,在从 InputBuffer 中读取 header 之后但在读取请求 body 之前。

根据 Remy 的建议,我通过查看 Inputbuffer 使其工作:

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;

我能看到发生的一个问题是机器人在我的端口上建立 TCP 连接并且该循环永无止境,因为没有 header 出现。我需要添加某种超时检查。

另一个使用 OnHeadersAvailable 的建议对我不起作用,因为它每次都在 OnCommandGet 之前被调用(即,当 KeepAlive 为 True 时每个连接调用多次)所以如果我去了,我还不如将测试放在 OnCommandGet 中那条路线。

编辑:

我也刚刚尝试在 OnConnect 处理程序中这样做:

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

因为我只需要 GET 行而且它总是第一个(对吧?)如果它被包含在内,我只需要找到第一个换行符。这解决了行终止符问题,并且有一个超时参数可以解决机器人问题。虽然这确实读取了第一行 header,但它也会导致立即断开连接并且永远不会调用 CommandGet。我做错了什么?