在 Servant 中发送通用 Content-Type

Sending Generic Content-Type in Servant

我正在尝试将一些 ByteString 中继回客户端(浏览器)。客户端将不知道所请求文档的 content-type,因此我试图将适当的 content-type 响应发送回客户端。文档可以是图片或pdf或word文档等

例如,客户端将请求 /document?id=55,服务器将以适当的 content-type 和关联的 ByteString 进行响应。

我按照示例 here: 为图像创建了一些东西。

 data IMAGE

 instance Accept IMAGE where
     contentType _ = "image" M.// "jpeg"

 instance MimeRender IMAGE LBS.ByteString where
     mimeRender _ = id

挑战在于客户端不会发送带有某些特定 Accept: header 的请求,因此我无法像完成时那样对适当的 Mime 类型做出反应 here.另外,以上仅适用于图像(假设浏览器会推断 png 即使我发回 jpeg)但不适用于 pdfdocx

我考虑过像 MyDynamicContent String 这样的参数化类型,我将在 run-time 处传入内容类型,但我不确定我将如何声明我的 API 即什么会我使用而不是 '[JSON]。不确定这样的事情是否可能,因为示例只是一个简单的数据类型。

所以我的问题是,如果我想发送一些 ByteString 作为响应并动态设置 Content-Type header,使用 servant

更新:我开了一个issue

有可能,但有点麻烦:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverlappingInstances #-}
module DynCT where

import Control.Monad.Trans.Either
import Data.ByteString.Lazy (ByteString)
import Servant
import Servant.API.ContentTypes
import Network.Wai.Handler.Warp

data WithCT = WithCT { header ::  ByteString, content :: ByteString }

instance AllCTRender xs WithCT where
  handleAcceptH _ _ (WithCT h c) = Just (h, c)

type API = Get '[] WithCT

api :: Proxy API
api = Proxy

server :: Server API
server = return $ WithCT { header = "example", content = "somecontent" }

main :: IO ()
main = run 8000 $ serve api server

正在测试:

  % curl localhost:8000 -v
...
< HTTP/1.1 200 OK
...
< Content-Type: example
<
...
somecontent%

这个想法只是通过为 AllCTRender 声明一个重叠实例来覆盖正常行为。请注意,如果您还使用这些,您可能还需要为 servant-clientservant-docs 等做一些额外的腿部工作。鉴于此,您可能想在回购协议中打开一个关于此的问题以获得更完整的支持。

截至目前,此 hack 有效

data WithCT = WithCT {header :: BS.ByteString, content :: BS.ByteString}

instance AllCTRender '[IMAGE] WithCT where
  handleAcceptH _ _ (WithCT h c) = Just (fromStrict h, fromStrict c)

data IMAGE deriving (Typeable)

instance MimeRender IMAGE BS.ByteString where
  mimeRender _ content = fromStrict content

instance Accept IMAGE where
  contentType _ = ""

type ImageApi = Capture "image_id" ImageId :> Get '[IMAGE] WithCT