packages feed

hsrelp (empty) → 0.1.0.0

raw patch · 4 files changed

+206/−0 lines, 4 filesdep +attoparsecdep +basedep +bytestringsetup-changed

Dependencies added: attoparsec, base, bytestring, network, utf8-string

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, Denis Afonin++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Denis Afonin nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hsrelp.cabal view
@@ -0,0 +1,34 @@+name:                hsrelp+version:             0.1.0.0+synopsis:            RELP (Reliable Event Logging Protocol) server implementation+description:         +  The specification of the RELP protocol:+  <http://www.rsyslog.com/doc/relp.html>++homepage:            https://github.com/verrens/hsrelp+bug-reports:         https://github.com/verrens/hsrelp/issues+license:             BSD3+license-file:        LICENSE+author:              Denis Afonin+maintainer:          verrens@yandex.ru+-- copyright:           +category:            Network+build-type:          Simple+-- extra-source-files:  +cabal-version:       >=1.10++library+  -- other-modules:       +  -- other-extensions:    +  build-depends:    base >=4.8 && <4.9,+                    network,+                    bytestring,+                    utf8-string,+                    attoparsec+  hs-source-dirs:   src+  default-language: Haskell2010+  exposed-modules:  Network.RELP.Server++Source-repository head+  Type:     git+  Location: https://github.com/verrens/hsrelp
+ src/Network/RELP/Server.hs view
@@ -0,0 +1,140 @@+-- | RELP (Reliable Event Logging Protocol) simple server+{-# LANGUAGE OverloadedStrings #-}+module Network.RELP.Server+  (+    -- * Running a standalone RELP server+    RelpMessageHandler+  , runRelpServer+  )+  where++import Prelude hiding (getContents, take)+import Network.Socket hiding (send, recv)+import Network.Socket.ByteString.Lazy+import Control.Concurrent (forkIO)+import Data.Attoparsec.ByteString+import qualified Data.Attoparsec.ByteString.Lazy as LBP+import qualified Data.ByteString.Lazy.Char8 as B8+import qualified Data.ByteString as B+import Data.ByteString (ByteString)+import Data.ByteString.UTF8 (toString)+import Data.Char+import Data.List (lookup)+import Control.Applicative+import Control.Monad++-- | Message handler callback.+type RelpMessageHandler =+  SockAddr -- ^ Client connection address+  -> ByteString -- ^ Log message+  -> IO Bool -- ^ Reject message (reply error RSP) if False++++data RelpCommand = RelpRSP | RelpOPEN | RelpSYSLOG | RelpCLOSE+  | RelpCommand ByteString+  deriving (Show, Eq)++data RelpMessage = RelpMessage+  { relpTxnr :: Int+  , relpCommand :: RelpCommand+  , relpData :: ByteString+  } deriving (Show, Eq)++type RelpOffers = [(ByteString, ByteString)]++-- | Provides a simple RELP server.+runRelpServer :: PortNumber -- ^ Port to listen on+  -> RelpMessageHandler -- ^ Message handler+  -> IO () -- ^ Never returns+runRelpServer port cb = withSocketsDo $ do+  sock <- socket AF_INET Stream defaultProtocol+  setSocketOption sock ReuseAddr 1+  bindSocket sock (SockAddrInet port iNADDR_ANY)+  listen sock 3+  handleConnection sock+  sClose sock+  where++  relpRsp :: Socket -> RelpMessage -> String -> IO ()+  relpRsp sock msg reply = sendAll sock mkReply+    -- putStrLn $ prettyHex $ B8.toStrict mkReply+    where+    mkReply = B8.pack $ (show $ relpTxnr msg) ++ " rsp "+      ++ (show $ length reply) ++ " " ++ reply ++ "\n"++  relpAck :: Socket -> RelpMessage -> IO ()+  relpAck sock msg = relpRsp sock msg "200 OK"++  relpNAck :: Socket -> RelpMessage -> String -> IO ()+  relpNAck sock msg err = relpRsp sock msg $ "500 " ++ err++  relpParser :: Parser RelpMessage+  relpParser = do+    txnr <- decimal <* space+    command <- parseCommand <* space+    datalen <- decimal <* space+    content <- take (datalen + 1) -- <* trailer+    return $ RelpMessage txnr command content+    where+    decimal :: Integral a => Parser a+    decimal = B.foldl' step 0 `fmap` takeWhile1 isDecimal where+      step a c = a * 10 + fromIntegral (c - 48)+      isDecimal c = c >= 48 && c <= 57+    space = word8 32+    trailer = word8 10+    parseCommand =+      string "syslog" *> return RelpSYSLOG+      <|> string "close" *> return RelpCLOSE+      <|> string "open" *> return RelpOPEN+      <|> string "rsp" *> return RelpRSP+      <|> RelpCommand <$> takeWhile1 (/= 32)++  relpOffersParser :: Parser RelpOffers +  relpOffersParser = many' $ pair <* word8 sep+    where+    sep = 10 -- \n+    der = 61 -- '='+    pair = liftA2 (,)+      (takeWhile1 (\c-> c /= der && c /= sep))+      (word8 der *> takeWhile1 (/= sep) <|> return "")++  -- just shortcuts+  parse_ err ok p = either err ok . parseOnly p+  parseLazy_ err ok p = either err ok . LBP.eitherResult . LBP.parse p+++  handleConnection sock = do+    accept sock >>= forkIO . handleMessage+    handleConnection sock++  handleMessage s@(sockh, srcAddr) = do+    status <- getContents sockh >>= processMessage s+    if status then handleMessage s+      else close sockh++  processMessage (sock, srcAddr) = parseLazy_ err process relpParser+    where+    err e = putStrLn ("ERROR: parser: " ++ show e) >> return False++    process msg@RelpMessage{ relpCommand = RelpOPEN } = do+      let offers = parse_ (const []) id relpOffersParser $ relpData msg+      -- NOTE only version 0 supported!+      let versionValid = maybe False (== "0") $ lookup "relp_version" offers+      -- TODO FIXME check commands offer?+      if versionValid then do+          relpRsp sock msg $ "200 OK "+            ++ "relp_version=0\nrelp_software=hsRELP\ncommands="+            ++ (maybe "syslog" toString $ lookup "commands" offers)+          return True+        else relpNAck sock msg "unsupported RELP version" >> return False++    process msg@RelpMessage{ relpCommand = RelpSYSLOG } = do+      status <- cb srcAddr (relpData msg)+      if status then relpAck sock msg else relpNAck sock msg "rejected"+      return status++    process msg = do+      putStrLn ("ERROR: strange message command: " ++ show msg)+      relpNAck sock msg "unexpected message command"+      return False