second-transfer-0.6.0.0: hs-src/SecondTransfer/Http1/Session.hs
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK hide #-}
module SecondTransfer.Http1.Session(
http11Attendant
) where
import Control.Lens
import Control.Exception (catch)
import Control.Concurrent (forkIO)
import qualified Data.ByteString as B
-- import qualified Data.ByteString.Lazy as LB
-- import Data.ByteString.Char8 (unpack)
-- import qualified Data.ByteString.Builder as Bu
import Data.Conduit
import Data.Conduit.List (consume)
-- import Data.Monoid (mconcat, mappend)
import SecondTransfer.MainLoop.CoherentWorker
import SecondTransfer.MainLoop.PushPullType
import SecondTransfer.Sessions.Internal (SessionsContext, acquireNewSessionTag, sessionsConfig)
-- Logging utilities
import System.Log.Logger
-- And we need the time
import System.Clock
import SecondTransfer.Http1.Parse
import SecondTransfer.Exception (IOProblem)
import SecondTransfer.Sessions.Config
import qualified SecondTransfer.Utils.HTTPHeaders as He
-- import Debug.Trace (traceShow)
-- | Session attendant that speaks HTTP/1.1
--
http11Attendant :: SessionsContext -> AwareWorker -> Attendant
http11Attendant sessions_context coherent_worker attendant_callbacks
=
do
new_session_tag <- acquireNewSessionTag sessions_context
infoM "Session.Session_HTTP11" $ "Starting new session with tag: " ++(show new_session_tag)
forkIO $ go new_session_tag (Just "") 1
return ()
where
push_action = attendant_callbacks ^. pushAction_AtC
-- pull_action = attendant_callbacks ^. pullAction_AtC
close_action = attendant_callbacks ^. closeAction_AtC
best_effort_pull_action = attendant_callbacks ^. bestEffortPullAction_AtC
go :: Int -> Maybe B.ByteString -> Int -> IO ()
go session_tag (Just leftovers) reuse_no = do
infoM "Session.Session_HTTP11" $ "(Re)Using session with tag: " ++ (show session_tag)
maybe_leftovers <- add_data newIncrementalHttp1Parser leftovers session_tag reuse_no
go session_tag maybe_leftovers (reuse_no + 1)
go _ Nothing _ =
return ()
add_data :: IncrementalHttp1Parser -> B.ByteString -> Int -> Int -> IO (Maybe B.ByteString)
add_data parser bytes session_tag reuse_no = do
let
completion = addBytes parser bytes
-- completion = addBytes parser $ traceShow ("At session " ++ (show session_tag) ++ " Received: " ++ (unpack bytes) ) bytes
case completion of
MustContinue_H1PC new_parser ->
-- print "MustContinue_H1PC"
catch
(do
-- Try to get at least 16 bytes. For HTTP/1 requests, that may not be always
-- possible
new_bytes <- best_effort_pull_action True
add_data new_parser new_bytes session_tag reuse_no
)
( (\ _e -> do
-- This is a pretty harmless condition that happens
-- often when the remote peer closes the connection
debugM "Session.HTTP1" "Could not receive data"
close_action
return Nothing
) :: IOProblem -> IO (Maybe B.ByteString) )
OnlyHeaders_H1PC headers leftovers -> do
-- print "OnlyHeaders_H1PC"
-- Ready for action...
-- ATTENTION: Not use for pushed streams here....
-- We must decide what to do if the user return those
-- anyway.
let
modified_headers = addExtraHeaders sessions_context headers
started_time <- getTime Monotonic
--(response_headers, _, data_and_conclusion)
principal_stream <- coherent_worker Request {
_headers_RQ = modified_headers,
_inputData_RQ = Nothing,
_perception_RQ = Perception {
_startedTime_Pr = started_time,
_streamId_Pr = reuse_no,
_sessionId_Pr = session_tag
}
}
let
data_and_conclusion = principal_stream ^. dataAndConclusion_PS
response_headers = principal_stream ^. headers_PS
(_, fragments) <- runConduit $ fuseBoth data_and_conclusion consume
let
response_text =
serializeHTTPResponse response_headers fragments
catch
(do
push_action response_text
return $ Just leftovers
)
((\ _e -> do
debugM "Session.HTTP1" "Session abandoned"
close_action
return Nothing
) :: IOProblem -> IO (Maybe B.ByteString) )
HeadersAndBody_H1PC _headers _stopcondition _recv_leftovers -> do
-- print "HeadersAndBody_H1PC"
-- Let's see if I can go through the basic movements first, then through
-- more complicated things.
-- TODO: Implement posts and other requests with bodies....
close_action
error "NotImplemented requests with bodies"
addExtraHeaders :: SessionsContext -> Headers -> Headers
addExtraHeaders sessions_context headers =
let
enriched_lens :: Lens' SessionsContext SessionsEnrichedHeaders
enriched_lens = (sessionsConfig . sessionsEnrichedHeaders)
-- Haskell laziness here!
headers_editor = He.fromList headers
-- TODO: Figure out which is the best way to put this contact in the
-- source code
protocol_lens = He.headerLens "second-transfer-eh--used-protocol"
add_used_protocol = sessions_context ^. (enriched_lens . addUsedProtocol )
he1 = if add_used_protocol
then set protocol_lens (Just "HTTP/1.1") headers_editor
else headers_editor
result = He.toList he1
in if add_used_protocol
-- Nothing will be computed here if the headers are not modified.
then result
else headers