{-# LANGUAGE OverloadedStrings #-} {-| Module : Web.ReqCatcher Description : A tiny tool to catch HTTP redirects from the browser Copyright : (c) 2016- hiratara License : BSD-style Maintainer : hiratara@cpan.org Stability : experimental Web.ReqCatcher starts a local HTTP server which handles just one request and returns that request to the client program. It is useful for CLI program to capture HTTP redirects from outer WEB services by using browser. @ import Web.Authenticate.OAuth oauth :: OAuth manager :: Manager main :: IO () main = do c <- newCatcher let url = pack (catcherUrl c) oauth' = oauth {oauthCallback = Just url} credential <- getTemporaryCredential oauth' manager putStrLn $ "Access to:\\n" ++ (authorizeUrl oauth credential) req <- catchRedirect c let (Just (Just verifier)) = lookup "oauth_verifier" (queryString req) ... @ -} module Web.ReqCatcher ( Catcher (catcherUrl) , newCatcher , newCatcherWithPort , catchRedirect ) where import qualified Control.Concurrent as CONC import qualified Control.Exception as EX import qualified Network.HTTP.Types as HTTP import qualified Network.Socket as NW import qualified Network.Wai as WAI import qualified Network.Wai.Handler.Warp as WARP import qualified Data.Text.Lazy as LTXT import qualified Data.Text.Lazy.Encoding as LTXT -- | Catcher holds the HTTP server instance and wait for a request. data Catcher = Catcher { catcherUrl :: String -- ^ Target URL of this Catcher , catcherWarpThread :: CONC.ThreadId , catcherSocket :: NW.Socket , catcherCought :: CONC.MVar WAI.Request } -- | Creates the new Catcher instance. newCatcher :: IO Catcher newCatcher = newCatcherWithPort =<< pickPort -- | Creates the new Catcher instance with the specific port. newCatcherWithPort :: WARP.Port -> IO Catcher newCatcherWithPort port = do mvar <- CONC.newEmptyMVar mvarSocket <- CONC.newEmptyMVar let set = WARP.setOnException (\_ _ -> return ()) . WARP.setPort port $ WARP.defaultSettings tid <- CONC.forkIO (httpWorker mvarSocket set (newCatchApp mvar)) socket <- CONC.takeMVar mvarSocket return $ Catcher (buildURL port) tid socket mvar httpWorker :: CONC.MVar NW.Socket -> WARP.Settings -> WAI.Application -> IO () httpWorker mvar set app = EX.bracket (NW.socket NW.AF_INET NW.Stream NW.defaultProtocol) NW.close (\socket -> do -- TODO: set Close-On-Exec to socket NW.setSocketOption socket NW.ReuseAddr 1 let addr = NW.SockAddrInet (toEnum $ WARP.getPort set) 0 NW.bind socket addr NW.listen socket 1 -- Handle Just 1 connection CONC.putMVar mvar socket WARP.runSettingsSocket set socket app return ()) -- | Returns the HTTP request cought by Catcher. -- This function blocks until Catcher catches some requests. catchRedirect :: Catcher -> IO WAI.Request catchRedirect catcher = do req <- CONC.takeMVar (catcherCought catcher) NW.close (catcherSocket catcher) return req pickPort :: IO WARP.Port pickPort = EX.bracket (NW.socket NW.AF_INET NW.Stream NW.defaultProtocol) NW.close (\sock -> do NW.setSocketOption sock NW.ReuseAddr 1 NW.bind sock (NW.SockAddrInet 0 0) port <- NW.socketPort sock NW.close sock return (fromEnum port)) buildURL :: WARP.Port -> String buildURL port = "http://localhost:" ++ show port newCatchApp :: CONC.MVar WAI.Request -> WAI.Application newCatchApp mvar req respond = do CONC.putMVar mvar req respond $ WAI.responseLBS HTTP.status200 [("Content-Type", "text/plain")] (LTXT.encodeUtf8 $ LTXT.pack $ show req)