X-Git-Url: http://git.cielonegro.org/gitweb.cgi?p=Lucu.git;a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FHeaders.hs;h=e56567e7d665b191a96d08b75dfab9bc781f614c;hp=06dc8f95f0f2ee2c9aca0c1927b9b049797abc49;hb=0678be8;hpb=72a3e24a952616e32845eeb4fc05048e841c91a2 diff --git a/Network/HTTP/Lucu/Headers.hs b/Network/HTTP/Lucu/Headers.hs index 06dc8f9..e56567e 100644 --- a/Network/HTTP/Lucu/Headers.hs +++ b/Network/HTTP/Lucu/Headers.hs @@ -1,108 +1,131 @@ {-# LANGUAGE - GeneralizedNewtypeDeriving + FlexibleInstances + , GeneralizedNewtypeDeriving + , MultiParamTypeClasses + , TemplateHaskell + , TypeSynonymInstances , OverloadedStrings , UnicodeSyntax #-} +{-# OPTIONS_GHC -fno-warn-missing-methods #-} +-- |An internal module for HTTP headers. module Network.HTTP.Lucu.Headers ( Headers , HasHeaders(..) - - , singleton - - , toHeaders - , fromHeaders - - , headersP - , printHeaders + , headers ) where -import Control.Applicative +import Control.Applicative hiding (empty) +import Control.Applicative.Unicode hiding ((∅)) +import Control.Monad import Data.Ascii (Ascii, AsciiBuilder, 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 qualified Data.Map.Unicode as M +import Data.Attoparsec.Char8 +import qualified Data.Collections.Newtype.TH as C +import Data.Convertible.Base +import Data.Convertible.Instances.Ascii () +import Data.Convertible.Utils +import Data.List (intersperse) +import qualified Data.Map as M (Map) +import Data.Collections +import Data.Collections.BaseInstances () import Data.Monoid import Data.Monoid.Unicode import Network.HTTP.Lucu.Parser.Http -import Network.HTTP.Lucu.Utils +import Prelude hiding (lookup, null) import Prelude.Unicode newtype Headers - = Headers (Map CIAscii Ascii) - deriving (Eq, Show, Monoid) + = Headers (M.Map CIAscii Ascii) + deriving (Eq, Show) class HasHeaders a where getHeaders ∷ a → Headers setHeaders ∷ a → Headers → a + modifyHeaders ∷ (Headers → Headers) → a → a + {-# INLINE modifyHeaders #-} + modifyHeaders = (setHeaders ⊛) ∘ (∘ getHeaders) + getHeader ∷ CIAscii → a → Maybe Ascii - getHeader key a - = case getHeaders a of - Headers m → M.lookup key m + {-# INLINE getHeader #-} + getHeader = (∘ getHeaders) ∘ lookup hasHeader ∷ CIAscii → a → Bool {-# INLINE hasHeader #-} - hasHeader key a - = case getHeaders a of - Headers m → key M.∈ m + hasHeader = (∘ getHeaders) ∘ member getCIHeader ∷ CIAscii → a → Maybe CIAscii {-# INLINE getCIHeader #-} - getCIHeader key a - = A.toCIAscii <$> getHeader key a + getCIHeader = ((A.toCIAscii <$>) ∘) ∘ getHeader deleteHeader ∷ CIAscii → a → a {-# INLINE deleteHeader #-} - deleteHeader key a - = case getHeaders a of - Headers m - → setHeaders a $ Headers $ M.delete key m + deleteHeader = modifyHeaders ∘ delete 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 + setHeader = (modifyHeaders ∘) ∘ insertWith const instance HasHeaders Headers where getHeaders = id setHeaders _ = id -singleton ∷ CIAscii → Ascii → Headers -{-# INLINE singleton #-} -singleton key val - = Headers $ M.singleton key val - -toHeaders ∷ [(CIAscii, Ascii)] → Headers -{-# INLINE toHeaders #-} -toHeaders = flip mkHeaders (∅) - -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 +C.derive [d| instance Foldable Headers (CIAscii, Ascii) + instance Collection Headers (CIAscii, Ascii) + instance Indexed Headers CIAscii Ascii + instance Map Headers CIAscii Ascii + instance SortingCollection Headers (CIAscii, Ascii) + |] + +-- |@'insert' (key, val)@ merges @val@ with an existing one if any. +instance Unfoldable Headers (CIAscii, Ascii) where + {-# INLINE insert #-} + insert (key, val) (Headers m) + = Headers $ insertWith merge key val m + {-# INLINE empty #-} + empty = Headers empty + {-# INLINE singleton #-} + singleton = Headers ∘ singleton + +instance Monoid Headers where + {-# INLINE mempty #-} + mempty = empty + {-# INLINE mappend #-} + mappend = insertMany + +merge ∷ Ascii → Ascii → Ascii +{-# INLINE merge #-} +merge a b + | nullA a ∧ nullA b = (∅) + | nullA a = b + | nullA b = a + | otherwise = a ⊕ ", " ⊕ b where - merge ∷ Ascii → Ascii → Ascii - {-# INLINE merge #-} - merge a b - | nullA a ∧ nullA b = (∅) - | nullA a = b - | nullA b = a - | otherwise = a ⊕ ", " ⊕ b - nullA ∷ Ascii → Bool {-# INLINE nullA #-} - nullA = BS.null ∘ A.toByteString - -fromHeaders ∷ Headers → [(CIAscii, Ascii)] -fromHeaders (Headers m) = M.toList m + nullA = null ∘ A.toByteString + +instance ConvertSuccess Headers Ascii where + {-# INLINE convertSuccess #-} + convertSuccess = convertSuccessVia ((⊥) ∷ AsciiBuilder) + +instance ConvertSuccess Headers AsciiBuilder where + {-# INLINEABLE convertSuccess #-} + convertSuccess (Headers m) + = mconcat (header <$> fromFoldable m) ⊕ cs ("\x0D\x0A" ∷ Ascii) + where + header ∷ (CIAscii, Ascii) → AsciiBuilder + {-# INLINE header #-} + header (name, value) + = cs name ⊕ + cs (": " ∷ Ascii) ⊕ + cs value ⊕ + cs ("\x0D\x0A" ∷ Ascii) + +deriveAttempts [ ([t| Headers |], [t| Ascii |]) + , ([t| Headers |], [t| AsciiBuilder |]) + ] {- message-header = field-name ":" [ field-value ] @@ -115,17 +138,17 @@ fromHeaders (Headers m) = M.toList m field-value の先頭および末尾にある LWS は全て削除され、それ以外の LWS は單一の SP に變換される。 -} -headersP ∷ Parser Headers -{-# INLINEABLE headersP #-} -headersP = do xs ← P.many header - crlf - return $ toHeaders xs +headers ∷ Parser Headers +{-# INLINEABLE headers #-} +headers = do xs ← many header + crlf + return $ fromFoldable xs where header ∷ Parser (CIAscii, Ascii) header = do name ← A.toCIAscii <$> token - _ ← char ':' + void $ char ':' skipMany lws - values ← sepBy content (try lws) + values ← content `sepBy` try lws skipMany (try lws) crlf return (name, joinValues values) @@ -134,20 +157,11 @@ headersP = do xs ← P.many header {-# INLINE content #-} content = A.unsafeFromByteString <$> - takeWhile1 (\c → (¬) (isSPHT c) ∧ isText c) + takeWhile1 (\c → isText c ∧ c ≢ '\x20') joinValues ∷ [Ascii] → Ascii {-# INLINE joinValues #-} - joinValues = A.fromAsciiBuilder ∘ joinWith "\x20" ∘ map A.toAsciiBuilder - -printHeaders ∷ Headers → AsciiBuilder -printHeaders (Headers m) - = mconcat (map printHeader (M.toList m)) ⊕ - A.toAsciiBuilder "\x0D\x0A" - where - printHeader ∷ (CIAscii, Ascii) → AsciiBuilder - printHeader (name, value) - = A.toAsciiBuilder (A.fromCIAscii name) ⊕ - A.toAsciiBuilder ": " ⊕ - A.toAsciiBuilder value ⊕ - A.toAsciiBuilder "\x0D\x0A" + joinValues = A.fromAsciiBuilder + ∘ mconcat + ∘ intersperse (A.toAsciiBuilder "\x20") + ∘ (A.toAsciiBuilder <$>)