在Haskell中,如何在Web客户端断开连接时中止计算

时间:2017-09-04 16:41:25

标签: haskell tcp haskell-snap-framework

我有一个基于Haskell的Web服务,它执行的计算对于某些输入可能需要很长时间才能完成。 (“真的很长”,这意味着一分钟)

因为执行该计算会占用服务器上所有可用的CPU,所以我将传入的请求放入队列中(当然,实际上是一个堆栈,原因与典型客户端有关,但除了这一点之外),当它们到达时在当前运行的计算完成时为它们提供服务。

我的问题是,客户端并不总是等待足够长的时间,有时会超时,断开连接并尝试不同的服务器(好吧,他们再次尝试并点击elb,通常会获得不同的实例) 。此外,由于外部因素,Web客户端偶尔要求的计算将被淘汰,并且Web客户端将被终止。

在那些情况下,我真的希望能够在我将下一个请求从堆栈中拉出并开始(昂贵的)计算之前检测到Web客户端已经消失。不幸的是,我使用snap的经验让我相信在该框架中无法询问“客户端的TCP连接是否仍然连接?”我还没有找到任何涵盖“客户端断开连接”案例的其他Web框架的文档。

那么是否有一个Haskell Web框架可以轻松检测Web客户端是否已断开连接?或者失败了,是否有一个至少使它成为可能?

(据我所知,在所有情况下都不可能完全确定TCP客户端是否仍在那里而不向另一端发送数据;但是,当客户端实际将RST数据包发送到服务器和服务器的框架时不让应用程序代码确定连接已经消失,这是一个问题)

顺便提一下,虽然有人可能会怀疑warp's onClose处理程序会让你这样做,但只有在响应准备好并写入客户端时才会触发,因此作为中止计算的方法无用进行中。似乎也无法访问已接受的套接字以设置SO_KEEPALIVE或类似内容。 (有多种方法可以访问初始侦听套接字,但不能接受已接受的套接字)

2 个答案:

答案 0 :(得分:2)

假设“Web服务”是指基于HTTP(S)的客户端,一种选择是使用RESTful方法。该服务可以接受请求并返回202 Accepted,而不是假设客户端将保持连接。正如HTTP status code specification概述:

  

该请求已被接受处理,但处理尚未完成[...]

     

202回复是故意不承诺的。其目的是允许服务器接受对某些其他进程的请求(可能是每天只运行一次的面向批处理的进程),而不要求用户代理与服务器的连接持续到进程完成为止。使用此响应返回的实体应该包括请求的当前状态的指示,以及指向状态监视器的指针或用户可以期望满足请求的某种估计。

服务器立即响应202 Accepted响应,并且还包含客户端可用于轮询状态的URL。一种选择是将此URL放在响应的Location标题中,但您也可以将URL放在响应正文中的链接中。

客户端可以轮询状态URL以获取状态。计算完成后,状态资源可以提供指向已完成结果的链接。

如果您担心客户端轮询太多,您可以将缓存标头添加到状态资源和最终结果。

REST in Practice概述了一般概念,而RESTful Web Services Cookbook则有很多好的细节。

我不是说你不能用HTTP或TCP / IP(我不知道)做某事,但如果你不能,那么以上是一个经过验证的类似解决方案问题。

显然,这完全独立于编程语言,但我的经验是REST and algebraic data types go well together

答案 1 :(得分:1)

所以我找到了一个适合我的答案,它可能适用于其他人。

事实证明,你可以用Warp的内部装置来实现这一点,但是你剩下的就是Warp的基本版本,如果你需要记录等等,需要添加其他包。

另外,请注意所谓的“半封闭”连接(当客户端关闭其发送端,但仍在等待数据时)将被检测为关闭,从而中断您的计算。我不知道任何处理半封闭连接的HTTP客户端,只是需要注意的事项。

无论如何,我所做的是首先复制由runSettingsrunSettingsSocket公开的函数Network.Wai.Handler.WarpNetwork.Wai.Handler.Warp.Internal,并制作调用我提供的函数的版本而不是{{ 1}},以便我有签名:

WarpI.socketConnection

这需要复制一些辅助方法,例如runSettings' :: Warp.Settings -> (Socket -> IO (IO WarpI.Connection)) -> Wai.Application -> IO () setSocketCloseOnExec。双重windowsThreadBlockHack签名可能看起来很奇怪,但这就是你想要的 - 外部IO在主线程(调用IO)和内部accept中运行在IO返回后分叉的每个连接线程中运行。原始accept函数Warp相当于:

runSettings

然后我做了:

\set -> runSettings' set (WarpI.socketConnection >=> return . return)