Blame Network/HTTP/HandleStream.hs

Packit acf257
-----------------------------------------------------------------------------
Packit acf257
-- |
Packit acf257
-- Module      :  Network.HTTP.HandleStream
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
-- A 'HandleStream'-based version of "Network.HTTP" interface.
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.HandleStream 
Packit acf257
       ( simpleHTTP      -- :: Request ty -> IO (Result (Response ty))
Packit acf257
       , simpleHTTP_     -- :: HStream ty => HandleStream ty -> Request ty -> IO (Result (Response ty))
Packit acf257
       , sendHTTP        -- :: HStream ty => HandleStream ty -> Request ty -> IO (Result (Response ty))
Packit acf257
       , sendHTTP_notify -- :: HStream ty => HandleStream ty -> Request ty -> IO () -> IO (Result (Response ty))
Packit acf257
       , receiveHTTP     -- :: HStream ty => HandleStream ty -> IO (Result (Request ty))
Packit acf257
       , respondHTTP     -- :: HStream ty => HandleStream ty -> Response ty -> IO ()
Packit acf257
       
Packit acf257
       , simpleHTTP_debug -- :: FilePath -> Request DebugString -> IO (Response DebugString)
Packit acf257
       ) where
Packit acf257
Packit acf257
-----------------------------------------------------------------
Packit acf257
------------------ Imports --------------------------------------
Packit acf257
-----------------------------------------------------------------
Packit acf257
Packit acf257
import Network.BufferType
Packit acf257
import Network.Stream ( fmapE, Result )
Packit acf257
import Network.StreamDebugger ( debugByteStream )
Packit acf257
import Network.TCP (HStream(..), HandleStream )
Packit acf257
Packit acf257
import Network.HTTP.Base
Packit acf257
import Network.HTTP.Headers
Packit acf257
import Network.HTTP.Utils ( trim, readsOne )
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
------------------ Misc -----------------------------------------
Packit acf257
-----------------------------------------------------------------
Packit acf257
Packit acf257
-- | @simpleHTTP@ transmits a resource across a non-persistent connection.
Packit acf257
simpleHTTP :: HStream ty => Request ty -> IO (Result (Response ty))
Packit acf257
simpleHTTP r = do 
Packit acf257
  auth <- getAuth r
Packit acf257
  failHTTPS (rqURI r)
Packit acf257
  c <- openStream (host auth) (fromMaybe 80 (port auth))
Packit acf257
  simpleHTTP_ c r
Packit acf257
Packit acf257
-- | @simpleHTTP_debug debugFile req@ behaves like 'simpleHTTP', but logs
Packit acf257
-- the HTTP operation via the debug file @debugFile@.
Packit acf257
simpleHTTP_debug :: HStream ty => FilePath -> Request ty -> IO (Result (Response ty))
Packit acf257
simpleHTTP_debug httpLogFile r = do 
Packit acf257
  auth <- getAuth r
Packit acf257
  failHTTPS (rqURI r)
Packit acf257
  c0   <- openStream (host auth) (fromMaybe 80 (port auth))
Packit acf257
  c    <- debugByteStream httpLogFile c0
Packit acf257
  simpleHTTP_ c r
Packit acf257
Packit acf257
-- | Like 'simpleHTTP', but acting on an already opened stream.
Packit acf257
simpleHTTP_ :: HStream ty => HandleStream ty -> Request ty -> IO (Result (Response ty))
Packit acf257
simpleHTTP_ s r = sendHTTP s r
Packit acf257
Packit acf257
-- | @sendHTTP hStream httpRequest@ transmits @httpRequest@ over
Packit acf257
-- @hStream@, but does not alter the status of the connection, nor request it to be
Packit acf257
-- closed upon receiving the response.
Packit acf257
sendHTTP :: HStream ty => HandleStream ty -> Request ty -> IO (Result (Response ty))
Packit acf257
sendHTTP conn rq = sendHTTP_notify conn rq (return ())
Packit acf257
Packit acf257
-- | @sendHTTP_notify hStream httpRequest action@ behaves like 'sendHTTP', but
Packit acf257
-- lets you supply an IO @action@ to execute once the request has been successfully
Packit acf257
-- transmitted over the connection. Useful when you want to set up tracing of
Packit acf257
-- request transmission and its performance.
Packit acf257
sendHTTP_notify :: HStream ty
Packit acf257
                => HandleStream ty
Packit acf257
                -> Request ty
Packit acf257
                -> IO ()
Packit acf257
                -> IO (Result (Response ty))
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 :: HStream ty
Packit acf257
         => HandleStream ty
Packit acf257
         -> Request ty
Packit acf257
         -> (IO ())
