Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending Generic Content-Type in Servant

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

like image 514
Ecognium Avatar asked Feb 25 '16 19:02

Ecognium


1 Answers

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.

like image 187
user2141650 Avatar answered Nov 12 '22 14:11

user2141650