|
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 |
|