scotty-0.30: examples/middleware.hs
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Web.Scotty
import Network.Wai.Middleware.RequestLogger
import Network.Wai.Middleware.ValidateHeaders (validateHeadersMiddleware, defaultValidateHeadersSettings)
import Data.Aeson (object, (.=))
import qualified Data.Text.Lazy as TL
import qualified Data.ByteString.Lazy as BL
main :: IO ()
main = do
putStrLn "\n=== Scotty Middleware Example ==="
putStrLn "\nThis example demonstrates two WAI middlewares:"
putStrLn "1. Request Logging - logs all HTTP requests to stdout"
putStrLn "2. Header Validation - validates incoming request headers\n"
putStrLn "The validateHeadersMiddleware checks REQUEST headers from clients."
putStrLn "It rejects requests with invalid headers (containing illegal characters)"
putStrLn "such as CR/LF, control characters, or invalid header names.\n"
putStrLn "Try these requests:"
putStrLn " curl http://localhost:3000/"
putStrLn " curl -H 'X-Custom: value' http://localhost:3000/headers"
putStrLn " curl -H 'X-Token!#$: valid' http://localhost:3000/headers"
putStrLn "\nTo test invalid headers (these will return 500):"
putStrLn " curl -H 'Invalid Header: value' http://localhost:3000/ # header name cannot contain spaces"
putStrLn " Note: Headers with CR/LF in values will also be rejected"
putStrLn "\nPress Ctrl+C to stop\n"
scotty 3000 $ do
-- Request logging middleware - logs all requests to stdout in development format
middleware logStdoutDev
-- Header validation middleware - validates REQUEST headers to prevent header injection
-- This middleware checks headers sent by the CLIENT and rejects malformed ones
-- It validates both header names and values according to HTTP specifications
middleware $ validateHeadersMiddleware defaultValidateHeadersSettings
-- Endpoint group 1: Basic endpoints that demonstrate logging
get "/" $ do
text "Welcome! This request was logged by the logging middleware."
get "/hello/:name" $ do
name <- pathParam "name"
text $ "Hello, " <> name <> "! Check the server logs to see the request details."
-- Endpoint group 2: Shows all request headers that passed validation
get "/headers" $ do
allHeaders <- headers
let formatHeader (name, value) =
name <> ": " <> value <> "\n"
headerList = map formatHeader allHeaders
text $ mconcat
[ "Your request headers (all validated by middleware):\n\n"
, mconcat headerList
, "\nThese headers passed validation. Invalid headers would have been rejected with 500.\n"
, "Try sending a request with invalid headers to see the middleware in action."
]
-- Endpoint group 3: Demonstrates specific header inspection
get "/user-agent" $ do
agent <- header "User-Agent"
case agent of
Just ua -> text $ "Your User-Agent: " <> ua
Nothing -> text "No User-Agent header provided"
-- Endpoint group 4: POST request to demonstrate logging of different methods
post "/echo" $ do
requestBody <- body
text $ "Request body received (" <> TL.pack (show (BL.length requestBody)) <> " bytes). Check server logs for details."
-- Endpoint group 5: JSON response with logging
get "/json" $ do
json $ object
[ "message" .= ("All request headers were validated" :: String)
, "middleware" .= object
[ "logging" .= ("logStdoutDev" :: String)
, "validation" .= ("validateHeadersMiddleware" :: String)
]
]