http-kit 0.1.0 → 0.2.0
raw patch · 8 files changed
+141/−65 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Network.HTTP.Toolkit.Header: MessageHeader :: a -> [Header] -> MessageHeader a
- Network.HTTP.Toolkit.Header: data MessageHeader a
- Network.HTTP.Toolkit.Header: instance Eq a => Eq (MessageHeader a)
- Network.HTTP.Toolkit.Header: instance Foldable MessageHeader
- Network.HTTP.Toolkit.Header: instance Functor MessageHeader
- Network.HTTP.Toolkit.Header: instance Show a => Show (MessageHeader a)
- Network.HTTP.Toolkit.Header: instance Traversable MessageHeader
- Network.HTTP.Toolkit.Header: readMessageHeaderWithLimit :: Limit -> Connection -> IO (MessageHeader ByteString)
- Network.HTTP.Toolkit.Request: type RequestHeader = MessageHeader (Method, RequestPath)
- Network.HTTP.Toolkit.Response: type ResponseHeader = MessageHeader Status
+ Network.HTTP.Toolkit: ChunkTooLarge :: ToolkitError
+ Network.HTTP.Toolkit: HeaderTooLarge :: ToolkitError
+ Network.HTTP.Toolkit: InvalidChunk :: ToolkitError
+ Network.HTTP.Toolkit: InvalidHeader :: ToolkitError
+ Network.HTTP.Toolkit: InvalidRequestLine :: ByteString -> ToolkitError
+ Network.HTTP.Toolkit: InvalidStatusLine :: ByteString -> ToolkitError
+ Network.HTTP.Toolkit: Request :: Method -> RequestPath -> [Header] -> a -> Request a
+ Network.HTTP.Toolkit: Response :: Status -> [Header] -> a -> Response a
+ Network.HTTP.Toolkit: connectionFromHandle :: Handle -> IO Connection
+ Network.HTTP.Toolkit: consumeBody :: BodyReader -> IO ByteString
+ Network.HTTP.Toolkit: data Connection
+ Network.HTTP.Toolkit: data Request a
+ Network.HTTP.Toolkit: data Response a
+ Network.HTTP.Toolkit: data ToolkitError
+ Network.HTTP.Toolkit: makeConnection :: IO ByteString -> IO Connection
+ Network.HTTP.Toolkit: readRequest :: Connection -> IO (Request BodyReader)
+ Network.HTTP.Toolkit: readRequestWithLimit :: Limit -> Connection -> IO (Request BodyReader)
+ Network.HTTP.Toolkit: readResponse :: Method -> Connection -> IO (Response BodyReader)
+ Network.HTTP.Toolkit: readResponseWithLimit :: Limit -> Method -> Connection -> IO (Response BodyReader)
+ Network.HTTP.Toolkit: requestBody :: Request a -> a
+ Network.HTTP.Toolkit: requestHeaders :: Request a -> [Header]
+ Network.HTTP.Toolkit: requestMethod :: Request a -> Method
+ Network.HTTP.Toolkit: requestPath :: Request a -> RequestPath
+ Network.HTTP.Toolkit: responseBody :: Response a -> a
+ Network.HTTP.Toolkit: responseHeaders :: Response a -> [Header]
+ Network.HTTP.Toolkit: responseStatus :: Response a -> Status
+ Network.HTTP.Toolkit: sendBody :: (ByteString -> IO ()) -> BodyReader -> IO ()
+ Network.HTTP.Toolkit: sendRequest :: (ByteString -> IO ()) -> Request BodyReader -> IO ()
+ Network.HTTP.Toolkit: sendResponse :: (ByteString -> IO ()) -> (Response BodyReader) -> IO ()
+ Network.HTTP.Toolkit: type BodyReader = IO ByteString
+ Network.HTTP.Toolkit.Body: fromByteString :: ByteString -> IO BodyReader
+ Network.HTTP.Toolkit.Connection: connectionFromHandle :: Handle -> IO Connection
+ Network.HTTP.Toolkit.Header: sendHeader :: (ByteString -> IO ()) -> ByteString -> [Header] -> IO ()
+ Network.HTTP.Toolkit.Request: Request :: Method -> RequestPath -> [Header] -> a -> Request a
+ Network.HTTP.Toolkit.Request: data Request a
+ Network.HTTP.Toolkit.Request: formatRequestLine :: Method -> RequestPath -> ByteString
+ Network.HTTP.Toolkit.Request: instance Eq a => Eq (Request a)
+ Network.HTTP.Toolkit.Request: instance Functor Request
+ Network.HTTP.Toolkit.Request: instance Show a => Show (Request a)
+ Network.HTTP.Toolkit.Request: requestBody :: Request a -> a
+ Network.HTTP.Toolkit.Request: requestHeaders :: Request a -> [Header]
+ Network.HTTP.Toolkit.Request: requestMethod :: Request a -> Method
+ Network.HTTP.Toolkit.Request: requestPath :: Request a -> RequestPath
+ Network.HTTP.Toolkit.Request: sendRequest :: (ByteString -> IO ()) -> Request BodyReader -> IO ()
+ Network.HTTP.Toolkit.Response: Response :: Status -> [Header] -> a -> Response a
+ Network.HTTP.Toolkit.Response: data Response a
+ Network.HTTP.Toolkit.Response: formatStatusLine :: Status -> ByteString
+ Network.HTTP.Toolkit.Response: instance Eq a => Eq (Response a)
+ Network.HTTP.Toolkit.Response: instance Functor Response
+ Network.HTTP.Toolkit.Response: instance Show a => Show (Response a)
+ Network.HTTP.Toolkit.Response: responseBody :: Response a -> a
+ Network.HTTP.Toolkit.Response: responseHeaders :: Response a -> [Header]
+ Network.HTTP.Toolkit.Response: responseStatus :: Response a -> Status
+ Network.HTTP.Toolkit.Response: sendResponse :: (ByteString -> IO ()) -> (Response BodyReader) -> IO ()
- Network.HTTP.Toolkit.Header: readMessageHeader :: Connection -> IO (MessageHeader ByteString)
+ Network.HTTP.Toolkit.Header: readMessageHeader :: Limit -> Connection -> IO (ByteString, [Header])
- Network.HTTP.Toolkit.Request: readRequest :: Connection -> IO (RequestHeader, BodyReader)
+ Network.HTTP.Toolkit.Request: readRequest :: Connection -> IO (Request BodyReader)
- Network.HTTP.Toolkit.Request: readRequestWithLimit :: Limit -> Connection -> IO (RequestHeader, BodyReader)
+ Network.HTTP.Toolkit.Request: readRequestWithLimit :: Limit -> Connection -> IO (Request BodyReader)
- Network.HTTP.Toolkit.Response: readResponse :: Method -> Connection -> IO (ResponseHeader, BodyReader)
+ Network.HTTP.Toolkit.Response: readResponse :: Method -> Connection -> IO (Response BodyReader)
- Network.HTTP.Toolkit.Response: readResponseWithLimit :: Limit -> Method -> Connection -> IO (ResponseHeader, BodyReader)
+ Network.HTTP.Toolkit.Response: readResponseWithLimit :: Limit -> Method -> Connection -> IO (Response BodyReader)
Files
- http-kit.cabal +7/−6
- src/Network/HTTP/Toolkit.hs +32/−0
- src/Network/HTTP/Toolkit/Body.hs +8/−2
- src/Network/HTTP/Toolkit/Connection.hs +6/−0
- src/Network/HTTP/Toolkit/Error.hs +4/−4
- src/Network/HTTP/Toolkit/Header.hs +22/−29
- src/Network/HTTP/Toolkit/Request.hs +30/−12
- src/Network/HTTP/Toolkit/Response.hs +32/−12
http-kit.cabal view
@@ -1,8 +1,8 @@ name: http-kit-version: 0.1.0+version: 0.2.0 synopsis: A low-level HTTP library description: A low-level HTTP library that can be used to build more- sophisticated HTTP applications on top of it.+ sophisticated applications on top of it. . The design goals are: .@@ -10,11 +10,11 @@ bounded. . [efficient] Message bodies are read in chunks, so that they- can be processed with constant space usage.+ can be processed in constant space. .- [universal] No framework for streaming data is used. This- allows you to build more sophisticated application on top of- it, using the libraries that fit your purpose.+ [universal] No framework for streaming @IO@ is used. This+ allows you to build on top of it, using the libraries that+ fit your purpose. license: MIT license-file: LICENSE copyright: (c) 2014 Zalora South East Asia Pte. Ltd@@ -34,6 +34,7 @@ hs-source-dirs: src exposed-modules:+ Network.HTTP.Toolkit Network.HTTP.Toolkit.Body Network.HTTP.Toolkit.Connection Network.HTTP.Toolkit.Header
+ src/Network/HTTP/Toolkit.hs view
@@ -0,0 +1,32 @@+module Network.HTTP.Toolkit (+-- * Connection+ Connection+, makeConnection+, connectionFromHandle++-- * Handling requests+, Request(..)+, readRequestWithLimit+, readRequest+, sendRequest++-- * Handling responses+, Response(..)+, readResponseWithLimit+, readResponse+, sendResponse++-- * Handling message bodies+, BodyReader+, sendBody+, consumeBody++-- * Error type+, ToolkitError(..)+) where++import Network.HTTP.Toolkit.Body+import Network.HTTP.Toolkit.Connection+import Network.HTTP.Toolkit.Request+import Network.HTTP.Toolkit.Response+import Network.HTTP.Toolkit.Error
src/Network/HTTP/Toolkit/Body.hs view
@@ -6,14 +6,13 @@ , makeBodyReader , consumeBody , sendBody+, fromByteString -- * Handling of specific body types , maxChunkSize , makeChunkedReader , readChunkSize- , makeLengthReader- , makeUnlimitedReader ) where @@ -21,6 +20,7 @@ import Control.Monad import Control.Exception import Text.Read (readMaybe)+import Data.Maybe import Data.Char import Data.Bits import Data.IORef@@ -103,6 +103,12 @@ -- all input has been consumed. sendBody :: (ByteString -> IO ()) -> BodyReader -> IO () sendBody send body = while (not . B.null) body send++-- | Create a `BodyReader` from provided `ByteString`.+fromByteString :: ByteString -> IO BodyReader+fromByteString input = do+ ref <- newIORef (Just input)+ return $ atomicModifyIORef ref $ ((,) Nothing) . fromMaybe "" -- | -- Create a reader for when the body length is determined by the server closing
src/Network/HTTP/Toolkit/Connection.hs view
@@ -1,6 +1,7 @@ module Network.HTTP.Toolkit.Connection ( Connection(..) , makeConnection+, connectionFromHandle , connectionRead , connectionUnread , connectionReadAtLeast@@ -8,6 +9,7 @@ import Prelude hiding (read) import Control.Monad (join, unless)+import System.IO (Handle) import Data.IORef import Data.ByteString (ByteString) import qualified Data.ByteString as B@@ -17,6 +19,10 @@ _read :: IO ByteString , _unread :: ByteString -> IO () }++-- | Create a `Connection` from provided `Handle`.+connectionFromHandle :: Handle -> IO Connection+connectionFromHandle h = makeConnection (B.hGetSome h 4096) -- | Create a `Connection` from provided @IO@ action. makeConnection :: IO ByteString -> IO Connection
src/Network/HTTP/Toolkit/Error.hs view
@@ -6,14 +6,14 @@ import Data.ByteString (ByteString) data ToolkitError =- -- | Parsing of HTTP request-line failed.+ -- | The request-line of the message is malformed. InvalidRequestLine ByteString- -- | Parsing of HTTP status-line failed.+ -- | The status-line of the message is malformed. | InvalidStatusLine ByteString -- | A header field is malformed. | InvalidHeader- -- | The message header exceeds the specified- -- `Network.HTTP.Toolkit.Header.Limit`.+ -- | The start-line of the message and all header fields together exceed+ -- the specified size `Network.HTTP.Toolkit.Header.Limit`. | HeaderTooLarge -- | The size of a body chunk exceeds -- `Network.HTTP.Toolkit.Body.maxChunkSize`.
src/Network/HTTP/Toolkit/Header.hs view
@@ -1,11 +1,10 @@-{-# LANGUAGE OverloadedStrings, DeriveFunctor, DeriveFoldable, DeriveTraversable #-}+{-# LANGUAGE OverloadedStrings #-} module Network.HTTP.Toolkit.Header (- MessageHeader(..)-, Limit+ Limit , readMessageHeader-, readMessageHeaderWithLimit , defaultHeaderSizeLimit , parseHeaderFields+, sendHeader -- * Internals , readHeaderLines@@ -15,8 +14,7 @@ import Control.Applicative import Control.Monad (when) import Control.Exception-import Data.Foldable (Foldable)-import Data.Traversable (Traversable)+import Data.Foldable (forM_) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as B import qualified Data.CaseInsensitive as CI@@ -28,34 +26,20 @@ -- | Message header size limit in bytes. type Limit = Int --- | An HTTP message header consiting of a start line and a list of header--- fields.-data MessageHeader a = MessageHeader a [Header]- deriving (Eq, Show, Functor, Foldable, Traversable)---- | Read `MessageHeader` from provided `Connection`.------ Throws:------ * `HeaderTooLarge` if the header size exceeds `defaultHeaderSizeLimit`.------ * `InvalidHeader` if header is malformed.-readMessageHeader :: Connection -> IO (MessageHeader ByteString)-readMessageHeader = readMessageHeaderWithLimit defaultHeaderSizeLimit---- | Read `MessageHeader` from provided `Connection`.+-- | Read start-line and message headers from provided `Connection`+-- (see <http://tools.ietf.org/html/rfc2616#section-4.1 RFC 2616, Section 4.1>). -- -- Throws: ----- * `HeaderTooLarge` if the header size exceeds the specified `Limit`.+-- * `HeaderTooLarge` if start-line and headers together exceed the specified size `Limit` ----- * `InvalidHeader` if header is malformed.-readMessageHeaderWithLimit :: Limit -> Connection -> IO (MessageHeader ByteString)-readMessageHeaderWithLimit limit c = do+-- * `InvalidHeader` if start-line is missing or a header is malformed+readMessageHeader :: Limit -> Connection -> IO (ByteString, [Header])+readMessageHeader limit c = do hs <- readHeaderLines limit c- case hs of- x : xs -> maybe (throwIO InvalidHeader) (return . MessageHeader x) (parseHeaderFields xs)- [] -> throwIO InvalidHeader+ maybe (throwIO InvalidHeader) return $ case hs of+ x : xs -> (,) x <$> parseHeaderFields xs+ [] -> Nothing -- | Parse header fields according to -- <http://tools.ietf.org/html/rfc2616#section-4.2 RFC 2616, Section 4.2>.@@ -124,3 +108,12 @@ stripCR bs | (not . B.null) bs && B.last bs == '\r' = B.init bs | otherwise = bs++-- | Send given start-line and message headers.+sendHeader :: (ByteString -> IO()) -> ByteString -> [Header] -> IO ()+sendHeader send startLine headers = do+ send startLine+ send "\r\n"+ forM_ headers $ \(k, v) -> do+ send $ B.concat [CI.original k, ": ", v, "\r\n"]+ send "\r\n"
src/Network/HTTP/Toolkit/Request.hs view
@@ -1,19 +1,20 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, DeriveFunctor #-} module Network.HTTP.Toolkit.Request ( RequestPath-, RequestHeader+, Request(..) , readRequest , readRequestWithLimit , parseRequestLine +, sendRequest+, formatRequestLine+ , determineRequestBodyType ) where import Control.Applicative-import Control.Monad (join) import Control.Exception import Data.Maybe-import Data.Traversable (sequenceA) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as B import Network.HTTP.Types@@ -24,10 +25,16 @@ import Network.HTTP.Toolkit.Body type RequestPath = ByteString-type RequestHeader = MessageHeader (Method, RequestPath) +data Request a = Request {+ requestMethod :: Method+, requestPath :: RequestPath+, requestHeaders :: [Header]+, requestBody :: a+} deriving (Eq, Show, Functor)+ -- | Same as `readRequestWithLimit` with a `Limit` of `defaultHeaderSizeLimit`.-readRequest :: Connection -> IO (RequestHeader, BodyReader)+readRequest :: Connection -> IO (Request BodyReader) readRequest = readRequestWithLimit defaultHeaderSizeLimit -- | Read request from provided connection.@@ -36,15 +43,16 @@ -- -- * `InvalidRequestLine` if request-line is malformed. ----- * `HeaderTooLarge` if the header size exceeds the specified `Limit`.+-- * `HeaderTooLarge` if request-line and headers together exceed the specified size `Limit` ----- * `InvalidHeader` if header is malformed.-readRequestWithLimit :: Limit -> Connection -> IO (RequestHeader, BodyReader)+-- * `InvalidHeader` if request-line is missing or a header is malformed+readRequestWithLimit :: Limit -> Connection -> IO (Request BodyReader) readRequestWithLimit limit c = do- r@(MessageHeader _ headers) <- join $ sequenceA . fmap parseRequestLine_ <$> readMessageHeaderWithLimit limit c- (,) r <$> makeBodyReader c (determineRequestBodyType headers)+ (startLine, headers) <- readMessageHeader limit c+ (method, path) <- parseRequestLine_ startLine+ Request method path headers <$> makeBodyReader c (determineRequestBodyType headers) -parseRequestLine_ :: ByteString -> IO (Method, ByteString)+parseRequestLine_ :: ByteString -> IO (Method, RequestPath) parseRequestLine_ input = maybe (throwIO $ InvalidRequestLine input) return (parseRequestLine input) -- | Parse request-line (see <http://tools.ietf.org/html/rfc2616#section-5.1 RFC 2616, Section 5.1>).@@ -57,3 +65,13 @@ -- of <http://tools.ietf.org/html/rfc2616#section-4.4 RFC 2616, Section 4.4>). determineRequestBodyType :: [Header] -> BodyType determineRequestBodyType = fromMaybe None . bodyTypeFromHeaders++-- | Format request-line.+formatRequestLine :: Method -> RequestPath -> ByteString+formatRequestLine method path = B.unwords [method, path, "HTTP/1.1"]++-- | Send an HTTP request.+sendRequest :: (ByteString -> IO()) -> Request BodyReader -> IO ()+sendRequest send (Request method path headers body) = do+ sendHeader send (formatRequestLine method path) headers+ sendBody send body
src/Network/HTTP/Toolkit/Response.hs view
@@ -1,19 +1,21 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, DeriveFunctor #-} module Network.HTTP.Toolkit.Response (- ResponseHeader+ Response(..) , readResponse , readResponseWithLimit , parseStatusLine +, sendResponse+, formatStatusLine+ , determineResponseBodyType ) where import Control.Applicative-import Control.Monad (join, guard)+import Control.Monad (guard) import Control.Exception import Text.Read (readMaybe) import Data.Maybe-import Data.Traversable (sequenceA) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as B import Network.HTTP.Types@@ -23,26 +25,34 @@ import Network.HTTP.Toolkit.Header import Network.HTTP.Toolkit.Body -type ResponseHeader = MessageHeader Status+data Response a = Response {+ responseStatus :: Status+, responseHeaders :: [Header]+, responseBody :: a+} deriving (Eq, Show, Functor) -- | Same as `readResponseWithLimit` with a `Limit` of -- `defaultHeaderSizeLimit`.-readResponse :: Method -> Connection -> IO (ResponseHeader, BodyReader)+readResponse :: Method -> Connection -> IO (Response BodyReader) readResponse = readResponseWithLimit defaultHeaderSizeLimit -- | Read response from provided connection. --+-- The corresponding request `Method` has to be specified so that the body length can be determined (see+-- <http://tools.ietf.org/html/rfc2616#section-4.4 RFC 2616, Section 4.4>).+-- -- Throws: ----- * `InvalidStatusLine` if request-line is malformed.+-- * `InvalidStatusLine` if status-line is malformed. ----- * `HeaderTooLarge` if the header size exceeds the specified `Limit`.+-- * `HeaderTooLarge` if status-line and headers together exceed the specified size `Limit` ----- * `InvalidHeader` if header is malformed.-readResponseWithLimit :: Limit -> Method -> Connection -> IO (ResponseHeader, BodyReader)+-- * `InvalidHeader` if status-line is missing or a header is malformed+readResponseWithLimit :: Limit -> Method -> Connection -> IO (Response BodyReader) readResponseWithLimit limit method c = do- r@(MessageHeader status headers) <- join $ sequenceA . fmap parseStatusLine_ <$> readMessageHeaderWithLimit limit c- (,) r <$> makeBodyReader c (determineResponseBodyType method status headers)+ (startLine, headers) <- readMessageHeader limit c+ status <- parseStatusLine_ startLine+ Response status headers <$> makeBodyReader c (determineResponseBodyType method status headers) parseStatusLine_ :: ByteString -> IO Status parseStatusLine_ input = maybe (throwIO $ InvalidStatusLine input) return (parseStatusLine input)@@ -66,3 +76,13 @@ || (100 <= code && code < 200) || code == 204 || code == 304++-- | Format status-line.+formatStatusLine :: Status -> ByteString+formatStatusLine status = B.concat ["HTTP/1.1 ", B.pack $ show (statusCode status), " ", statusMessage status]++-- | Send an HTTP response.+sendResponse :: (ByteString -> IO ()) -> (Response BodyReader) -> IO ()+sendResponse send (Response status headers body) = do+ sendHeader send (formatStatusLine status) headers+ sendBody send body