on develop help sign in
Merge branch 'newxmlhttprequest_async_handle_errors' of github.com:fpco/reflex-dom into ghcjs-improved-base-2

Conflicts: src/Reflex/Dom/Xhr.hs

commit6a1c2cfb8f115fbc4abfb35b4c61f44d50e39bb0 authorAli Abrar <aliabrar@gmail.com> dateFri 15 Jan 2016, 01:05 (11 years ago) parentd87cf28341f2d5
6 files changed+137−34
default.nix+2−2 reflex-dom.cabal+4−1 src-ghc/Reflex/Dom/Xhr/Foreign.hs+55−13 src-ghcjs/Reflex/Dom/Xhr/Foreign.hs+9−1 src/Reflex/Dom/Xhr.hs+55−17 src/Reflex/Dom/Xhr/Exception.hs+12
default.nix+2−2
@@ -2,7 +2,7 @@
22, mtl, ref-tf, reflex, safe, text, these
33, transformers, data-default, semigroups, aeson
44, ghc, webkitgtk3-javascriptcore, exception-transformers
5−, dependent-sum-template, bifunctors
5+, dependent-sum-template, bifunctors, raw-strings-qq
66}:
77
88mkDerivation {
@@ -26,6 +26,6 @@ mkDerivation {
2626 exception-transformers
2727 dependent-sum-template
2828 bifunctors
29− ] ++ (if (ghc.pname or null) == "ghcjs" then [ ] else [ webkitgtk3-javascriptcore ]);
29+ ] ++ (if (ghc.pname or null) == "ghcjs" then [ ] else [ webkitgtk3-javascriptcore raw-strings-qq ]);
3030 license = null;
3131}
reflex-dom.cabal+4−1
@@ -16,6 +16,7 @@ extra-source-files: src-ghc/Reflex/Dom/Internal/Foreign.hs
1616 src-ghcjs/Reflex/Dom/Internal/Foreign.hs
1717 src-ghcjs/Reflex/Dom/Xhr/Foreign.hs
1818 src/Reflex/Dom/Xhr/ResponseType.hs
19+ src/Reflex/Dom/Xhr/Exception.hs
1920
2021library
2122 hs-source-dirs: src
@@ -53,7 +54,8 @@ library
5354 glib == 0.13.*,
5455 gtk3 >= 0.13.0 && < 0.15,
5556 webkitgtk3 == 0.14.*,
56− webkitgtk3-javascriptcore == 0.13.*
57+ webkitgtk3-javascriptcore == 0.13.*,
58+ raw-strings-qq
5759 if !os(windows)
5860 build-depends: unix == 2.7.*
5961
@@ -75,6 +77,7 @@ library
7577 Reflex.Dom.WebSocket.Foreign
7678 Reflex.Dom.Xhr.Foreign
7779 Reflex.Dom.Xhr.ResponseType
80+ Reflex.Dom.Xhr.Exception
7881
7982 other-extensions: TemplateHaskell
8083 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
src-ghc/Reflex/Dom/Xhr/Foreign.hs+55−13
@@ -1,4 +1,4 @@
1−{-# LANGUAGE ForeignFunctionInterface #-}
1+{-# LANGUAGE QuasiQuotes, ForeignFunctionInterface #-}
22module Reflex.Dom.Xhr.Foreign where
33
44import qualified Data.Text as T
@@ -11,9 +11,14 @@ import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSStringRef
1111import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
1212import Graphics.UI.Gtk.WebKit.JavaScriptCore.WebFrame
1313import Reflex.Dom.Xhr.ResponseType
14+import Reflex.Dom.Xhr.Exception
15+import Control.Concurrent.MVar
16+import Control.Exception.Base
1417
1518import Reflex.Dom.Internal.Foreign
1619
20+import Text.RawString.QQ
21+
1722data XMLHttpRequest
1823 = XMLHttpRequest { xhrValue :: JSValueRef
1924 , xhrContext :: JSContextRef
@@ -110,18 +115,55 @@ xmlHttpRequestGetResponseText xhr = do
110115xmlHttpRequestSend :: XMLHttpRequest -> Maybe String -> IO ()
111116xmlHttpRequestSend xhr payload = do
112117 let c = xhrContext xhr
113− (o,s) <- case payload of
114− Nothing -> do
115− o <- toJSObject c [xhrValue xhr]
116− s <- jsstringcreatewithutf8cstring "this[0].send();"
117− return (o,s)
118− Just payload' -> do
119− d <- stringToJSValue c payload'
120− o <- toJSObject c [xhrValue xhr, d]
121− s <- jsstringcreatewithutf8cstring "this[0].send(this[1])"
122− return (o,s)
123− _ <- jsevaluatescript c s o nullPtr 1 nullPtr
124− return ()
118+ result <- newEmptyMVar
119+ let wrapper' x = wrapper $ \_ _ _ _ _ _ -> putMVar result x >> jsvaluemakeundefined c
120+ bracket (wrapper' $ Just XhrException_Aborted) freeHaskellFunPtr $ \a -> do
121+ onAbort <- jsobjectmakefunctionwithcallback c nullPtr a
122+ bracket (wrapper' $ Just XhrException_Error) freeHaskellFunPtr $ \e -> do
123+ onError <- jsobjectmakefunctionwithcallback c nullPtr e
124+ bracket (wrapper' Nothing) freeHaskellFunPtr $ \l -> do
125+ onLoad <- jsobjectmakefunctionwithcallback c nullPtr l
126+ (o,s) <- case payload of
127+ Nothing -> do
128+ d <- jsvaluemakeundefined c
129+ o <- toJSObject c [xhrValue xhr, d, onError, onAbort, onLoad]
130+ s <- jsstringcreatewithutf8cstring send
131+ return (o,s)
132+ Just payload' -> do
133+ d <- stringToJSValue c payload'
134+ o <- toJSObject c [xhrValue xhr, d, onError, onAbort, onLoad]
135+ s <- jsstringcreatewithutf8cstring send
136+ return (o,s)
137+ _ <- jsevaluatescript c s o nullPtr 1 nullPtr
138+ takeMVar result >>= mapM_ throwIO
139+ where
140+ send = [r|
141+ (function (xhr, d, onError, onAbort, onLoad) {
142+ var clear;
143+ var error = function () {
144+ clear(); onError();
145+ };
146+ var abort = function () {
147+ clear(); onAbort();
148+ };
149+ var load = function () {
150+ clear(); onLoad();
151+ };
152+ clear = function () {
153+ xhr.removeEventListener('error', error);
154+ xhr.removeEventListener('abort', abort);
155+ xhr.removeEventListener('load', load);
156+ }
157+ xhr.addEventListener('error', error);
158+ xhr.addEventListener('abort', abort);
159+ xhr.addEventListener('load', load);
160+ if(d) {
161+ xhr.send(d);
162+ } else {
163+ xhr.send();
164+ }
165+ })(this[0], this[1], this[2], this[3], this[4])
166+ |]
125167
126168xmlHttpRequestSetRequestHeader :: XMLHttpRequest -> String -> String -> IO ()
127169xmlHttpRequestSetRequestHeader xhr header value = do
src-ghcjs/Reflex/Dom/Xhr/Foreign.hs+9−1
@@ -15,8 +15,11 @@ import GHCJS.DOM.XMLHttpRequest
1515import Data.Maybe (fromMaybe)
1616import GHCJS.DOM.EventTarget (dispatchEvent)
1717import GHCJS.DOM.EventM (EventM, on)
18+import GHCJS.DOM.JSFFI.XMLHttpRequest (XHRError(..))
1819import GHCJS.Types
20+import Reflex.Dom.Xhr.Exception
1921import Reflex.Dom.Xhr.ResponseType
22+import Control.Exception (catch, throwIO)
2023
2124data XhrResponseBody = XhrResponseBody { unXhrResponseBody :: JSVal }
2225
@@ -31,9 +34,14 @@ xmlHttpRequestOpen ::
3134 XMLHttpRequest -> method -> url -> Bool -> user -> password -> IO ()
3235xmlHttpRequestOpen = open
3336
37+convertException :: XHRError -> XhrException
38+convertException e = case e of
39+ XHRError -> XhrException_Error
40+ XHRAborted -> XhrException_Aborted
41+
3442-- This used to be a non blocking call, but now it uses an interruptible ffi
3543xmlHttpRequestSend :: ToJSString payload => XMLHttpRequest -> Maybe payload -> IO ()
36−xmlHttpRequestSend self = maybe (send self) (sendString self)
44+xmlHttpRequestSend self p = (maybe (send self) (sendString self) p) `catch` (throwIO . convertException)
3745
3846xmlHttpRequestSetRequestHeader :: (ToJSString header, ToJSString value)
3947 => XMLHttpRequest -> header -> value -> IO ()
src/Reflex/Dom/Xhr.hs+55−17
@@ -5,6 +5,7 @@ module Reflex.Dom.Xhr
55 , XMLHttpRequest
66 , XhrResponseBody(..)
77 , XhrResponseType(..)
8+ , XhrException(..)
89 , responseTextToText
910 , xmlHttpRequestGetReadyState
1011 , xmlHttpRequestGetResponseText
@@ -20,6 +21,7 @@ module Reflex.Dom.Xhr
2021where
2122
2223import Control.Concurrent
24+import Control.Exception (catch)
2325import Control.Lens
2426import Control.Monad hiding (forM)
2527import Control.Monad.IO.Class
@@ -37,9 +39,11 @@ import Data.Text.Encoding
3739import Data.Traversable
3840import Reflex
3941import Reflex.Dom.Class
42+import Reflex.Dom.Xhr.Exception
4043import Reflex.Dom.Xhr.Foreign
4144import Reflex.Dom.Xhr.ResponseType
4245import Data.Typeable
46+-- import GHCJS.DOM.JSFFI.XMLHttpRequest (XHRError(..))
4347
4448data XhrRequest
4549 = XhrRequest { _xhrRequest_method :: String
@@ -85,12 +89,24 @@ instance Default XhrRequestConfig where
8589xhrRequest :: String -> String -> XhrRequestConfig -> XhrRequest
8690xhrRequest = XhrRequest
8791
88−newXMLHttpRequest :: (HasWebView m, MonadIO m, HasPostGui t h m) => XhrRequest -> (XhrResponse -> h ()) -> m XMLHttpRequest
92+-- | Make a new asyncronous XHR request. This does not block (it forks),
93+-- and returns an XHR object immediately (which you can use to abort
94+-- the XHR connection), and will pass an exception ('XhrException') to the
95+-- continuation if the connection cannot be made (or is aborted).
96+newXMLHttpRequest
97+ :: (HasWebView m, MonadIO m, HasPostGui t h m)
98+ => XhrRequest
99+ -- ^ The request to make.
100+ -> (Either XhrException XhrResponse -> h ())
101+ -- ^ A continuation to be called once a response comes back, or in
102+ -- case of error.
103+ -> m XMLHttpRequest
104+ -- ^ The XHR request, which could for example be aborted.
89105newXMLHttpRequest req cb = do
90106 wv <- askWebView
91107 postGui <- askPostGui
92− liftIO $ do
93− xhr <- xmlHttpRequestNew wv
108+ xhr <- liftIO $ xmlHttpRequestNew wv
109+ void $ liftIO $ forkIO $ flip catch (postGui . cb . Left) $ void $ do
94110 let c = _xhrRequest_config req
95111 rt = _xhrRequestConfig_responseType c
96112 xmlHttpRequestOpen
@@ -112,24 +128,42 @@ newXMLHttpRequest req cb = do
112128 then liftIO $ xmlHttpRequestGetResponseText xhr
113129 else return Nothing
114130 r <- liftIO $ xmlHttpRequestGetResponse xhr
115− _ <- liftIO $ postGui $ cb $ XhrResponse { _xhrResponse_status = status
116− , _xhrResponse_statusText = statusTextToText statusText
117− , _xhrResponse_response = r
118− , _xhrResponse_responseText = responseTextToText t
119− }
131+ _ <- liftIO $ postGui $ cb $ Right $
132+ XhrResponse { _xhrResponse_status = status
133+ , _xhrResponse_statusText = statusTextToText statusText
134+ , _xhrResponse_response = r
135+ , _xhrResponse_responseText = responseTextToText t
136+ }
120137 return ()
121138 else return ()
122139 _ <- xmlHttpRequestSend xhr (_xhrRequestConfig_sendData c)
123− return xhr
124−
125−-- | Given Event of requests, issue them when the Event fires. Returns Event of corresponding responses.
126−performRequestAsync :: (MonadWidget t m) => Event t XhrRequest -> m (Event t XhrResponse)
140+ return ()
141+ return xhr
142+
143+-- | Given Event of requests, issue them when the Event fires.
144+-- Returns Event of corresponding responses.
145+--
146+-- The request is processed asynchronously, therefore handling does
147+-- not block or cause a delay while creating the connection.
148+performRequestAsync
149+ :: (MonadWidget t m)
150+ => Event t XhrRequest -> m (Event t (Either XhrException XhrResponse))
127151performRequestAsync req = performEventAsync $ ffor req $ \r cb -> do
128152 _ <- newXMLHttpRequest r $ liftIO . cb
129153 return ()
130154
131−-- | Issues a collection of requests when the supplied Event fires. When ALL requests from a given firing complete, the results are collected and returned via the return Event.
132−performRequestsAsync :: (Traversable f, MonadWidget t m) => Event t (f XhrRequest) -> m (Event t (f XhrResponse))
155+-- | Issues a collection of requests when the supplied Event fires.
156+-- When ALL requests from a given firing complete, the results are
157+-- collected and returned via the return Event.
158+--
159+-- The requests are processed asynchronously, therefore handling does
160+-- not block or cause a delay while creating the connection.
161+--
162+-- Order of request execution and completion is not guaranteed, but
163+-- order of creation and the collection result is preserved.
164+performRequestsAsync
165+ :: (Traversable f, MonadWidget t m)
166+ => Event t (f XhrRequest) -> m (Event t (f (Either XhrException XhrResponse)))
133167performRequestsAsync req = performEventAsync $ ffor req $ \rs cb -> do
134168 resps <- forM rs $ \r -> do
135169 resp <- liftIO newEmptyMVar
@@ -142,11 +176,16 @@ performRequestsAsync req = performEventAsync $ ffor req $ \rs cb -> do
142176getAndDecode :: (FromJSON a, MonadWidget t m) => Event t String -> m (Event t (Maybe a))
143177getAndDecode url = do
144178 r <- performRequestAsync $ fmap (\x -> XhrRequest "GET" x def) url
145− return $ fmap decodeXhrResponse r
179+ return $
180+ fmap (\r ->
181+ case r of
182+ Left{} -> Nothing
183+ Right xhr -> decodeXhrResponse xhr)
184+ r
146185
147186-- | Create a "POST" request from an URL and thing with a JSON representation
148187postJson :: (ToJSON a) => String -> a -> XhrRequest
149−postJson url a =
188+postJson url a =
150189 XhrRequest "POST" url $ def { _xhrRequestConfig_headers = headerUrlEnc
151190 , _xhrRequestConfig_sendData = Just body
152191 }
@@ -164,4 +203,3 @@ decodeText = decode . BL.fromStrict . encodeUtf8
164203-- | Convenience function to decode JSON-encoded responses.
165204decodeXhrResponse :: FromJSON a => XhrResponse -> Maybe a
166205decodeXhrResponse = join . fmap decodeText . _xhrResponse_responseText
167−
src/Reflex/Dom/Xhr/Exception.hsnew file+12
@@ -0,0 +1,12 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+
3+module Reflex.Dom.Xhr.Exception where
4+
5+import Data.Typeable
6+import Control.Exception (Exception(..))
7+
8+data XhrException = XhrException_Error
9+ | XhrException_Aborted
10+ deriving (Show, Read, Eq, Ord, Typeable)
11+
12+instance Exception XhrException