packages feed

second-transfer 0.6.1.0 → 0.7.1.0

raw patch · 22 files changed

+1990/−393 lines, 22 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- SecondTransfer.Types: AttendantCallbacks :: PushAction -> PullAction -> BestEffortPullAction -> CloseAction -> AttendantCallbacks
- SecondTransfer.Types: [_bestEffortPullAction_AtC] :: AttendantCallbacks -> BestEffortPullAction
- SecondTransfer.Types: [_closeAction_AtC] :: AttendantCallbacks -> CloseAction
- SecondTransfer.Types: [_pullAction_AtC] :: AttendantCallbacks -> PullAction
- SecondTransfer.Types: [_pushAction_AtC] :: AttendantCallbacks -> PushAction
- SecondTransfer.Types: bestEffortPullAction_AtC :: Lens' AttendantCallbacks BestEffortPullAction
- SecondTransfer.Types: closeAction_AtC :: Lens' AttendantCallbacks CloseAction
- SecondTransfer.Types: data AttendantCallbacks
- SecondTransfer.Types: pullAction_AtC :: Lens' AttendantCallbacks PullAction
- SecondTransfer.Types: pushAction_AtC :: Lens' AttendantCallbacks PushAction
+ SecondTransfer.Exception: ClientSessionAbortedException :: ConnectionCloseReason -> ClientSessionAbortedException
+ SecondTransfer.Exception: IOChannelClosed_CCR :: ConnectionCloseReason
+ SecondTransfer.Exception: NormalTermination_CCR :: ConnectionCloseReason
+ SecondTransfer.Exception: ProtocolError_CCR :: ConnectionCloseReason
+ SecondTransfer.Exception: SessionAlreadyClosed_CCR :: ConnectionCloseReason
+ SecondTransfer.Exception: data ClientSessionAbortedException
+ SecondTransfer.Exception: data ConnectionCloseReason
+ SecondTransfer.Exception: instance GHC.Exception.Exception SecondTransfer.Exception.ClientSessionAbortedException
+ SecondTransfer.Exception: instance GHC.Show.Show SecondTransfer.Exception.ClientSessionAbortedException
+ SecondTransfer.Exception: instance GHC.Show.Show SecondTransfer.Exception.ConnectionCloseReason
+ SecondTransfer.Sessions.Config: SessionClientPollThread_HTTP2SessionComponent :: SessionComponent
+ SecondTransfer.Types: IOCallbacks :: PushAction -> PullAction -> BestEffortPullAction -> CloseAction -> IOCallbacks
+ SecondTransfer.Types: InterruptConnectionAfter_IEf :: InterruptEffect
+ SecondTransfer.Types: InterruptConnectionNow_IEf :: InterruptEffect
+ SecondTransfer.Types: [_bestEffortPullAction_IOC] :: IOCallbacks -> BestEffortPullAction
+ SecondTransfer.Types: [_closeAction_IOC] :: IOCallbacks -> CloseAction
+ SecondTransfer.Types: [_interrupt_Ef] :: Effect -> Maybe InterruptEffect
+ SecondTransfer.Types: [_pullAction_IOC] :: IOCallbacks -> PullAction
+ SecondTransfer.Types: [_pushAction_IOC] :: IOCallbacks -> PushAction
+ SecondTransfer.Types: bestEffortPullAction_IOC :: Lens' IOCallbacks BestEffortPullAction
+ SecondTransfer.Types: closeAction_IOC :: Lens' IOCallbacks CloseAction
+ SecondTransfer.Types: data IOCallbacks
+ SecondTransfer.Types: data InterruptEffect
+ SecondTransfer.Types: interrupt_Ef :: Lens' Effect (Maybe InterruptEffect)
+ SecondTransfer.Types: pullAction_IOC :: Lens' IOCallbacks PullAction
+ SecondTransfer.Types: pushAction_IOC :: Lens' IOCallbacks PushAction
+ SecondTransfer.Types: type BestEffortPullAction = Bool -> IO ByteString
- SecondTransfer: type Attendant = AttendantCallbacks -> IO ()
+ SecondTransfer: type Attendant = IOCallbacks -> IO ()
- SecondTransfer.Types: Effect :: Maybe FragmentDeliveryCallback -> Maybe Int -> Effect
+ SecondTransfer.Types: Effect :: Maybe FragmentDeliveryCallback -> Maybe Int -> Maybe InterruptEffect -> Effect
- SecondTransfer.Types: type Attendant = AttendantCallbacks -> IO ()
+ SecondTransfer.Types: type Attendant = IOCallbacks -> IO ()

Files

README.md view
@@ -41,27 +41,13 @@ Example ------- -There is a very basic example at `tests/tests-hs-src/compiling_ok.hs`.+There is a very basic example at `tests/tests-hs-src/compiling_ok.hs`, and a somewhat more complicated one at+`examples/attempt_bust`; that one shows how to do HTTP/2.0 push from the library. -Roadmap-------- -Done:--- Version 0.1: Having something that can run. No unit-testing, nothing-               fancy.--- Version 0.2: Absolutely minimal amount of unit tests.--- Version 0.3: More sensible logging.--Pending:--- Benchmarking.--Internal---------+Development+----------- -Uploading documentation (provided you have access to the package):+Uploading documentation (provided you have access to the package in Hackage):      $ ./hackage-upload-docs.sh second-transfer 0.5.4.0 <hackage-user> <hackage-password>
cbits/tlsinc.c view
@@ -26,6 +26,50 @@ #include <openssl/ssl.h> #include <openssl/err.h> ++//+++// Some constants+#define ALL_OK  0+#define BAD_HAPPENED 1+#define TIMEOUT_REACHED 3+// This is also a failed IO with SSL, but this one may be quite+// natural and we want to handle it differently+#define TRANSPORT_CLOSED 2+++#if OPENSSL_VERSION_NUMBER < 0x1000200fL+#define ALPN_DISABLED+#warning Due to low OpenSSL version, this build is completely broken. Check second-transfer.cabal to see how+#warning to indicate a recent OpenSSL version.++int SSL_CTX_set_ecdh_auto(SSL *s, int onoff)+{+    fprintf(stderr, "Fake OPENSSL function called. Updated your SSL version!!\n");+}++void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx,+                                  int (*cb) (SSL *ssl,+                                             const unsigned char **out,+                                             unsigned char *outlen,+                                             const unsigned char *in,+                                             unsigned int inlen,+                                             void *arg),+                                void *arg)+{+    fprintf(stderr, "Fake OPENSSL function called. Updated your SSL version!!\n");+}+++void SSL_get0_alpn_selected(SSL*a , int*b,  int*c)+{+    fprintf(stderr, "Fake OPENSSL function called. Updated your SSL version!!\n");+}++#endif++ // Simple structure to keep track of the handle, and // of what needs to be freed later. typedef struct {@@ -49,12 +93,7 @@ // Call when you are done void close_connection(connection_t* conn); // Wait for the next one...-#define ALL_OK  0-#define BAD_HAPPENED 1-#define TIMEOUT_REACHED 3-// This is also a failed IO with SSL, but this one may be quite-// natural and we want to handle it differently-#define TRANSPORT_CLOSED 2+ int wait_for_connection(connection_t* conn, int microseconds, wired_session_t** wired_session); int send_data(wired_session_t* ws, char* buffer, int buffer_size); int recv_data(wired_session_t* ws, char* inbuffer, int buffer_size, int* data_recvd);
changelog.md view
@@ -1,3 +1,8 @@+- 0.7.1.0 :+    * A few important memory leaks got fixed+    * Changed I/O interface to live in IOCallbacks+    * Working now around congestion window.+ - 0.6.1.0 :     * Improved documentation.     * Improved ALPN negotiation handling.
hs-src/SecondTransfer.hs view
@@ -6,14 +6,14 @@ Stability   : experimental Portability : POSIX -SecondTransfer is a HTTP/1.1 and HTTP/2 server session library, with an emphasis towards+SecondTransfer is a HTTP\/1.1 and HTTP\/2 server session library, with an emphasis towards experimentation (so far). -This library implements enough of the HTTP/2  to build-compliant HTTP/2 servers. It also implements enough of-HTTP/1.1 so you can actually use it to build polyglot web-servers.+This library implements enough of the HTTP\/2  to build+compliant HTTP\/2 servers. It also implements enough of+HTTP\/1.1 so you can actually use it to build polyglot web-servers. -For HTTP/2, frame encoding and decoding is done with+For HTTP\/2, frame encoding and decoding is done with Kazu Yamamoto's <http://hackage.haskell.org/package/http2 http2> package. This library just takes care of making sense of sent and received frames.@@ -111,7 +111,8 @@ most times you can do with a simplified version called `CoherentWorker`. The function `coherentToAwareWorker` does the conversion. The difference between the two callbacks is  the level of information that you manage. With AwareWorker, you get a record on the request-with all sort of details, things like the session id and the +with all sort of details, things like the session id, the protocol the client is using and in+the future things like the remote address.   The callback is used to handle all requests to the server on a given negotiated ALPN protocol. If you need routing functionality (and you most certainly will need it), you need
hs-src/SecondTransfer/Exception.hs view
@@ -9,7 +9,9 @@     ,BadPrefaceException (..)     ,HTTP11Exception (..)     ,HTTP11SyntaxException (..)+    ,ClientSessionAbortedException(..)     ,HTTP500PrecursorException (..)+    ,ConnectionCloseReason(..)     ,convertHTTP500PrecursorExceptionToException     ,getHTTP500PrecursorExceptionFromException     ,ContentLengthMissingException (..)@@ -55,6 +57,25 @@     toException   = convertHTTP2SessionExceptionToException     fromException = getHTTP2SessionExceptionFromException +-- TODO below: add the other protocol reasons++-- | Reasons for a remote server interrupting a connectionn of this client+data ConnectionCloseReason =+    NormalTermination_CCR     -- ^ Corresponds to NO_ERROR+    |SessionAlreadyClosed_CCR -- ^ A request was done after the session was previously closed.+    |IOChannelClosed_CCR      -- ^ This one happens when one of the IO channels is closed and a BlockedIndefinitelyOnMVar bubbles up. It should only happen in the test suite, as the OpenSSL_TLS channel uses a specialized exception type. If you see it in the wild, it is a bug.+    |ProtocolError_CCR        -- ^ Any other reason+    deriving Show++-- | Concrete Exception. Used internally to signal that the server broke+--   the connection. This is a public exception that clients of the library+--   will see when acting as an HTTP client.+data ClientSessionAbortedException = ClientSessionAbortedException ConnectionCloseReason+    deriving (Typeable, Show)++instance Exception ClientSessionAbortedException where+    toException = convertHTTP2SessionExceptionToException+    fromException = getHTTP2SessionExceptionFromException  -- | Abstract exception. Thrown when encoding/decoding of a frame fails data FramerException = forall e . Exception e => FramerException e
hs-src/SecondTransfer/Http1/Session.hs view
@@ -48,10 +48,10 @@         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+    push_action = attendant_callbacks ^. pushAction_IOC+    -- pull_action = attendant_callbacks ^. pullAction_IOC+    close_action = attendant_callbacks ^. closeAction_IOC+    best_effort_pull_action = attendant_callbacks ^. bestEffortPullAction_IOC      go :: Int -> Maybe B.ByteString -> Int -> IO ()     go session_tag (Just leftovers) reuse_no = do
hs-src/SecondTransfer/Http2/Framer.hs view
@@ -1,3 +1,4 @@+ -- The framer has two functions: to convert bytes to Frames and the other way around, -- and two keep track of flow-control quotas. {-# LANGUAGE OverloadedStrings, StandaloneDeriving, FlexibleInstances,@@ -5,6 +6,7 @@ {-# OPTIONS_HADDOCK hide #-} module SecondTransfer.Http2.Framer (     BadPrefaceException,+    SessionPayload(..),      wrapSession,     http2FrameLength,@@ -46,6 +48,7 @@  import qualified Data.HashTable.IO                      as H import           System.Clock                           (Clock(..),getTime)+-- import           System.Mem.Weak  import           SecondTransfer.Sessions.Internal       (                                                          sessionExceptionHandler,@@ -119,19 +122,34 @@   data FramerSessionData = FramerSessionData {-      _stream2flow           :: MVar Stream2AvailSpace++    -- A dictionary (protected by a lock) from stream id to flow control command.+    _stream2flow           :: MVar Stream2AvailSpace+     -- Two members below: the place where you put data which is going to be flow-controlled,     -- and the place with the ordinal+    -- Flow control dictionary. It goes from stream id to the stream data-output gate (an-mvar)+    -- and to a mutable register for the ordinal. The ordinal for packets inside a stream is used+    -- for priority and reporting     , _stream2outputBytes    :: MVar ( HashTable GlobalStreamId (MVar LB.ByteString, MVar Int) )++    -- The default flow-control window advertised by the peer (e.g., the browser)     , _defaultStreamWindow   :: MVar Int -    -- Wait variable to output bytes to the channel+    -- Wait variable to output bytes to the channel. This is needed to avoid output data races.     , _canOutput             :: MVar CanOutput+     -- Flag that says if the session has been unwound... if such,     -- threads are adviced to exit as early as possible     , _outputIsForbidden     :: MVar Bool++    -- Exclusive lock for headers, needed since the specs forbid interleaving header and data     , _noHeadersInChannel    :: MVar NoHeadersInChannel++    -- The push action     , _pushAction            :: PushAction++    -- The close action     , _closeAction           :: CloseAction      -- Global id of the session, used for e.g. error reporting.@@ -141,10 +159,19 @@     , _sessionsContext       :: SessionsContext      -- For GoAway frames-    , _lastStream            :: MVar Int+    -- We keep the value below updated with the highest incoming stream+    -- frame. For example, it is updated as soon as headers are received from+    -- the client on that stream.+    , _lastInputStream       :: MVar Int+    -- We update this one as soon as an outgoing frame is seen with such a+    -- high output number.+    , _lastOutputStream      :: MVar Int      -- For sending data orderly     , _prioritySendState     :: PrioritySendState++    -- Need to know this for the preface+    , _sessionRole_FSD       :: SessionRole     }  L.makeLenses ''FramerSessionData@@ -152,27 +179,41 @@  type FramerSession = ReaderT FramerSessionData IO +data SessionPayload =+    AwareWorker_SP AwareWorker   -- I'm a server+    |ClientState_SP ClientState  -- I'm a client -wrapSession :: AwareWorker -> SessionsContext -> Attendant-wrapSession aware_worker sessions_context attendant_callbacks = do+wrapSession :: SessionPayload -> SessionsContext -> Attendant+wrapSession session_payload sessions_context io_callbacks = do      let         session_id_mvar = view nextSessionId sessions_context-        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+        push_action = io_callbacks ^. pushAction_IOC+        pull_action = io_callbacks ^. pullAction_IOC+        close_action = io_callbacks ^. closeAction_IOC+        -- best_effort_pull_action = io_callbacks ^. bestEffortPullAction_IOC       new_session_id <- modifyMVarMasked         session_id_mvar $         \ session_id -> return $ session_id `seq` (session_id + 1, session_id) -    (session_input, session_output) <- http2Session-                                        aware_worker-                                        new_session_id-                                        sessions_context+    (session_input, session_output) <- case session_payload of+        AwareWorker_SP aware_worker ->  http2ServerSession+                                            aware_worker+                                            new_session_id+                                            sessions_context+        ClientState_SP client_state -> http2ClientSession+                                            client_state+                                            new_session_id+                                            sessions_context +    let+        session_role = case session_payload of+            AwareWorker_SP _ ->  Server_SR+            ClientState_SP _ ->  Client_SR++     -- TODO : Add type annotations....     s2f                       <- H.new     stream2flow_mvar          <- newMVar s2f@@ -182,6 +223,7 @@     can_output                <- newMVar CanOutput     no_headers_in_channel     <- newMVar NoHeadersInChannel     last_stream_id            <- newMVar 0+    last_output_stream_id     <- newMVar 0     output_is_forbidden       <- newMVar False      prio_mvar                 <- STM.atomically $ newTMVar PQ.empty@@ -200,12 +242,14 @@         ,_closeAction         = close_action         ,_sessionIdAtFramer   = new_session_id         ,_sessionsContext     = sessions_context-        ,_lastStream          = last_stream_id+        ,_lastInputStream     = last_stream_id+        ,_lastOutputStream    = last_output_stream_id         ,_outputIsForbidden   = output_is_forbidden         ,_prioritySendState   = PrioritySendState {                                     _semToSend = sem_to_send,                                     _prioQ = prio_mvar                                 }+        ,_sessionRole_FSD     = session_role         }  @@ -226,7 +270,7 @@         exc_handler :: Int -> SessionsContext -> FramerException -> IO ()         exc_handler x y e = do             modifyMVar_ output_is_forbidden (\ _ -> return True)-            INSTRUMENTATION( errorM "HTTP2.Framer" "Exception went up" )+            -- INSTRUMENTATION( errorM "HTTP2.Framer" "Exception went up" )             sessionExceptionHandler Framer_HTTP2SessionComponent x y e          io_exc_handler :: Int -> SessionsContext -> IOProblem -> IO ()@@ -265,6 +309,10 @@         GlobalStreamId ->         Int           ->         FramerSession Bool+addCapacity _         0         =+    -- By the specs, a WINDOW_UPDATE with 0 of credit should be considered a protocol+    -- error+    return False addCapacity 0         delta_cap =     -- TODO: Implement session flow control     return True@@ -273,7 +321,7 @@         table_mvar <- view stream2flow         val <- liftIO $ withMVar table_mvar $ \ table ->             H.lookup table stream_id-        last_stream_mvar <- view lastStream+        last_stream_mvar <- view lastInputStream         last_stream <- liftIO . readMVar $ last_stream_mvar         case val of             Nothing | stream_id > last_stream ->@@ -340,25 +388,30 @@ -- -- This function also does part of the flow control: it registers WindowUpdate frames and triggers -- quota updates on the streams.+--+-- Also, when this function exits its caller will issue a close_action inputGatherer :: PullAction -> SessionInput -> FramerSession () inputGatherer pull_action session_input = do-    -- We can start by reading off the prefix....-    prefix <- liftIO $ pull_action http2PrefixLength-    if prefix /= NH2.connectionPreface-      then do-        sendGoAwayFrame NH2.ProtocolError-        liftIO $-            -- We just use the GoAway frame, although this is awfully early-            -- and probably wrong-            throwIO BadPrefaceException-      else-        INSTRUMENTATION(  debugM "HTTP2.Framer" "Prologue validated" )++    session_role <- view sessionRole_FSD++    when (session_role == Server_SR) $ do+        -- We can start by reading off the prefix....+        prefix <- liftIO $ pull_action http2PrefixLength+        when (prefix /= NH2.connectionPreface) $ do+            sendGoAwayFrame NH2.ProtocolError+            liftIO $+                -- We just use the GoAway frame, although this is awfully early+                -- and probably wrong+                throwIO BadPrefaceException+     let         source::Source FramerSession (Maybe NH2.Frame)         source = transPipe liftIO $ readNextFrame pull_action     source $$ consume True   where +     sendToSession :: Bool -> InputFrame -> IO ()     sendToSession starting frame =       -- print(NH2.streamId $ NH2.frameHeader frame)@@ -383,9 +436,15 @@     consume starting = do         maybe_maybe_frame <- await -        case maybe_maybe_frame of+        output_is_forbidden_mvar <- view outputIsForbidden+        output_is_forbidden <- liftIO $ readMVar output_is_forbidden_mvar +        -- Consumption ends automatically when the output is forbidden, this might help avoiding+        -- attacks where a peer refuses to close its socket.+        unless output_is_forbidden $ case maybe_maybe_frame of+             Just Nothing      ->+                -- Only way to get here is by a closed connection condition I guess.                 abortSession              Just (Just right_frame) -> do@@ -395,7 +454,10 @@                         -- Bookkeep the increase on bytes on that stream                         -- liftIO $ putStrLn $ "Extra capacity for stream " ++ (show stream_id)                         succeeded <- lift $ addCapacity stream_id (fromIntegral credit)-                        unless succeeded abortSession+                        if not succeeded then+                            abortSession+                        else+                            consume_continue                       frame@(NH2.Frame _ (NH2.SettingsFrame settings_list) ) -> do@@ -426,18 +488,18 @@                         -- And send the frame down to the session, so that session specific settings                         -- can be applied.                         liftIO $ sendToSession starting $! frame-+                        consume_continue                      a_frame@(NH2.Frame (NH2.FrameHeader _ _ stream_id) _ )   -> do                         -- Update the keep of last stream                         -- lift . startStreamOutputQueueIfNotExists (NH2.fromStreamIdentifier stream_id) priority-                        lift . updateLastStream $ stream_id+                        lift . updateLastInputStream $ stream_id                          -- Send frame to the session                         liftIO $ sendToSession starting a_frame -                -- tail recursion: go again...-                consume_continue+                        -- tail recursion: go again...+                        consume_continue              Nothing    ->                 -- We may as well exit this thread@@ -447,6 +509,13 @@ -- All the output frames come this way first outputGatherer :: SessionOutput -> FramerSession () outputGatherer session_output = do++    session_role <- view sessionRole_FSD++    -- When acting as a client, the first step is to send the prefix...+    when (session_role == Client_SR) $+        pushPrefix+     frame_sent_report_callback <- view $        sessionsContext            .        sessionsConfig             .@@ -454,7 +523,6 @@        dataDeliveryCallback_SC      session_id <- view sessionIdAtFramer-     let         dataForFrame p1 p2 =@@ -467,16 +535,24 @@            command_or_frame  <- liftIO $ getFrameFromSession session_output            case command_or_frame of -               Left CancelSession_SOC -> do-                   -- The session wants to cancel things-                   INSTRUMENTATION(  debugM "HTTP2.Framer" "CancelSession_SOC processed")+               Left (CancelSession_SOC error_code) -> do+                   -- The session wants to cancel things as harshly as possible, send a GoAway frame with+                   -- the information I have here.+                   sendGoAwayFrame error_code                    releaseFramer+                   -- And this causes this thread to finish, so that no new frames+                   -- are taken from the session. Correspondingly, an exception is raised in+                   -- the session if it tries to write another frame -               Left (FinishStream_SOC stream_id ) ->+               Left (SpecificTerminate_SOC last_stream_id) -> do+                   -- This is used when the session wants to finish in a specific way.+                   sendSpecificTerminateGoAway last_stream_id+                   releaseFramer++               Left (FinishStream_SOC stream_id ) -> do                    -- Session knows that we are done with the given stream, and that we can release                    -- the flow control structures-                   -- NOTICE: Unfortunately, this doesn't work, so ignore the message for now-                   -- finishFlowControlForStream stream_id.+                   finishFlowControlForStream stream_id                    cont                 Right ( p1@(NH2.EncodeInfo _ stream_idii _), p2@(NH2.DataFrame _), ef ) -> do@@ -546,11 +622,18 @@     loopPart session_id frame_sent_report_callback  -updateLastStream :: GlobalStreamId  -> FramerSession ()-updateLastStream stream_id = do-    last_stream_id_mvar <- view lastStream+updateLastInputStream :: GlobalStreamId  -> FramerSession ()+updateLastInputStream stream_id = do+    last_stream_id_mvar <- view lastInputStream     liftIO $ modifyMVar_ last_stream_id_mvar (\ x -> return $ max x stream_id) ++updateLastOutputStream :: GlobalStreamId  -> FramerSession ()+updateLastOutputStream stream_id = do+    last_stream_id_mvar <- view lastOutputStream+    liftIO $ modifyMVar_ last_stream_id_mvar (\ x -> return $ max x stream_id)++ startStreamOutputQueueIfNotExists :: GlobalStreamId -> Int -> FramerSession () startStreamOutputQueueIfNotExists stream_id priority = do     table_mvar <- view stream2flow@@ -574,13 +657,12 @@      s2o_mvar <- view stream2outputBytes -    liftIO . withMVar s2o_mvar $ \ s2o ->  H.insert s2o stream_id (bytes_chan, ordinal_num)+    liftIO . withMVar s2o_mvar $ {-# SCC e1  #-}  \ s2o ->  H.insert s2o stream_id (bytes_chan, ordinal_num)      stream2flow_mvar <- view stream2flow  -    liftIO . withMVar stream2flow_mvar  $ \ s2c ->  H.insert s2c stream_id command_chan-+    liftIO . withMVar stream2flow_mvar  $  {-# SCC e2  #-}  \ s2c ->  H.insert s2c stream_id command_chan     --     initial_cap_mvar <- view defaultStreamWindow     initial_cap <- liftIO $ readMVar initial_cap_mvar@@ -592,23 +674,22 @@     -- And don't forget the thread itself     let         close_on_error session_id session_context comp =-            E.finally                 (E.catch                     comp                     (exc_handler session_id session_context)                 )-                close_action          exc_handler :: Int -> SessionsContext -> IOProblem -> IO ()         exc_handler x y e = do             -- Let's also decree that other streams don't even try             modifyMVar_ output_is_forbidden_mvar ( \ _ -> return True)             sessionExceptionHandler Framer_HTTP2SessionComponent x y e+            close_action       read_state <- ask     liftIO $ forkIO $ close_on_error session_id' sessions_context  $ runReaderT-        (flowControlOutput stream_id priority initial_cap 0 "" command_chan bytes_chan)+        ({-# SCC forkFlowControlOutput  #-} flowControlOutput stream_id priority initial_cap 0 "" command_chan bytes_chan)         read_state      return (bytes_chan , command_chan)@@ -670,15 +751,31 @@     sendBytes bs  +pushPrefix :: FramerSession ()+pushPrefix = do+    let bs = LB.fromStrict NH2.connectionPreface+    sendBytes bs+++-- Default sendGoAwayFrame. This one assumes that actions are taken as soon as stream is+-- received. This is a good default strategy for streams that cause things to happen, that+-- is, the ones with POST and GET. sendGoAwayFrame :: NH2.ErrorCodeId -> FramerSession () sendGoAwayFrame error_code = do-    last_stream_id_mvar <- view lastStream+    last_stream_id_mvar <- view lastInputStream     last_stream_id <- liftIO $ readMVar last_stream_id_mvar     pushFrame         (NH2.EncodeInfo NH2.defaultFlags 0 Nothing)         (NH2.GoAwayFrame last_stream_id error_code "")  +sendSpecificTerminateGoAway :: GlobalStreamId -> FramerSession ()+sendSpecificTerminateGoAway last_stream =+    pushFrame+        (NH2.EncodeInfo NH2.defaultFlags 0 Nothing)+        (NH2.GoAwayFrame last_stream NH2.NoError "")++ -- From this point on data is really serialized, sendBytes :: LB.ByteString -> FramerSession () sendBytes bs = do@@ -699,14 +796,21 @@ -- -- There is one of these for each stream ---flowControlOutput :: Int -> Int -> Int -> Int ->  LB.ByteString -> MVar FlowControlCommand -> MVar LB.ByteString ->  FramerSession ()+flowControlOutput :: Int+                     -> Int+                     -> Int+                     -> Int+                     ->  LB.ByteString+                     -> MVar FlowControlCommand+                     -> MVar LB.ByteString+                     ->  FramerSession () flowControlOutput stream_id priority capacity ordinal leftovers commands_chan bytes_chan =-    if leftovers == ""-      then do+    ordinal `seq` if leftovers == ""+      then {-# SCC fcOBranch1  #-} do         -- Get more data (possibly block waiting for it)-        bytes_to_send <- liftIO $ takeMVar bytes_chan+        bytes_to_send <- liftIO $ {-# SCC perfectlyHarmlessExceptionPoint #-} takeMVar bytes_chan         flowControlOutput stream_id priority capacity ordinal  bytes_to_send commands_chan bytes_chan-      else do+      else {-# SCC fcOBranch2  #-}  do         -- Length?         let amount = fromIntegral  (LB.length leftovers - 9)         if  amount <= capacity@@ -715,11 +819,11 @@             -- I can send ... if no headers are in process....             -- liftIO . logit $ "set-priority (stream_id, prio, ordinal) " `mappend` (pack . show) (__stream_id, priority, ordinal)             withPrioritySend priority stream_id ordinal leftovers-            flowControlOutput  stream_id priority (capacity - amount) (ordinal+1) "" commands_chan bytes_chan+            flowControlOutput  stream_id priority (capacity - amount) (ordinal+1 ) "" commands_chan bytes_chan           else do             -- I can not send because flow-control is full, wait for a command instead-            command <- liftIO $ takeMVar commands_chan-            case command of+            command <- liftIO $ {-# SCC t2 #-} takeMVar commands_chan+            case  {-# SCC t3 #-} command of                 AddBytes_FCM delta_cap ->                     -- liftIO $ putStrLn $ "Flow control delta_cap stream " ++ (show stream_id)                     flowControlOutput stream_id priority (capacity + delta_cap) ordinal leftovers commands_chan bytes_chan@@ -728,7 +832,8 @@ releaseFramer :: FramerSession () releaseFramer =     -- Release any resources pending...-+    -- This is not needed as of right now, since garbage collection works well.+    -- I'm leaving it here just in case we need to re-activate it in the future.     return ()  
hs-src/SecondTransfer/Http2/MakeAttendant.hs view
@@ -4,24 +4,20 @@     ) where  -import           SecondTransfer.Http2.Framer            (wrapSession)+import           SecondTransfer.Http2.Framer            (wrapSession, SessionPayload(..)) import           SecondTransfer.Sessions.Internal       (SessionsContext) import           SecondTransfer.MainLoop.CoherentWorker import           SecondTransfer.MainLoop.PushPullType   (Attendant) --- | The type of this function is equivalent to:------ @---      http2Attendant :: CoherentWorker -> AttendantCallbacks ->  IO ()--- @+-- | ----- Given a `CoherentWorker`, this function wraps it with flow control, multiplexing,+-- Given an `AwareWorker`, this function wraps it with flow control, multiplexing, -- and state maintenance needed to run an HTTP/2 session. ----- Notice that this function is  using HTTP/2 over TLS. We haven't implemented yet--- a session handling mechanism for HTTP/1.1 .+-- Notice that this function is  using HTTP/2 over TLS. There is an equivalent for HTTP+-- 1.1. http2Attendant :: SessionsContext -> AwareWorker -> Attendant http2Attendant sessions_context coherent_worker attendant_callbacks = do     let-        attendant = wrapSession coherent_worker sessions_context+        attendant = wrapSession (AwareWorker_SP coherent_worker) sessions_context     attendant attendant_callbacks
hs-src/SecondTransfer/Http2/Session.hs view
@@ -4,11 +4,14 @@ {-# LANGUAGE FlexibleContexts, Rank2Types, TemplateHaskell, OverloadedStrings #-} {-# OPTIONS_HADDOCK hide #-} module SecondTransfer.Http2.Session(-    http2Session+    http2ServerSession+    ,http2ClientSession     ,getFrameFromSession     ,sendFirstFrameToSession     ,sendMiddleFrameToSession     ,sendCommandToSession+    ,makeClientState+    ,pendingRequests_ClS      ,CoherentSession     ,SessionInput(..)@@ -20,6 +23,8 @@     ,SessionsCallbacks(..)     ,SessionsConfig(..)     ,ErrorCallback+    ,ClientState(..)+    ,SessionRole(..)      -- Internal stuff     ,OutputFrame@@ -29,7 +34,7 @@ #include "Logging.cpphs"  -- System grade utilities-import           Control.Concurrent                     (ThreadId, forkIO, threadDelay)+import           Control.Concurrent                     (ThreadId, forkIO) import           Control.Concurrent.Chan import           Control.Exception                      (throwTo) import qualified Control.Exception                      as E@@ -48,6 +53,7 @@ import qualified Data.HashTable.IO                      as H import qualified Data.IntSet                            as NS import           Data.Maybe                             (isJust)+import           Data.Typeable #ifndef IMPLICIT_MONOID import           Data.Monoid                            (mappend) #endif@@ -67,6 +73,8 @@ import           SecondTransfer.MainLoop.CoherentWorker import           SecondTransfer.MainLoop.Tokens import           SecondTransfer.MainLoop.Protocol+import           SecondTransfer.MainLoop.ClientPetitioner+ import           SecondTransfer.Sessions.Config import           SecondTransfer.Sessions.Internal       (sessionExceptionHandler,                                                          SessionsContext,@@ -74,8 +82,10 @@ import           SecondTransfer.Utils                   (unfoldChannelAndSource) import           SecondTransfer.Exception import qualified SecondTransfer.Utils.HTTPHeaders       as He-import           SecondTransfer.MainLoop.Logging        (logWithExclusivity, logit)+import           SecondTransfer.MainLoop.Logging        (logit) +--import           Debug.Trace+ -- Unfortunately the frame encoding API of Network.HTTP2 is a bit difficult to -- use :-( type OutputFrame = (NH2.EncodeInfo, NH2.FramePayload, Effect)@@ -93,11 +103,16 @@ -- end of data. Middle value is delay in microseconds type DataOutputToConveyor = (GlobalStreamId, Maybe B.ByteString, Effect) -+-- What to do regarding headers data HeaderOutputMessage =+    -- Send the headers of the principal stream     NormalResponse_HM (GlobalStreamId, MVar HeadersSent, Headers, Effect)-    --+    -- Send a push-promise     |PushPromise_HM   (GlobalStreamId, GlobalStreamId, Headers, Effect)+    -- Send a reset stream notification for the stream given below+    |ResetStream_HM   (GlobalStreamId, Effect)+    -- Send a GoAway, where last-stream is the stream given below.+    |GoAway_HM (GlobalStreamId, Effect)   -- Settings imposed by the peer@@ -179,13 +194,27 @@   deriving Show  --- temporary+-- Session output commands indicating something to the Framer data  SessionOutputCommand =-    CancelSession_SOC+    -- Command sent to the framer to close the session as harshly as possible. The framer+    -- will pick its own last valid stream.+    CancelSession_SOC NH2.ErrorCodeId+    -- Command sent to the framer to close the session specifying a specific last-stream.+    -- The reason used will be "NoError"+    |SpecificTerminate_SOC GlobalStreamId+    -- Command sent to the framer to notify it of a normal end of stream+    -- condition, this is used to naturally close each stream.     |FinishStream_SOC GlobalStreamId   deriving Show  +-- The role of a session is either server or client. There are small+-- differences between both.+data SessionRole  =+    Client_SR+   |Server_SR+  deriving (Eq,Show)+ -- Here is how we make a session type SessionMaker = SessionsContext -> IO Session @@ -195,7 +224,78 @@  data PostInputMechanism = PostInputMechanism (MVar (Maybe B.ByteString), InputDataStream) +------------- Regarding client state+type Message = (Headers,InputDataStream) +type RequestResult = Either ConnectionCloseReason Message++data ClientState = ClientState {+    -- Holds a queue. The client here puts the message (the request) and an VAR+    -- where it will receive the response.+    _pendingRequests_ClS       :: MVar (Message, MVar RequestResult)++    -- This is the id of the next available stream+    ,_nextStream_ClS           :: MVar Int++    -- Says if the client has been closed+    ,_clientIsClosed_ClS      :: MVar Bool++    -- A dictionary from stream id to the client which is waiting for the+    -- message+    ,_response2Waiter_ClS      ::HashTable GlobalStreamId (MVar RequestResult)+    }++makeLenses ''ClientState+++makeClientState :: IO ClientState+makeClientState = do+    request_chan <- newEmptyMVar+    next_stream_mvar <- newMVar 3+    client_is_closed_mvar <- newMVar False+    new_h <- H.new++    return ClientState {+        _pendingRequests_ClS = request_chan+        ,_nextStream_ClS = next_stream_mvar+        ,_response2Waiter_ClS = new_h+        ,_clientIsClosed_ClS = client_is_closed_mvar+        }+++handleRequest' :: ClientState -> Headers -> InputDataStream -> IO (Headers,InputDataStream)+handleRequest' client_state headers input_data = runReaderT (handleRequest headers input_data) client_state++type ClientMonad = ReaderT ClientState IO++handleRequest :: Headers -> InputDataStream -> ClientMonad Message+handleRequest headers input_data = do+    pending_requests <- view pendingRequests_ClS+    response_mvar <- liftIO $ newEmptyMVar++    liftIO $ E.catch+       (do+           {-# SCC cause1 #-} putMVar pending_requests ((headers,input_data),response_mvar)+           either_reason_or_message <- {-# SCC cause2 #-} liftIO $ takeMVar response_mvar+           case either_reason_or_message of+               Left break_reason -> E.throw $ ClientSessionAbortedException break_reason+               Right message -> return message+       )+       ( ( \ _ -> E.throw $ ClientSessionAbortedException SessionAlreadyClosed_CCR ):: E.BlockedIndefinitelyOnMVar -> IO Message )+++++instance ClientPetitioner ClientState where++    request = handleRequest'++-------------- Regarding client state+++-- SessionData is the actual state of the session, including the channels to the framer+-- outside.+-- -- NH2.Frame != Frame data SessionData = SessionData {     -- ATTENTION: Ignore the warning coming from here for now@@ -209,12 +309,15 @@      -- Use to encode     ,_toEncodeHeaders            :: MVar HP.DynamicTable+     -- And used to decode     ,_toDecodeHeaders            :: MVar HP.DynamicTable+     -- While I'm receiving headers, anything which     -- is not a header should end in the connection being     -- closed     ,_receivingHeaders           :: MVar (Maybe Int)+     -- _lastGoodStream is used both to report the last good stream in the     -- GoAwayFrame and to keep track of streams oppened by the client. In     -- other words, it contains the stream_id of the last valid client@@ -229,8 +332,13 @@     -- I make copies of it in different contexts, and as needed.     ,_forWorkerThread            :: WorkerThreadEnvironment +    -- When acting as a server, this is the handler for processing requests     ,_awareWorker                :: AwareWorker +    -- When acting as a client, this is where new requests are taken+    -- from+    ,_simpleClient               :: ClientState+     -- Some streams may be cancelled     ,_streamsCancelled           :: MVar NS.IntSet @@ -251,15 +359,28 @@      -- What is the next stream available for push?     ,_nextPushStream             :: MVar Int++    -- What role does this session has?+    ,_sessionRole                :: SessionRole+     }   makeLenses ''SessionData  +http2ServerSession :: AwareWorker -> Int -> SessionsContext -> IO Session+http2ServerSession a i sctx = http2Session Server_SR a (error "NotAClient") i sctx+++http2ClientSession :: ClientState -> Int -> SessionsContext -> IO Session+http2ClientSession client_state session_id sctx =+    http2Session Client_SR (error "NotAServer") client_state session_id sctx++ --                                v- {headers table size comes here!!}-http2Session :: AwareWorker -> Int -> SessionsContext -> IO Session-http2Session aware_worker session_id sessions_context =   do+http2Session :: SessionRole -> AwareWorker -> ClientState  -> Int -> SessionsContext -> IO Session+http2Session session_role aware_worker client_state session_id sessions_context =   do     session_input             <- newChan     session_output            <- newChan     session_output_mvar       <- newMVar session_output@@ -309,6 +430,7 @@         ,_stream2HeaderBlockFragment = stream_request_headers         ,_forWorkerThread            = for_worker_thread         ,_awareWorker                = aware_worker+        ,_simpleClient               = client_state         ,_streamsCancelled           = cancelled_streams_mvar         ,_stream2PostInputMechanism  = stream2postinputmechanism         ,_stream2WorkerThread        = stream2workerthread@@ -317,18 +439,60 @@         ,_sessionSettings            = session_settings         ,_lastGoodStream             = last_good_stream_mvar         ,_nextPushStream             = next_push_stream+        ,_sessionRole                = session_role         }      let+        io_exc_handler :: SessionComponent -> E.BlockedIndefinitelyOnMVar -> IO ()+        io_exc_handler _component _e = do+           case session_role of+               Server_SR -> do+                   -- sessionExceptionHandler component session_id sessions_context e+                   -- Just ignore this kind of exceptions here, very explicitly, as they should naturally+                   -- happen when the framer is closed+                   return ()++               Client_SR -> do+                   clientSideTerminate client_state IOChannelClosed_CCR++         exc_handler :: SessionComponent -> HTTP2SessionException -> IO ()-        exc_handler component e = sessionExceptionHandler component session_id sessions_context e+        exc_handler component e = do+            case session_role of+                Server_SR ->+                    sessionExceptionHandler component session_id sessions_context e++                Client_SR -> do+                    let+                        maybe_client_session_aborted :: Maybe ClientSessionAbortedException+                        maybe_client_session_aborted | HTTP2SessionException ee <- e  = cast ee++                        maybe_protocol_error :: Maybe HTTP2ProtocolException+                        maybe_protocol_error | HTTP2SessionException ee <- e = cast ee++                    case maybe_client_session_aborted of+                        Just (ClientSessionAbortedException reason) -> do+                            clientSideTerminate client_state reason+++                        Nothing ->+                            if isJust maybe_protocol_error+                                then+                                    clientSideTerminate client_state ProtocolError_CCR+                                else do+                                    E.throw e+         exc_guard :: SessionComponent -> IO () -> IO ()-        exc_guard component action = E.catch-            action-            (\e -> do-                -- INSTRUMENTATION( errorM "HTTP2.Session" "Exception processed" )-                exc_handler component e-            )+        exc_guard component action =+                E.catch+                    (E.catch+                         action+                         (\e -> do+                             -- INSTRUMENTATION( errorM "HTTP2.Session" "Exception processed" )+                             exc_handler component e+                         )+                    )+                    (io_exc_handler component)          use_chunk_length =  sessions_context ^.  sessionsConfig . dataFrameSize @@ -345,6 +509,13 @@     forkIO $ exc_guard SessionDataOutputThread_HTTP2SessionComponent            $ dataOutputThread use_chunk_length data_output session_output_mvar +    -- If I'm a client, I also need a thread to poll for requests+    when (session_role == Client_SR) $ do+         forkIO $ exc_guard SessionClientPollThread_HTTP2SessionComponent+                $ sessionPollThread session_data headers_output+         return ()++     -- The two previous threads fill the session_output argument below (they write to it)     -- the session machinery in the other end is in charge of sending that data through the     -- socket.@@ -362,18 +533,19 @@     -- these values in a closure...     session_input             <- view sessionInput -    decode_headers_table_mvar <- view toDecodeHeaders-    stream_request_headers    <- view stream2HeaderBlockFragment+    -- decode_headers_table_mvar <- view toDecodeHeaders+    -- stream_request_headers    <- view stream2HeaderBlockFragment     cancelled_streams_mvar    <- view streamsCancelled-    coherent_worker           <- view awareWorker+    -- coherent_worker           <- view awareWorker -    for_worker_thread_uns     <- view forWorkerThread+    -- for_worker_thread_uns     <- view forWorkerThread     stream2workerthread       <- view stream2WorkerThread-    receiving_headers_mvar    <- view receivingHeaders-    last_good_stream_mvar     <- view lastGoodStream-    current_session_id        <- view sessionIdAtSession+    -- receiving_headers_mvar    <- view receivingHeaders+    -- last_good_stream_mvar     <- view lastGoodStream+    -- current_session_id        <- view sessionIdAtSession -    input                     <- liftIO $ readChan session_input+    input                     <- {-# SCC session_input #-} liftIO $ readChan session_input+    session_role              <- view sessionRole      case input of @@ -423,145 +595,19 @@         -- TODO: As it stands now, the server will happily start a new stream with         -- a CONTINUATION frame instead of a HEADERS frame. That's against the         -- protocol.-        MiddleFrame_SIC frame | Just (stream_id, bytes) <- isAboutHeaders frame -> do-            -- Just append the frames to streamRequestHeaders-            opens_stream <- appendHeaderFragmentBlock stream_id bytes--            if opens_stream-              then do-                maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar-                case maybe_rcv_headers_of of-                  Just _ -> do-                    -- Bad client, it is already sending headers-                    -- and trying to open another one-                    closeConnectionBecauseIsInvalid NH2.ProtocolError-                    -- An exception will be thrown above, so to not complicate-                    -- control flow here too much.-                  Nothing -> do-                    -- Signal that we are receiving headers now, for this stream-                    liftIO $ putMVar receiving_headers_mvar (Just stream_id)-                    -- And go to check if the stream id is valid-                    last_good_stream <- liftIO $ takeMVar last_good_stream_mvar-                    if (odd stream_id ) && (stream_id > last_good_stream)-                      then do-                        -- We are golden, set the new good stream-                        liftIO $ putMVar last_good_stream_mvar (stream_id)-                      else do-                        -- We are not golden-                        -- INSTRUMENTATION( errorM "HTTP2.Session" "Protocol error: bad stream id")-                        closeConnectionBecauseIsInvalid NH2.ProtocolError-                -- So, now we know, we are ignoring priority frames at the moment-                -- liftIO . logit $ "Priority: " `mappend` (pack . show $ getHeadersPriority frame)-              else do-                maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar-                case maybe_rcv_headers_of of-                    Just a_stream_id | a_stream_id == stream_id -> do-                        -- Nothing to complain about-                        liftIO $ putMVar receiving_headers_mvar maybe_rcv_headers_of--                    Nothing -> error "InternalError, this should be set"--            if frameEndsHeaders frame then-              do-                -- Ok, let it be known that we are not receiving more headers-                liftIO $ modifyMVar_-                    receiving_headers_mvar-                    (\ _ -> return Nothing )-                -- Lets get a time-                headers_arrived_time      <- liftIO $ getTime Monotonic-                -- Let's decode the headers-                let for_worker_thread     = set streamId stream_id for_worker_thread_uns-                headers_bytes             <- getHeaderBytes stream_id-                dyn_table                 <- liftIO $ takeMVar decode_headers_table_mvar-                (new_table, header_list ) <- liftIO $ HP.decodeHeader dyn_table headers_bytes-#if LOGIT_SWITCH_TIMINGS-                let-                    (Just path) = He.fetchHeader header_list ":path"-                liftIO $ logit $ (pack . show $ stream_id ) `mappend` " -> " `mappend` path-#endif-                -- /DEBUG-                -- Good moment to remove the headers from the table.... we don't want a space-                -- leak here-                liftIO $ do-                    H.delete stream_request_headers stream_id-                    putMVar decode_headers_table_mvar new_table--                -- TODO: Validate headers, abort session if the headers are invalid.-                -- Otherwise other invariants will break!!-                -- THIS IS PROBABLY THE BEST PLACE FOR DOING IT.-                let-                    headers_editor = He.fromList header_list--                maybe_good_headers_editor <- validateIncomingHeaders headers_editor--                good_headers <- case maybe_good_headers_editor of-                    Just yes_they_are_good -> return yes_they_are_good-                    Nothing -> closeConnectionBecauseIsInvalid NH2.ProtocolError--                -- Add any extra headers, on demand-                headers_extra_good      <- addExtraHeaders good_headers-                let-                    header_list_after = He.toList headers_extra_good-                -- liftIO $ putStrLn $ "header list after " ++ (show header_list_after)--                -- If the headers end the request....-                post_data_source <- if not (frameEndsStream frame)-                  then do-                    mechanism <- createMechanismForStream stream_id-                    let source = postDataSourceFromMechanism mechanism-                    return $ Just source-                  else do-                    return Nothing--                let-                  perception = Perception {-                      _startedTime_Pr = headers_arrived_time,-                      _streamId_Pr = stream_id,-                      _sessionId_Pr = current_session_id,-                      _protocol_Pr = Http2_HPV,-                      _anouncedProtocols_Pr = Nothing-                      }-                  request = Request {-                      _headers_RQ = header_list_after,-                      _inputData_RQ = post_data_source,-                      _perception_RQ = perception-                      }--                -- TODO: Handle the cases where a request tries to send data-                -- even if the method doesn't allow for data.--                -- I'm clear to start the worker, in its own thread-                ---                -- NOTE: Some late internal errors from the worker thread are-                --       handled here by clossing the session.-                ---                -- TODO: Log exceptions handled here.-                liftIO $ do-                    thread_id <- forkIO $ E.catch-                        (runReaderT-                            (workerThread-                                   request-                                   coherent_worker)-                            for_worker_thread-                        )-                        (-                            (   \ _ ->  do-                                -- Actions to take when the thread breaks....-                                writeChan session_input InternalAbort_SIC-                            )-                            :: HTTP500PrecursorException -> IO ()-                        )--                    H.insert stream2workerthread stream_id thread_id--                return ()-            else-                -- Frame doesn't end the headers... it was added before... so-                -- probably do nothing-                return ()+        MiddleFrame_SIC frame | Just (_stream_id, _bytes) <- isAboutHeaders frame ->+            case session_role of+                Server_SR -> do+                    -- Just append the frames to streamRequestHeaders+                    serverProcessIncomingHeaders frame+                    continue -            continue+                Client_SR -> do+                    clientProcessIncomingHeaders frame+                    continue +        -- Peer can order a stream aborted, meaning that we shall not send any more data+        -- on it.         MiddleFrame_SIC frame@(NH2.Frame _ (NH2.RSTStreamFrame _error_code_id)) -> do             let stream_id = streamIdFromFrame frame             liftIO $ do@@ -659,6 +705,19 @@             handleSettingsFrame settings_list             continue +        MiddleFrame_SIC (NH2.Frame _ (NH2.GoAwayFrame _ _ _ ))+            | Server_SR <- session_role -> do+                -- I was sent a go away, so go-away...+                closeConnectionBecauseIsInvalid NH2.NoError+                return ()++            | Client_SR <- session_role -> do+                -- I was sent a go away, so go-away, but use the kind of exception+                -- that will unwind the stack gracefully+                closeConnectionForClient NH2.NoError+                return ()++         MiddleFrame_SIC somethingelse ->  unlessReceivingHeaders $ do             -- An undhandled case here....             -- liftIO . logit $ "Strange frame:" `mappend` (pack . show $ somethingelse)@@ -691,6 +750,257 @@             (NH2.SettingsFrame [])  +serverProcessIncomingHeaders :: NH2.Frame ->  ReaderT SessionData IO ()+serverProcessIncomingHeaders frame | Just (stream_id, bytes) <- isAboutHeaders frame = do++    -- Just append the frames to streamRequestHeaders+    opens_stream              <- appendHeaderFragmentBlock stream_id bytes+    receiving_headers_mvar    <- view receivingHeaders+    last_good_stream_mvar     <- view lastGoodStream+    for_worker_thread_uns     <- view forWorkerThread+    decode_headers_table_mvar <- view toDecodeHeaders+    stream_request_headers    <- view stream2HeaderBlockFragment+    coherent_worker           <- view awareWorker+    current_session_id        <- view sessionIdAtSession+    session_input             <- view sessionInput+    stream2workerthread       <- view stream2WorkerThread++    if opens_stream+      then do+        maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar+        case maybe_rcv_headers_of of+          Just _ -> do+            -- Bad peer, it is already sending headers+            -- and trying to open another one+            {-# SCC ccB1 #-} closeConnectionBecauseIsInvalid NH2.ProtocolError+            -- An exception will be thrown above, so to not complicate+            -- control flow here too much.+          Nothing -> do+            -- Signal that we are receiving headers now, for this stream+            liftIO $ putMVar receiving_headers_mvar (Just stream_id)+            -- And go to check if the stream id is valid+            last_good_stream <- liftIO $ takeMVar last_good_stream_mvar+            if (odd stream_id ) && (stream_id > last_good_stream)+              then do+                -- We are golden, set the new good stream+                liftIO $ putMVar last_good_stream_mvar (stream_id)+              else do+                -- We are not golden+                -- INSTRUMENTATION( errorM "HTTP2.Session" "Protocol error: bad stream id")+                {-# SCC ccB2 #-} closeConnectionBecauseIsInvalid NH2.ProtocolError+        -- So, now we know, we are ignoring priority frames at the moment+        -- liftIO . logit $ "Priority: " `mappend` (pack . show $ getHeadersPriority frame)+      else do+        maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar+        case maybe_rcv_headers_of of+            Just a_stream_id | a_stream_id == stream_id -> do+                -- Nothing to complain about+                liftIO $ putMVar receiving_headers_mvar maybe_rcv_headers_of++            Nothing -> error "InternalError, this should be set"++    if frameEndsHeaders frame then+      do+        -- Ok, let it be known that we are not receiving more headers+        liftIO $ modifyMVar_+            receiving_headers_mvar+            (\ _ -> return Nothing )+        -- Lets get a time+        headers_arrived_time      <- liftIO $ getTime Monotonic+        -- Let's decode the headers+        let for_worker_thread     = set streamId stream_id for_worker_thread_uns+        headers_bytes             <- getHeaderBytes stream_id+        dyn_table                 <- liftIO $ takeMVar decode_headers_table_mvar+        (new_table, header_list ) <- liftIO $ HP.decodeHeader dyn_table headers_bytes+#if LOGIT_SWITCH_TIMINGS+        let+            (Just path) = He.fetchHeader header_list ":path"+        liftIO $ logit $ (pack . show $ stream_id ) `mappend` " -> " `mappend` path+#endif+        -- /DEBUG+        -- Good moment to remove the headers from the table.... we don't want a space+        -- leak here+        liftIO $ do+            H.delete stream_request_headers stream_id+            putMVar decode_headers_table_mvar new_table++        -- TODO: Validate headers, abort session if the headers are invalid.+        -- Otherwise other invariants will break!!+        -- THIS IS PROBABLY THE BEST PLACE FOR DOING IT.+        let+            headers_editor = He.fromList header_list++        maybe_good_headers_editor <- validateIncomingHeadersServer headers_editor++        good_headers <- case maybe_good_headers_editor of+            Just yes_they_are_good -> return yes_they_are_good+            Nothing -> {-# SCC ccB3 #-} closeConnectionBecauseIsInvalid NH2.ProtocolError++        -- Add any extra headers, on demand+        headers_extra_good      <- addExtraHeaders good_headers+        let+            header_list_after = He.toList headers_extra_good+        -- liftIO $ putStrLn $ "header list after " ++ (show header_list_after)++        -- If the headers end the request....+        post_data_source <- if not (frameEndsStream frame)+          then do+            mechanism <- createMechanismForStream stream_id+            let source = postDataSourceFromMechanism mechanism+            return $ Just source+          else do+            return Nothing++        let+          perception = Perception {+              _startedTime_Pr = headers_arrived_time,+              _streamId_Pr = stream_id,+              _sessionId_Pr = current_session_id,+              _protocol_Pr = Http2_HPV,+              _anouncedProtocols_Pr = Nothing+              }+          request = Request {+              _headers_RQ = header_list_after,+              _inputData_RQ = post_data_source,+              _perception_RQ = perception+              }++        -- TODO: Handle the cases where a request tries to send data+        -- even if the method doesn't allow for data.++        -- I'm clear to start the worker, in its own thread+        --+        -- NOTE: Some late internal errors from the worker thread are+        --       handled here by clossing the session.+        --+        -- TODO: Log exceptions handled here.+        liftIO $ do+            thread_id <- forkIO $ E.catch+                (runReaderT+                    (workerThread+                           request+                           coherent_worker)+                    for_worker_thread+                )+                (+                    (   \ _ ->  do+                        -- Actions to take when the thread breaks....+                        writeChan session_input InternalAbort_SIC+                    )+                    :: HTTP500PrecursorException -> IO ()+                )++            H.insert stream2workerthread stream_id thread_id++        return ()+    else+        -- Frame doesn't end the headers... it was added before... so+        -- probably do nothing+        return ()+++clientProcessIncomingHeaders :: NH2.Frame ->  ReaderT SessionData IO ()+clientProcessIncomingHeaders frame | Just (stream_id, bytes) <- isAboutHeaders frame = do++    opens_stream              <- appendHeaderFragmentBlock stream_id bytes+    receiving_headers_mvar    <- view receivingHeaders+    last_good_stream_mvar     <- view lastGoodStream+    decode_headers_table_mvar <- view toDecodeHeaders+    stream_request_headers    <- view stream2HeaderBlockFragment+    response2waiter           <- view (simpleClient . response2Waiter_ClS)++    if opens_stream+      then do+        maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar+        case maybe_rcv_headers_of of+          Just _ -> do+            -- Bad peer, it is already sending headers+            -- and trying to open another one+            closeConnectionBecauseIsInvalid NH2.ProtocolError+            -- An exception will be thrown above, so to not complicate+            -- control flow here too much.+          Nothing -> do+            -- Signal that we are receiving headers now, for this stream+            liftIO $ putMVar receiving_headers_mvar (Just stream_id)+            -- And go to check if the stream id is valid+            last_good_stream <- liftIO $ takeMVar last_good_stream_mvar+            stream_initiated_by_client <- streamInitiatedByClient stream_id+            if (odd stream_id ) && stream_initiated_by_client+              then do+                -- We are golden, set the new good stream+                liftIO $ putMVar last_good_stream_mvar (stream_id)+              else do+                -- We are not golden+                -- TODO: Control for pushed streams here.+                closeConnectionBecauseIsInvalid NH2.ProtocolError+        -- So, now we know, we are ignoring priority frames at the moment+        -- liftIO . logit $ "Priority: " `mappend` (pack . show $ getHeadersPriority frame)+      else do+        maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar+        case maybe_rcv_headers_of of+            Just a_stream_id | a_stream_id == stream_id -> do+                -- Nothing to complain about+                liftIO $ putMVar receiving_headers_mvar maybe_rcv_headers_of++            Nothing -> error "InternalError, this should be set"++    if frameEndsHeaders frame then+      do+        -- Ok, let it be known that we are not receiving more headers+        liftIO $ modifyMVar_+            receiving_headers_mvar+            (\ _ -> return Nothing )+        -- Lets get a time+        -- headers_arrived_time      <- liftIO $ getTime Monotonic+        -- Let's decode the headers+        headers_bytes             <- getHeaderBytes stream_id+        dyn_table                 <- liftIO $ takeMVar decode_headers_table_mvar+        (new_table, header_list ) <- liftIO $ HP.decodeHeader dyn_table headers_bytes++        -- Good moment to remove the headers from the table.... we don't want a space+        -- leak here+        liftIO $ do+            H.delete stream_request_headers stream_id+            putMVar decode_headers_table_mvar new_table++        -- TODO: Validate headers, abort session if the headers are invalid.+        -- Otherwise other invariants will break!!+        -- THIS IS PROBABLY THE BEST PLACE FOR DOING IT.+        let+            headers_editor = He.fromList header_list++        maybe_good_headers_editor <- validateIncomingHeadersClient headers_editor++        good_headers <- case maybe_good_headers_editor of+            Just yes_they_are_good -> return yes_they_are_good+            Nothing -> closeConnectionBecauseIsInvalid NH2.ProtocolError+++        -- If the headers end the request....+        post_data_source <- if not (frameEndsStream frame)+          then do+            mechanism <-  createMechanismForStream stream_id+            return $ postDataSourceFromMechanism mechanism+          else+            return $ return ()++        (Just response_mvar) <- liftIO $ H.lookup response2waiter stream_id+        liftIO $ putMVar response_mvar $ Right (He.toList good_headers, post_data_source)++        return ()+    else+        -- Frame doesn't end the headers... it was added before... so+        -- probably do nothing+        return ()+++streamInitiatedByClient :: GlobalStreamId -> ReaderT SessionData IO Bool+streamInitiatedByClient stream_id = do+    response2waiter <- view (simpleClient . response2Waiter_ClS)+    response_handle_maybe <- liftIO $ H.lookup response2waiter stream_id+    return $ isJust response_handle_maybe++ sendOutFrame :: NH2.EncodeInfo -> NH2.FramePayload  -> ReaderT SessionData IO () sendOutFrame encode_info payload = do     session_output_mvar <- view sessionOutput@@ -730,8 +1040,9 @@         else return headers_editor  -validateIncomingHeaders :: He.HeaderEditor -> ReaderT SessionData IO (Maybe He.HeaderEditor)-validateIncomingHeaders headers_editor = do+-- TODO: Close connection on unexepcted pseudo-headers+validateIncomingHeadersServer :: He.HeaderEditor -> ReaderT SessionData IO (Maybe He.HeaderEditor)+validateIncomingHeadersServer headers_editor = do     -- Check that the headers block comes with all mandatory headers.     -- Right now I'm not checking that they come in the mandatory order though...     --@@ -757,7 +1068,27 @@         else             return Nothing +validateIncomingHeadersClient :: He.HeaderEditor -> ReaderT SessionData IO (Maybe He.HeaderEditor)+validateIncomingHeadersClient headers_editor = do+    -- Check that the headers block comes with all mandatory headers.+    -- Right now I'm not checking that they come in the mandatory order though...+    --+    -- Notice that this function will transform a "host" header to an ":authority"+    -- one.+    let+        h1 = He.replaceHostByAuthority headers_editor+        -- Check that headers are lowercase+        headers_are_lowercase = He.headersAreLowercaseAtHeaderEditor headers_editor+        -- Check that we have mandatory headers+        maybe_status = h1 ^. (He.headerLens ":status") +    if (isJust maybe_status)+        then+            return (Just h1)+        else+            return Nothing++ -- Sends a GO_AWAY frame and raises an exception, effectively terminating the input -- thread of the session. closeConnectionBecauseIsInvalid :: NH2.ErrorCodeId -> ReaderT SessionData IO a@@ -791,7 +1122,7 @@         -- that it stops accepting frames from connected sources         -- (Streams?)         session_output <- takeMVar session_output_mvar-        writeChan session_output $ Left CancelSession_SOC+        writeChan session_output $ Left (CancelSession_SOC error_code)         putMVar session_output_mvar session_output          -- And unwind the input thread in the session, so that the@@ -799,6 +1130,59 @@         E.throw HTTP2ProtocolException  +-- Sends a GO_AWAY frame and raises an exception, effectively terminating the input+-- thread of the session. This one for the client is different because it throws+-- an exception of type ClientSessionAbortedException+closeConnectionForClient :: NH2.ErrorCodeId -> ReaderT SessionData IO a+closeConnectionForClient error_code = do+    let+        use_reason = case error_code of+            NH2.NoError -> NormalTermination_CCR+            _           -> ProtocolError_CCR++    -- liftIO $ errorM "HTTP2.Session" "closeConnectionBecauseIsInvalid called!"+    last_good_stream_mvar <- view lastGoodStream+    last_good_stream <- liftIO $ takeMVar last_good_stream_mvar+    session_output_mvar <- view sessionOutput+    stream2workerthread <- view stream2WorkerThread+    sendOutFrame+        (NH2.EncodeInfo+            NH2.defaultFlags+            0+            Nothing+        )+        (NH2.GoAwayFrame+            last_good_stream+            error_code+            ""+        )++    client_is_closed_mvar <- view (simpleClient . clientIsClosed_ClS )++    liftIO $ do+        -- Close all active threads for this session+        H.mapM_+            ( \(_stream_id, thread_id) ->+                    throwTo thread_id StreamCancelledException+            )+            stream2workerthread++        -- Notify the framer that the session is closing, so+        -- that it stops accepting frames from connected sources+        -- (Streams?)+        session_output <- takeMVar session_output_mvar+        writeChan session_output . Left . CancelSession_SOC $ error_code+        putMVar session_output_mvar session_output++        -- Let's also mark the session as closed from the client side, so that+        -- any further requests end with the correct exception+        modifyMVar_ client_is_closed_mvar (\ _ -> return True)++        -- And unwind the input thread in the session, so that the+        -- exception handler runs....+        E.throw $ ClientSessionAbortedException use_reason++ frameEndsStream :: InputFrame -> Bool frameEndsStream (NH2.Frame (NH2.FrameHeader _ flags _) _)  = NH2.testEndStream flags @@ -839,7 +1223,10 @@     case pim_maybe of          Just (PostInputMechanism (chan, _))  ->-            liftIO $ putMVar chan Nothing+            liftIO $ do+                putMVar chan Nothing+                -- Not sure if this will work+                H.delete  stream2postinputmechanism stream_id          Nothing ->             -- TODO: This is a protocol error, handle it properly@@ -929,6 +1316,37 @@      -- Pieces of the header     let+       effects              = principal_stream ^. effect_PS+       interrupt_maybe      = effects          ^. interrupt_Ef++++    case interrupt_maybe of++        Nothing -> do+            normallyHandleStream principal_stream++        Just (InterruptConnectionAfter_IEf) -> do+            normallyHandleStream principal_stream+            liftIO . writeChan headers_output $ GoAway_HM (stream_id, effects)++        Just (InterruptConnectionNow_IEf) -> do+            -- Not one hundred-percent sure of this being correct, but we don't want+            -- to acknowledge reception of this stream then+            let use_stream_id = stream_id - 1+            liftIO . writeChan headers_output $ GoAway_HM (use_stream_id, effects)+++normallyHandleStream :: PrincipalStream -> WorkerMonad ()+normallyHandleStream principal_stream = do+    headers_output <- view headersOutput+    stream_id      <- view streamId+    session_settings_mvar <- view sessionSettings_WTE+    session_settings <- liftIO $ readMVar session_settings_mvar+    next_push_stream_mvar <-  view nextPushStream_WTE++    -- Pieces of the header+    let        headers              = principal_stream ^. headers_PS        data_and_conclusion  = principal_stream ^. dataAndConclusion_PS        effects              = principal_stream ^. effect_PS@@ -936,6 +1354,8 @@         can_push             = session_settings ^. pushEnabled +      -- This gets executed in normal conditions, when no interruption is required.+     -- There are several possible moments where the PUSH_PROMISEs can be sent,     -- but a default safe one is before sending the response HEADERS, so that     -- LINK headers in the response come after any potential promises.@@ -1060,10 +1480,13 @@             Nothing ->                 -- This is how we finish sending data                 liftIO $ putMVar data_output (stream_id, Nothing, effect)-            Just bytes -> do-                liftIO $ do-                    putMVar data_output (stream_id, Just bytes, effect)-                consumer data_output+            Just bytes+                | B.length bytes > 0 -> do+                    liftIO $ do+                        putMVar data_output (stream_id, Just bytes, effect)+                    consumer data_output+                | otherwise ->+                    liftIO $ putMVar data_output (stream_id, Nothing, effect)   -- Returns if the frame is the first in the stream@@ -1121,8 +1544,8 @@      use_chunk_length <- view $ sessionsContext .  sessionsConfig . dataFrameSize -    header_output_request <- liftIO $ readChan input_chan-    case header_output_request of+    header_output_request <- {-# SCC input_chan  #-} liftIO $ readChan input_chan+    {-# SCC case_ #-} case header_output_request of         NormalResponse_HM (stream_id, headers_ready_mvar, headers, effect)  -> do              -- First encode the headers using the table@@ -1148,6 +1571,15 @@                     putMVar headers_ready_mvar HeadersSent                     ) +        GoAway_HM (stream_id, _effect) -> do+           -- This is in charge of sending an interrupt message to the framer+           let+               message = Left (SpecificTerminate_SOC stream_id)+           liftIO . withMVar session_output_mvar $+               (\session_output -> do+                   writeChan session_output message+               )+         PushPromise_HM (parent_stream_id, child_stream_id, promise_headers, effect) -> do              encode_dyn_table_mvar <- view toEncodeHeaders@@ -1287,14 +1719,111 @@     writeContinuations fragments stream_id effect =       mapM_ (\ fragment ->                   withLockedSessionOutput-                      (\ session_output -> writeChan session_output $ Right (-                           NH2.EncodeInfo {-                               NH2.encodeFlags     = NH2.defaultFlags-                               ,NH2.encodeStreamId = stream_id-                               ,NH2.encodePadding  = Nothing-                               },-                           NH2.DataFrame fragment,-                           effect )+                      (\ session_output -> do+                             -- TODO:+                             when (B.length fragment == use_chunk_length) $ do+                                 -- Force tons and tons of ping frames to see if we get less delays due to+                                 -- congestion...+                                 writeChan session_output $ Right (+                                     NH2.EncodeInfo {+                                         NH2.encodeFlags     = NH2.defaultFlags+                                         ,NH2.encodeStreamId = 0+                                         ,NH2.encodePadding  = Nothing+                                         },+                                     NH2.PingFrame "talk  on",+                                     effect )++                             writeChan session_output $ Right (+                                 NH2.EncodeInfo {+                                     NH2.encodeFlags     = NH2.defaultFlags+                                     ,NH2.encodeStreamId = stream_id+                                     ,NH2.encodePadding  = Nothing+                                     },+                                 NH2.DataFrame fragment,+                                 effect )                       )              )              fragments+++sessionPollThread :: SessionData -> Chan HeaderOutputMessage -> IO ()+sessionPollThread  session_data headers_output = do+    let+        pending_requests_mvar  = session_data ^. (simpleClient . pendingRequests_ClS)+        client_next_stream_mvar  = session_data ^. (simpleClient . nextStream_ClS)+        response2waiter       = session_data ^. (simpleClient . response2Waiter_ClS)+        for_worker_thread  = session_data ^. forWorkerThread+        session_input  = session_data ^. sessionInput++    ((headers, input_data_stream), response_mvar) <- takeMVar pending_requests_mvar++    new_stream_id <- modifyMVar client_next_stream_mvar (\ n -> return (n + 1, n) )+    let+        worker_environment = set streamId new_stream_id for_worker_thread+        effects = defaultEffects++    -- Store the response place+    new_stream_id `seq` H.insert response2waiter new_stream_id response_mvar++    -- Start by sending the headers of the request+    headers_sent <- liftIO  newEmptyMVar+    liftIO $ writeChan headers_output $ NormalResponse_HM (new_stream_id, headers_sent, headers, effects)++    liftIO . forkIO $ E.catch+        (runReaderT+            (clientWorkerThread new_stream_id effects headers_sent input_data_stream )+            worker_environment+        )+        (+            (   \ _ ->  do+                -- Actions to take when the thread breaks....+                writeChan session_input InternalAbort_SIC+            )+            :: HTTP500PrecursorException -> IO ()+        )++    sessionPollThread session_data headers_output+++clientWorkerThread :: GlobalStreamId -> Effect -> MVar HeadersSent -> InputDataStream -> WorkerMonad ()+clientWorkerThread stream_id effects headers_sent input_data_stream = do+    -- And now work on sending the data, if any...+    runConduit $+        transPipe liftIO input_data_stream+        `fuseBothMaybe`+        sendDataOfStream stream_id headers_sent effects+    return ()+++clientSideTerminate  :: ClientState -> ConnectionCloseReason  -> IO ()+clientSideTerminate client_state reason = do+    let+        terminateOnQueue :: ConnectionCloseReason -> MVar (Message, MVar RequestResult) -> IO ()+        terminateOnQueue reason' q = do+            w <- tryTakeMVar q+            case w of+                Just (_, m) -> do+                    tryPutMVar m (Left reason')+                    return ()++                Nothing ->+                    return ()+++        terminate :: ConnectionCloseReason -> IO ()+        terminate reason' = do+            terminateOnQueue reason' (client_state ^. pendingRequests_ClS)++            -- Kill any waiters in the map+            H.mapM_+                (\(_k,v) -> tryPutMVar v (Left reason))+                (client_state ^. response2Waiter_ClS)++        client_is_closed_mvar = client_state ^. clientIsClosed_ClS++    -- Let's also mark the session as closed from the client side, so that+    -- any further requests end with the correct exception+    modifyMVar_ client_is_closed_mvar $ \ client_is_closed -> do+            unless client_is_closed $+                terminate reason+            return True
+ hs-src/SecondTransfer/Http2/SimpleClient.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}+-- TODO: Remove this file, it is not needed!!+module SecondTransfer.Http2.SimpleClient (+    ClientState(..)  -- reduced functionality is exported to the user, but+                     -- the framework may need a lot.+    ,pendingRequests_ClS+                                   ) where+++import           Control.Lens+import           Control.Concurrent.Chan+-- System grade utilities+import           Control.Concurrent                     (ThreadId, forkIO)+import           Control.Concurrent.Chan+import           Control.Exception                      (throwTo)+import qualified Control.Exception                      as E+import           Control.Monad                          (forever,unless, when, mapM_, forM, forM_)+import           Control.Monad.IO.Class                 (liftIO)+import           Control.DeepSeq                        ( ($!!), deepseq )+import           Control.Monad.Trans.Reader+-- import           Control.Monad.Catch                    (throwM)+import           Control.Concurrent.MVar+++import           Data.Conduit+--import++import           SecondTransfer.MainLoop.ClientPetitioner+import           SecondTransfer.MainLoop.CoherentWorker                       (Headers,InputDataStream)+import           SecondTransfer.Http2.Session                                 (ClientState(..),+                                                                               pendingRequests_ClS+                                                                              )++void::()+void = ()
+ hs-src/SecondTransfer/MainLoop/ClientPetitioner.hs view
@@ -0,0 +1,15 @@+module SecondTransfer.MainLoop.ClientPetitioner(+    ClientPetitioner(..)+                                               ) where+++--import           Control.Lens+--import           Data.Conduit+import qualified Data.ByteString                                    as B+++import SecondTransfer.MainLoop.CoherentWorker                       (Headers,InputDataStream)+++class ClientPetitioner a where+    request :: a -> Headers -> InputDataStream -> IO (Headers,InputDataStream)
hs-src/SecondTransfer/MainLoop/CoherentWorker.hs view
@@ -29,6 +29,7 @@     , TupledPrincipalStream     , TupledRequest     , FragmentDeliveryCallback+    , InterruptEffect(..)      , headers_RQ     , inputData_RQ@@ -47,6 +48,7 @@     , protocol_Pr     , fragmentDeliveryCallback_Ef     , priorityEffect_Ef+    , interrupt_Ef      , defaultEffects     , coherentToAwareWorker@@ -83,7 +85,7 @@ type Headers = [Header]  -- |This is a Source conduit (see Haskell Data.Conduit library from Michael Snoyman)--- that you can use to retrieve the data sent by the client piece-wise.+-- that you can use to retrieve the data sent by the peer piece-wise. type InputDataStream = Source IO B.ByteString  @@ -157,16 +159,30 @@ type FragmentDeliveryCallback = Int -> TimeSpec -> IO ()  +-- | Types of interrupt effects that can be signaled by aware workers. These include whole+--   connection shutdowns and stream resets. In all the cases, the reason given will be+--   NO_ERROR.+data InterruptEffect = InterruptConnectionAfter_IEf   -- ^ Close and send GoAway /after/ this stream finishes delivery+                       |InterruptConnectionNow_IEf    -- ^ Close and send GoAway /without/ delivering this stream.  This implies that+                                                      --   other fields of the PrincipalStream record will be ignored.+                    -- |InterruptThisStream_IEf       -- ^ Just reset this stream, disabled for now.++ -- | Sometimes a response needs to be handled a bit specially, --   for example by reporting delivery details back to the worker data Effect = Effect {+  -- | A callback to be called whenever a data-packet for this stream is called.   _fragmentDeliveryCallback_Ef :: Maybe FragmentDeliveryCallback -  -- In certain circunstances a stream can use an internal priority,+  -- | In certain circunstances a stream can use an internal priority,   -- not given by the browser and the protocol. Lowest values here are   -- given more priority. Default (when Nothing) is given zero. Cases   -- with negative numbers also work.   ,_priorityEffect_Ef :: Maybe Int++  -- | There are situations when it is desirable to close a stream or the entire+  --   connection. Use this member to indicate that.+  ,_interrupt_Ef :: Maybe InterruptEffect   }  makeLenses ''Effect@@ -174,13 +190,14 @@ defaultEffects :: Effect defaultEffects = Effect {   _fragmentDeliveryCallback_Ef = Nothing,-  _priorityEffect_Ef = Nothing+  _priorityEffect_Ef = Nothing,+  _interrupt_Ef = Nothing    }   -- | You use this type to answer a request. The `Headers` are thus response --   headers and they should contain the :status pseudo-header. The `PushedStreams`---   is a list of pushed streams...(I don't thaink that I'm handling those yet)+--   is a list of pushed streams... they will be pushed to the client. data PrincipalStream = PrincipalStream {   _headers_PS              :: Headers,   _pushedStreams_PS        :: PushedStreams,
hs-src/SecondTransfer/MainLoop/OpenSSL_TLS.hs view
@@ -301,7 +301,7 @@ smallWaitTime = 50000  -- provideActions :: Wired_Ptr -> IO (LB.ByteString -> IO (), Int -> IO B.ByteString, IO ())-provideActions :: Wired_Ptr -> IO AttendantCallbacks+provideActions :: Wired_Ptr -> IO IOCallbacks provideActions wired_ptr = do     can_write_mvar <- newMVar True     can_read_mvar  <- newMVar True@@ -370,9 +370,9 @@                       else                           return (False,False) -    return  AttendantCallbacks {-        _pushAction_AtC = pushAction,-        _pullAction_AtC = pullAction,-        _closeAction_AtC = closeAction,-        _bestEffortPullAction_AtC = bestEffortPullAction+    return  IOCallbacks {+        _pushAction_IOC = pushAction,+        _pullAction_IOC = pullAction,+        _closeAction_IOC = closeAction,+        _bestEffortPullAction_IOC = bestEffortPullAction     }
hs-src/SecondTransfer/MainLoop/PushPullType.hs view
@@ -3,14 +3,15 @@ module SecondTransfer.MainLoop.PushPullType (     PushAction     ,PullAction+    ,BestEffortPullAction     ,Attendant     ,CloseAction-    ,AttendantCallbacks(..)+    ,IOCallbacks(..) -    ,pushAction_AtC-    ,pullAction_AtC-    ,closeAction_AtC-    ,bestEffortPullAction_AtC+    ,pushAction_IOC+    ,pullAction_IOC+    ,closeAction_IOC+    ,bestEffortPullAction_IOC     ) where  @@ -49,22 +50,22 @@  -- | A set of functions describing how to do I/O in a session. --   As usual, we provide lenses accessors.-data AttendantCallbacks = AttendantCallbacks {+data IOCallbacks = IOCallbacks {     -- | put some data in the channel-    _pushAction_AtC               :: PushAction,+    _pushAction_IOC               :: PushAction,     -- | get exactly this much data from the channel. This function can     --   be used by HTTP/2 since lengths are pretty well built inside the     --   protocoll itself.-    _pullAction_AtC               :: PullAction,+    _pullAction_IOC               :: PullAction,     -- | pull data from the channel, as much as the TCP stack wants to provide.     --   we have no option but use this one when talking HTTP/1.1, where the best     --   way to know the length is to scan until a Content-Length is found.-    _bestEffortPullAction_AtC     :: BestEffortPullAction,+    _bestEffortPullAction_IOC     :: BestEffortPullAction,     -- | this is called when we wish to close the channel.-    _closeAction_AtC              :: CloseAction+    _closeAction_IOC              :: CloseAction     } -makeLenses ''AttendantCallbacks+makeLenses ''IOCallbacks  -- | This is an intermediate type. It represents what you obtain --   by combining something that speaks the protocol and an AwareWorker.@@ -83,4 +84,4 @@ --   This library supplies two of such Attendant factories, --   'SecondTransfer.Http1.http11Attendant' for --   HTTP 1.1 sessions, and 'SecondTransfer.Http2.http2Attendant' for HTTP/2 sessions.-type Attendant = AttendantCallbacks -> IO ()+type Attendant = IOCallbacks -> IO ()
hs-src/SecondTransfer/Sessions/Config.hs view
@@ -59,6 +59,7 @@     SessionInputThread_HTTP2SessionComponent     |SessionHeadersOutputThread_HTTP2SessionComponent     |SessionDataOutputThread_HTTP2SessionComponent+    |SessionClientPollThread_HTTP2SessionComponent     |Framer_HTTP2SessionComponent     |Session_HTTP11     deriving Show
hs-src/SecondTransfer/Sessions/Internal.hs view
@@ -10,7 +10,7 @@ import           Control.Lens            ((^.), makeLenses)  -import            System.Log.Logger+import           System.Log.Logger   @@ -45,7 +45,6 @@         }  - sessionExceptionHandler ::     E.Exception e => SessionComponent -> Int -> SessionsContext -> e -> IO () sessionExceptionHandler session_component session_id sessions_context e =@@ -61,7 +60,9 @@             )     in case maybe_error_callback of         Nothing ->-            errorM component_tag (show e)+            -- errorM component_tag (show e)+            -- When no callback, ignore the exception+            return ()          Just callback ->             callback error_tuple
second-transfer.cabal view
@@ -7,7 +7,7 @@ -- PVP       summary:      +-+------- breaking API changes --                         | | +----- non-breaking API additions --                         | | | +--- code changes with no API change-version     :              0.6.1.0+version     :              0.7.1.0  synopsis    :              Second Transfer HTTP/2 web server @@ -53,7 +53,7 @@ source-repository this   type:     git   location: git@github.com:alcidesv/second-transfer.git-  tag:      0.6.1.0+  tag:      0.7.1.0  library @@ -79,6 +79,8 @@                 , SecondTransfer.MainLoop.Tokens                 , SecondTransfer.MainLoop.Framer                 , SecondTransfer.MainLoop.Logging+                , SecondTransfer.MainLoop.ClientPetitioner+                 , SecondTransfer.Sessions.Internal                  , SecondTransfer.MainLoop.OpenSSL_TLS@@ -87,6 +89,7 @@                 , SecondTransfer.Http2.Framer                 , SecondTransfer.Http2.MakeAttendant                 , SecondTransfer.Http2.Session+                , SecondTransfer.Http2.SimpleClient                  , SecondTransfer.Http1.Session @@ -139,26 +142,25 @@   -- Base language which the package is written in.   default-language: Haskell2010 -  -- Very specific directory with the version of openssl-  -- that I'm using. As of February 2015-- this one is not-  -- commonly installed+  -- NOTE: Very specific directory with the version of openssl+  -- that I'm using. As of February 2015-- openssl 1.0.2 is not+  -- commonly installed. Update this path in your build  +  -- or otherwise your build will be broken.   include-dirs: /opt/openssl-1.0.2/include    c-sources: cbits/tlsinc.c -  -- cc-options: -fPIC -pthread -g -O0   if flag(debug)       cc-options: -O0 -g3       ld-options: -g3 -  -- cc-options: -g3 -O0--  -- ghc-options: -O2 -cpp  -pgmPcpphs  -optP--cpp   ghc-options: -pgmPcpphs  -optP--cpp    extra-libraries: ssl crypto -  -- NOTICE: Please fill-in with an-up-to date library path here+  -- NOTE: Please fill-in with an-up-to date library path here.+  -- It should point to openssl 1.0.2 or greater. See note for+  -- include-dirs above.   extra-lib-dirs: /opt/openssl-1.0.2/lib    include-dirs: macros/@@ -177,6 +179,7 @@   + Test-Suite hunit-tests   type               : exitcode-stdio-1.0   main-is            : hunit_tests.hs@@ -230,9 +233,14 @@                        , SecondTransfer.MainLoop.Tokens                        , SecondTransfer.MainLoop.Framer                        , SecondTransfer.MainLoop.Logging+                       , SecondTransfer.MainLoop.Protocol                        , SecondTransfer.Sessions.Internal+                       , SecondTransfer.MainLoop.ClientPetitioner                        , SecondTransfer.Utils                        , SecondTransfer.Http2.Framer                        , SecondTransfer.Http2.MakeAttendant                        , SecondTransfer.Http2.Session                        , SecondTransfer.Http1.Session+                       , Tests.HTTP1Parse+                       , Tests.HTTP2Session+                       , Tests.Utils
tests/tests-hs-src/SecondTransfer/Test/DecoySession.hs view
@@ -5,79 +5,203 @@   import           Control.Concurrent-import           Control.Concurrent.MVar-import           Control.Concurrent.Chan+import           Control.Concurrent.STM.TMVar+import           Control.Concurrent.STM.TChan import           Control.Exception-import           Control.Lens            (view, (^.))-import qualified Control.Lens            as L+import           Control.Lens                     (view, (^.), over)+import qualified Control.Lens                     as L+import           Control.Monad.STM                (atomically) -import qualified Network.HTTP2           as NH2-import qualified Network.HPACK           as HP+import qualified Network.HTTP2                    as NH2+import qualified Network.HPACK                    as HP -import qualified Data.ByteString         as B-import qualified Data.ByteString.Lazy    as LB+import qualified Data.ByteString                  as B+import qualified Data.ByteString.Lazy             as LB+import qualified Data.ByteString.Builder          as Bu+import qualified Data.Sequence                    as Sq+import           Data.Sequence                    ( (<|), (|>), ViewL(..), Seq )  import           SecondTransfer.Http2 import           SecondTransfer.Types+import           SecondTransfer.Http2.Session+import           SecondTransfer.Sessions.Config+import           SecondTransfer.Sessions.Internal+import           SecondTransfer.Http2.Framer import           SecondTransfer.MainLoop.Internal+import           Debug.Trace +-- "Internal imports"+import           SecondTransfer.MainLoop.CoherentWorker (defaultEffects) ++data DataContinuityEngine = DataContinuityEngine {+    _incoming_DCE :: Seq B.ByteString+    }+++L.makeLenses ''DataContinuityEngine+++emptyDCE :: DataContinuityEngine+emptyDCE =  DataContinuityEngine  Sq.empty+++dceAddData :: DataContinuityEngine -> B.ByteString -> DataContinuityEngine+dceAddData dce moredata | B.length moredata > 0 = over incoming_DCE ( |>  moredata ) dce+dceAddData dce moredata | otherwise             = dce+++tryGetThisManyData :: DataContinuityEngine -> Int -> Maybe (DataContinuityEngine, B.ByteString)+tryGetThisManyData dce n = let+    go_result = go n "" (dce ^. incoming_DCE)+    go :: Int -> Bu.Builder -> Seq B.ByteString -> Maybe (Seq B.ByteString, Bu.Builder)+    go n bld seq  | Sq.EmptyL <- Sq.viewl seq, n > 0   = Nothing+                  | Sq.EmptyL <- Sq.viewl seq, n == 0  = Just (seq, bld)+                  | lead :< rest <- Sq.viewl seq, n > B.length lead    = go (n - B.length lead) (bld `mappend` Bu.byteString lead) rest+                  | lead :< rest <- Sq.viewl seq, n == B.length lead   = Just ( rest, bld `mappend` Bu.byteString lead)+                  | lead :< rest <- Sq.viewl seq, n < B.length lead    = let+                      (tk, lv) = B.splitAt n lead+                      in Just ( lv <| rest, bld `mappend`  Bu.byteString tk  )+  in case go_result of+    Just (new_dce, bld ) -> Just+        (DataContinuityEngine new_dce,  LB.toStrict . Bu.toLazyByteString $ bld )++    Nothing -> Nothing+++dceIsEmpty :: DataContinuityEngine -> Bool+dceIsEmpty dce = Sq.null $ dce ^. incoming_DCE+++dceGetAllData :: DataContinuityEngine -> (DataContinuityEngine, B.ByteString)+dceGetAllData dce = (emptyDCE, all_together)+  where+    all_together = LB.toStrict . Bu.toLazyByteString .  L.foldMapOf (incoming_DCE . L.folded) Bu.byteString $ dce++ data DecoySession = DecoySession {-    _inputDataChannel   :: Chan B.ByteString-    ,_outputDataChannel :: Chan LB.ByteString-    ,_sessionThread     :: ThreadId-    ,_remainingOutputBit:: MVar B.ByteString-    ,_nh2Settings       :: NH2.Settings-    ,_sessionThrowed    :: MVar Bool-    ,_waiting           :: MVar ThreadId-    ,_encodeHeadersHere :: MVar HP.DynamicTable-    ,_decodeHeadersHere :: MVar HP.DynamicTable+    _inputDataChannel     :: TChan B.ByteString+    ,_outputDataChannel   :: TChan B.ByteString+    ,_sessionThread       :: ThreadId+    ,_remainingOutputBit  :: TMVar B.ByteString+    ,_nh2Settings         :: NH2.Settings+    ,_sessionThrowed      :: TMVar Bool+    ,_waiting             :: TMVar ThreadId+    ,_encodeHeadersHere   :: TMVar HP.DynamicTable+    ,_decodeHeadersHere   :: TMVar HP.DynamicTable++    ,_incomingData        :: TMVar DataContinuityEngine+    ,_clientState         :: ClientState+    -- Execute this when you want to use a powerfull client session+    ,_startClientSessionCallback :: IO ClientState     }   L.makeLenses ''DecoySession  ++-- errorsSessionConfig :: MVar Bool -> SessionsConfig+-- errorsSessionConfig mvar = set (sessionsCallbacks . reportErrorCallback_SC)+--      (Just $ setError mvar) defaultSessionsConfig+++channelsToIOCallbacks :: TChan B.ByteString -> TChan B.ByteString -> TMVar DataContinuityEngine -> IOCallbacks+channelsToIOCallbacks input_data_channel output_data_channel incoming_data_tmvar =+  let+    push_action  :: PushAction+    push_action lazy_bs  =  atomically $+        writeTChan output_data_channel $ LB.toStrict lazy_bs++    pull_action  :: PullAction+    pull_action byte_count = atomically $ do+        incoming_data <- takeTMVar incoming_data_tmvar+        let+            go dce = let+                maybe_stuff = tryGetThisManyData dce byte_count+                in case maybe_stuff of+                    Nothing -> do+                        new_data <- readTChan input_data_channel+                        let+                            new_dce = dceAddData dce new_data+                        go new_dce++                    Just (new_bce, bs) -> do+                        putTMVar incoming_data_tmvar new_bce+                        return bs+        go incoming_data++    best_effort_pull_action :: BestEffortPullAction+    best_effort_pull_action can_block = atomically $ do+        incoming_data <- takeTMVar incoming_data_tmvar+        if dceIsEmpty incoming_data+          then+            if can_block+              then do+                -- Wait for the next item+                new_data <- readTChan input_data_channel+                -- Leave the empty thing alone+                putTMVar incoming_data_tmvar incoming_data+                return new_data+              else do+                putTMVar incoming_data_tmvar incoming_data+                return ""+          else do+              let+                  (new_dce, all_together) = dceGetAllData incoming_data+              putTMVar incoming_data_tmvar new_dce+              return all_together++    close_action :: CloseAction+    close_action =  return ()+  in+    IOCallbacks {+       _pushAction_IOC = push_action,+       _pullAction_IOC = pull_action,+       _closeAction_IOC = close_action,+       _bestEffortPullAction_IOC = best_effort_pull_action+       }++ -- Supposed to be an HTTP/2 attendant createDecoySession :: Attendant -> IO DecoySession createDecoySession attendant = do-    input_data_channel  <- newChan-    output_data_channel <- newChan+    input_data_channel  <- newTChanIO+    output_data_channel <- newTChanIO+    incoming_data_tmvar  <- newTMVarIO emptyDCE+    reverse_data_tmvar <- newTMVarIO emptyDCE+    session_throwed_tmvar <- newTMVarIO False+    client_state <- makeClientState+    client_sessions_context <- makeSessionsContext defaultSessionsConfig     let-        push_action  :: PushAction-        push_action = writeChan output_data_channel-        pull_action  :: PullAction-        pull_action = readChan input_data_channel-        close_action :: CloseAction-        best_effort_pull_action _ = error "NotImplemented"-        close_action =  return ()-        attendant_callbacks = AttendantCallbacks {-            _pushAction_AtC = push_action,-            _pullAction_AtC = pull_action,-            _closeAction_AtC = close_action,-            _bestEffortPullAction_AtC = best_effort_pull_action-            }+        attendant_callbacks = channelsToIOCallbacks input_data_channel output_data_channel incoming_data_tmvar+        client_callbacks = channelsToIOCallbacks output_data_channel input_data_channel reverse_data_tmvar+        start_client_session_callback :: IO ClientState+        start_client_session_callback = do+            wrapSession (ClientState_SP client_state) client_sessions_context client_callbacks+            return client_state +     dtable_for_encoding <- HP.newDynamicTableForEncoding 4096-    dtable_for_encoding_mvar <- newMVar dtable_for_encoding+    dtable_for_encoding_mvar <- newTMVarIO dtable_for_encoding     dtable_for_decoding <- HP.newDynamicTableForDecoding 4096-    dtable_for_decoding_mvar <- newMVar dtable_for_decoding+    dtable_for_decoding_mvar <- newTMVarIO dtable_for_decoding -    waiting_mvar <- newEmptyMVar+    waiting_mvar <- newEmptyTMVarIO      thread_id <- forkIO $ catch         (do-            attendant push_action pull_action close_action+            attendant attendant_callbacks         )         ((\ e -> do             putStrLn $ "Exception: " ++ (show e)-            maybe_waiting <- tryTakeMVar waiting_mvar+            maybe_waiting <- atomically $ tryReadTMVar waiting_mvar             case maybe_waiting of                 Just thread_id -> throwTo thread_id e                 Nothing -> return ()         ):: SomeException -> IO ()) -    remaining_output_bit <- newMVar ""+    remaining_output_bit <- newTMVarIO ""     let session = DecoySession {         _inputDataChannel  = input_data_channel,         _outputDataChannel = output_data_channel,@@ -86,7 +210,11 @@         _nh2Settings       = NH2.defaultSettings,         _waiting           = waiting_mvar,         _encodeHeadersHere = dtable_for_encoding_mvar,-        _decodeHeadersHere = dtable_for_decoding_mvar+        _decodeHeadersHere = dtable_for_decoding_mvar,+        _incomingData      = incoming_data_tmvar,+        _sessionThrowed    = session_throwed_tmvar,+        _clientState       = client_state,+        _startClientSessionCallback = start_client_session_callback         }     return session @@ -97,13 +225,15 @@   -- Send a frame to a session-sendFrameToSession :: DecoySession -> OutputFrame -> IO ()+sendFrameToSession :: DecoySession -> (NH2.EncodeInfo, NH2.FramePayload) -> IO () sendFrameToSession session (encode_info, frame_payload) = do     let         bs_list = NH2.encodeFrameChunks encode_info frame_payload         input_data_channel = session ^. inputDataChannel -    writeChan2Chan input_data_channel bs_list+    atomically $+        mapM_+            (writeTChan input_data_channel) bs_list   -- Read a frame from a session... if possible. It will block@@ -114,22 +244,30 @@     let         output_data_channel = decoy_session ^. outputDataChannel         remaining_output_bit_mvar = decoy_session ^. remainingOutputBit-        pull_action = fmap LB.toStrict $ readChan output_data_channel+        pull_action =  readTChan output_data_channel         settings = decoy_session ^. nh2Settings         waiting_for_read = decoy_session ^. waiting     my_thread_id <- myThreadId-    putMVar waiting_for_read my_thread_id-    remaining_output_bit <- takeMVar remaining_output_bit_mvar-    (packet, rest) <- readNextChunkAndContinue http2FrameLength remaining_output_bit pull_action-    putMVar remaining_output_bit_mvar rest-    takeMVar waiting_for_read+    packet <- atomically $ do+        {-# SCC aT #-} putTMVar waiting_for_read my_thread_id+        remaining_output_bit <- {-# SCC bT #-}  takeTMVar remaining_output_bit_mvar+        (packet, rest) <-  {-# SCC cT #-}  readNextChunkAndContinue http2FrameLength remaining_output_bit pull_action+        {-# SCC dT #-} putTMVar remaining_output_bit_mvar rest+        {-# SCC eT #-} takeTMVar waiting_for_read+        return packet     let         error_or_frame = NH2.decodeFrame settings packet     case error_or_frame of         Left   _      -> return Nothing         Right  frame  -> return $ Just frame +-- Reads all frames from a session and assembles a response. Notice that this is a buggy hack,+-- because continuation frames and such are not considered.+-- T+-- readResponseFromSession :: DecoySession -> NH2.StreamId -> IO (Headers, B.ByteString, [NH2.Frame]) ++ -- Encode headers to send to the session -- TODO: There is an important bug here... we are using the default encoding -- strategy everywhere@@ -138,21 +276,20 @@     do         let             mv = decoy_session ^. encodeHeadersHere-        dtable <- takeMVar mv+        dtable <- atomically $ takeTMVar mv         (dtable', bs ) <- HP.encodeHeader HP.defaultEncodeStrategy dtable headers-        putMVar mv dtable'+        atomically $ putTMVar mv dtable'         return bs  -- Decode headers to receive them from the session decodeHeadersForSession :: DecoySession -> B.ByteString -> IO Headers-decodeHeadersForSession decoy_session bs =-    do-        let-            mv = decoy_session ^. decodeHeadersHere-        dtable <- takeMVar mv-        (dtable', headers) <- HP.decodeHeader dtable bs-        putMVar mv dtable'-        return headers+decodeHeadersForSession decoy_session bs =  do+    let+        mv = decoy_session ^. decodeHeadersHere+    dtable <- atomically $ takeTMVar mv+    (dtable', headers) <- HP.decodeHeader dtable bs+    atomically $ putTMVar mv dtable'+    return headers  -- Send raw data to a session sendRawDataToSession :: DecoySession -> B.ByteString -> IO ()@@ -162,9 +299,10 @@         waiting_for_write = decoy_session ^. waiting      thread_id <- myThreadId-    putMVar waiting_for_write thread_id-    writeChan input_data_channel data_to_send-    takeMVar waiting_for_write+    atomically $ do+        putTMVar waiting_for_write thread_id+        writeTChan input_data_channel data_to_send+        takeTMVar waiting_for_write     return ()  
+ tests/tests-hs-src/Tests/HTTP1Parse.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE OverloadedStrings #-}+module Tests.HTTP1Parse where+++import qualified Data.ByteString                     as B+import           Data.Maybe                          (isJust)++import           Test.HUnit++import           SecondTransfer                      (Headers)+import           SecondTransfer.Http1.Parse+++testParse :: Test+testParse = TestCase $ do+    let+        headers_text                   = "GET /helo.html HTTP/1.1\r\nHost: www.auther.com \r\n\r\n"+        headers_text2                   = "POST /helo.html HTTP/1.1\r\nHost: www.auther.com \r\nContent-Length: 1\r\n\r\np"+        a0                             = newIncrementalHttp1Parser+        isDone (OnlyHeaders_H1PC _ _)  = True+        isDone _                       = False+        a1                             = addBytes a0 headers_text+        (OnlyHeaders_H1PC h0 leftovers)= a1+        (HeadersAndBody_H1PC h1 cond0 l2) = addBytes a0 headers_text2+        waitForBodyOk (HeadersAndBody_H1PC _ _ _)   = True+        waitForBodyOk _ = False+    assertBool "testParse.IsDone" (isDone a1)+    assertEqual "testParse.NoLeftovers" leftovers ""+    assertEqual "testParse.YesLeftovers" l2 "p"+    assertEqual "testParse.FinishWellSeen" (UseBodyLength_BSC 1) cond0+++testGenerate :: Test+testGenerate = TestCase $ do+    let+        headers_list :: Headers+        headers_list = [+            (":status", "200"),+            ("host", "www.example.com"),+            ("etag", "afrh")+            ]+        fragments = ["hello world"]+        serialized = serializeHTTPResponse headers_list fragments+    assertEqual "testGenerate.1"+        "HTTP/1.1 200 OK\r\ncontent-length: 11\r\netag: afrh\r\nhost: www.example.com\r\n\r\nhello world"+        serialized
+ tests/tests-hs-src/Tests/HTTP2Session.hs view
@@ -0,0 +1,584 @@+{-# LANGUAGE OverloadedStrings #-}+module Tests.HTTP2Session where+++import           Data.Typeable++import           Control.Concurrent               (threadDelay)+import qualified Control.Concurrent               as C (yield)+import           Control.Concurrent.MVar+import           Control.Exception+import           Control.Lens+import qualified Control.Lens                     as L+import           Control.Monad.IO.Class           (liftIO)+import qualified Network.HTTP2                    as NH2+import           Test.HUnit+import           SecondTransfer.Exception+import           SecondTransfer.Http2             (http2Attendant)+import           SecondTransfer.Sessions+import           SecondTransfer.Test.DecoySession+import           SecondTransfer.Types+import           SecondTransfer.Utils.HTTPHeaders (fetchHeader)+import           SecondTransfer.MainLoop.CoherentWorker (defaultEffects)+import           SecondTransfer.MainLoop.ClientPetitioner++++import           Data.Conduit                     (yield)++++saysHello :: DataAndConclusion+saysHello = do+    yield "Hello world!\ns"+    -- No footers+    return []+++simpleWorker :: AwareWorker+simpleWorker = coherentToAwareWorker . const $ return (+    [+        (":status", "200")+    ],+    [], -- No pushed streams+    saysHello+    )+++erringWorker :: AwareWorker+erringWorker = coherentToAwareWorker . const $ return (+    [+        (":status", "500")+    ],+    [], -- No pushed streams+    saysHello+    )+++abortingWorker :: AwareWorker+abortingWorker req_ = do+    pr1 <- erringWorker req_+    let+        pr2 = L.set (effect_PS . interrupt_Ef) (Just InterruptConnectionAfter_IEf) pr1+    return pr2+++earlyAbortingWorker :: AwareWorker+earlyAbortingWorker req_ = do+    pr1 <- erringWorker req_+    let+        pr2 = L.set (effect_PS . interrupt_Ef) (Just InterruptConnectionNow_IEf) pr1+    return pr2+++data Internal500Exception = Internal500Exception+    deriving (Typeable, Show)++instance Exception Internal500Exception where+    toException = convertHTTP500PrecursorExceptionToException+    fromException = getHTTP500PrecursorExceptionFromException+++throwingWorker :: AwareWorker+throwingWorker _ = throwIO Internal500Exception++throwingWorker2 :: AwareWorker+throwingWorker2  = coherentToAwareWorker . const .  return $ (+    [+        -- These headers will be already sent by the time+        -- the exception is discovered...+        (":status", "200")+    ],+    [], -- No pushed streams+    do+        yield "Error coming down"+        liftIO $ throwIO Internal500Exception+        return []+    )+++simpleRequestHeaders :: Headers+simpleRequestHeaders = [+    (":path", "/"),+    (":authority", "www.example.com"),+    (":scheme", "https"),+    (":method", "GET")+    ]+++badRequestHeaders :: Headers+badRequestHeaders = [+    (":path", "/"),+    (":authority", "www.example.com"),+    (":scheme", "https")+    ]+++setError :: MVar Bool -> ErrorCallback+setError mvar = const $ modifyMVar_ mvar (const $ return True )+++errorForPrefaceOk :: MVar Bool ->  ErrorCallback+errorForPrefaceOk ok_mvar (_, _, some_exception) = do+    let+        maybe_blocked :: Maybe BlockedIndefinitelyOnMVar+        maybe_blocked = fromException some_exception+    case maybe_blocked of+        Just _ -> modifyMVar_ ok_mvar (const . return $ True)+        Nothing -> modifyMVar_ ok_mvar (const . return $ False)+++errorsSessionConfig :: MVar Bool -> SessionsConfig+errorsSessionConfig mvar = set (sessionsCallbacks . reportErrorCallback_SC)+    (Just $ setError mvar) defaultSessionsConfig++errorsSessionConfigForMVar :: MVar Bool -> SessionsConfig+errorsSessionConfigForMVar mvar = set (sessionsCallbacks . reportErrorCallback_SC)+    (Just $ errorForPrefaceOk mvar) defaultSessionsConfig+++-- Test disabled due to irrelevance+-- testPrefaceChecks :: Test+-- testPrefaceChecks = TestCase $ do+--     errors_mvar <- newMVar False+--     sessions_context <- makeSessionsContext (errorsSessionConfigForMVar errors_mvar)+--     let+--         attendant = http2Attendant sessions_context simpleWorker+--     decoy_session <- createDecoySession attendant+--     -- This should work+--     sendRawDataToSession decoy_session "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"+--     threadDelay 1000000+--     error_ok <- readMVar errors_mvar+--     if not error_ok then+--         assertFailure "TypeOfErrorNotOK"+--     else+--         return ()+++testPrefaceChecks2 :: Test+testPrefaceChecks2 = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context simpleWorker+    decoy_session <- createDecoySession attendant+    -- This should work+    sendRawDataToSession decoy_session "PRI * HXXP/2.0\r\n\r\nSM\r\n\r\n"+    threadDelay 1000000+    got_error <- readMVar errors_mvar+    if not got_error then do+        assertFailure "Exception didn't raise"+    else+        return ()+++testFirstFrameMustBeSettings :: Test+testFirstFrameMustBeSettings = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context simpleWorker+    decoy_session <- createDecoySession attendant+    sendRawDataToSession decoy_session "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"+    maybe_frame <- recvFrameFromSession decoy_session+    case maybe_frame of+        Nothing ->+            assertFailure "Waiting a frame, received none"+        Just (NH2.Frame _ (NH2.SettingsFrame _)) -> -- Ok+            return ()++        _ ->+            assertFailure "Waiting a settings frame, received something else"+++testFirstFrameMustBeSettings2 :: Test+testFirstFrameMustBeSettings2 = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context simpleWorker+    decoy_session <- createDecoySession attendant+    sendRawDataToSession decoy_session "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"+    maybe_frame <- recvFrameFromSession decoy_session+    case maybe_frame of+        Nothing ->+            assertFailure "Waiting a frame, received none"+        Just (NH2.Frame _ (NH2.SettingsFrame _)) -> -- Ok+            return ()++        _ ->+            assertFailure "Waiting a settings frame, received something else"++    -- Send a settings frame now+    sendFrameToSession+        decoy_session+        ( (NH2.EncodeInfo NH2.defaultFlags 0 Nothing),+          (NH2.SettingsFrame [])+        )++    -- Check session is alive+    got_error <- readMVar errors_mvar+    if got_error then do+        assertFailure "Exception raised unexpectedly"+    else+        return ()++testFirstFrameMustBeSettings3 :: Test+testFirstFrameMustBeSettings3 = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context simpleWorker+    decoy_session <- createDecoySession attendant+    sendRawDataToSession decoy_session "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"++    let d2 = ( (NH2.EncodeInfo NH2.defaultFlags 0 Nothing),+          (NH2.PingFrame "01234567") )++    -- Send a ping frame now, so that we get an error+    sendFrameToSession+        decoy_session+        d2++    -- We need to give some time to the framework to react to problems+    threadDelay 20000++    -- Check session is alive+    got_error <- readMVar errors_mvar+    if not got_error then do+        assertFailure "Exception didn't raise properly"+    else+        return ()+++testIGet500Status :: Test+testIGet500Status = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context throwingWorker+    decoy_session <- createDecoySession attendant++    -- Send the prologue+    sendRawDataToSession decoy_session "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"++    -- Send a settings frame now, otherwise the session will bark....+    sendFrameToSession+        decoy_session+        ( (NH2.EncodeInfo NH2.defaultFlags 0 Nothing),+          (NH2.SettingsFrame []) )++    -- Now perform a simple, mocking request+    performRequestSimple decoy_session 1 [+        (":method", "get"),+        (":scheme", "https"),+        (":authority", "www.example.com"),+        (":path", "/hi")+        ]++    -- Now we read a few frames+    seen <- return False+    f0 <- recvFrameFromSession decoy_session+    seen1 <- frameIsStatus500 decoy_session seen f0++    f1 <- recvFrameFromSession decoy_session+    seen2 <- frameIsStatus500 decoy_session seen f1++    f2 <- recvFrameFromSession decoy_session+    seen3 <- frameIsStatus500 decoy_session seen f2++    if not seen3 then do+        assertFailure "Didn't see that 500"+    else+        return ()+++frameIsStatus500 :: DecoySession -> Bool -> Maybe NH2.Frame -> IO Bool+frameIsStatus500 decoy_session prev maybe_frame =+    case prev of+        True -> return True+        False ->+            case maybe_frame of+                Just (NH2.Frame _  (NH2.HeadersFrame _ bs ) ) -> do+                    headers <- decodeHeadersForSession decoy_session bs+                    let+                        maybe_status = fetchHeader headers ":status"+                    case maybe_status of+                        Just x | x == "500" -> return True+                        _                   -> return False++                _ ->+                    return False+++testSessionBreaksOnLateError :: Test+testSessionBreaksOnLateError = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context throwingWorker2+    decoy_session <- createDecoySession attendant++    -- Send the prologue+    sendRawDataToSession decoy_session "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"++    -- Send a settings frame now, otherwise the session will bark....+    sendFrameToSession+        decoy_session+        ( (NH2.EncodeInfo NH2.defaultFlags 0 Nothing),+          (NH2.SettingsFrame []) )++    -- Now perform a simple, mocking request+    performRequestSimple decoy_session 1 [+        (":method", "get"),+        (":scheme", "https"),+        (":authority", "www.example.com"),+        (":path", "/hi")+        ]++    -- Now we read a few frames+    seen <- return False+    f0 <- recvFrameFromSession decoy_session+    seen1 <- frameIsGoAwayBecauseInternalError decoy_session seen f0++    f1 <- recvFrameFromSession decoy_session+    seen2 <- frameIsGoAwayBecauseInternalError decoy_session seen1 f1++    f2 <- recvFrameFromSession decoy_session+    seen3 <- frameIsGoAwayBecauseInternalError decoy_session seen2 f2++    f3 <- recvFrameFromSession decoy_session+    seen4 <- frameIsGoAwayBecauseInternalError decoy_session seen3 f3++    if not seen4 then do+        assertFailure "Didn't see GoAwayFrame"+    else+        return ()+++frameIsGoAwayBecauseInternalError :: DecoySession -> Bool -> Maybe NH2.Frame -> IO Bool+frameIsGoAwayBecauseInternalError decoy_session prev maybe_frame = do+    case prev of+        True -> return True+        False ->+            case maybe_frame of+                Just (NH2.Frame _  (NH2.GoAwayFrame _ ec _) ) -> do+                    case ec of+                        NH2.InternalError   -> return True+                        _                   -> return False++                _ ->+                    return False+++frameIsGoAwayBecauseProtocolError :: DecoySession -> Bool -> Maybe NH2.Frame -> IO Bool+frameIsGoAwayBecauseProtocolError decoy_session prev maybe_frame = do+    case prev of+        True -> return True+        False ->+            case maybe_frame of+                Just (NH2.Frame _  (NH2.GoAwayFrame _ ec _) ) -> do+                    case ec of+                        NH2.ProtocolError   -> return True+                        _                   -> return False++                _ ->+                    return False+++testUpdateWindowFrameAborts :: Test+testUpdateWindowFrameAborts = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context simpleWorker+    decoy_session <- createDecoySession attendant+    sendRawDataToSession decoy_session "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"+    maybe_frame <- recvFrameFromSession decoy_session+    case maybe_frame of+        Nothing ->+            assertFailure "Waiting a frame, received none"+        Just (NH2.Frame _ (NH2.SettingsFrame _)) -> -- Ok+            return ()++        _ ->+            assertFailure "Waiting a settings frame, received something else"++    -- Send a settings frame now+    sendFrameToSession+        decoy_session+        ( NH2.EncodeInfo NH2.defaultFlags 0 Nothing,+          NH2.SettingsFrame [] )++    -- And now send a WindowUpdate frame+    sendFrameToSession+        decoy_session+        (NH2.EncodeInfo NH2.defaultFlags 51 Nothing,+         NH2.WindowUpdateFrame 10 )++    -- Now we read a few frames+    seen <- return False+    f0 <- recvFrameFromSession decoy_session+    seen1 <- frameIsGoAwayBecauseProtocolError decoy_session seen f0++    f1 <- recvFrameFromSession decoy_session+    seen2 <- frameIsGoAwayBecauseInternalError decoy_session seen1 f1++    -- f2 <- recvFrameFromSession decoy_session+    -- seen3 <- frameIsGoAwayBecauseProtocolError decoy_session seen2 f2++    -- f3 <- recvFrameFromSession decoy_session+    -- seen4 <- frameIsGoAwayBecauseProtocolError decoy_session seen3 f3++    if not seen2 then do+        assertFailure "Didn't see GoAwayFrame"+    else+        return ()++    -- Check session is alive+    got_error <- readMVar errors_mvar+    if got_error then do+        assertFailure "Exception raised unexpectedly"+    else+        return ()++testClosedInteraction0 :: Test+testClosedInteraction0 = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context simpleWorker+    decoy_session <- createDecoySession attendant+    let+        start_client =  decoy_session ^. startClientSessionCallback+    start_client+    got_error <- readMVar errors_mvar+    if got_error then do+        assertFailure "Exception raised unexpectedly"+    else+        return ()+++testClosedInteraction1 :: Test+testClosedInteraction1 = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context simpleWorker+    decoy_session <- createDecoySession attendant+    let+        start_client =  decoy_session ^. startClientSessionCallback+    client_state <- start_client+    got_error <- readMVar errors_mvar+    if got_error then do+        assertFailure "Exception raised unexpectedly"+    else+        return ()++    (headers, input_data_stream) <- request client_state simpleRequestHeaders (return ())+    if length headers <= 0+      then+        assertFailure "NoHeadersBack"+      else+        return ()+    return ()++++testClosedInteraction3 :: Test+testClosedInteraction3 = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context simpleWorker+    decoy_session <- createDecoySession attendant+    let+        start_client =  decoy_session ^. startClientSessionCallback+    client_state <- start_client+    ee_mvar <- newMVar False+    catch+        (do+            request client_state badRequestHeaders (return ())+            return ()+        )+        ((\ _ -> modifyMVar_ ee_mvar ( \ _ -> return $ True)  ):: ClientSessionAbortedException -> IO ()  )+    -- if got_error then do+    --     return ()+    -- else+    --     assertFailure "IWasExpectingAnError"+    ee <- takeMVar ee_mvar+    if ee+      then+        return ()+      else+        assertFailure "IWasExpectingAnError--"++++testWorkerClosesAfter :: Test+testWorkerClosesAfter = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context abortingWorker+    decoy_session <- createDecoySession attendant+    let+        start_client =  decoy_session ^. startClientSessionCallback+    client_state <- start_client+    got_error <- readMVar errors_mvar+    if got_error then do+        assertFailure "Exception raised unexpectedly"+    else+        return ()++    (headers, _) <- request client_state simpleRequestHeaders (return ())+    if length headers <= 0+      then+        assertFailure "NoHeadersBack"+      else+        return ()++    -- Now, when I try to connect, something bad should happen, since the server must+    -- be closed++    got_error2 <- newMVar False+    catch+        (request client_state simpleRequestHeaders (return ()) >> return () )+        ((\ _ -> modifyMVar_ got_error2 ( \ _ -> return $ True)  )::ClientSessionAbortedException -> IO () )+    ee <- readMVar got_error2+    if ee+      then+        return ()+      else+        assertFailure "Server Must Be Closed"++    return ()++testWorkerClosesBefore :: Test+testWorkerClosesBefore = TestCase $ do+    errors_mvar <- newMVar False+    sessions_context <- makeSessionsContext (errorsSessionConfig errors_mvar)+    let+        attendant = http2Attendant sessions_context earlyAbortingWorker+    decoy_session <- createDecoySession attendant+    let+        start_client =  decoy_session ^. startClientSessionCallback+    client_state <- start_client+    got_error <- readMVar errors_mvar+    if got_error then do+        assertFailure "Exception raised unexpectedly"+    else+        return ()++    got_error2 <- newMVar False+    catch+        (request client_state simpleRequestHeaders (return ()) >> return () )+        ((\ _ -> modifyMVar_ got_error2 ( \ _ -> return $ True)  )::ClientSessionAbortedException -> IO () )+    ee <- readMVar got_error2+    if ee+      then+        return ()+      else+        assertFailure "Server Must Be Closed"++    return ()
+ tests/tests-hs-src/Tests/Utils.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE OverloadedStrings #-}+module Tests.Utils where+++import qualified Data.ByteString                  as B++import           Test.HUnit++import           SecondTransfer                         (Headers)+import           SecondTransfer.Utils.HTTPHeaders +import           SecondTransfer.Http1.Parse    (locateCRLFs)+++testLowercaseHeaders :: Test+testLowercaseHeaders = TestCase $ do +    let +        irregular = [+            ("A", "B"),+            ("C-D", "D")+            ] :: Headers+        regular = lowercaseHeaders irregular+    assertEqual "lowercase" regular [+        ("a", "B"),+        ("c-d", "D")+        ]+    assertEqual "test-lowercase-1" (headersAreLowercase irregular) False+    assertEqual "test-lowercase-2" (headersAreLowercase regular) True+++testCRLFLocate :: Test +testCRLFLocate = TestCase $ do +    let +        input_text = "hello\r\nworld"+        (positions, length_, lastchar) = locateCRLFs 0 [] 0 input_text+    assertEqual "position-is-5.position" (head positions) 5+    assertEqual "position-is-5.length" length_ (B.length input_text)+    assertEqual "position-is-5.lastchar" lastchar (fromIntegral . fromEnum $ 'd')+++    let +        input_text_2 = "hello\r\nworld\r\n\r\n"+        (positions, length_, lastchar) = locateCRLFs 0 [] 0 input_text_2+    assertEqual "many-positions.position" positions [14,12,5]+    assertEqual "many-positions.length" length_ (B.length input_text_2)+    assertEqual "many-positions.lastchar" lastchar (fromIntegral . fromEnum $ '\n')+++testReplaceHostByAuthority :: Test +testReplaceHostByAuthority = TestCase $ do +    let +        headers_pre = [+            ("A", "B"),+            ("C-D", "D"),+            ("Host", "D")+            ] :: Headers+        regular = toList. replaceHostByAuthority . fromList . lowercaseHeaders $ headers_pre+    -- Do not pass in headers with uppercase+    assertEqual "replace-host-by-authority" +        [+            (":authority", "D"),+            ("a", "B"),+            ("c-d", "D")+        ]+        regular
tests/tests-hs-src/hunit_tests.hs view
@@ -14,24 +14,29 @@   tests = TestList [-    TestLabel "testPrefaceChecks" testPrefaceChecks,-    TestLabel "testPrefaceChecks2" testPrefaceChecks2,-    TestLabel "testLowercaseHeaders" testLowercaseHeaders,-    TestLabel "testCRLFLocate" testCRLFLocate,-    TestLabel "testHTTP1Parse" testParse,-    TestLabel "testGenerate" testGenerate,-    TestLabel "testReplaceHostByAuthority" testReplaceHostByAuthority,-    TestLabel "testFirstFrameIsSettings" testFirstFrameMustBeSettings,-    TestLabel "testFirstFrameIsSettings2" testFirstFrameMustBeSettings2,-    TestLabel "testFirstFrameIsSettings3" testFirstFrameMustBeSettings3,-    TestLabel "testIGet500Status" testIGet500Status,-    TestLabel "testSessionBreaksOnLateError" testSessionBreaksOnLateError,-    TestLabel "WindowUpdate to unexistent stream" testUpdateWindowFrameAborts+    --TestLabel "testPrefaceChecks" testPrefaceChecks+    TestLabel "testPrefaceChecks2" testPrefaceChecks2+    ,TestLabel "testLowercaseHeaders" testLowercaseHeaders+    ,TestLabel "testCRLFLocate" testCRLFLocate+    ,TestLabel "testHTTP1Parse" testParse+    ,TestLabel "testGenerate" testGenerate+    ,TestLabel "testReplaceHostByAuthority" testReplaceHostByAuthority+    ,TestLabel "testFirstFrameIsSettings" testFirstFrameMustBeSettings+    ,TestLabel "testFirstFrameIsSettings2" testFirstFrameMustBeSettings2+    ,TestLabel "testFirstFrameIsSettings3" testFirstFrameMustBeSettings3+    ,TestLabel "testIGet500Status" testIGet500Status+    ,TestLabel "testSessionBreaksOnLateError" testSessionBreaksOnLateError+    ,TestLabel "WindowUpdate to unexistent stream" testUpdateWindowFrameAborts+    ,TestLabel "testClosedInteraction0" testClosedInteraction0+    ,TestLabel "testClosedInteraction1" testClosedInteraction1+    ,TestLabel "testClosedInteraction3" testClosedInteraction3+    ,TestLabel "testWorkerClosesAfter"  testWorkerClosesAfter+    ,TestLabel "testWorkerClosesBefore" testWorkerClosesBefore     ]  -main = do -    rets <- runTestTT tests +main = do+    rets <- runTestTT tests     if (errors rets == 0 && failures rets == 0)         then exitWith ExitSuccess         else exitWith $ ExitFailure (-1)