什么力量驱使WAI应用程序重新设计五次? [英] What forces drove WAI Application to be redesigned five times?

查看:173
本文介绍了什么力量驱使WAI应用程序重新设计五次?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 WAI 界面有一个好奇的看法,虽然看起来很简单,但我很惊讶地看到它需要多少迭代来稳定当前窗体!



我曾假设CPS的资源安全风格是最有趣的事情,但看起来还有很多需要学习的东西!

  $ git log -p --reverse  -  wai / Network / Wai.hs | grep'\ + type Application'
+ type Application = Request - > Iteratee B.ByteString IO Response
+ type Application = Request - > ResourceT IO响应
+ type Application =请求 - > C.ResourceT IO Response
+ type Application = Request - > IO回应
+类型应用程序=请求 - > (响应 - > IO b) - > IO b)
+ type应用程序=请求 - > (Response - > IO ResponseReceived)
- > IO ResponseReceived

某些考古学产生的结果可能不令人满意:

  $ git log --reverse -G'type Application'--pretty = oneline  -  wai / Network / Wai.hs |猫
879d4a23047c3585e1cba4cdd7c3e8fc13e17592所有内容迁移到围子
360442ac74f7e79bb0e320110056b3f44e15107c开始将围/经到管道
af7d1a79cbcada0b18883bcc5e5e19a1cd06ae7b管道0.3
fe2032ad4c7435709ed79683acac3b91110bba04传递,而不是生活在ResourceT
63ad533299a0a5bad01a36171d98511fdf8d5821应用围绕InternalState使用括号格式
1e1b8c222cce96c3d58cd27318922c318642050d ResponseReceived,避免存在问题


解决方案

所有的设计似乎都是由三个主要关注点驱动的:


  • 请求可以有流式处理的主体(所以我们不必在开始处理它们之前将它们全部加载到内存中)。如何最好地表示它?

  • 响应也可以流式传输。如何最好地表示它?

  • 如何确保在生成响应时分配的资源能够正确释放? (例如,如何确保文件句柄在提供文件后被释放?)


    $ hr

      type Application = Request  - > Iteratee B.ByteString IO Response 

    这个版本使用iteratees,它是Haskell流式数据的早期解决方案。迭代消费者必须以基于推送的方式编写,与现代流媒体库中使用的基于拉取的消费者相比,这种方式可以说并不自然。

    请求的流式主体被馈送到迭代器,最后得到一个 Response 值。 Response 包含一个枚举器(一个将流响应字节提供给服务器提供的响应迭代器的函数)。据推测,枚举器可以使用括号之类的函数来控制资源分配。




      type Application = Request  - > ResourceT IO Response 

    这个版本使用 resourcet monad转换器用于资源管理,而不是在枚举器中进行。在 Request 和 Response 中都有一个特殊的 Source 它处理流数据(这是有点难以理解的恕我直言)。






      type Application = Request  - > IO Response 

    该版本使用来自 conduit ,但是避免使用resourcet,而是提供一个类似括号的 responseSourceBracket 用于处理流式响应中的资源。 p>




      type Application = Request  - > (响应 - > IO b) - > IO b)
    类型应用程序=请求 - > (Response - > IO ResponseReceived) - > IO ResponseReceived

    这个版本转向基于延续的方法,使处理函数能够使用常规括号类似的函数来控制资源分配。回到原来的方式,在这方面!



    管道不再用于流式传输。现在有一个简单的请求 - > IO ByteString 函数来读取请求主体的块,并且(Builder - > IO()) - > IO() - > IO() Response 中的<>函数。 (服务器提供了 Builder - > IO()写入函数以及刷新操作。)



    与基于资源的版本类似,并且与基于迭代器的版本不同,此实现允许您重叠读取请求主体并传播响应。

    多态处理程序是一种巧妙的技巧,可确保响应式回调响应 - >总是调用IO b :处理程序需要返回 b ,唯一的办法是实际调用回调函数!



    这种多态的解决方案似乎导致了一些问题(可能将处理程序存储在容器中?)而不是使用多态性,我们可以使用 ResponseReceived 标记。效果是一样的:处理程序代码获取它需要返回的令牌的唯一方法是调用回调函数。


    I took a curious look at WAI interface and while it looks simple, I was surprised to see how many iterations it took to stabilize at the current form!

    I had assumed that CPS style for resource safety would be the most interesting thing but it looks like there is much more to learn from!

    $ git log -p --reverse -- wai/Network/Wai.hs | grep '\+type Application'
    +type Application = Request -> Iteratee B.ByteString IO Response
    +type Application = Request -> ResourceT IO Response
    +type Application = Request -> C.ResourceT IO Response
    +type Application = Request -> IO Response
    +type Application = Request -> (forall b. (Response -> IO b) -> IO b)
    +type Application = Request -> (Response -> IO ResponseReceived)
                                -> IO ResponseReceived
    

    Some archeology yields somewhat unsatisfactory results:

    $ git log --reverse -G 'type Application' --pretty=oneline -- wai/Network/Wai.hs | cat
    879d4a23047c3585e1cba4cdd7c3e8fc13e17592 Moved everything to wai subfolder
    360442ac74f7e79bb0e320110056b3f44e15107c Began moving wai/warp to conduit
    af7d1a79cbcada0b18883bcc5e5e19a1cd06ae7b conduit 0.3
    fe2032ad4c7435709ed79683acac3b91110bba04 Pass around an InternalState instead of living in ResourceT
    63ad533299a0a5bad01a36171d98511fdf8d5821 Application uses bracket pattern
    1e1b8c222cce96c3d58cd27318922c318642050d ResponseReceived, to avoid existential issues
    

    解决方案

    All the designs seem to be driven by three main concerns:

    • Requests can have streamed bodies (so we don't have to load them all in memory before starting to process them). How to best represent it?
    • Responses can be streamed as well. How to best represent it?
    • How to ensure that resources allocated in the production of a response are properly freed? (For example, how to ensure that file handles are freed after serving a file?)

    type Application = Request -> Iteratee B.ByteString IO Response
    

    This version uses iteratees, which were an early solution for streaming data in Haskell. Iteratee consumers had to be written in a "push-based" way, which was arguably less natural than the "pull-based" consumers used in modern streaming libraries.

    The streamed body of the request is fed to the iteratee and we get a Response value at the end. The Response contains an enumerator (a function that feeds streamed response bytes to a response iteratee supplied by the server). Presumably, the enumerator would control resource allocation using functions like bracket.


    type Application = Request -> ResourceT IO Response
    

    This version uses the resourcet monad transformer for resource management, instead of doing it in the enumerator. There is a special Source type inside both Request and Response which handles streamed data (and which is a bit hard to understant IMHO).


    type Application = Request -> IO Response
    

    This version uses the streaming abstractions from conduit, but eschews resourcet and instead provides a bracket-like responseSourceBracket function for handling resources in streamed responses.


    type Application = Request -> (forall b. (Response -> IO b) -> IO b)
    type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
    

    This version moves to a continuation-based approach which enables the handler function to use regular bracket-like functions to control resource allocation. Back to square one, in that respect!

    Conduits are no longer used for streaming. Now there is a plain Request -> IO ByteString function for reading chunks of the request body, and a (Builder -> IO ()) -> IO () -> IO () function in the Response for generating the response stream. (The Builder -> IO () write function along with a flush action are supplied by the server.)

    Like the resourcet-based versions, and unlike the iteratee-based version, this implementation lets you overlap reading the request body with streaming the response.

    The polymorphic handler is a neat trick to ensure that the response-taking callback Response -> IO b is always called: the handler needs to return a b, and the only way to get one is to actually invoke the callback!

    This polymorphic solution seems to have caused some problems (perhaps with storing handlers in containers?) Instead of using polymorphism, we can use a ResponseReceived token without a public constructor. The effect is the same: the only way for handler code to get hold of the token it needs to return is to invoke the callback.

    这篇关于什么力量驱使WAI应用程序重新设计五次?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