X-Git-Url: http://git.cielonegro.org/gitweb.cgi?a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FHeaders.hs;h=cfb3fb2dd98e5797fe6c96b6ca861ac93e5f4307;hb=65a16e9;hp=65a4940026d5b626d4a11bbec50449d101745256;hpb=d05d8c883eaca12ee621975a2b95c5ebdc2357d2;p=Lucu.git diff --git a/Network/HTTP/Lucu/Headers.hs b/Network/HTTP/Lucu/Headers.hs index 65a4940..cfb3fb2 100644 --- a/Network/HTTP/Lucu/Headers.hs +++ b/Network/HTTP/Lucu/Headers.hs @@ -1,52 +1,88 @@ +{-# LANGUAGE + BangPatterns + , GeneralizedNewtypeDeriving + , OverloadedStrings + , UnicodeSyntax + #-} module Network.HTTP.Lucu.Headers ( Headers , HasHeaders(..) - , emptyHeaders + + , toHeaders + , fromHeaders + , headersP , hPutHeaders ) where +import Control.Applicative +import Data.Ascii (Ascii, CIAscii) +import qualified Data.Ascii as A +import Data.Attoparsec.Char8 as P +import qualified Data.ByteString as BS +import Data.Map (Map) +import qualified Data.Map as M +import Data.Monoid +import Data.Monoid.Unicode +import Network.HTTP.Lucu.HandleLike +import Network.HTTP.Lucu.Parser.Http +import Network.HTTP.Lucu.Utils +import Prelude.Unicode -import Data.Char -import Data.List -import Network.HTTP.Lucu.Parser -import Network.HTTP.Lucu.Parser.Http -import Network.HTTP.Lucu.Utils -import System.IO - -type Headers = [ (String, String) ] +newtype Headers + = Headers (Map CIAscii Ascii) + deriving (Eq, Show, Monoid) class HasHeaders a where - getHeaders :: a -> Headers - setHeaders :: a -> Headers -> a + getHeaders ∷ a → Headers + setHeaders ∷ a → Headers → a + + getHeader ∷ CIAscii → a → Maybe Ascii + {-# INLINE getHeader #-} + getHeader !key !a + = case getHeaders a of + Headers m → M.lookup key m - getHeader :: String -> a -> Maybe String - getHeader key a - = key `seq` a `seq` - fmap snd $ find (noCaseEq' key . fst) (getHeaders a) + deleteHeader ∷ CIAscii → a → a + {-# INLINE deleteHeader #-} + deleteHeader !key !a + = case getHeaders a of + Headers m + → setHeaders a $ Headers $ M.delete key m - deleteHeader :: String -> a -> a - deleteHeader key a - = key `seq` a `seq` - setHeaders a $ filter (not . noCaseEq' key . fst) (getHeaders a) + setHeader ∷ CIAscii → Ascii → a → a + {-# INLINE setHeader #-} + setHeader !key !val !a + = case getHeaders a of + Headers m + → setHeaders a $ Headers $ M.insert key val m - addHeader :: String -> String -> a -> a - addHeader key val a - = key `seq` val `seq` a `seq` - setHeaders a $ (getHeaders a) ++ [(key, val)] +toHeaders ∷ [(CIAscii, Ascii)] → Headers +{-# INLINE toHeaders #-} +toHeaders = flip mkHeaders (∅) - setHeader :: String -> String -> a -> a - setHeader key val a - = key `seq` val `seq` a `seq` - let list = getHeaders a - deleted = filter (not . noCaseEq' key . fst) list - added = deleted ++ [(key, val)] - in - setHeaders a added +mkHeaders ∷ [(CIAscii, Ascii)] → Headers → Headers +mkHeaders [] (Headers m) = Headers m +mkHeaders ((key, val):xs) (Headers m) + = mkHeaders xs $ Headers $ + case M.lookup key m of + Nothing → M.insert key val m + Just old → M.insert key (merge old val) m + where + merge ∷ Ascii → Ascii → Ascii + {-# INLINE merge #-} + merge a b + | nullA a ∧ nullA b = (∅) + | nullA a = b + | nullA b = a + | otherwise = a ⊕ ", " ⊕ b -emptyHeaders :: Headers -emptyHeaders = [] + nullA ∷ Ascii → Bool + {-# INLINE nullA #-} + nullA = BS.null ∘ A.toByteString +fromHeaders ∷ Headers → [(CIAscii, Ascii)] +fromHeaders (Headers m) = M.toList m {- message-header = field-name ":" [ field-value ] @@ -59,48 +95,39 @@ emptyHeaders = [] field-value の先頭および末尾にある LWS は全て削除され、それ以外の LWS は單一の SP に變換される。 -} -headersP :: Parser Headers -headersP = do xs <- many header +headersP ∷ Parser Headers +{-# INLINEABLE headersP #-} +headersP = do xs ← P.many header crlf - return xs + return $ toHeaders xs where - header :: Parser (String, String) - header = do name <- token - char ':' - -- FIXME: これは多少インチキだが、RFC 2616 のこの部分 - -- の記述はひどく曖昧であり、この動作が本當に間違って - -- ゐるのかどうかも良く分からない。例へば - -- quoted-string の内部にある空白は纏めていいのか惡い - -- のか?直勸的には駄目さうに思へるが、そんな記述は見 - -- 付からない。 - contents <- many (lws <|> many1 text) + header ∷ Parser (CIAscii, Ascii) + header = try $ + do name ← A.toCIAscii <$> token + _ ← char ':' + skipMany lws + values ← sepBy content lws + skipMany lws crlf - let value = foldr (++) "" contents - return (name, normalize value) - - normalize :: String -> String - normalize = trimBody . trim isWhiteSpace - - trimBody = foldr (++) [] - . map (\ s -> if head s == ' ' then - " " - else - s) - . group - . map (\ c -> if isWhiteSpace c - then ' ' - else c) - - -hPutHeaders :: Handle -> Headers -> IO () -hPutHeaders h hds - = h `seq` hds `seq` - mapM_ putH hds >> hPutStr h "\r\n" + return (name, joinValues values) + + content ∷ Parser Ascii + {-# INLINE content #-} + content = A.unsafeFromByteString + <$> + takeWhile1 (\c → ((¬) (isSPHT c)) ∧ isText c) + + joinValues ∷ [Ascii] → Ascii + {-# INLINE joinValues #-} + joinValues = A.fromAsciiBuilder ∘ joinWith "\x20" ∘ map A.toAsciiBuilder + +hPutHeaders ∷ HandleLike h => h → Headers → IO () +hPutHeaders !h !(Headers m) + = mapM_ putH (M.toList m) >> hPutBS h "\r\n" where - putH :: (String, String) -> IO () - putH (name, value) - = name `seq` value `seq` - do hPutStr h name - hPutStr h ": " - hPutStr h value - hPutStr h "\r\n" + putH ∷ (CIAscii, Ascii) → IO () + putH (!name, !value) + = do hPutBS h (A.ciToByteString name) + hPutBS h ": " + hPutBS h (A.toByteString value) + hPutBS h "\r\n"