gemini-server-0.1.0.0: Network/Gemini/Server.hs
module Network.Gemini.Server (
Request
, Response(..)
, Handler
, runServer
, okGemini
, okPlain
, redirect
) where
import Network.Socket (HostName, ServiceName, SockAddr, getPeerName)
import Network.Socket.ByteString.Lazy (recv, sendAll)
import Network.Run.TCP (runTCPServer)
import Network.URI (URI(URI), parseURI, uriToString)
import qualified Data.ByteString.Lazy as LBS
import Data.ByteString.Lazy.UTF8 (toString)
import Data.String (fromString)
import Control.Exception (SomeException, try)
import System.Log.Logger
( updateGlobalLogger, setLevel, logM, Priority(INFO, ERROR) )
--MAYBE switch to a more modern/efficient uri library
--TODO add client cert
type Request = URI
data Response = Response
{ responseStatus :: Int
, responseMeta :: String
, responseBody :: LBS.ByteString }
type Handler = Request -> IO Response
renderHeader :: Int -> String -> LBS.ByteString
renderHeader status meta =
fromString (show status) <>
fromString " " <>
fromString meta <>
fromString "\CR\LF"
runServer :: Maybe HostName -> ServiceName -> (Request -> IO Response) -> IO ()
runServer host service handler = do
updateGlobalLogger "Network.Gemini.Server" $ setLevel INFO
runTCPServer host service talk -- MAYBE server config
where
talk s = do --TODO timeouts on send and receive (and maybe on handler)
msg <- toString <$> recv s 1025 -- 1024 + CR or LF
-- It makes sense to be very lenient here
let mURI = parseURI $ takeWhile (not . (`elem` ['\CR', '\LF'])) msg
peer <- getPeerName s
case mURI of
Nothing -> do
logRequest INFO peer (Left msg) 59 Nothing
sendAll s $ renderHeader 59 $ fromString "Invalid URL"
Just uri@(URI "gemini:" _ _ _ _) -> do
response <- try $ handler uri
case response of
Right (Response status meta body) -> do
logRequest INFO peer (Right uri) status $ Just meta
sendAll s $ renderHeader status meta
sendAll s body
Left e -> do
logRequest ERROR peer (Right uri) 42 $ Just $ show (e :: SomeException)
sendAll s $ renderHeader 42 $ fromString "Internal server error"
Just uri@(URI scheme _ _ _ _) -> do
logRequest INFO peer (Right uri) 59 Nothing
sendAll s $ renderHeader 59 $ fromString $ "Invalid scheme: " <> scheme
logRequest :: Priority -> SockAddr -> Either String URI -> Int -> Maybe String -> IO ()
logRequest p peer uri code meta = logM "Network.Gemini.Server" p $ unwords
[ show peer
, either show show uri
, show code
, maybe "-" show meta ]
-- | Shorthand for @Response 20 "text/gemini"@
okGemini :: LBS.ByteString -> Response
okGemini = Response 20 $ fromString "text/gemini"
-- | Shorthand for @Response 20 "text/plain"@
okPlain :: LBS.ByteString -> Response
okPlain = Response 20 $ fromString "text/plain"
redirect :: URI -> Response
redirect uri = Response 30 (uriToString id uri "") mempty