packages feed

http-common (empty) → 0.7.0.1

raw patch · 6 files changed

+911/−0 lines, 6 filesdep +basedep +base64-bytestringdep +blaze-buildersetup-changed

Dependencies added: base, base64-bytestring, blaze-builder, bytestring, case-insensitive, directory, mtl, network, text, transformers, unordered-containers

Files

+ LICENCE view
@@ -0,0 +1,32 @@+An HTTP client for use with io-streams++Copyright © 2012 Operational Dynamics Consulting, Pty Ltd+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++    1. Redistributions of source code must retain the above copyright+       notice, this list of conditions and the following disclaimer.++    2. Redistributions in binary form must reproduce the above+       copyright notice, this list of conditions and the following+       disclaimer in the documentation and/or other materials provided+       with the distribution.+      +    3. Neither the name of the project nor the names of its contributors+       may be used to endorse or promote products derived from this +       software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ http-common.cabal view
@@ -0,0 +1,66 @@+cabal-version:       >= 1.10+name:                http-common+version:             0.7.0.1+synopsis:            Common types for HTTP clients and servers+description:+ /Overview/+ .+ Base types used by a variety of HTTP clients and servers. See http-streams+ "Network.Http.Client" or pipes-http "Pipes.Http.Client" for full+ documentation. You can import @Network.Http.Types@ if you like, but both+ http-streams and pipes-http re-export this package's types and functions.++license:             BSD3+license-file:        LICENCE+author:              Andrew Cowie <andrew@operationaldynamics.com>+maintainer:          Andrew Cowie <andrew@operationaldynamics.com>+copyright:           © 2012-2013 Operational Dynamics Consulting, Pty Ltd and Others+category:            Web+tested-with:         GHC == 7.6+stability:           experimental+homepage:            http://research.operationaldynamics.com/projects/http-streams/+bug-reports:         https://github.com/afcowie/http-common/issues++build-type:          Simple++library+  default-language:  Haskell2010++  build-depends:     base >= 4 && <5,+                     directory,+                     base64-bytestring,+                     blaze-builder,+                     bytestring,+                     case-insensitive,+                     mtl,+                     transformers,+                     network,+                     text,+                     unordered-containers++  hs-source-dirs:    src+  exposed-modules:   Network.Http.Types,+                     Network.Http.RequestBuilder,+                     Network.Http.Internal+  other-modules:     ++  ghc-options:       -O2+                     -Wall+                     -Wwarn+                     -fwarn-tabs+                     -funbox-strict-fields+                     -fno-warn-missing-signatures+                     -fno-warn-unused-binds+                     -fno-warn-unused-do-bind++  include-dirs:      .++  ghc-prof-options:  -prof -fprof-auto-top+++source-repository    head+  type:              git+  location:          git://github.com/afcowie/http-common.git+++-- vim: set tabstop=21 expandtab:
+ src/Network/Http/Internal.hs view
@@ -0,0 +1,454 @@+--+-- HTTP types for use with io-streams and pipes+--+-- Copyright © 2012-2013 Operational Dynamics Consulting, Pty Ltd+--+-- The code in this file, and the program it is a part of, is+-- made available to you by its authors as open source software:+-- you can redistribute it and/or modify it under the terms of+-- the BSD licence.+--++{-# LANGUAGE BangPatterns       #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings  #-}+{-# OPTIONS_HADDOCK hide, prune #-}++--+-- | If you're not http-streams or pipes-http and you're importing this,+-- you're Doing It Wrong.+--++module Network.Http.Internal (+    Hostname,+    Port,+    Request(..),+    EntityBody(..),+    ExpectMode(..),+    Response(..),+    StatusCode,+    TransferEncoding(..),+    ContentEncoding(..),+    getStatusCode,+    getStatusMessage,+    getHeader,+    Method(..),+    Headers,+    emptyHeaders,+    updateHeader,+    removeHeader,+    buildHeaders,+    lookupHeader,+    retrieveHeaders,+    HttpType (getHeaders),+    HttpParseException(..),++    -- for testing+    composeRequestBytes,+    composeResponseBytes+) where++import Prelude hiding (lookup)++import Blaze.ByteString.Builder (Builder)+import qualified Blaze.ByteString.Builder as Builder (copyByteString,+                                                      copyByteString,+                                                      fromByteString,+                                                      fromByteString,+                                                      toByteString)+import qualified Blaze.ByteString.Builder.Char8 as Builder (fromChar,+                                                            fromShow,+                                                            fromString)+import Control.Exception (Exception)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as S+import Data.CaseInsensitive (CI, mk, original)+import Data.HashMap.Strict (HashMap, delete, empty, foldrWithKey, insert,+                            insertWith, lookup, toList)+import Data.Int (Int64)+import Data.List (foldl')+import Data.Monoid (mconcat, mempty)+import Data.Typeable (Typeable)+import Data.Word (Word16)++{-+    This is a String because that's what the uri package works in. There+    was a fairly detailed disucssion on haskell-cafe about this, with+    the conclusion that URLs are composed of characters, not octets.+-}++type Hostname = ByteString++type Port = Word16++--+-- | HTTP Methods, as per RFC 2616+--+data Method+    = GET+    | HEAD+    | POST+    | PUT+    | DELETE+    | TRACE+    | OPTIONS+    | CONNECT+    | PATCH+    | Method ByteString+        deriving (Show, Read, Ord)+++instance Eq Method where+    GET          == GET              = True+    HEAD         == HEAD             = True+    POST         == POST             = True+    PUT          == PUT              = True+    DELETE       == DELETE           = True+    TRACE        == TRACE            = True+    OPTIONS      == OPTIONS          = True+    CONNECT      == CONNECT          = True+    PATCH        == PATCH            = True+    GET          == Method "GET"     = True+    HEAD         == Method "HEAD"    = True+    POST         == Method "POST"    = True+    PUT          == Method "PUT"     = True+    DELETE       == Method "DELETE"  = True+    TRACE        == Method "TRACE"   = True+    OPTIONS      == Method "OPTIONS" = True+    CONNECT      == Method "CONNECT" = True+    PATCH        == Method "PATCH"   = True+    Method a     == Method b         = a == b+    m@(Method _) == other            = other == m+    _            == _                = False++--+-- | A description of the request that will be sent to the server. Note+-- unlike other HTTP libraries, the request body is /not/ a part of this+-- object; that will be streamed out by you when actually sending the+-- request with 'sendRequest'.+--+-- 'Request' has a useful @Show@ instance that will output the request+-- line and headers (as it will be sent over the wire but with the @\\r@+-- characters stripped) which can be handy for debugging.+--+-- Note that the actual @Host:@ header is not set until the request is sent,+-- so you will not see it in the Show instance (unless you call 'setHostname'+-- to override the value inherited from the @Connection@).+--+data Request+    = Request {+        qMethod  :: !Method,+        qHost    :: !(Maybe ByteString),+        qPath    :: !ByteString,+        qBody    :: !EntityBody,+        qExpect  :: !ExpectMode,+        qHeaders :: !Headers+    }++instance Show Request where+    show q = {-# SCC "Request.show" #-}+        S.unpack $ S.filter (/= '\r') $ Builder.toByteString $ composeRequestBytes q "<default>"+++data EntityBody = Empty | Chunking | Static Int64++data ExpectMode = Normal | Continue++{-+    The bit that builds up the actual string to be transmitted. This+    is on the critical path for every request, so we'll want to revisit+    this to improve performance.++    - Rewrite rule for Method?+    - How can serializing the Headers be made efficient?++    This code includes the RFC compliant CR-LF sequences as line+    terminators, which is why the Show instance above has to bother+    with removing them.+-}++composeRequestBytes :: Request -> ByteString -> Builder+composeRequestBytes q h' =+    mconcat+       [requestline,+        hostLine,+        headerFields,+        crlf]+  where+    requestline = mconcat+       [method,+        sp,+        uri,+        sp,+        version,+        crlf]++    method = case qMethod q of+        GET     -> Builder.fromString "GET"+        HEAD    -> Builder.fromString "HEAD"+        POST    -> Builder.fromString "POST"+        PUT     -> Builder.fromString "PUT"+        DELETE  -> Builder.fromString "DELETE"+        TRACE   -> Builder.fromString "TRACE"+        OPTIONS -> Builder.fromString "OPTIONS"+        CONNECT -> Builder.fromString "CONNECT"+        PATCH   -> Builder.fromString "PATCH"+        (Method x) -> Builder.fromByteString x++    uri = Builder.copyByteString $ qPath q++    version = Builder.fromString "HTTP/1.1"++    hostLine = mconcat+       [Builder.fromString "Host: ",+        hostname,+        crlf]++    hostname = case qHost q of+        Just x' -> Builder.copyByteString x'+        Nothing -> Builder.copyByteString h'++    headerFields = joinHeaders $ unWrap $ qHeaders q+++crlf = Builder.fromString "\r\n"++sp = Builder.fromChar ' '+++type StatusCode = Int++--+-- | A description of the response received from the server. Note+-- unlike other HTTP libraries, the response body is /not/ a part+-- of this object; that will be streamed in by you when calling+-- 'receiveResponse'.+--+-- Like 'Request', 'Response' has a @Show@ instance that will output+-- the status line and response headers as they were received from the+-- server.+--+data Response+    = Response {+        pStatusCode       :: !StatusCode,+        pStatusMsg        :: !ByteString,+        pTransferEncoding :: !TransferEncoding,+        pContentEncoding  :: !ContentEncoding,+        pContentLength    :: !(Maybe Int64),+        pHeaders          :: !Headers+    }++instance Show Response where+    show p =     {-# SCC "Response.show" #-}+        S.unpack $ S.filter (/= '\r') $ Builder.toByteString $ composeResponseBytes p+++data TransferEncoding = None | Chunked++data ContentEncoding = Identity | Gzip | Deflate+    deriving (Show)+++--+-- | Get the HTTP response status code.+--+getStatusCode :: Response -> StatusCode+getStatusCode = pStatusCode+{-# INLINE getStatusCode #-}++--+-- | Get the HTTP response status message. Keep in mind that this is+-- /not/ normative; whereas 'getStatusCode' values are authoritative.+--+getStatusMessage :: Response -> ByteString+getStatusMessage = pStatusMsg+{-# INLINE getStatusMessage #-}++--+-- | Lookup a header in the response. HTTP header field names are+-- case-insensitive, so you can specify the name to lookup however you+-- like. If the header is not present @Nothing@ will be returned.+--+-- >     let n = case getHeader p "Content-Length" of+-- >                Just x' -> read x' :: Int+-- >                Nothing -> 0+--+-- which of course is essentially what goes on inside the client library when+-- it receives a response from the server and has to figure out how many bytes+-- to read.+--+-- There is a fair bit of complexity in some of the other HTTP response+-- fields, so there are a number of specialized functions for reading+-- those values where we've found them useful.+--+getHeader :: Response -> ByteString -> Maybe ByteString+getHeader p k =+    lookupHeader h k+  where+    h = pHeaders p++--+-- | Accessors common to both the outbound and return sides of an HTTP+-- connection.+--+class HttpType τ where++    --+    -- | Get the Headers from a Request or Response. Most people do not need+    -- this; for most cases you just need to get a header or two from the+    -- response, for which you can use 'getHeader'. On the other hand, if you+    -- do need to poke around in the raw headers,+    --+    -- @ import Network.Http.Types @+    --+    -- will give you functions like 'lookupHeader' and 'updateHeader' to to+    -- work with.+    --+    getHeaders :: τ -> Headers++instance HttpType Request where+    getHeaders q = qHeaders q++instance HttpType Response where+    getHeaders p = pHeaders p+++composeResponseBytes :: Response -> Builder+composeResponseBytes p =+    mconcat+       [statusline,+        headerFields,+        crlf]+  where+    statusline = mconcat+       [version,+        sp,+        code,+        sp,+        message,+        crlf]++    code = Builder.fromShow $ pStatusCode p++    message = Builder.copyByteString $ pStatusMsg p++    version = Builder.fromString "HTTP/1.1"++    headerFields = joinHeaders $ unWrap $ pHeaders p+++--+-- | The map of headers in a 'Request' or 'Response'. Note that HTTP+-- header field names are case insensitive, so if you call 'setHeader'+-- on a field that's already defined but with a different capitalization+-- you will replace the existing value.+--+{-+    This is a fair bit of trouble just to avoid using a typedef here.+    Probably worth it, though; every other HTTP client library out there+    exposes the gory details of the underlying map implementation, and+    to use it you need to figure out all kinds of crazy imports. Indeed,+    this code used here in the Show instance for debugging has been+    copied & pasted around various projects of mine since I started+    writing Haskell. It's quite tedious, and very arcane! So, wrap it+    up.+-}+newtype Headers = Wrap {+    unWrap :: HashMap (CI ByteString) ByteString+}++instance Show Headers where+    show x = S.unpack $ S.filter (/= '\r') $ Builder.toByteString $ joinHeaders $ unWrap x++joinHeaders :: HashMap (CI ByteString) ByteString -> Builder+joinHeaders m = foldrWithKey combine mempty m++combine :: CI ByteString -> ByteString -> Builder -> Builder+combine k v acc =+    mconcat [acc, key, Builder.fromString ": ", value, crlf]+  where+    key = Builder.copyByteString $ original k+    value = Builder.fromByteString v+{-# INLINE combine #-}++emptyHeaders :: Headers+emptyHeaders =+    Wrap empty++--+-- | Set a header field to the specified value. This will overwrite+-- any existing value for the field. Remember that HTTP fields names+-- are case insensitive!+--+updateHeader :: Headers -> ByteString -> ByteString -> Headers+updateHeader x k v =+    Wrap result+  where+    !result = insert (mk k) v m+    !m = unWrap x++--+-- | Remove a header from the map. If a field with that name is not present,+-- then this will have no effect.+--+removeHeader :: Headers -> ByteString -> Headers+removeHeader x k =+    Wrap result+  where+    !result = delete (mk k) m+    !m = unWrap x+++--+-- | Given a list of field-name,field-value pairs, construct a Headers map.+--+{-+    This is only going to be used by RequestBuilder and ResponseParser,+    obviously. And yes, as usual, we go to a lot of trouble to splice out the+    function doing the work, in the name of type sanity.+-}+buildHeaders :: [(ByteString, ByteString)] -> Headers+buildHeaders hs =+    Wrap result+  where+    result = foldl' addHeader empty hs++{-+    insertWith is used here for the case where a header is repeated+    (for example, Set-Cookie) and the values need to be intercalated+    with ',' as per RFC 2616 §4.2.+-}+addHeader+    :: HashMap (CI ByteString) ByteString+    -> (ByteString,ByteString)+    -> HashMap (CI ByteString) ByteString+addHeader m (k,v) =+    insertWith f (mk k) v m+  where+    f new old = S.concat [old, ",", new]+++lookupHeader :: Headers -> ByteString -> Maybe ByteString+lookupHeader x k =+    lookup (mk k) m+  where+    !m = unWrap x+++--+-- | Get the headers as a field-name,field-value association list.+--+retrieveHeaders :: Headers -> [(ByteString, ByteString)]+retrieveHeaders x =+    map down $ toList m+  where+    !m = unWrap x++down :: (CI ByteString, ByteString) -> (ByteString, ByteString)+down (k, v) =+    (original k, v)++data HttpParseException = HttpParseException String+        deriving (Typeable, Show)++instance Exception HttpParseException
+ src/Network/Http/RequestBuilder.hs view
@@ -0,0 +1,289 @@+--+-- HTTP types for use with io-streams and pipes+--+-- Copyright © 2012-2013 Operational Dynamics Consulting, Pty Ltd+--+-- The code in this file, and the program it is a part of, is+-- made available to you by its authors as open source software:+-- you can redistribute it and/or modify it under the terms of+-- the BSD licence.+--++{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# OPTIONS_HADDOCK hide #-}++module Network.Http.RequestBuilder (+    RequestBuilder,+    buildRequest,+    http,+    setHostname,+    setAccept,+    setAccept',+    setAuthorizationBasic,+    ContentType,+    setContentType,+    setContentLength,+    setExpectContinue,+    setHeader+) where++import Blaze.ByteString.Builder (Builder)+import qualified Blaze.ByteString.Builder as Builder (fromByteString,+                                                      toByteString)+import qualified Blaze.ByteString.Builder.Char8 as Builder (fromShow,+                                                            fromString)+import Control.Monad.State+import Data.ByteString (ByteString)+import qualified Data.ByteString.Base64 as BS64+import Data.ByteString.Char8 ()+import qualified Data.ByteString.Char8 as S+import Data.Int (Int64)+import Data.List (intersperse)+import Data.Monoid (mconcat)++import Network.Http.Internal++--+-- | The RequestBuilder monad allows you to abuse do-notation to+-- conveniently setup a 'Request' object.+--+newtype RequestBuilder α = RequestBuilder (State Request α)+  deriving (Monad, MonadState Request)++--+-- | Run a RequestBuilder, yielding a Request object you can use on the+-- given connection.+--+-- >     q <- buildRequest $ do+-- >         http POST "/api/v1/messages"+-- >         setContentType "application/json"+-- >         setHostname "clue.example.com" 80+-- >         setAccept "text/html"+-- >         setHeader "X-WhoDoneIt" "The Butler"+--+-- Obviously it's up to you to later actually /send/ JSON data.+--+buildRequest :: RequestBuilder α -> IO Request+buildRequest mm = do+    let (RequestBuilder s) = (mm)+    let q = Request {+        qHost = Nothing,+        qMethod = GET,+        qPath = "/",+        qBody = Empty,+        qExpect = Normal,+        qHeaders = emptyHeaders+    }+    return $ execState s q+++--+-- | Begin constructing a Request, starting with the request line.+--+http :: Method -> ByteString -> RequestBuilder ()+http m p' = do+    q <- get+    let h1 = qHeaders q+    let h2 = updateHeader h1 "Accept-Encoding" "gzip"++    let e  = case m of+            GET   -> Empty+            TRACE -> Empty+            _     -> Chunking++    let h3 = case e of+            Chunking    -> updateHeader h2 "Transfer-Encoding" "chunked"+            _           -> h2++    put q {+        qMethod = m,+        qPath = p',+        qBody = e,+        qHeaders = h3+    }++--+-- | Set the [virtual] hostname for the request. In ordinary conditions+-- you won't need to call this, as the @Host:@ header is a required+-- header in HTTP 1.1 and is set directly from the name of the server+-- you connected to when calling 'Network.Http.Connection.openConnection'.+--+setHostname :: Hostname -> Port -> RequestBuilder ()+setHostname h' p = do+    q <- get+    put q {+        qHost = Just v'+    }+  where+    v' :: ByteString+    v' = if p == 80+        then h'+        else Builder.toByteString $ mconcat+           [Builder.fromByteString h',+            Builder.fromString ":",+            Builder.fromShow p]++--+-- | Set a generic header to be sent in the HTTP request. The other+-- methods in the RequestBuilder API are expressed in terms of this+-- function, but we recommend you use them where offered for their+-- stronger types.+--+setHeader :: ByteString -> ByteString -> RequestBuilder ()+setHeader k' v' = do+    q <- get+    let h0 = qHeaders q+    let h1 = updateHeader h0 k' v'+    put q {+        qHeaders = h1+    }++deleteHeader :: ByteString -> RequestBuilder ()+deleteHeader k' = do+    q <- get+    let h0 = qHeaders q+    let h1 = removeHeader h0 k'+    put q {+        qHeaders = h1+    }++{-# INLINE setEntityBody #-}+setEntityBody :: EntityBody -> RequestBuilder ()+setEntityBody e = do+    q <- get+    put q {+        qBody = e+    }++{-# INLINE setExpectMode #-}+setExpectMode :: ExpectMode -> RequestBuilder ()+setExpectMode e = do+    q <- get+    put q {+        qExpect = e+    }++--+-- | Indicate the content type you are willing to receive in a reply+-- from the server. For more complex @Accept:@ headers, use+-- 'setAccept''.+--+setAccept :: ByteString -> RequestBuilder ()+setAccept v' = do+    setHeader "Accept" v'++--+-- | Indicate the content types you are willing to receive in a reply+-- from the server in order of preference. A call of the form:+--+-- >         setAccept' [("text/html", 1.0),+-- >                     ("application/xml", 0.8),+-- >                     ("*/*", 0)]+--+-- will result in an @Accept:@ header value of+-- @text\/html; q=1.0, application\/xml; q=0.8, \*\/\*; q=0.0@ as you+-- would expect.+--+setAccept' :: [(ByteString,Float)] -> RequestBuilder ()+setAccept' tqs = do+    setHeader "Accept" v'+  where+    v' = Builder.toByteString v+    v  = mconcat $ intersperse (Builder.fromString ", ") $ map format tqs++    format :: (ByteString,Float) -> Builder+    format (t',q) =+        mconcat+           [Builder.fromByteString t',+            Builder.fromString "; q=",+            Builder.fromShow q]+++--+-- | Set username and password credentials per the HTTP basic+-- authentication method.+--+-- >         setAuthorizationBasic "Aladdin" "open sesame"+--+-- will result in an @Authorization:@ header value of+-- @Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==@.+--+-- Basic authentication does /not/ use a message digest function to+-- encipher the password; the above string is only base-64 encoded and+-- is thus plain-text visible to any observer on the wire and all+-- caches and servers at the other end, making basic authentication+-- completely insecure. A number of web services, however, use SSL to+-- encrypt the connection that then use HTTP basic authentication to+-- validate requests. Keep in mind in these cases the secret is still+-- sent to the servers on the other side and passes in clear through+-- all layers after the SSL termination. Do /not/ use basic+-- authentication to protect secure or user-originated privacy-sensitve+-- information.+--+{-+    This would be better using Builder, right?+-}+setAuthorizationBasic :: ByteString -> ByteString -> RequestBuilder ()+setAuthorizationBasic user' passwd' = do+    setHeader "Authorization" v'+  where+    v'   = S.concat ["Basic ", msg']+    msg' = BS64.encode str'+    str' = S.concat [user', ":", passwd']+++type ContentType = ByteString+++--+-- | Set the MIME type corresponding to the body of the request you are+-- sending. Defaults to @\"text\/plain\"@, so usually you need to set+-- this if 'PUT'ting.+--+setContentType :: ContentType -> RequestBuilder ()+setContentType v' = do+    setHeader "Content-Type" v'++--+-- | Specify the length of the request body, in bytes.+--+-- RFC 2616 requires that we either send a @Content-Length@ header or+-- use @Transfer-Encoding: chunked@. If you know the exact size ahead+-- of time, then call this function; the body content will still be+-- streamed out by @io-streams@ in more-or-less constant space.+--+-- This function is special: in a PUT or POST request, @http-streams@+-- will assume chunked transfer-encoding /unless/ you specify a content+-- length here, in which case you need to ensure your body function+-- writes precisely that many bytes.+--+--+setContentLength :: Int64 -> RequestBuilder ()+setContentLength n = do+    deleteHeader "Transfer-Encoding"+    setHeader "Content-Length" (S.pack $ show n)+    setEntityBody $ Static n++--+-- | Specify that this request should set the expectation that the+-- server needs to approve the request before you send it.+--+-- This function is special: in a PUT or POST request, @http-streams@+-- will wait for the server to reply with an HTTP/1.1 100 Continue+-- status before sending the entity body. This is handled internally;+-- you will get the real response (be it successful 2xx, client error,+-- 4xx, or server error 5xx) in 'receiveResponse'. In theory, it+-- should be 417 if the expectation failed.+--+-- Only bother with this if you know the service you're talking to+-- requires clients to send an @Expect: 100-continue@ header and will+-- handle it properly. Most servers don't do any precondition checking,+-- automatically send an intermediate 100 response, and then just read+-- the body regardless, making this a bit of a no-op in most cases.+--+setExpectContinue :: RequestBuilder ()+setExpectContinue = do+    setHeader "Expect" "100-continue"+    setExpectMode Continue+
+ src/Network/Http/Types.hs view
@@ -0,0 +1,68 @@+--+-- HTTP types for use with io-streams and pipes+--+-- Copyright © 2012-2013 Operational Dynamics Consulting, Pty Ltd+--+-- The code in this file, and the program it is a part of, is+-- made available to you by its authors as open source software:+-- you can redistribute it and/or modify it under the terms of+-- the BSD licence.+--++{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_HADDOCK not-home #-}++--+-- | Basic types used in HTTP communications. This modules is re-exported by+-- both "Network.Http.Client" and "Pipes.Http.Client", so if you're using+-- either of those you don't need to explicitly import this module.+--++module Network.Http.Types (+    -- * Requests+    Hostname,+    Port,+    Request,+    EntityBody(..),+    ExpectMode(..),++    RequestBuilder,+    buildRequest,+    http,+    setHostname,+    setAccept,+    setAccept',+    setAuthorizationBasic,+    ContentType,+    setContentType,+    setContentLength,+    setExpectContinue,+    setHeader,++    -- * Responses+    Response,+    StatusCode,+    TransferEncoding(..),+    ContentEncoding(..),+    getStatusCode,+    getStatusMessage,+    getHeader,+    Method(..),++    -- * Headers+    Headers,+    emptyHeaders,+    updateHeader,+    removeHeader,+    buildHeaders,+    lookupHeader,+    retrieveHeaders,+    HttpType (getHeaders),++    -- * Exceptions+    HttpParseException(..)++) where++import Network.Http.Internal+import Network.Http.RequestBuilder