urbit/pkg/hs-urbit/lib/Vere/Http/Server.hs

212 lines
6.1 KiB
Haskell
Raw Normal View History

2019-08-02 08:07:20 +03:00
{-# OPTIONS_GHC -Wwarn #-}
2019-05-16 03:00:10 +03:00
module Vere.Http.Server where
2019-08-02 08:07:20 +03:00
import Arvo hiding (ServerId, secure)
2019-07-12 22:18:14 +03:00
import Noun
2019-08-02 08:07:20 +03:00
import UrbitPrelude
import Vere.Http hiding (Method)
import Vere.Pier.Types
2019-05-16 03:00:10 +03:00
2019-07-12 22:24:44 +03:00
import Control.Concurrent (ThreadId, forkIO, killThread)
2019-05-31 05:53:00 +03:00
import qualified Network.HTTP.Types as H
import qualified Network.Wai as W
import qualified Network.Wai.Handler.Warp as W
2019-05-25 02:03:46 +03:00
import qualified Network.Wai.Handler.WarpTLS as W
2019-05-24 02:58:18 +03:00
2019-08-02 08:07:20 +03:00
2019-07-12 22:18:14 +03:00
-- Types -----------------------------------------------------------------------
2019-08-02 08:07:20 +03:00
type ReqId = Word
type SeqId = Word
2019-05-16 03:00:10 +03:00
2019-08-02 08:07:20 +03:00
newtype Drv = Drv { unDrv :: MVar (Maybe Serv) }
2019-08-02 08:07:20 +03:00
data Serv = Serv
{ sConfig :: HttpServerConf
, sThread :: Async ()
, sLiveReqs :: TVar (Map ReqId (TMVar (SeqId, HttpEvent)))
}
2019-08-02 08:07:20 +03:00
-- Generic Service Restart and Stop Logic --------------------------------------
2019-08-02 08:07:20 +03:00
{-
Restart a running service.
2019-05-16 03:00:10 +03:00
2019-08-02 08:07:20 +03:00
This can probably be made simpler, but it
2019-05-16 03:00:10 +03:00
2019-08-02 08:07:20 +03:00
- Sets the MVar to Nothing if there was an exception whil starting
or stopping the service.
2019-05-16 03:00:10 +03:00
2019-08-02 08:07:20 +03:00
- Keeps the MVar lock until the restart process finishes.
-}
restartService :: forall s r
. MVar (Maybe s)
-> IO (s, r)
-> (s -> IO ())
-> IO (Either SomeException r)
restartService vServ sstart kkill = do
modifyMVar vServ $ \case
Nothing -> doStart
Just sv -> doRestart sv
where
doRestart :: s -> IO (Maybe s, Either SomeException r)
doRestart serv =
try (kkill serv) >>= \case
Left exn -> pure (Nothing, Left exn)
Right () -> doStart
2019-05-16 03:04:21 +03:00
2019-08-02 08:07:20 +03:00
doStart :: IO (Maybe s, Either SomeException r)
doStart =
try sstart <&> \case
Right (s,r) -> (Just s, Right r)
Left exn -> (Nothing, Left exn)
stopService :: forall s
. MVar (Maybe s)
-> (s -> IO ())
-> IO (Either SomeException ())
stopService vServ kkill = do
modifyMVar vServ $ \case
Nothing -> pure (Nothing, Right ())
Just sv -> do res <- try (kkill sv)
pure (Nothing, res)
2019-05-16 03:00:10 +03:00
2019-05-24 02:58:18 +03:00
2019-08-02 08:07:20 +03:00
-- Utilities -------------------------------------------------------------------
servEv :: HttpServerEv -> Ev
servEv = EvBlip . BlipEvHttpServer
bornEv :: KingId -> Ev
bornEv inst = servEv $ HttpServerEvBorn (fromIntegral inst, ()) ()
liveEv :: KingId -> Port -> Maybe Port -> Ev
liveEv inst non sec = servEv $ HttpServerEvLive (inst, ()) non sec
2019-05-25 02:03:46 +03:00
2019-07-12 22:18:14 +03:00
--------------------------------------------------------------------------------
2019-08-02 08:07:20 +03:00
startServ :: HttpServerConf -> IO (Serv, (Port, Maybe Port))
startServ conf = do
(insecurePort, securePort) <- undefined
serv <- Serv conf <$> async undefined <*> newTVarIO mempty
pure (insecurePort, securePort)
killServ :: Serv -> IO ()
killServ Serv{sThread} = cancel sThread >> wait sThread
restart :: Drv -> HttpServerConf -> IO (Port, Maybe Port)
restart (Drv var) conf = do
fromEither =<< restartService var (startServ conf) killServ
kill :: Drv -> IO ()
kill (Drv v) = stopService v killServ >>= fromEither
respond :: Drv -> ReqId -> SeqId -> HttpEvent -> IO ()
respond (Drv v) req seq ev = do
readMVar v >>= \case
Nothing -> pure ()
Just sv -> atomically $ do
liveReqs <- readTVar (sLiveReqs sv)
lookup req liveReqs & \case
Nothing -> pure ()
Just tm -> putTMVar tm (seq, ev)
-- Top-Level Driver Interface --------------------------------------------------
serv :: KingId
-> QueueEv
-> ([Ev], Acquire (EffCb HttpServerEf))
serv inst plan =
(initialEvents, runHttpServer)
where
initialEvents :: [Ev]
initialEvents = [ bornEv inst ]
runHttpServer :: Acquire (EffCb HttpServerEf)
runHttpServer = handleEf <$> mkAcquire (Drv <$> newMVar Nothing) kill
handleEf :: Drv -> HttpServerEf -> IO ()
handleEf drv = \case
HSESetConfig (i, ()) conf ->
when (i == fromIntegral inst) $ do
(i, s) <- restart drv conf
atomically (plan (liveEv inst i s))
HSEResponse (i, req, sec, ()) ev ->
when (i == fromIntegral inst) $
respond drv (fromIntegral req) (fromIntegral sec) ev
2019-05-25 02:03:46 +03:00
2019-08-02 08:07:20 +03:00
--------------------------------------------------------------------------------
{-
data ClientResponse
= Progress ResponseHeader Int (Maybe Int) (Maybe ByteString)
| Finished ResponseHeader (Maybe MimeData)
| Cancel ()
data MimeData = MimeData Text ByteString
-}
{-
Alright, so the flow here is:
2019-05-25 02:03:46 +03:00
2019-08-02 08:07:20 +03:00
· Once we receive a request, send a %request or %request-local event.
· The request thread should stick an MVar into a map, and wait on
it for a response.
-}
2019-05-25 02:03:46 +03:00
2019-08-02 08:07:20 +03:00
{-
data HttpServerEv
= HttpServerEvRequest (KingId, Word, Word, ()) HttpServerReq
| HttpServerEvRequestLocal Path HttpServerReq
| HttpServerEvLive (KingId, ()) Port (Maybe Port)
-}
2019-05-25 02:03:46 +03:00
2019-08-02 08:07:20 +03:00
{-
2019-07-22 21:10:27 +03:00
cordBytes :: Cord -> ByteString
cordBytes = encodeUtf8 . unCord
2019-08-02 08:07:20 +03:00
startServer :: ServDrv -> Config -> IO ()
2019-05-25 02:03:46 +03:00
startServer s c = do
tls <- case (secure c) of
Nothing -> error "no wai"
2019-06-19 03:04:57 +03:00
Just (PEM key, PEM cert) ->
2019-07-22 21:10:27 +03:00
pure (W.tlsSettingsMemory (cordBytes cert) (cordBytes key))
2019-05-25 02:03:46 +03:00
-- we need to do the dance where we do the socket checking dance. or shove a
-- socket into it.
2019-05-25 02:08:59 +03:00
tid <- forkIO $ W.runTLS tls W.defaultSettings (app s)
2019-08-02 08:07:20 +03:00
putMVar (sdThread s) (Just (c, tid))
2019-05-25 02:03:46 +03:00
2019-08-02 08:07:20 +03:00
app :: ServDrv -> W.Application
2019-05-25 02:03:46 +03:00
app s req respond = bracket_
(pure ())
(pure ())
(respond $ W.responseLBS H.status200 [] "Hello World")
cookMeth :: W.Request -> Maybe Method
2019-05-24 02:58:18 +03:00
cookMeth re =
2019-05-25 02:03:46 +03:00
case H.parseMethod (W.requestMethod re) of
2019-07-12 22:24:44 +03:00
Left _ -> Nothing
2019-05-24 02:58:18 +03:00
Right m -> Just m
2019-05-25 02:03:46 +03:00
readEvents :: W.Request -> IO Request
2019-07-22 21:10:27 +03:00
readEvents req = do
let Just meth = cookMeth req
url = Cord $ decodeUtf8 $ W.rawPathInfo req
headers = convertHeaders (W.requestHeaders req)
bodyLbs <- W.strictRequestBody req
2019-05-24 02:58:18 +03:00
let body = if length bodyLbs == 0 then Nothing
2019-07-22 21:10:27 +03:00
else Just $ Octs (toStrict bodyLbs)
2019-05-24 02:58:18 +03:00
-- TODO: Check if wai just deletes the 'host': header like h2o does?
2019-07-22 21:10:27 +03:00
pure (Request meth url headers body)
2019-08-02 08:07:20 +03:00
-}