I am trying to relay some ByteString
back to the client (browser). The client will not know the content-type of the document being requested so I am trying to send appropriate content-type response back to the client. The document could be an image or pdf or word document, etc.
For example, the client will request /document?id=55
and the server will respond with the appropriate content-type and the associated ByteString
.
I followed the example here: and I created something for an image.
data IMAGE
instance Accept IMAGE where
contentType _ = "image" M.// "jpeg"
instance MimeRender IMAGE LBS.ByteString where
mimeRender _ = id
The challenge is the client will not be sending the request with some specific Accept:
header so there is no way for me to react with an appropriate Mime Type like it is done here. Plus the above will only work for images (assuming browsers will infer a png
even I send back jpeg
) but not for pdf
, docx
,etc.
I thought about a paramaterized type like MyDynamicContent String
and I will pass in the content type at run-time but I am not sure how I will declare my API i.e., what will I use instead of '[JSON]
. Not sure such thing is even possible as the examples are just a simple datatype.
So my question is, if I want to send some ByteString
as a response and set the Content-Type
header dynamically, what will be the best way to do it using servant
Update: I have opened an issue
It's possible, but a bit of a hack:
{-# 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
Testing it:
% curl localhost:8000 -v
...
< HTTP/1.1 200 OK
...
< Content-Type: example
<
...
somecontent%
The idea is just to override the normal behaviour by declaring an overlapping instance for AllCTRender
. Note that you'll probably also have to do some extra leg work for servant-client
, servant-docs
etc. if you're also using those. Given that, you may want to open an issue in the repo about this for more complete support.