restman-0.7.6.0: echo/Main.hs
{-# language OverloadedStrings #-}
-- | Standalone diagnostic echo server for local restman testing.
--
-- Accepts any HTTP request and responds with a JSON object reflecting
-- the method, path, query string, request headers, and body — identical
-- schema to the in-process 'HTTP.EchoServer' used by the test suite.
--
-- Usage:
-- stack run restman-echo --flag restman:echo-server
-- PORT=9000 stack run restman-echo --flag restman:echo-server
--
-- Every incoming request is pretty-printed to stdout, including a UTC
-- timestamp, so both sides of a request/response cycle can evaluate
-- what actually arrived.
module Main (main) where
import Data.Aeson (encode, object, (.=))
import Data.Aeson.Encode.Pretty (encodePretty)
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BLC8
import qualified Data.CaseInsensitive as CI
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Data.Time.Clock (getCurrentTime)
import Network.HTTP.Types (status200)
import Network.Wai
( Application
, rawPathInfo
, rawQueryString
, requestHeaders
, requestMethod
, responseLBS
, strictRequestBody
)
import Network.Wai.Handler.Warp (Port, run)
import System.Environment (lookupEnv)
import System.IO (hFlush, stdout)
-- | Build the echo payload — identical schema to HTTP.EchoServer.
buildPayload
:: T.Text -- ^ method
-> T.Text -- ^ path
-> T.Text -- ^ query string (including leading '?')
-> Map T.Text T.Text -- ^ lowercased headers
-> T.Text -- ^ body
-> BL.ByteString
buildPayload method path query hdrs body =
encode $ object
[ "method" .= method
, "path" .= path
, "query" .= query
, "headers" .= hdrs
, "body" .= body
]
-- | Log a received request to stdout in pretty-printed JSON, with a
-- UTC timestamp in the log entry (timestamp is *not* included in the
-- HTTP response body so the schema stays compatible with EchoServer).
logRequest
:: T.Text -- ^ method
-> T.Text -- ^ path
-> T.Text -- ^ query string
-> Map T.Text T.Text -- ^ lowercased headers
-> T.Text -- ^ body
-> IO ()
logRequest method path query hdrs body = do
ts <- getCurrentTime
let logEntry = encodePretty $ object
[ "timestamp" .= show ts
, "method" .= method
, "path" .= path
, "query" .= query
, "headers" .= hdrs
, "body" .= body
]
BLC8.putStrLn logEntry
putStrLn "\x2192 200 OK"
hFlush stdout
-- | WAI application: log the request then echo it back as compact JSON.
echoApp :: Application
echoApp req respond = do
rawBody <- strictRequestBody req
let method = TE.decodeUtf8 (requestMethod req)
path = TE.decodeUtf8 (rawPathInfo req)
query = TE.decodeUtf8 (rawQueryString req)
hdrs = Map.fromList
[ (TE.decodeUtf8 (CI.foldedCase k), TE.decodeUtf8 v)
| (k, v) <- requestHeaders req
] :: Map T.Text T.Text
bodyTxt = TE.decodeUtf8 (BL.toStrict rawBody)
logRequest method path query hdrs bodyTxt
let payload = buildPayload method path query hdrs bodyTxt
respond $ responseLBS status200
[("Content-Type", "application/json")]
payload
-- | Read PORT from the environment (default 8080) and run the server.
main :: IO ()
main = do
portStr <- lookupEnv "PORT"
let port = maybe 8080 read portStr :: Port
putStrLn $ "restman-echo listening on port " <> show port
putStrLn "(press Ctrl-C to stop)\n"
hFlush stdout
run port echoApp