【问题标题】:Advanced look at HTTP headers高级查看 HTTP 标头
【发布时间】:2021-11-15 11:49:35
【问题描述】:

是否可以在不干扰后续OnCommandGet 事件的情况下读取Indy TIdHTTPServer.OnConnect 事件中的HTTP 标头(特别是GET 标头)?

如果我尝试使用ReadLn 的循环来拉动它们,那么OnCommandGet 永远不会触发。我需要在不将它们从输入缓冲区中拉出的情况下对它们进行高级查看。

【问题讨论】:

    标签: delphi indy


    【解决方案1】:

    是否可以在不干扰后续OnCommandGet 事件的情况下读取Indy TIdHTTPServer.OnConnect 事件中的HTTP 标头(特别是GET 标头)?

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

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

    但是,这有一些限制:

    • 它假设每一行都以字节序列$0D $0A 结束,因此标题以字节序列$0D $0A $0D $0A 结束。根据 HTTP 标准,这在技术上是正确的,并且通常会如此。但是,有些客户端确实 仅以$0A 终止行,因此标题将由$0A $0A 终止。 TIdHTTPServer 通常会处理得很好,但使用 WaitFor() 不会。

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

    • 如果同一连接上有多个 HTTP 请求,这将不起作用,如果使用 HTTP keep-alives 或 HTTP 管道,就会发生这种情况。您只会“偷看”连接上第一个 HTTP 请求的标头。

    如果我尝试使用ReadLn 的循环来拉动它们,那么OnCommandGet 永远不会触发。

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

    我需要在不从输入缓冲区中拉出它们的情况下对它们进行高级查看。

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

    您应该检查TIdHTTPServer.OnHeadersAvailable 事件是否适合您的需要。它在每个 HTTP 请求开始时触发,在从 InputBuffer 读取标头之后但在读取请求正文之前。

    【讨论】:

    • 在 CommandGet 之前这样做的原因是因为我有一些连接管理例程,这些例程限制来自一组用户(玩家)而不是另一组(管理员)的连接。我知道哪个是 GET 标头中的哪个。由于单个连接会产生多个 CommandGet,因此我不想等到那时,因为我必须插入测试以防止多次记录同一连接。如果必须,我会这样做,但在那之前会更简单。我会在上面研究你的cmets,看看我是否能做到这一点。谢谢。
    • @kbriggs 官方 http 协议是无状态的,因此它没有“连接” - tcp 层需要建立连接才能交换 http 协议数据。所以在我看来,如果你想使用 http 作为传输,你应该尊重 http 的工作方式并接受 tcp 连接,然后适当地响应 GET 命令(可能必须等到你有所有的标头)。跨度>
    • @kbriggs 似乎可以通过OnHeadersAvailable 事件轻松完成。您可以将用户数据存储在TIdContextTIdHTTPSession 中。在新 TCP 连接的第一个事件中,测试您需要什么并将结果存储在 Context 或 HTTPSession 中。在后续事件中,如果结果已存储,则跳过您的测试。
    【解决方案2】:

    根据 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 连接,并且由于没有标头出现,因此该循环永无止境。我需要添加某种超时检查。

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

    编辑:

    我也刚刚尝试在 OnConnect 处理程序中执行此操作:

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

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

    【讨论】:

    • 仅供参考,我使用的是 Indy 10.6.2.5469
    猜你喜欢
    • 2011-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-05
    • 2019-08-06
    • 2017-06-01
    相关资源
    最近更新 更多