packages feed

http3-0.0.2: Network/HQ/Server.hs

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

-- | A server library for HTTP/0.9.

module Network.HQ.Server (
  -- * Runner
    run
  -- * Runner arguments
  , Config(..)
  , allocSimpleConfig
  , freeSimpleConfig
  -- * HQ server
  , Server
  -- * Request
  , Request
  -- ** Accessing request
  , H2.requestPath
  -- * Response
  , Response
  -- ** Creating response
  , H2.responseNoBody
  , H2.responseFile
  , H2.responseStreaming
  , H2.responseBuilder
  ) where

import Control.Concurrent
import Data.ByteString.Builder (Builder)
import qualified Data.ByteString.Builder.Extra as B
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString as BS
import Data.IORef
import Foreign.ForeignPtr
import Network.HPACK (HeaderTable, toHeaderTable)
import Network.HTTP2.Internal (InpObj(..))
import qualified Network.HTTP2.Internal as H2
import qualified Network.HTTP2.Server as H2
import Network.HTTP2.Server (Server, PushPromise)
import Network.HTTP2.Server.Internal (Request(..), Response(..), Aux(..))
import Network.QUIC (Connection, Stream)
import qualified Network.QUIC as QUIC
import Network.SockAddr (showSockAddrBS)
import Network.Socket (SockAddr)
import qualified System.TimeManager as T

import Imports
import Network.HTTP3.Config
import Network.HTTP3.Recv (newSource, readSource)

-- | Running an HQ server.
run :: Connection -> Config -> Server -> IO ()
run conn conf server = do
    myaddr <- QUIC.localSockAddr <$> QUIC.getConnectionInfo conn
    forever $ do
        strm <- QUIC.acceptStream conn
        forkFinally (processRequest conf myaddr server strm) (\_ -> QUIC.closeStream strm)

processRequest :: Config -> SockAddr -> Server -> Stream -> IO ()
processRequest conf myaddr server strm
  | QUIC.isClientInitiatedBidirectional sid = do
        th <- T.register (confTimeoutManager conf) (return ())
        vt <- recvHeader strm myaddr
        src <- newSource strm
        refH <- newIORef Nothing
        let readB = readSource src
            req = Request $ InpObj vt Nothing readB refH
            aux = Aux th
        server req aux $ sendResponse conf strm
  | otherwise = return () -- fixme: should consume the data?
  where
    sid = QUIC.streamId strm

recvHeader :: Stream -> SockAddr -> IO HeaderTable
recvHeader strm myaddr = do
    (method,path) <- recvRequestLine id
    let auth = showSockAddrBS myaddr
        vt = (":path",path)
           : (":method", method)
           : (":scheme", "https")
           : (":authority", auth)
           : []
    toHeaderTable vt
  where
    recvRequestLine builder = do
        bs <- QUIC.recvStream strm 1024
        if bs == "" then do
            return $ parseRequestLine $ BS.concat $ builder []
          else
            recvRequestLine (builder . (bs :))
    parseRequestLine bs = (method,path)
      where
        method = "GET"
        path0 = BS.drop 4 bs
        path = BS.take (BS.length path0 - 2) path0

sendResponse :: Config -> Stream -> Response -> [PushPromise] -> IO ()
sendResponse conf strm (Response outobj) _ = case H2.outObjBody outobj of
    H2.OutBodyNone -> return ()
    H2.OutBodyFile (H2.FileSpec path fileoff bytecount) -> do
        (pread, sentinel') <- confPositionReadMaker conf path
        let timmgr = confTimeoutManager conf
        refresh <- case sentinel' of
                     H2.Closer closer       -> do
                         th <- T.register timmgr closer
                         return $ T.tickle th
                     H2.Refresher refresher -> return refresher
        let next = H2.fillFileBodyGetNext pread fileoff bytecount refresh
        sendNext strm next
    H2.OutBodyBuilder builder -> do
        let next = H2.fillBuilderBodyGetNext builder
        sendNext strm next
    H2.OutBodyStreaming strmbdy -> sendStreaming strm strmbdy

sendNext :: Stream -> H2.DynaNext -> IO ()
sendNext strm action = do
    fp <- BS.mallocByteString 2048
    H2.Next len _reqflush mnext <- withForeignPtr fp $ \buf -> action buf 2048 65536 -- window size
    if len == 0 then
        return ()
      else do
        let bs = PS fp 0 len
        QUIC.sendStream strm bs
        case mnext of
          Nothing   -> return ()
          Just next -> sendNext strm next

sendStreaming :: Stream -> ((Builder -> IO ()) -> IO () -> IO ()) -> IO ()
sendStreaming strm strmbdy = do
    strmbdy write flush
  where
    flush = return ()
    write builder = do
        newByteStringAndSend strm (B.runBuilder builder) >>= loop
      where
        loop B.Done = return ()
        loop (B.More _ writer) =
            newByteStringAndSend strm writer >>= loop
        loop (B.Chunk bs writer) = do
            QUIC.sendStream strm bs
            newByteStringAndSend strm writer >>= loop

newByteStringAndSend :: Stream -> B.BufferWriter -> IO B.Next
newByteStringAndSend strm action = do
    fp <- BS.mallocByteString 2048
    (len, signal) <- withForeignPtr fp $ \buf -> action buf 2048
    if len == 0 then
        return signal
      else do
        let bs = PS fp 0 len
        QUIC.sendStream strm bs
        return signal