packages feed

http3-0.1.2: util/ClientX.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}

module ClientX (
    Misc (..),
    Cli,
    clientHQ,
    clientH3,
) where

import Control.Concurrent
import Control.Concurrent.Async
import qualified Control.Exception as E
import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as C8
import Network.HTTP.Types
import Network.QUIC
import System.IO

import qualified Network.HQ.Client as HQ
import Network.HTTP3.Client (
    Client,
    ClientConfig (..),
    Config,
    Path,
    SendRequest,
 )
import qualified Network.HTTP3.Client as H3
import qualified Network.QUIC.Internal as QUIC

data Misc = Misc
    { miscAuthority :: String
    , miscDebug :: String -> IO ()
    , miscShow :: ByteString -> IO ()
    , miscCheckClose :: IO Bool
    , miscInteractive :: Bool
    }

type Cli = Misc -> [Path] -> Connection -> IO ()

clientHQ :: Int -> Cli
clientHQ n = clientX n HQ.run

clientH3 :: Int -> Cli
clientH3 n = clientX n H3.run

clientX
    :: Int
    -> (Connection -> ClientConfig -> Config -> Client () -> IO ())
    -> Cli
clientX n0 run misc@Misc{..} paths conn = E.bracket H3.allocSimpleConfig H3.freeSimpleConfig $ \conf ->
    run conn cliconf conf $ client conn misc n0 paths
  where
    cliconf =
        H3.defaultClientConfig
            { authority = miscAuthority
            }

client :: Connection -> Misc -> Int -> [Path] -> Client ()
client conn misc n0 paths sendRequest aux
    | miscInteractive misc = do
        console paths aux conn go
        return ()
    | otherwise = go
  where
    go =
        foldr1 concurrently_ $
            map (client' misc n0 sendRequest) paths

client' :: Misc -> Int -> SendRequest -> Path -> IO ()
client' Misc{..} n0 sendRequest path = loop n0
  where
    req =
        H3.requestNoBody
            methodGet
            path
            [("User-Agent", "HaskellQuic/0.0.0")]
    loop 0 = return ()
    loop n = do
        () <- sendRequest req $ \rsp -> do
            miscDebug "GET"
            miscShow "------------------------"
            consume rsp
            miscShow "------------------------"
        when (n /= 1) $ do
            threadDelay 100000
            loop (n - 1)
    consume rsp = do
        bs <- H3.getResponseBodyChunk rsp
        if bs == ""
            then do
                miscDebug "Fin received"
            else do
                miscShow bs
                miscDebug $ show (C8.length bs) ++ " bytes received"
                consume rsp

console :: [Path] -> H3.Aux -> Connection -> IO () -> IO ()
console paths aux conn go = do
    waitEstablished conn
    putStrLn "q -- quit"
    putStrLn "g -- get"
    putStrLn "p -- ping"
    putStrLn "n -- NAT rebinding"
    mvar <- newEmptyMVar
    loop mvar `E.catch` \(E.SomeException _) -> return ()
  where
    loop mvar = do
        hSetBuffering stdout NoBuffering
        putStr "> "
        hSetBuffering stdout LineBuffering
        l <- getLine
        case l of
            "q" -> putStrLn "bye"
            "g" -> do
                mapM_ (\p -> putStrLn $ "GET " ++ C8.unpack p) paths
                _ <- go >> putMVar mvar ()
                takeMVar mvar
                loop mvar
            "p" -> do
                putStrLn "Ping"
                H3.auxSendPing aux
                loop mvar
            "n" -> do
                QUIC.controlConnection conn QUIC.NATRebinding >>= print
                loop mvar
            _ -> do
                putStrLn "No such command"
                loop mvar