Blame Network/HTTP/Stream.hs

Packit acf257
-----------------------------------------------------------------------------
Packit acf257
-- |
Packit acf257
-- Module      :  Network.HTTP.Stream
Packit acf257
-- Copyright   :  See LICENSE file
Packit acf257
-- License     :  BSD
Packit acf257
-- 
Packit acf257
-- Maintainer  :  Ganesh Sittampalam <ganesh@earth.li>
Packit acf257
-- Stability   :  experimental
Packit acf257
-- Portability :  non-portable (not tested)
Packit acf257
--
Packit acf257
-- Transmitting HTTP requests and responses holding @String@ in their payload bodies.
Packit acf257
-- This is one of the implementation modules for the "Network.HTTP" interface, representing
Packit acf257
-- request and response content as @String@s and transmitting them in non-packed form
Packit acf257
-- (cf. "Network.HTTP.HandleStream" and its use of @ByteString@s.) over 'Stream' handles.
Packit acf257
-- It is mostly here for backwards compatibility, representing how requests and responses
Packit acf257
-- were transmitted up until the 4.x releases of the HTTP package.
Packit acf257
--
Packit acf257
-- For more detailed information about what the individual exports do, please consult
Packit acf257
-- the documentation for "Network.HTTP". /Notice/ however that the functions here do
Packit acf257
-- not perform any kind of normalization prior to transmission (or receipt); you are
Packit acf257
-- responsible for doing any such yourself, or, if you prefer, just switch to using
Packit acf257
-- "Network.HTTP" function instead.
Packit acf257
-- 
Packit acf257
-----------------------------------------------------------------------------
Packit acf257
module Network.HTTP.Stream 
Packit acf257
       ( module Network.Stream
Packit acf257
Packit acf257
       , simpleHTTP      -- :: Request_String -> IO (Result Response_String)
Packit acf257
       , simpleHTTP_     -- :: Stream s => s -> Request_String -> IO (Result Response_String)
Packit acf257
       , sendHTTP        -- :: Stream s => s -> Request_String -> IO (Result Response_String)
Packit acf257
       , sendHTTP_notify -- :: Stream s => s -> Request_String -> IO () -> IO (Result Response_String)
Packit acf257
       , receiveHTTP     -- :: Stream s => s -> IO (Result Request_String)
Packit acf257
       , respondHTTP     -- :: Stream s => s -> Response_String -> IO ()
Packit acf257
       
Packit acf257
       ) where
Packit acf257
Packit acf257
-----------------------------------------------------------------
Packit acf257
------------------ Imports --------------------------------------
Packit acf257
-----------------------------------------------------------------
Packit acf257
Packit acf257
import Network.Stream
Packit acf257
import Network.StreamDebugger (debugStream)
Packit acf257
import Network.TCP (openTCPPort)
Packit acf257
import Network.BufferType ( stringBufferOp )
Packit acf257
Packit acf257
import Network.HTTP.Base
Packit acf257
import Network.HTTP.Headers
Packit acf257
import Network.HTTP.Utils ( trim )
Packit acf257
Packit acf257
import Data.Char     (toLower)
Packit acf257
import Data.Maybe    (fromMaybe)
Packit acf257
import Control.Exception (onException)
Packit acf257
import Control.Monad (when)
Packit acf257
Packit acf257
Packit acf257
-- Turn on to enable HTTP traffic logging
Packit acf257
debug :: Bool
Packit acf257
debug = False
Packit acf257
Packit acf257
-- File that HTTP traffic logs go to
Packit acf257
httpLogFile :: String
Packit acf257
httpLogFile = "http-debug.log"
Packit acf257
Packit acf257
-----------------------------------------------------------------
Packit acf257
------------------ Misc -----------------------------------------
Packit acf257
-----------------------------------------------------------------
Packit acf257
Packit acf257
Packit acf257
-- | Simple way to transmit a resource across a non-persistent connection.
Packit acf257
simpleHTTP :: Request_String -> IO (Result Response_String)
Packit acf257
simpleHTTP r = do 
Packit acf257
   auth <- getAuth r
Packit acf257
   c    <- openTCPPort (host auth) (fromMaybe 80 (port auth))
Packit acf257
   simpleHTTP_ c r
Packit acf257
Packit acf257
-- | Like 'simpleHTTP', but acting on an already opened stream.
Packit acf257
simpleHTTP_ :: Stream s => s -> Request_String -> IO (Result Response_String)
Packit acf257
simpleHTTP_ s r
Packit acf257
 | not debug    = sendHTTP s r
Packit acf257
 | otherwise    = do
Packit acf257
      s' <- debugStream httpLogFile s
