2026-07-19 23:57:21 +09:00
|
|
|
module Nuthatch.Probe.Curl
|
2026-07-20 23:06:01 +09:00
|
|
|
( CurlTransportFailure (..)
|
2026-07-19 23:57:21 +09:00
|
|
|
, curl
|
|
|
|
|
, curlToFile
|
|
|
|
|
) where
|
|
|
|
|
|
2026-07-20 05:22:08 +09:00
|
|
|
import Control.Exception (Exception, throwIO)
|
2026-07-19 23:57:21 +09:00
|
|
|
import qualified Data.ByteString as BS
|
|
|
|
|
import qualified Data.ByteString.Char8 as B8
|
2026-07-20 23:06:01 +09:00
|
|
|
import qualified Data.CaseInsensitive as CI
|
2026-07-19 23:57:21 +09:00
|
|
|
import Data.Maybe (mapMaybe)
|
2026-07-20 23:06:01 +09:00
|
|
|
import Network.HTTP.Types (Header)
|
|
|
|
|
import Nuthatch.Probe.Http (ProbeResponse (..))
|
2026-07-19 23:57:21 +09:00
|
|
|
import System.Exit (ExitCode (..))
|
|
|
|
|
import System.IO.Temp (withSystemTempDirectory)
|
|
|
|
|
import System.Process (readProcessWithExitCode)
|
|
|
|
|
import Text.Read (readMaybe)
|
|
|
|
|
|
2026-07-20 05:22:08 +09:00
|
|
|
data CurlTransportFailure = CurlTransportFailure
|
|
|
|
|
{ curlTransportExitCode :: Int
|
|
|
|
|
, curlTransportStderr :: String
|
|
|
|
|
} deriving stock (Eq, Show)
|
|
|
|
|
|
|
|
|
|
instance Exception CurlTransportFailure
|
|
|
|
|
|
2026-07-20 23:06:01 +09:00
|
|
|
curl :: [String] -> IO ProbeResponse
|
2026-07-19 23:57:21 +09:00
|
|
|
curl arguments =
|
|
|
|
|
withSystemTempDirectory "nuthatch-curl" $ \directory -> do
|
|
|
|
|
let bodyPath = directory <> "/body"
|
|
|
|
|
response <- runCurl bodyPath arguments
|
|
|
|
|
body <- BS.readFile bodyPath
|
2026-07-20 23:06:01 +09:00
|
|
|
pure response { probeBody = body }
|
2026-07-19 23:57:21 +09:00
|
|
|
|
2026-07-20 23:06:01 +09:00
|
|
|
curlToFile :: FilePath -> [String] -> IO ProbeResponse
|
2026-07-19 23:57:21 +09:00
|
|
|
curlToFile outputPath arguments =
|
|
|
|
|
runCurl outputPath arguments
|
|
|
|
|
|
2026-07-20 23:06:01 +09:00
|
|
|
runCurl :: FilePath -> [String] -> IO ProbeResponse
|
2026-07-19 23:57:21 +09:00
|
|
|
runCurl outputPath arguments =
|
|
|
|
|
withSystemTempDirectory "nuthatch-curl-headers" $ \directory -> do
|
|
|
|
|
let headersPath = directory <> "/headers"
|
|
|
|
|
common =
|
|
|
|
|
[ "--insecure"
|
|
|
|
|
, "--silent"
|
|
|
|
|
, "--show-error"
|
|
|
|
|
, "--dump-header", headersPath
|
|
|
|
|
, "--output", outputPath
|
|
|
|
|
, "--write-out", "%{http_code}"
|
|
|
|
|
]
|
|
|
|
|
(exitCode, statusText, stderrText) <-
|
|
|
|
|
readProcessWithExitCode "curl" (common <> arguments) ""
|
|
|
|
|
status <- case readMaybe statusText of
|
|
|
|
|
Just value -> pure value
|
|
|
|
|
Nothing ->
|
|
|
|
|
fail ("curl returned an invalid status: " <> statusText)
|
|
|
|
|
case exitCode of
|
|
|
|
|
ExitSuccess -> pure ()
|
|
|
|
|
ExitFailure code ->
|
2026-07-20 05:22:08 +09:00
|
|
|
throwIO CurlTransportFailure
|
|
|
|
|
{ curlTransportExitCode = code
|
|
|
|
|
, curlTransportStderr = stderrText
|
|
|
|
|
}
|
2026-07-19 23:57:21 +09:00
|
|
|
headers <- parseHeaders <$> BS.readFile headersPath
|
2026-07-20 23:06:01 +09:00
|
|
|
pure ProbeResponse
|
|
|
|
|
{ probeStatus = status
|
|
|
|
|
, probeHeaders = headers
|
|
|
|
|
, probeBody = BS.empty
|
2026-07-19 23:57:21 +09:00
|
|
|
}
|
|
|
|
|
|
2026-07-20 23:06:01 +09:00
|
|
|
parseHeaders :: BS.ByteString -> [Header]
|
2026-07-19 23:57:21 +09:00
|
|
|
parseHeaders input =
|
|
|
|
|
case filter (not . null) (splitBlocks (B8.lines input)) of
|
|
|
|
|
[] -> []
|
|
|
|
|
blocks -> mapMaybe parseHeader (last blocks)
|
|
|
|
|
where
|
|
|
|
|
splitBlocks = foldr splitLine [[]]
|
|
|
|
|
splitLine line (block : blocks)
|
|
|
|
|
| BS.null (stripCarriageReturn line) = [] : block : blocks
|
|
|
|
|
| otherwise = (stripCarriageReturn line : block) : blocks
|
|
|
|
|
splitLine _ [] = []
|
|
|
|
|
|
2026-07-20 23:06:01 +09:00
|
|
|
parseHeader :: BS.ByteString -> Maybe Header
|
2026-07-19 23:57:21 +09:00
|
|
|
parseHeader line =
|
|
|
|
|
case B8.break (== ':') line of
|
|
|
|
|
(_, remainder) | BS.null remainder -> Nothing
|
|
|
|
|
(name, remainder) ->
|
2026-07-20 23:06:01 +09:00
|
|
|
Just (CI.mk name, B8.dropWhile (== ' ') (BS.drop 1 remainder))
|
2026-07-19 23:57:21 +09:00
|
|
|
|
|
|
|
|
stripCarriageReturn :: BS.ByteString -> BS.ByteString
|
|
|
|
|
stripCarriageReturn value =
|
|
|
|
|
case B8.unsnoc value of
|
|
|
|
|
Just (prefix, '\r') -> prefix
|
|
|
|
|
_ -> value
|