Packit acf257
         -> IO (Result (Response ty))
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 (buf_fromStr bufferOps $ 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
   -- 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
Packit acf257
switchResponse :: HStream ty
Packit acf257
               => HandleStream ty
Packit acf257
               -> Bool {- allow retry? -}
Packit acf257
               -> Bool {- is body sent? -}
Packit acf257
               -> Result ResponseData
Packit acf257
               -> Request ty
Packit acf257
               -> IO (Result (Response ty))
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
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 -> do {- Time to send the body -}
Packit acf257
        writeBlock conn (rqBody rqst) >>= either (return . Left)
Packit acf257
           (\ _ -> do
Packit acf257
              rsp <- getResponseHead conn
Packit acf257
              switchResponse conn allow_retry True rsp rqst)
Packit acf257
      | otherwise    -> do {- keep waiting -}
Packit acf257
        rsp <- getResponseHead conn
Packit acf257
        switchResponse conn allow_retry bdy_sent rsp rqst
Packit acf257
Packit acf257
     Retry -> do {- Request with "Expect" header failed.
Packit acf257
                    Trouble is the request contains Expects
Packit acf257
                    other than "100-Continue" -}
Packit acf257
        -- TODO review throwing away of result
Packit acf257
        _ <- writeBlock conn ((buf_append bufferOps)
Packit acf257
                                     (buf_fromStr bufferOps (show rqst))
Packit acf257
                                     (rqBody rqst))
Packit acf257
        rsp <- getResponseHead conn
Packit acf257
        switchResponse conn False bdy_sent rsp rqst
Packit acf257
                     
Packit acf257
     Done -> do
Packit acf257
       when (findConnClose hdrs)
Packit acf257
            (closeOnEnd conn True)
Packit acf257
       return (Right $ Response cd rn hdrs (buf_empty bufferOps))
Packit acf257
Packit acf257
     DieHorribly str -> do
Packit acf257
       close conn
Packit acf257
       return (responseParseError "Invalid response:" str)
Packit acf257
     ExpectEntity -> do
Packit acf257
       r <- fmapE (\ (ftrs,bdy) -> Right (Response cd rn (hdrs++ftrs) bdy)) $
Packit acf257
             maybe (maybe (hopefulTransfer bo (readLine conn) [])
Packit acf257
                       (\ x ->
Packit acf257
                          readsOne (linearTransfer (readBlock conn))
Packit acf257
                                   (return$responseParseError "unrecognized content-length value" x)
Packit acf257
                                   x)
Packit acf257
                        cl)
Packit acf257
                   (ifChunked (chunkedTransfer bo (readLine conn) (readBlock conn))
Packit acf257
                              (uglyDeathTransfer "sendHTTP"))
Packit acf257
                   tc
Packit acf257
       case r of
Packit acf257
         Left{} -> do
Packit acf257
           close conn
Packit acf257
           return r
Packit acf257
         Right (Response _ _ hs _) -> do
Packit acf257
           when (findConnClose hs)
Packit acf257
                (closeOnEnd conn True)
Packit acf257
           return r
Packit acf257
Packit acf257
      where
Packit acf257
       tc = lookupHeader HdrTransferEncoding hdrs
Packit acf257
       cl = lookupHeader HdrContentLength hdrs
Packit acf257
       bo = bufferOps
Packit acf257
                    
Packit acf257
-- reads and parses headers
Packit acf257
getResponseHead :: HStream ty => HandleStream ty -> IO (Result ResponseData)
Packit acf257
getResponseHead conn = 
Packit acf257
   fmapE (\es -> parseResponseHead (map (buf_toStr bufferOps) es))
Packit acf257
         (readTillEmpty1 bufferOps (readLine conn))
Packit acf257
Packit acf257
-- | @receiveHTTP hStream@ reads a 'Request' from the 'HandleStream' @hStream@
Packit acf257
receiveHTTP :: HStream bufTy => HandleStream bufTy -> IO (Result (Request bufTy))
Packit acf257
receiveHTTP conn = getRequestHead >>= either (return . Left) processRequest
Packit acf257
  where
Packit acf257
    -- reads and parses headers
Packit acf257
   getRequestHead :: IO (Result RequestData)
Packit acf257
   getRequestHead = do
Packit acf257
      fmapE (\es -> parseRequestHead (map (buf_toStr bufferOps) es))
Packit acf257
            (readTillEmpty1 bufferOps (readLine conn))
Packit acf257
Packit acf257
   processRequest (rm,uri,hdrs) =
Packit acf257
      fmapE (\ (ftrs,bdy) -> Right (Request uri rm (hdrs++ftrs) bdy)) $
Packit acf257
             maybe
Packit acf257
              (maybe (return (Right ([], buf_empty bo))) -- hopefulTransfer ""
Packit acf257
                     (\ x -> readsOne (linearTransfer (readBlock conn))
Packit acf257
                                      (return$responseParseError "unrecognized Content-Length value" x)
Packit acf257
                                      x)
Packit acf257
Packit acf257
                     cl)
Packit acf257
              (ifChunked (chunkedTransfer bo (readLine conn) (readBlock conn))
Packit acf257
                         (uglyDeathTransfer "receiveHTTP"))
Packit acf257
              tc
Packit acf257
    where
Packit acf257
     -- FIXME : Also handle 100-continue.
Packit acf257
     tc = lookupHeader HdrTransferEncoding hdrs
Packit acf257
     cl = lookupHeader HdrContentLength hdrs
Packit acf257
     bo = bufferOps
Packit acf257
Packit acf257
-- | @respondHTTP hStream httpResponse@ transmits an HTTP 'Response' over
Packit acf257
-- the 'HandleStream' @hStream@. It could be used to implement simple web
Packit acf257
-- server interactions, performing the dual role to 'sendHTTP'.
Packit acf257
respondHTTP :: HStream ty => HandleStream ty -> Response ty -> IO ()
Packit acf257
respondHTTP conn rsp = do 
Packit acf257
  -- TODO: review throwing away of result
Packit acf257
  _ <- writeBlock conn (buf_fromStr bufferOps $ 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 ()
Packit acf257
Packit acf257
------------------------------------------------------------------------------
Packit acf257
Packit acf257
headerName :: String -> String
Packit acf257
headerName x = map toLower (trim x)
Packit acf257
Packit acf257
ifChunked :: a -> a -> String -> a
Packit acf257
ifChunked a b s = 
Packit acf257
  case headerName s of
Packit acf257
    "chunked" -> a
Packit acf257
    _ -> b
Packit acf257