George
George

Reputation: 7327

Encoding JSON as Base64 Before HTTP POSTing It (in Haskell)

Problem: I am attempting to POST some JSON to an HTTP endpoint that only accepts Base64 encoding.

Code Sample: Here is a code sample which successfully posts without Base64 encoding:

{-# LANGUAGE OverloadedStrings #-}

module Lib where

import           Data.Aeson                 (encode, object, (.=))
import qualified Data.ByteString.Lazy.Char8 as L8
import           Network.HTTP.Client
import           Network.HTTP.Client.TLS
import           Network.HTTP.Types.Status  (statusCode)
import qualified Data.ByteString.Base64 as B64

postJSON :: IO ()
postJSON = do
    manager <- newManager tlsManagerSettings

    -- Nested JSON object to POST:
    let requestObject = object
            [ "event" .= ("App launched." :: String)
            , "properties"  .=  object [  "distinct_id" .= ("user" :: String)
                                       ,  "token"       .= ("f793bae9548d8e123cef251fd81df487" :: String)
                                       ]
            ]  
    initialRequest <- parseRequest "http://api.mixpanel.com/track"
    let request = initialRequest
            { method = "POST"
            , requestBody = RequestBodyLBS $ encode requestObject
            , requestHeaders =
                [ ("Content-Type", "application/json; charset=utf-8")
                ]
            }

    response <- httpLbs request manager
    putStrLn $ "The status code was: "
            ++ show (statusCode $ responseStatus response)
    L8.putStrLn $ responseBody response

Attempt: In order to send the JSON as Base64 encoded, I tried replacing requestBody = RequestBodyLBS $ encode requestObject with requestBody = RequestBodyLBS $ Data.Bytestring.Base64.encode (encode requestObject), but I get a type error. So how do I encode the JSON as Base64 for this HTTP POST?

Upvotes: 0

Views: 893

Answers (2)

liminalisht
liminalisht

Reputation: 1253

I have two things to add to jberryman's answer.

First, if (as it now appears) you're going to be putting this in a query string, you need to make sure you don't just use a base64 encoded bytestring, but instead use a base64 url-encoded bytestring. So don't use Data.Bytestring.Base64 (as jberryman linked to), but rather Data.Bytestring.Base64.URL (here).

Second, while he pointed you in the right direction on the Base64 encoding part, it seems you're still hung up on setting the querystring. For that, you should check out the setQueryString function in the http-client library you're already using (link here).

That function has the signature:

setQueryString :: [(ByteString, Maybe ByteString)] -> Request -> Request

So if you're base64 encoded bytestring is built like this

let urlEncodedBytestring = Data.Bytestring.Base64.URL.encode . L8.toStrict $ encode requestObject

and if you're attemtping to set the data key in the querystring of your request, then you'll probably want:

let requestWithQueryStringSet = setQueryString [("data", (Just urlEncodedBytestring))] request

Upvotes: 2

jberryman
jberryman

Reputation: 16645

B64.encode here is a function from strict ByteString to strict ByteString (you'll need to hover over the type name in the haddocks to see this if you're just browsing), while Aeson.encode returns a lazy bytestring (from the Data.ByteString.Lazy module). These are two distinct types although they have the same name.

You probably have to do something like:

   ...
   requestBody = RequestBodyLBS $ L8.fromStrict $ Data.Bytestring.Base64.encode (L8.toStrict $ encode requestObject)

Upvotes: 3

Related Questions