http-types 0.1.1 → 0.2.0
raw patch · 2 files changed
+166/−73 lines, 2 filesdep +arrayPVP ok
version bump matches the API change (PVP)
Dependencies added: array
API changes (from Hackage documentation)
- Network.HTTP.Types: HttpCIByteString :: !ByteString -> !ByteString -> HttpCIByteString
- Network.HTTP.Types: data HttpCIByteString
- Network.HTTP.Types: data MethodADT
- Network.HTTP.Types: instance Eq HttpCIByteString
- Network.HTTP.Types: instance Eq MethodADT
- Network.HTTP.Types: instance IsString HttpCIByteString
- Network.HTTP.Types: instance Ord HttpCIByteString
- Network.HTTP.Types: instance Ord MethodADT
- Network.HTTP.Types: instance Show HttpCIByteString
- Network.HTTP.Types: instance Show MethodADT
- Network.HTTP.Types: methodADTToString :: MethodADT -> String
- Network.HTTP.Types: methodFromADT :: MethodADT -> Method
- Network.HTTP.Types: methodToADT :: Method -> MethodADT
- Network.HTTP.Types: mkHttpCIByteString :: ByteString -> HttpCIByteString
- Network.HTTP.Types: stringToMethodADT :: String -> MethodADT
- Network.HTTP.Types: type QuerySimple = [(ByteString, ByteString)]
+ Network.HTTP.Types: CIByteString :: !ByteString -> !ByteString -> CIByteString
+ Network.HTTP.Types: data CIByteString
+ Network.HTTP.Types: data StdMethod
+ Network.HTTP.Types: headerAccept :: ByteString -> Header
+ Network.HTTP.Types: headerCacheControl :: ByteString -> Header
+ Network.HTTP.Types: headerConnection :: ByteString -> Header
+ Network.HTTP.Types: headerContentLength :: ByteString -> Header
+ Network.HTTP.Types: headerContentMD5 :: ByteString -> Header
+ Network.HTTP.Types: headerContentType :: ByteString -> Header
+ Network.HTTP.Types: headerDate :: ByteString -> Header
+ Network.HTTP.Types: instance Bounded StdMethod
+ Network.HTTP.Types: instance Enum StdMethod
+ Network.HTTP.Types: instance Eq CIByteString
+ Network.HTTP.Types: instance Eq StdMethod
+ Network.HTTP.Types: instance IsString CIByteString
+ Network.HTTP.Types: instance Ix StdMethod
+ Network.HTTP.Types: instance Ord CIByteString
+ Network.HTTP.Types: instance Ord StdMethod
+ Network.HTTP.Types: instance Read StdMethod
+ Network.HTTP.Types: instance Show CIByteString
+ Network.HTTP.Types: instance Show StdMethod
+ Network.HTTP.Types: mkCIByteString :: ByteString -> CIByteString
+ Network.HTTP.Types: parseMethod :: Method -> Either ByteString StdMethod
+ Network.HTTP.Types: parseQuery :: ByteString -> Query
+ Network.HTTP.Types: parseSimpleQuery :: ByteString -> SimpleQuery
+ Network.HTTP.Types: renderMethod :: StdMethod -> Method
+ Network.HTTP.Types: renderQuery :: Bool -> Query -> ByteString
+ Network.HTTP.Types: renderSimpleQuery :: Bool -> SimpleQuery -> ByteString
+ Network.HTTP.Types: type Header = (CIByteString, ByteString)
+ Network.HTTP.Types: type QueryItem = (ByteString, Maybe ByteString)
+ Network.HTTP.Types: type SimpleQuery = [SimpleQueryItem]
+ Network.HTTP.Types: type SimpleQueryItem = (ByteString, ByteString)
+ Network.HTTP.Types: urlDecode :: ByteString -> ByteString
+ Network.HTTP.Types: urlEncode :: ByteString -> ByteString
- Network.HTTP.Types: CONNECT :: MethodADT
+ Network.HTTP.Types: CONNECT :: StdMethod
- Network.HTTP.Types: DELETE :: MethodADT
+ Network.HTTP.Types: DELETE :: StdMethod
- Network.HTTP.Types: GET :: MethodADT
+ Network.HTTP.Types: GET :: StdMethod
- Network.HTTP.Types: HEAD :: MethodADT
+ Network.HTTP.Types: HEAD :: StdMethod
- Network.HTTP.Types: OPTIONS :: MethodADT
+ Network.HTTP.Types: OPTIONS :: StdMethod
- Network.HTTP.Types: POST :: MethodADT
+ Network.HTTP.Types: POST :: StdMethod
- Network.HTTP.Types: PUT :: MethodADT
+ Network.HTTP.Types: PUT :: StdMethod
- Network.HTTP.Types: TRACE :: MethodADT
+ Network.HTTP.Types: TRACE :: StdMethod
- Network.HTTP.Types: ciLowerCase :: HttpCIByteString -> !ByteString
+ Network.HTTP.Types: ciLowerCase :: CIByteString -> !ByteString
- Network.HTTP.Types: ciOriginal :: HttpCIByteString -> !ByteString
+ Network.HTTP.Types: ciOriginal :: CIByteString -> !ByteString
- Network.HTTP.Types: type Query = [(ByteString, Maybe ByteString)]
+ Network.HTTP.Types: type Query = [QueryItem]
- Network.HTTP.Types: type RequestHeaders = [(HttpCIByteString, ByteString)]
+ Network.HTTP.Types: type RequestHeaders = [Header]
- Network.HTTP.Types: type ResponseHeaders = [(HttpCIByteString, ByteString)]
+ Network.HTTP.Types: type ResponseHeaders = [Header]
Files
- Network/HTTP/Types.hs +162/−71
- http-types.cabal +4/−2
Network/HTTP/Types.hs view
@@ -1,8 +1,8 @@ module Network.HTTP.Types ( -- * Case insensitive HTTP ByteStrings- HttpCIByteString(..)-, mkHttpCIByteString+ CIByteString(..)+, mkCIByteString -- * Methods , Method , methodGet@@ -13,11 +13,9 @@ , methodTrace , methodConnect , methodOptions-, MethodADT(GET, POST, HEAD, PUT, DELETE, TRACE, CONNECT, OPTIONS)-, methodToADT-, methodFromADT-, stringToMethodADT-, methodADTToString+, StdMethod(..)+, parseMethod+, renderMethod -- * Versions , HttpVersion(..) , http09@@ -37,70 +35,88 @@ , status405, statusNotAllowed , status500, statusServerError -- * Headers+, Header , RequestHeaders , ResponseHeaders+, headerAccept+, headerCacheControl+, headerConnection+, headerContentLength+, headerContentType+, headerContentMD5+, headerDate -- * Query string+, QueryItem , Query-, QuerySimple+, SimpleQueryItem+, SimpleQuery+, renderQuery+, renderSimpleQuery+, parseQuery+, parseSimpleQuery+ -- * URL encoding / decoding+, urlEncode+, urlDecode ) where +import Control.Arrow (second)+import Data.Array import Data.Char+import Data.List import Data.Maybe import Data.String+import Numeric import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as Ascii -localError :: String -> String -> a-localError f s = error $ "Network.HTTP.Types." ++ f ++ ": " ++ s- -- | Case-insensitive HTTP ByteStrings, mostly for use in Header names.-data HttpCIByteString- = HttpCIByteString {+data CIByteString+ = CIByteString { ciOriginal :: !B.ByteString , ciLowerCase :: !B.ByteString } -mkHttpCIByteString :: B.ByteString -> HttpCIByteString-mkHttpCIByteString orig = HttpCIByteString {+mkCIByteString :: B.ByteString -> CIByteString+mkCIByteString orig = CIByteString { ciOriginal = orig , ciLowerCase = Ascii.map toLower orig } -instance Eq HttpCIByteString where- HttpCIByteString { ciLowerCase = a } == HttpCIByteString { ciLowerCase = b } +instance Eq CIByteString where+ CIByteString { ciLowerCase = a } == CIByteString { ciLowerCase = b } = a == b -instance Ord HttpCIByteString where- compare HttpCIByteString { ciLowerCase = a } HttpCIByteString { ciLowerCase = b } +instance Ord CIByteString where+ compare CIByteString { ciLowerCase = a } CIByteString { ciLowerCase = b } = compare a b -instance Show HttpCIByteString where+instance Show CIByteString where show = show . ciOriginal -instance IsString HttpCIByteString where- fromString = mkHttpCIByteString . Ascii.pack+instance IsString CIByteString where+ fromString = mkCIByteString . Ascii.pack -- | HTTP method (flat string type). type Method = B.ByteString -- | HTTP Method constants. methodGet, methodPost, methodHead, methodPut, methodDelete, methodTrace, methodConnect, methodOptions :: Method-methodGet = Ascii.pack "GET"-methodPost = Ascii.pack "POST"-methodHead = Ascii.pack "HEAD"-methodPut = Ascii.pack "PUT"-methodDelete = Ascii.pack "DELETE"-methodTrace = Ascii.pack "TRACE"-methodConnect = Ascii.pack "CONNECT"-methodOptions = Ascii.pack "OPTIONS"+methodGet = renderMethod GET+methodPost = renderMethod POST+methodHead = renderMethod HEAD+methodPut = renderMethod PUT+methodDelete = renderMethod DELETE+methodTrace = renderMethod TRACE+methodConnect = renderMethod CONNECT+methodOptions = renderMethod OPTIONS --- | HTTP method (ADT version).+-- | HTTP standard method (as defined by RFC 2616). -- -- Note that the Show instance is only for debugging and should NOT be used to generate HTTP method strings; use 'methodToByteString' instead. -- -- The constructor 'OtherMethod' is not exported for forwards compatibility reasons.-data MethodADT+data StdMethod = GET | POST | HEAD @@ -109,46 +125,24 @@ | TRACE | CONNECT | OPTIONS- | OtherMethod B.ByteString- deriving (Show, Eq, Ord)-+ deriving (Read, Show, Eq, Ord, Enum, Bounded, Ix) -- These are ordered by suspected frequency. More popular methods should go first.--- The reason is that methodListA and methodListB are used with lookup.+-- The reason is that methodList is used with lookup. -- lookup is probably faster for these few cases than setting up an elaborate data structure.-methodListA :: [(Method, MethodADT)]-methodListA - = [ (methodGet, GET)- , (methodPost, POST)- , (methodHead, HEAD)- , (methodPut, PUT)- , (methodDelete, DELETE)- , (methodTrace, TRACE)- , (methodConnect, CONNECT)- , (methodOptions, OPTIONS)- ] -methodListB :: [(MethodADT, Method)]-methodListB = map (\(a, b) -> (b, a)) methodListA---- | Convert a method 'ByteString' to a 'MethodADT'.-methodToADT :: Method -> MethodADT-methodToADT bs = fromMaybe (OtherMethod bs) $ lookup bs methodListA+methodArray :: Array StdMethod Method+methodArray = listArray (minBound, maxBound) $ map (Ascii.pack . show) [minBound :: StdMethod .. maxBound] --- | Convert a 'MethodADT' to a 'ByteString'.-methodFromADT :: MethodADT -> Method-methodFromADT m- = case m of- OtherMethod bs -> bs- _ -> fromMaybe (localError "methodToByteString" "This should not happen (methodListB is incomplete)") $- lookup m methodListB+methodList :: [(Method, StdMethod)]+methodList = map (\(a, b) -> (b, a)) (assocs methodArray) --- | Convert a method 'String' to a 'MethodADT'.-stringToMethodADT :: String -> MethodADT-stringToMethodADT = methodToADT . Ascii.pack+-- | Convert a method 'ByteString' to a 'StdMethod' if possible.+parseMethod :: Method -> Either B.ByteString StdMethod+parseMethod bs = maybe (Left bs) Right $ lookup bs methodList --- | Convert a 'MethodADT' to a 'String'.-methodADTToString :: MethodADT -> String-methodADTToString = Ascii.unpack . methodFromADT+-- | Convert a 'StdMethod' to a 'ByteString'.+renderMethod :: StdMethod -> Method+renderMethod m = methodArray ! m -- | HTTP Version. -- @@ -248,17 +242,114 @@ status500 = Status 500 $ Ascii.pack "Internal Server Error" statusServerError = status500 --- | Request Header-type RequestHeaders = [(HttpCIByteString, B.ByteString)]+-- | Header+type Header = (CIByteString, B.ByteString) +-- | Request Headers+type RequestHeaders = [Header]+ -- | Response Headers-type ResponseHeaders = [(HttpCIByteString, B.ByteString)]+type ResponseHeaders = [Header] +makeHeader :: String -> B.ByteString -> Header+makeHeader a = \b -> (a', b)+ where a' = mkCIByteString $ Ascii.pack a++-- | HTTP Headers+headerAccept, headerCacheControl, headerConnection, headerContentLength, headerContentType, headerContentMD5, headerDate :: B.ByteString -> Header+headerAccept = makeHeader "Accept"+headerCacheControl = makeHeader "Cache-Control"+headerConnection = makeHeader "Connection"+headerContentLength = makeHeader "Content-Length"+headerContentType = makeHeader "Content-Type"+headerContentMD5 = makeHeader "Content-MD5"+headerDate = makeHeader "Date"++-- | Query item+type QueryItem = (B.ByteString, Maybe B.ByteString)+ -- | Query. -- -- General form: a=b&c=d, but if the value is Nothing, it becomes -- a&c=d.-type Query = [(B.ByteString, Maybe B.ByteString)]+type Query = [QueryItem] +-- | Simplified Query item type without support for parameter-less items.+type SimpleQueryItem = (B.ByteString, B.ByteString)+ -- | Simplified Query type without support for parameter-less items.-type QuerySimple = [(B.ByteString, B.ByteString)]+type SimpleQuery = [SimpleQueryItem]++-- | Convert 'Query' to 'ByteString'.+renderQuery :: Bool -> Query -> B.ByteString+renderQuery useQuestionMark = B.concat + . addQuestionMark+ . intercalate [Ascii.pack "&"] + . map showQueryItem + where+ addQuestionMark :: [B.ByteString] -> [B.ByteString]+ addQuestionMark [] = []+ addQuestionMark xs | useQuestionMark = Ascii.pack "?" : xs+ | otherwise = xs+ + showQueryItem :: (B.ByteString, Maybe B.ByteString) -> [B.ByteString]+ showQueryItem (n, Nothing) = [urlEncode n]+ showQueryItem (n, Just v) = [urlEncode n, Ascii.pack "=", urlEncode v]++-- | Convert 'SimpleQuery' to 'ByteString'.+renderSimpleQuery :: Bool -> SimpleQuery -> B.ByteString+renderSimpleQuery useQuestionMark = renderQuery useQuestionMark . map (\(k, v) -> (k, Just v))++-- | Parse 'Query' from a 'ByteString'.+parseQuery :: B.ByteString -> Query+parseQuery bs = case Ascii.uncons bs of+ Nothing -> []+ Just ('?', bs') -> parseQuery' bs'+ _ -> parseQuery' bs+ where+ parseQuery' = map parseQueryItem . Ascii.split '&'+ parseQueryItem q = (k, v)+ where (k', v') = Ascii.break (== '=') q+ k = urlDecode k'+ v = if B.null v'+ then Nothing+ else Just $ urlDecode $ B.tail v'++-- | Parse 'SimpleQuery' from a 'ByteString'.+parseSimpleQuery :: B.ByteString -> SimpleQuery+parseSimpleQuery = map (second $ fromMaybe B.empty) . parseQuery++-- | Percent-encoding for URLs.+urlEncode :: B.ByteString -> B.ByteString+urlEncode = Ascii.concatMap (Ascii.pack . encodeChar)+ where+ encodeChar :: Char -> [Char]+ encodeChar ch | unreserved ch = [ch]+ | otherwise = h2 $ ord ch+ + unreserved :: Char -> Bool+ unreserved ch | ch >= 'A' && ch <= 'Z' = True + | ch >= 'a' && ch <= 'z' = True+ | ch >= '0' && ch <= '9' = True + unreserved '-' = True+ unreserved '_' = True+ unreserved '.' = True+ unreserved '~' = True+ unreserved _ = False+ + h2 :: Int -> [Char]+ h2 v = let (a, b) = v `divMod` 16 in ['%', h a, h b]+ + h :: Int -> Char+ h i | i < 10 = chr $ ord '0' + i+ | otherwise = chr $ ord 'A' + i - 10++-- | Percent-decoding.+urlDecode :: B.ByteString -> B.ByteString+urlDecode bs = case Ascii.uncons bs of+ Nothing -> B.empty+ Just ('%', x) -> case readHex $ Ascii.unpack pc of+ [(v, "")] -> chr v `Ascii.cons` urlDecode bs'+ _ -> Ascii.cons '%' $ urlDecode x+ where (pc, bs') = Ascii.splitAt 2 x+ Just (c, bs') -> Ascii.cons c $ urlDecode bs'
http-types.cabal view
@@ -7,7 +7,7 @@ -- The package version. See the Haskell package versioning policy -- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for -- standards guiding when and how versions should be incremented.-Version: 0.1.1+Version: 0.2.0 -- A short (one-line) description of the package. Synopsis: Generic HTTP types for Haskell (for both client and server code).@@ -54,7 +54,9 @@ GHC-Options: -Wall -- Packages needed in order to build this package.- Build-depends: base >= 4 && < 5, bytestring >=0.9.1.5 && <0.10+ Build-depends: base >= 4 && < 5,+ bytestring >=0.9.1.5 && <0.10,+ array >=0.3 && <0.4 -- Modules not exported by this package. -- Other-modules: