【问题标题】:HTTP Response sent before async call returns在异步调用返回之前发送的 HTTP 响应
【发布时间】:2016-01-29 06:55:47
【问题描述】:

我还没有理解 Web 服务器线程的行为,如果我对数据库进行异步调用,并立即向客户端返回响应(说 OK),甚至无需等待异步调用返回。首先,这是一个好方法吗?进行异步调用的线程会发生什么,如果它再次用于服务另一个请求,然后之前的异步调用返回到这个特定的线程。或者网络服务器是否让这个线程等待它发出的异步调用返回。然后问题将是许多挂起的线程将打开,并且 Web 服务器将可用于接受更多请求。我正在寻找答案。

【问题讨论】:

    标签: http asynchronous request webserver response


    【解决方案1】:

    这取决于您的 HTTP 服务器的工作方式。但是你应该非常谨慎。

    假设您有一个处理传入 HTTP 连接的主事件循环,以及管理 HTTP 通信的工作线程。 只有当工作线程有效地完全准备好接受新的 HTTP 请求管理时,它才应被视为已准备好接受新的 HTTP 请求管理。

    就纯 HTTP 而言,更重要的是避免在收到整个查询之前发送响应。这看起来很简单,而且通常都是这样。但是如果查询作为一个主体,可能是一个分块的主体,那么接收整个消息可能需要一些时间。 您应该从不之前发送响应,除非它类似于 400 错误请求响应,然后是真正的 tcp/ip 连接关闭。如果您没有这样做,并且您遇到消息长度解析问题,那么您在查询结束之前发送响应的事实可能会导致安全问题。在某种 http 走私问题中,它可用于利用服务器与服务器前面的任何其他 HTTP 代理(ssl 终结器、反向代理等)之间的消息解析差异。对于这个代理来说,如果你做出了回应,就意味着你已经掌握了整个消息,它可以发送下一条消息,你实际上会认为这只是正文的另一部分。

    现在,如果您有完整的消息,您可以决定发送早期响应并分离异步任务以真正执行某种东西。但这意味着:

    • 您必须假设不应再生成输出,您不会尝试向请求发出者发送任何输出,您应该认为通信现在已关闭
    • 工作线程不应该接收新的管理请求,这是困难的部分。如果此线程被标记为可用于新请求,它也可能被线程管理器杀死(您在 Nginx 或 Apache 中具有与工作人员相关联的请求计数器,并且它们在达到限制后被杀死,创造新鲜的)。它也可能会收到一个优雅的重新加载命令(通常是杀死),等等。

    因此,您开始进入一个您应该了解 HTTP 服务器内部结构的区域,该区域可能由您管理,也可能不由您管理,并且迟早会出现更改。然后你开始制作非常奇怪的东西,这通常会导致奇怪的问题,难以重现。

    通常处理异步任务的最佳方式是使用消息系统,同时仍能理解发生了什么。将任务列表放入queue,并获得一个并行异步工作进程,它会处理这些任务。如果需要,跟踪这些任务的状态。 同样的事情也适用于客户端,在收到一个非常快的 HTTP 应答后,它可能需要对任务状态执行一些 ajax 状态轮询。您可能只需要检查队列中任务的状态即可发送响应。

    您将对整个事情有更多的控制权。

    对我来说,我真的不喜欢分离的线程、来自奇怪的代码、执行繁重的任务而没有任何输出状态或报告错误的方式,并且可能会阻止漂亮的应用程序停止调用(仍在等待奇怪的线程加入)并不意味着杀戮。

    【讨论】:

    • 谢谢@regilero,这对我有很大帮助,我也给了你我的赏金。这是我的理解:a)线程获取请求,b)不关心数据库中插入的状态(比如缓存层)c)以异步模式启动插入调用 d)将硬编码的响应发送给调用者 - - 这不是一个好方法,因为 Web 服务器管理线程可能会在正常关闭之前等待这些分离的线程完成。这只是唯一的问题吗?由于这里的响应不关心异步调用的状态。只需硬编码就足够了(假设)
    【解决方案2】:

    这取决于此异步操作是否执行应通知客户端的操作。

    如果您返回 200 OK(即成功完成)并且稍后异步操作失败,则客户端将不知道错误。

    您当然有一些选项,例如通过 websocket 发送某种推送通知或发送另一个请求,该请求将返回实际结果等。所以基本上取决于你的需求...

    【讨论】:

    • 谢谢,但我的问题是:这个线程仍然可以接受网络服务器的另一个请求吗?或者虽然发送了响应,但线程仍在等待异步调用返回并且不接受任何其他请求( HTTP )。因此,假设有人要求我构建一个 REST API,他/她不关心 POST 请求最终是否写入 db。然后我可以有一个队列,只需将响应放入队列中即可将其发回。另一个是:对 DB 进行异步调用,然后返回响应 OK 吗?这样做安全吗?
    • 是的,只要您从您的方法返回,该线程就可用于其他请求。对于另一部分 - 我可能会使用 Executor 服务来执行异步操作,而不是启动另一个线程。如果您为此使用了一些第三方库,我认为他们已经利用了这一点。
    • 那么假设这个线程现在开始接受另一个请求,同时之前进行的异步调用返回并抛出异常。会发生什么 ?新请求会惊讶于是什么让它跳转到异常。不是吗?为清楚起见:a)线程 T1,接受请求 b)进行 ASYNC - db 调用 c)立即返回响应,而不等待 ASYNC 调用的状态 d)T1 现在接受新的 HTTP 请求,e)在处理新请求时,先前ASYNC 调用向 T1 抛出异常。会发生什么 ? T1 会很困惑不是吗?
    • 异常是在执行操作的线程中抛出的,而不是原来的。一旦它处理新请求,其他线程就与它没有任何关系。
    • @siddhusingh 没有什么像父线程。如果您没有在执行线程中捕获异常,则该异常将被忽略或最多只是记录到 JVM 日志中(通常是 stderr)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-01
    • 1970-01-01
    • 2018-11-16
    • 2020-04-20
    • 1970-01-01
    • 2020-04-20
    相关资源
    最近更新 更多