pong-server 0.0.2.1 → 0.0.3.0
raw patch · 4 files changed
+148/−65 lines, 4 filesdep +bytestringdep +classy-preludedep +exceptionsdep −data-defaultdep ~basedep ~network
Dependencies added: bytestring, classy-prelude, exceptions, http-types
Dependencies removed: data-default
Dependency ranges changed: base, network
Files
- pong-server.cabal +11/−14
- src/Axial/Pong.hs +0/−49
- src/Network/Pong.hs +135/−0
- test/Spec.hs +2/−2
pong-server.cabal view
@@ -1,5 +1,5 @@ name: pong-server-version: 0.0.2.1+version: 0.0.3.0 synopsis: A simple embedded pingable server that runs in the background. description: Please see README.md on GitHub homepage: http://github.com/RobertFischer/pong-server#readme@@ -7,7 +7,7 @@ license-file: UNLICENSE author: Robert Fischer maintainer: smokejumperit+pong-server@gmail.com-copyright: 2016 Robert Fischer+copyright: 2017 Robert Fischer category: utilities build-type: Simple -- extra-source-files:@@ -15,20 +15,17 @@ library hs-source-dirs: src- exposed-modules: Axial.Pong- build-depends: base >= 4.7 && < 5- , network >= 2.6.2.1 && < 2.7- , data-default >= 0.7.1.1 && < 0.8+ exposed-modules: Network.Pong+ build-depends: base >= 4.9.1.0 && < 5+ , network >= 2.6.3.1 && < 2.7+ , classy-prelude >= 1.2.0.1 && < 2+ , bytestring >= 0.10.8.1 && < 0.11+ , http-types >= 0.9.1 && < 1+ , exceptions >= 0.8.3 && < 0.9 default-language: Haskell2010- ghc-options: -Wall + ghc-options: -Wall -O3 default-extensions: OverloadedStrings- , LambdaCase- , MultiWayIf- , TransformListComp- , FlexibleInstances- , NoMonomorphismRestriction- , DeriveGeneric- , ScopedTypeVariables+ , AllowAmbiguousTypes test-suite pong-server-test type: exitcode-stdio-1.0
− src/Axial/Pong.hs
@@ -1,49 +0,0 @@-module Axial.Pong- (- PongConfig(..)- , defaultPongConfig- , withPongServer- ) where--import Data.Default (Default, def)-import Network (withSocketsDo, listenOn, accept, sClose, PortID(..))-import System.IO (hClose, hPutStr, hSetBuffering, BufferMode(..))-import Control.Exception (bracket, finally)-import Control.Concurrent (forkIO, forkFinally, ThreadId, killThread)--data PongConfig = PongConfig {- pongPort :: Int,- pongMessage :: IO String-}--newtype PongServer = PongServer ThreadId--defaultPongConfig :: PongConfig-defaultPongConfig = PongConfig 10411 $ pure "pong"--instance Default PongConfig where- def = defaultPongConfig--withPongServer :: PongConfig -> IO a -> IO a-withPongServer cfg action = bracket (startServer cfg) stopServer $ const action--startServer :: PongConfig -> IO PongServer-startServer cfg = withSocketsDo $ do- socket <- listenOn portNum- threadId <- forkIO $ socketHandler socket- pure $ PongServer threadId- where- socketHandler sock = finally (socketHandlerLoop sock) (sClose sock)- socketHandlerLoop sock = do- (handle, _, _) <- accept sock- _ <- forkFinally (body handle) (const $ hClose handle)- socketHandlerLoop sock- body handle = do- msg <- (pongMessage cfg)- hSetBuffering handle NoBuffering- hPutStr handle msg- portNum = PortNumber $ fromIntegral pongPortNum- pongPortNum = pongPort cfg--stopServer :: PongServer -> IO ()-stopServer (PongServer threadId) = killThread threadId
+ src/Network/Pong.hs view
@@ -0,0 +1,135 @@+{-|+Module : Network.Pong+Description : Ping server that runs in the background.+Copyright : (c) Robert Fischer, 2017+License : Unlicense+Maintainer : smokejumper+network.pong@gmail.com+Stability : experimental+Portability : POSIX+++This is an exceedingly simple server: it listens on the given port on all interfaces,+and when it retrieves a connection, it simply prints out the result of the 'pongMessage'+action, which is just the four characters @pong@ by default.++The purpose of this server is to provide something for automated smoke checks (such as+load balancers) to hit against. It provides a rudimentary server smoke check while+requiring a minimal amount of overhead.++There are two customary ways to use this server. The first customary way uses whether+the response is empty or not to signal whether there is an error or not. If the resulting+body is empty, there is an error. If the response is non-empty, the server is fine. This+is supported by 'pongCatch'. The+second customary way to use this server is to treat it as a server running @HTTP/0.9@,+and to use 200 response status codes to mean the server is good and 500 response status codes+to mean the server is having a problem. This is supported by the 'pongCatchHttp' and the other+@pong*Http*@ functions.++A third way to use this server is to have the body of the message actually contain some+meaningful values about the state of the system. The reason the response is a 'ByteString'+is specifically so that you could implement a binary protocol if you deeply wanted to.+If you are heading down that path, though, please first look into the @ekg@ package. Keep+'Network.Pong' in play for automated smoke checks, and use @ekg@ for more precise status+monitoring.++To run this server, simply add this into your main function:++@+ main = withPongServer defaultPongConfig $ do+@++It will then run the pong server on port @10411@. Of course, you can customize the+server through the configuration object.++-}+module Network.Pong+ (+ PongConfig(..)+ , defaultPongConfig+ , pongMessageHttp200+ , pongMessageHttp500+ , pongMessageHttp+ , pongCatch+ , pongCatchHttp+ , withPongServer+ ) where++import Prelude ()+import ClassyPrelude hiding (handle)++import Control.Concurrent (forkIO)+import Network.HTTP.Types.Status (ok200, internalServerError500, Status(..))+import Network (withSocketsDo, listenOn, accept, sClose, PortID(..))+import System.IO (hClose, hSetBuffering, BufferMode(..))++import qualified Data.ByteString.Char8 as C++-- | This is the type of the response. It is mostly aliased to make type signatures more obvious.+type PongAction = IO ByteString++-- | This is the type of the response for HTTP-based responses. It is mostly aliased to make type signatures more obvious.+type PongHttpAction = IO Status++-- | This provides the configuration for the pong server.+data PongConfig = PongConfig {+ pongPort :: Int, -- ^ The port that this server will run on+ pongMessage :: PongAction -- ^ The action which generates a response message+}++pongMessageHttp200 :: PongAction+-- ^ Provides an action generating an @HTTP/0.9@ response message for the 'ok200' 'Status'.+pongMessageHttp200 = pongMessageHttp ok200++pongMessageHttp500 :: PongAction+-- ^ Provides an action generating an @HTTP/0.9@ response message for the 'internalServerError500' 'Status'.+pongMessageHttp500 = pongMessageHttp internalServerError500++pongMessageHttp :: Status -> PongAction+-- ^ Provides an action generating an @HTTP/0.9@ response message for the given 'Status'.+pongMessageHttp status =+ return $ concat ["HTTP/0.9 ", statusCodeBS status, " ", statusMessage status]+ where+ statusCodeBS :: Status -> ByteString+ statusCodeBS = C.pack . show . statusCode++pongCatchHttp :: (Exception e) => PongHttpAction -> (e -> PongHttpAction) -> PongAction+-- ^ Allows for customization of the result 'Status' given different exceptions.+pongCatchHttp good bad = (catch good bad) >>= pongMessageHttp++pongCatch :: (Exception e) => PongAction -> (e -> PongAction) -> PongAction+-- ^ Allows for customization of the result message given different exceptions.+pongCatch = catch -- Yes, we could have just let people use 'catch', but it may be non-obvious++-- | Handle for a running server+newtype PongServer = PongServer ThreadId++defaultPongConfig :: PongConfig+-- ^ Default config that runs on port @10411@ and just prints out the four characters @pong@.+defaultPongConfig = PongConfig 10411 $ (return $ C.pack $ "pong")++withPongServer :: PongConfig -> IO a -> IO a+-- ^ Entry point to the pong server.+withPongServer cfg action = bracket (startServer cfg) stopServer $ const action++startServer :: PongConfig -> IO PongServer+-- ^ Implementation of actually starting the server.+startServer cfg = do+ socket <- withSocketsDo $ listenOn portNum+ threadId <- forkIO $ socketHandler socket+ return $ PongServer threadId+ where+ portNum = PortNumber . fromIntegral $ pongPortNum+ pongPortNum = pongPort cfg+ socketHandler sock = finally (socketHandlerLoop sock) (sClose sock)+ socketHandlerLoop sock = do+ (handle, _, _) <- accept sock+ _ <- forkFinally (body handle) (const $ hClose handle)+ socketHandlerLoop sock+ body handle = do+ hSetBuffering handle NoBuffering+ msg <- pongMessage cfg+ C.hPutStr handle msg++stopServer :: (MonadIO m) => PongServer -> m ()+-- ^ Implementation of actually stopping the server+stopServer (PongServer threadId) = liftIO $ killThread threadId
test/Spec.hs view
@@ -1,4 +1,4 @@-import Axial.Pong+import Network.Pong import Data.Maybe (isJust, fromJust) @@ -15,7 +15,7 @@ main :: IO () main = hspec $ do- describe "Axial Pong server" $ do+ describe "Pong server" $ do it "cannot be reached when not running" $ do reached <- serverReachable ()