@@ -26,6 +26,7 @@ import Control.Monad.Trans.Control (MonadBaseControl (..))
26
26
import Control.Monad.Trans.Except
27
27
import Data.ByteString.Builder (toLazyByteString )
28
28
import qualified Data.ByteString.Char8 as BS
29
+ import qualified Data.ByteString.Lazy as BL
29
30
import Data.CaseInsensitive
30
31
import Data.Char
31
32
import Data.Foldable (toList )
@@ -34,11 +35,14 @@ import Data.IORef (modifyIORef, newIORef, readIORef)
34
35
import Data.Proxy (Proxy (.. ))
35
36
import qualified Data.Sequence as Seq
36
37
import Data.String.Conversions
38
+ import Data.Typeable (Typeable )
37
39
import Foreign.StablePtr
38
40
import GHC.Generics
41
+ import qualified GHCJS.Buffer as Buffer
39
42
import GHCJS.Foreign.Callback
40
43
import GHCJS.Prim
41
44
import GHCJS.Types
45
+ import JavaScript.TypedArray.ArrayBuffer ( ArrayBuffer )
42
46
import JavaScript.Web.Location
43
47
import Network.HTTP.Media (renderHeader )
44
48
import Network.HTTP.Types
@@ -48,14 +52,34 @@ newtype JSXMLHttpRequest = JSXMLHttpRequest JSVal
48
52
49
53
newtype JSXMLHttpRequestClass = JSXMLHttpRequestClass JSVal
50
54
55
+ -- | The environment in which a request is run.
51
56
newtype ClientEnv
52
57
= ClientEnv
53
58
{ baseUrl :: BaseUrl }
54
59
deriving (Eq , Show )
55
60
61
+ -- | Generates a set of client functions for an API.
62
+ --
63
+ -- Example:
64
+ --
65
+ -- > type API = Capture "no" Int :> Get '[JSON] Int
66
+ -- > :<|> Get '[JSON] [Bool]
67
+ -- >
68
+ -- > api :: Proxy API
69
+ -- > api = Proxy
70
+ -- >
71
+ -- > getInt :: Int -> ClientM Int
72
+ -- > getBools :: ClientM [Bool]
73
+ -- > getInt :<|> getBools = client api
74
+ --
75
+ -- NOTE: Does not support constant space streaming of the request body!
56
76
client :: HasClient ClientM api => Proxy api -> Client ClientM api
57
77
client api = api `clientIn` (Proxy :: Proxy ClientM )
58
78
79
+ -- | @ClientM@ is the monad in which client functions run. Contains the
80
+ -- 'BaseUrl' used for requests in the reader environment.
81
+ --
82
+ -- NOTE: Does not support constant space streaming of the request body!
59
83
newtype ClientM a = ClientM
60
84
{ runClientM' :: ReaderT ClientEnv (ExceptT ServantError IO ) a }
61
85
deriving ( Functor , Applicative , Monad , MonadIO , Generic
@@ -76,8 +100,15 @@ instance MonadBaseControl IO ClientM where
76
100
instance Alt ClientM where
77
101
a <!> b = a `catchError` const b
78
102
103
+ data StreamingNotSupportedException = StreamingNotSupportedException
104
+ deriving ( Typeable , Show )
105
+
106
+ instance Exception StreamingNotSupportedException where
107
+ displayException _ = " streamingRequest: streaming is not supported!"
108
+
79
109
instance RunClient ClientM where
80
110
runRequest = performRequest
111
+ streamingRequest _ = liftIO $ throwIO StreamingNotSupportedException
81
112
throwServantError = throwError
82
113
83
114
instance ClientLike (ClientM a ) (ClientM a ) where
@@ -153,6 +184,7 @@ performXhr xhr burl request = do
153
184
154
185
openXhr xhr (cs $ requestMethod request) (toUrl burl request) True
155
186
setHeaders xhr request
187
+ js_setResponseType xhr " arraybuffer"
156
188
body <- toBody request
157
189
sendXhr xhr body
158
190
takeMVar waiter
@@ -187,6 +219,9 @@ openXhr xhr method url =
187
219
foreign import javascript unsafe " $1.open($2, $3, $4)"
188
220
js_openXhr :: JSXMLHttpRequest -> JSVal -> JSVal -> Bool -> IO ()
189
221
222
+ foreign import javascript unsafe " $1.responseType = $2;"
223
+ js_setResponseType :: JSXMLHttpRequest -> JSString -> IO ()
224
+
190
225
toUrl :: BaseUrl -> Request -> String
191
226
toUrl burl request =
192
227
let pathS = cs $ toLazyByteString $ requestPath request
@@ -217,42 +252,47 @@ setHeaders xhr request = do
217
252
foreign import javascript unsafe " $1.setRequestHeader($2, $3)"
218
253
js_setRequestHeader :: JSXMLHttpRequest -> JSVal -> JSVal -> IO ()
219
254
220
- sendXhr :: JSXMLHttpRequest -> Maybe String -> IO ()
255
+ sendXhr :: JSXMLHttpRequest -> Maybe ArrayBuffer -> IO ()
221
256
sendXhr xhr Nothing = js_sendXhr xhr
222
257
sendXhr xhr (Just body) =
223
- js_sendXhrWithBody xhr (toJSString body)
258
+ js_sendXhrWithBody xhr body
224
259
225
260
foreign import javascript unsafe " $1.send()"
226
261
js_sendXhr :: JSXMLHttpRequest -> IO ()
227
262
228
263
foreign import javascript unsafe " $1.send($2)"
229
- js_sendXhrWithBody :: JSXMLHttpRequest -> JSVal -> IO ()
264
+ js_sendXhrWithBody :: JSXMLHttpRequest -> ArrayBuffer -> IO ()
230
265
231
- toBody :: Request -> IO (Maybe String )
266
+ toBody :: Request -> IO (Maybe ArrayBuffer )
232
267
toBody request = case requestBody request of
233
268
Nothing -> return Nothing
234
- Just (a, _) -> go a
269
+ Just (a, _) -> Just <$> go a
235
270
236
271
where
237
- go :: RequestBody -> IO ( Maybe String )
272
+ go :: RequestBody -> IO ArrayBuffer
238
273
go x = case x of
239
- RequestBodyLBS x -> return $ mBody x
274
+ RequestBodyLBS x -> return $ mBody $ BL. toStrict x
240
275
RequestBodyBS x -> return $ mBody x
241
- RequestBodyBuilder _ x -> return $ mBody $ toLazyByteString x
276
+ RequestBodyBuilder _ x -> return $ mBody $ BL. toStrict $ toLazyByteString x
242
277
RequestBodyStream _ x -> mBody <$> readBody x
243
278
RequestBodyStreamChunked x -> mBody <$> readBody x
244
279
RequestBodyIO x -> x >>= go
245
280
246
- mBody :: ConvertibleStrings a String => a -> Maybe String
247
- mBody x = let y = cs x in if y == " " then Nothing else Just y
281
+ mBody :: BS. ByteString -> ArrayBuffer
282
+ mBody bs = js_bufferSlice offset len $ Buffer. getArrayBuffer buffer
283
+ where
284
+ (buffer, offset, len) = Buffer. fromByteString bs
248
285
286
+ readBody :: ((IO BS. ByteString -> IO () ) -> IO a ) -> IO BS. ByteString
249
287
readBody writer = do
250
288
m <- newIORef mempty
251
289
_ <- writer (\ bsAct -> do
252
290
bs <- bsAct
253
291
modifyIORef m (<> bs))
254
292
readIORef m
255
293
294
+ foreign import javascript unsafe " $3.slice($1, $1 + $2)"
295
+ js_bufferSlice :: Int -> Int -> ArrayBuffer -> ArrayBuffer
256
296
257
297
-- * inspecting the xhr response
258
298
@@ -266,10 +306,10 @@ toResponse xhr = do
266
306
_ -> liftIO $ do
267
307
statusText <- cs <$> getStatusText xhr
268
308
headers <- parseHeaders <$> getAllResponseHeaders xhr
269
- responseText <- cs <$> getResponseText xhr
309
+ response <- getResponse xhr
270
310
pure Response
271
311
{ responseStatusCode = mkStatus status statusText
272
- , responseBody = responseText
312
+ , responseBody = response
273
313
, responseHeaders = Seq. fromList headers
274
314
, responseHttpVersion = http11 -- this is made up
275
315
}
@@ -288,14 +328,19 @@ getAllResponseHeaders xhr =
288
328
foreign import javascript unsafe " $1.getAllResponseHeaders()"
289
329
js_getAllResponseHeaders :: JSXMLHttpRequest -> IO JSVal
290
330
291
- getResponseText :: JSXMLHttpRequest -> IO String
292
- getResponseText xhr = fromJSString <$> js_responseText xhr
293
- foreign import javascript unsafe " $1.responseText"
294
- js_responseText :: JSXMLHttpRequest -> IO JSVal
331
+ getResponse :: JSXMLHttpRequest -> IO BL. ByteString
332
+ getResponse xhr =
333
+ BL. fromStrict
334
+ . Buffer. toByteString 0 Nothing
335
+ . Buffer. createFromArrayBuffer
336
+ <$> js_response xhr
337
+
338
+ foreign import javascript unsafe " $1.response"
339
+ js_response :: JSXMLHttpRequest -> IO ArrayBuffer
295
340
296
341
parseHeaders :: String -> ResponseHeaders
297
342
parseHeaders s =
298
- ( first mk . first strip . second strip . parseHeader) <$>
343
+ first mk . first strip . second strip . parseHeader <$>
299
344
splitOn " \r\n " (cs s)
300
345
where
301
346
parseHeader :: BS. ByteString -> (BS. ByteString , BS. ByteString )
0 commit comments