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-07-23 05:35:15 +03:00
|
|
|
|
2019-08-02 08:07:20 +03:00
|
|
|
data Serv = Serv
|
|
|
|
{ sConfig :: HttpServerConf
|
|
|
|
, sThread :: Async ()
|
|
|
|
, sLiveReqs :: TVar (Map ReqId (TMVar (SeqId, HttpEvent)))
|
|
|
|
}
|
2019-07-23 05:35:15 +03:00
|
|
|
|
|
|
|
|
2019-08-02 08:07:20 +03:00
|
|
|
-- Generic Service Restart and Stop Logic --------------------------------------
|
2019-06-26 03:15:49 +03:00
|
|
|
|
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
|
|
|
-}
|