【发布时间】:2021-11-15 11:49:35
【问题描述】:
是否可以在不干扰后续OnCommandGet 事件的情况下读取Indy TIdHTTPServer.OnConnect 事件中的HTTP 标头(特别是GET 标头)?
如果我尝试使用ReadLn 的循环来拉动它们,那么OnCommandGet 永远不会触发。我需要在不将它们从输入缓冲区中拉出的情况下对它们进行高级查看。
【问题讨论】:
是否可以在不干扰后续OnCommandGet 事件的情况下读取Indy TIdHTTPServer.OnConnect 事件中的HTTP 标头(特别是GET 标头)?
如果我尝试使用ReadLn 的循环来拉动它们,那么OnCommandGet 永远不会触发。我需要在不将它们从输入缓冲区中拉出的情况下对它们进行高级查看。
【问题讨论】:
是否可以在不干扰后续
OnCommandGet事件的情况下读取IndyTIdHTTPServer.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 读取标头之后但在读取请求正文之前。
【讨论】:
OnHeadersAvailable 事件轻松完成。您可以将用户数据存储在TIdContext 或TIdHTTPSession 中。在新 TCP 连接的第一个事件中,测试您需要什么并将结果存储在 Context 或 HTTPSession 中。在后续事件中,如果结果已存储,则跳过您的测试。
根据 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。我做错了什么?
【讨论】: