servant-client-ghcjs: Support binary requests

Introduces support for both sending and receiving binary data
This commit is contained in:
Falco Peijnenburg 2018-04-01 22:54:37 +02:00 committed by Falco Peijnenburg
parent 0c66b9c055
commit 108df0857e

View file

@ -26,6 +26,7 @@ import Control.Monad.Trans.Control (MonadBaseControl (..))
import Control.Monad.Trans.Except import Control.Monad.Trans.Except
import Data.ByteString.Builder (toLazyByteString) import Data.ByteString.Builder (toLazyByteString)
import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as BL
import Data.CaseInsensitive import Data.CaseInsensitive
import Data.Char import Data.Char
import Data.Foldable (toList) import Data.Foldable (toList)
@ -36,9 +37,11 @@ import qualified Data.Sequence as Seq
import Data.String.Conversions import Data.String.Conversions
import Foreign.StablePtr import Foreign.StablePtr
import GHC.Generics import GHC.Generics
import qualified GHCJS.Buffer as Buffer
import GHCJS.Foreign.Callback import GHCJS.Foreign.Callback
import GHCJS.Prim import GHCJS.Prim
import GHCJS.Types import GHCJS.Types
import JavaScript.TypedArray.ArrayBuffer ( ArrayBuffer )
import JavaScript.Web.Location import JavaScript.Web.Location
import Network.HTTP.Media (renderHeader) import Network.HTTP.Media (renderHeader)
import Network.HTTP.Types import Network.HTTP.Types
@ -153,6 +156,7 @@ performXhr xhr burl request = do
openXhr xhr (cs $ requestMethod request) (toUrl burl request) True openXhr xhr (cs $ requestMethod request) (toUrl burl request) True
setHeaders xhr request setHeaders xhr request
js_setResponseType xhr "arraybuffer"
body <- toBody request body <- toBody request
sendXhr xhr body sendXhr xhr body
takeMVar waiter takeMVar waiter
@ -187,6 +191,9 @@ openXhr xhr method url =
foreign import javascript unsafe "$1.open($2, $3, $4)" foreign import javascript unsafe "$1.open($2, $3, $4)"
js_openXhr :: JSXMLHttpRequest -> JSVal -> JSVal -> Bool -> IO () js_openXhr :: JSXMLHttpRequest -> JSVal -> JSVal -> Bool -> IO ()
foreign import javascript unsafe "$1.responseType = $2;"
js_setResponseType :: JSXMLHttpRequest -> JSString -> IO ()
toUrl :: BaseUrl -> Request -> String toUrl :: BaseUrl -> Request -> String
toUrl burl request = toUrl burl request =
let pathS = cs $ toLazyByteString $ requestPath request let pathS = cs $ toLazyByteString $ requestPath request
@ -217,35 +224,38 @@ setHeaders xhr request = do
foreign import javascript unsafe "$1.setRequestHeader($2, $3)" foreign import javascript unsafe "$1.setRequestHeader($2, $3)"
js_setRequestHeader :: JSXMLHttpRequest -> JSVal -> JSVal -> IO () js_setRequestHeader :: JSXMLHttpRequest -> JSVal -> JSVal -> IO ()
sendXhr :: JSXMLHttpRequest -> Maybe String -> IO () sendXhr :: JSXMLHttpRequest -> Maybe ArrayBuffer -> IO ()
sendXhr xhr Nothing = js_sendXhr xhr sendXhr xhr Nothing = js_sendXhr xhr
sendXhr xhr (Just body) = sendXhr xhr (Just body) =
js_sendXhrWithBody xhr (toJSString body) js_sendXhrWithBody xhr body
foreign import javascript unsafe "$1.send()" foreign import javascript unsafe "$1.send()"
js_sendXhr :: JSXMLHttpRequest -> IO () js_sendXhr :: JSXMLHttpRequest -> IO ()
foreign import javascript unsafe "$1.send($2)" foreign import javascript unsafe "$1.send($2)"
js_sendXhrWithBody :: JSXMLHttpRequest -> JSVal -> IO () js_sendXhrWithBody :: JSXMLHttpRequest -> ArrayBuffer -> IO ()
toBody :: Request -> IO (Maybe String) toBody :: Request -> IO (Maybe ArrayBuffer)
toBody request = case requestBody request of toBody request = case requestBody request of
Nothing -> return Nothing Nothing -> return Nothing
Just (a, _) -> go a Just (a, _) -> Just <$> go a
where where
go :: RequestBody -> IO (Maybe String) go :: RequestBody -> IO ArrayBuffer
go x = case x of go x = case x of
RequestBodyLBS x -> return $ mBody x RequestBodyLBS x -> return $ mBody $ BL.toStrict x
RequestBodyBS x -> return $ mBody x RequestBodyBS x -> return $ mBody x
RequestBodyBuilder _ x -> return $ mBody $ toLazyByteString x RequestBodyBuilder _ x -> return $ mBody $ BL.toStrict $ toLazyByteString x
RequestBodyStream _ x -> mBody <$> readBody x RequestBodyStream _ x -> mBody <$> readBody x
RequestBodyStreamChunked x -> mBody <$> readBody x RequestBodyStreamChunked x -> mBody <$> readBody x
RequestBodyIO x -> x >>= go RequestBodyIO x -> x >>= go
mBody :: ConvertibleStrings a String => a -> Maybe String mBody :: BS.ByteString -> ArrayBuffer
mBody x = let y = cs x in if y == "" then Nothing else Just y mBody bs = js_bufferSlice offset len $ Buffer.getArrayBuffer buffer
where
(buffer, offset, len) = Buffer.fromByteString bs
readBody :: ((IO BS.ByteString -> IO ()) -> IO a) -> IO BS.ByteString
readBody writer = do readBody writer = do
m <- newIORef mempty m <- newIORef mempty
_ <- writer (\bsAct -> do _ <- writer (\bsAct -> do
@ -253,6 +263,8 @@ toBody request = case requestBody request of
modifyIORef m (<> bs)) modifyIORef m (<> bs))
readIORef m readIORef m
foreign import javascript unsafe "$3.slice($1, $1 + $2)"
js_bufferSlice :: Int -> Int -> ArrayBuffer -> ArrayBuffer
-- * inspecting the xhr response -- * inspecting the xhr response
@ -266,10 +278,10 @@ toResponse xhr = do
_ -> liftIO $ do _ -> liftIO $ do
statusText <- cs <$> getStatusText xhr statusText <- cs <$> getStatusText xhr
headers <- parseHeaders <$> getAllResponseHeaders xhr headers <- parseHeaders <$> getAllResponseHeaders xhr
responseText <- cs <$> getResponseText xhr response <- getResponse xhr
pure Response pure Response
{ responseStatusCode = mkStatus status statusText { responseStatusCode = mkStatus status statusText
, responseBody = responseText , responseBody = response
, responseHeaders = Seq.fromList headers , responseHeaders = Seq.fromList headers
, responseHttpVersion = http11 -- this is made up , responseHttpVersion = http11 -- this is made up
} }
@ -288,14 +300,19 @@ getAllResponseHeaders xhr =
foreign import javascript unsafe "$1.getAllResponseHeaders()" foreign import javascript unsafe "$1.getAllResponseHeaders()"
js_getAllResponseHeaders :: JSXMLHttpRequest -> IO JSVal js_getAllResponseHeaders :: JSXMLHttpRequest -> IO JSVal
getResponseText :: JSXMLHttpRequest -> IO String getResponse :: JSXMLHttpRequest -> IO BL.ByteString
getResponseText xhr = fromJSString <$> js_responseText xhr getResponse xhr =
foreign import javascript unsafe "$1.responseText" BL.fromStrict
js_responseText :: JSXMLHttpRequest -> IO JSVal . Buffer.toByteString 0 Nothing
. Buffer.createFromArrayBuffer
<$> js_response xhr
foreign import javascript unsafe "$1.response"
js_response :: JSXMLHttpRequest -> IO ArrayBuffer
parseHeaders :: String -> ResponseHeaders parseHeaders :: String -> ResponseHeaders
parseHeaders s = parseHeaders s =
(first mk . first strip . second strip . parseHeader) <$> first mk . first strip . second strip . parseHeader <$>
splitOn "\r\n" (cs s) splitOn "\r\n" (cs s)
where where
parseHeader :: BS.ByteString -> (BS.ByteString, BS.ByteString) parseHeader :: BS.ByteString -> (BS.ByteString, BS.ByteString)