Packit acf257
      sendHTTP s' r
Packit acf257
Packit acf257
sendHTTP :: Stream s => s -> Request_String -> IO (Result Response_String)
Packit acf257
sendHTTP conn rq = sendHTTP_notify conn rq (return ())
Packit acf257
Packit acf257
sendHTTP_notify :: Stream s => s -> Request_String -> IO () -> IO (Result Response_String)
Packit acf257
sendHTTP_notify conn rq onSendComplete = do
Packit acf257
   when providedClose $ (closeOnEnd conn True)
Packit acf257
   onException (sendMain conn rq onSendComplete)
Packit acf257
               (close conn)
Packit acf257
 where
Packit acf257
  providedClose = findConnClose (rqHeaders rq)
Packit acf257
Packit acf257
-- From RFC 2616, section 8.2.3:
Packit acf257
-- 'Because of the presence of older implementations, the protocol allows
Packit acf257
-- ambiguous situations in which a client may send "Expect: 100-
Packit acf257
-- continue" without receiving either a 417 (Expectation Failed) status
Packit acf257
-- or a 100 (Continue) status. Therefore, when a client sends this
Packit acf257
-- header field to an origin server (possibly via a proxy) from which it
Packit acf257
-- has never seen a 100 (Continue) status, the client SHOULD NOT wait
Packit acf257
-- for an indefinite period before sending the request body.'
Packit acf257
--
Packit acf257
-- Since we would wait forever, I have disabled use of 100-continue for now.
Packit acf257
sendMain :: Stream s => s -> Request_String -> IO () -> IO (Result Response_String)
Packit acf257
sendMain conn rqst onSendComplete =  do 
Packit acf257
    --let str = if null (rqBody rqst)
Packit acf257
    --              then show rqst
Packit acf257
    --              else show (insertHeader HdrExpect "100-continue" rqst)
Packit acf257
    -- TODO review throwing away of result
Packit acf257
   _ <- writeBlock conn (show rqst)
Packit acf257
    -- write body immediately, don't wait for 100 CONTINUE
Packit acf257
   -- TODO review throwing away of result
Packit acf257
   _ <- writeBlock conn (rqBody rqst)
Packit acf257
   onSendComplete
Packit acf257
   rsp <- getResponseHead conn
Packit acf257
   switchResponse conn True False rsp rqst
Packit acf257
        
Packit acf257
-- reads and parses headers
Packit acf257
getResponseHead :: Stream s => s -> IO (Result ResponseData)
Packit acf257
getResponseHead conn = do
Packit acf257
   lor <- readTillEmpty1 stringBufferOp (readLine conn)
Packit acf257
   return $ lor >>= parseResponseHead
Packit acf257
Packit acf257
-- Hmmm, this could go bad if we keep getting "100 Continue"
Packit acf257
-- responses...  Except this should never happen according
Packit acf257
-- to the RFC.
Packit acf257
switchResponse :: Stream s
Packit acf257
               => s
Packit acf257
               -> Bool {- allow retry? -}
Packit acf257
               -> Bool {- is body sent? -}
Packit acf257
               -> Result ResponseData
Packit acf257
               -> Request_String
Packit acf257
               -> IO (Result Response_String)
Packit acf257
switchResponse _ _ _ (Left e) _ = return (Left e)
Packit acf257
        -- retry on connreset?
Packit acf257
        -- if we attempt to use the same socket then there is an excellent
Packit acf257
        -- chance that the socket is not in a completely closed state.
Packit acf257
switchResponse conn allow_retry bdy_sent (Right (cd,rn,hdrs)) rqst =
Packit acf257
            case matchResponse (rqMethod rqst) cd of
Packit acf257
                Continue
Packit acf257
                    | not bdy_sent -> {- Time to send the body -}
Packit acf257
                        do { val <- writeBlock conn (rqBody rqst)
Packit acf257
                           ; case val of
Packit acf257
                                Left e -> return (Left e)
Packit acf257
                                Right _ ->
Packit acf257
                                    do { rsp <- getResponseHead conn
Packit acf257
                                       ; switchResponse conn allow_retry True rsp rqst
Packit acf257
                                       }
Packit acf257
                           }
Packit acf257
                    | otherwise -> {- keep waiting -}
Packit acf257
                        do { rsp <- getResponseHead conn
Packit acf257
                           ; switchResponse conn allow_retry bdy_sent rsp rqst                           
Packit acf257
                           }
Packit acf257
Packit acf257
                Retry -> {- Request with "Expect" header failed.
Packit acf257
                                Trouble is the request contains Expects
Packit acf257
                                other than "100-Continue" -}
