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