Packit acf257
                    do { -- TODO review throwing away of result
Packit acf257
                         _ <- writeBlock conn (show rqst ++ rqBody rqst)
Packit acf257
                       ; rsp <- getResponseHead conn
Packit acf257
                       ; switchResponse conn False bdy_sent rsp rqst
Packit acf257
                       }   
Packit acf257
                     
Packit acf257
                Done -> do
Packit acf257
                    when (findConnClose hdrs)
Packit acf257
                         (closeOnEnd conn True)
Packit acf257
                    return (Right $ Response cd rn hdrs "")
Packit acf257
Packit acf257
                DieHorribly str -> do
Packit acf257
                    close conn
Packit acf257
                    return $ responseParseError "sendHTTP" ("Invalid response: " ++ str)
Packit acf257
Packit acf257
                ExpectEntity ->
Packit acf257
                    let tc = lookupHeader HdrTransferEncoding hdrs
Packit acf257
                        cl = lookupHeader HdrContentLength hdrs
Packit acf257
                    in
Packit acf257
                    do { rslt <- case tc of
Packit acf257
                          Nothing -> 
Packit acf257
                              case cl of
Packit acf257
                                  Just x  -> linearTransfer (readBlock conn) (read x :: Int)
Packit acf257
                                  Nothing -> hopefulTransfer stringBufferOp {-null (++) []-} (readLine conn) []
Packit acf257
                          Just x  -> 
Packit acf257
                              case map toLower (trim x) of
Packit acf257
                                  "chunked" -> chunkedTransfer stringBufferOp
Packit acf257
                                                               (readLine conn) (readBlock conn)
Packit acf257
                                  _         -> uglyDeathTransfer "sendHTTP"
Packit acf257
                       ; case rslt of
Packit acf257
                           Left e -> close conn >> return (Left e)
Packit acf257
                           Right (ftrs,bdy) -> do
Packit acf257
                            when (findConnClose (hdrs++ftrs))
Packit acf257
                                 (closeOnEnd conn True)
Packit acf257
                            return (Right (Response cd rn (hdrs++ftrs) bdy))
Packit acf257
                       }
Packit acf257
Packit acf257
-- | Receive and parse a HTTP request from the given Stream. Should be used 
Packit acf257
--   for server side interactions.
Packit acf257
receiveHTTP :: Stream s => s -> IO (Result Request_String)
Packit acf257
receiveHTTP conn = getRequestHead >>= processRequest
Packit acf257
    where
Packit acf257
        -- reads and parses headers
Packit acf257
        getRequestHead :: IO (Result RequestData)
Packit acf257
        getRequestHead =
Packit acf257
            do { lor <- readTillEmpty1 stringBufferOp (readLine conn)
Packit acf257
               ; return $ lor >>= parseRequestHead
Packit acf257
               }
Packit acf257
Packit acf257
        processRequest (Left e) = return $ Left e
Packit acf257
        processRequest (Right (rm,uri,hdrs)) =
Packit acf257
            do -- FIXME : Also handle 100-continue.
Packit acf257
               let tc = lookupHeader HdrTransferEncoding hdrs
Packit acf257
                   cl = lookupHeader HdrContentLength hdrs
Packit acf257
               rslt <- case tc of
Packit acf257
                          Nothing ->
Packit acf257
                              case cl of
Packit acf257
                                  Just x  -> linearTransfer (readBlock conn) (read x :: Int)
Packit acf257
                                  Nothing -> return (Right ([], "")) -- hopefulTransfer ""
Packit acf257
                          Just x  ->
Packit acf257
                              case map toLower (trim x) of
Packit acf257
                                  "chunked" -> chunkedTransfer stringBufferOp
Packit acf257
                                                               (readLine conn) (readBlock conn)
Packit acf257
                                  _         -> uglyDeathTransfer "receiveHTTP"
Packit acf257
               
Packit acf257
               return $ do
Packit acf257
                  (ftrs,bdy) <- rslt
Packit acf257
                  return (Request uri rm (hdrs++ftrs) bdy)
Packit acf257
Packit acf257
-- | Very simple function, send a HTTP response over the given stream. This 
Packit acf257
--   could be improved on to use different transfer types.
Packit acf257
respondHTTP :: Stream s => s -> Response_String -> IO ()
Packit acf257
respondHTTP conn rsp = do -- TODO review throwing away of result
Packit acf257
                          _ <- writeBlock conn (show rsp)
Packit acf257
                          -- write body immediately, don't wait for 100 CONTINUE
Packit acf257
                          -- TODO review throwing away of result
Packit acf257
                          _ <- writeBlock conn (rspBody rsp)
Packit acf257
                          return ()