Converted wai-handler-launch (I miss conduit)

This commit is contained in:
Michael Snoyman 2014-05-22 14:32:06 +03:00
parent 4504e4bf1b
commit 800577cd03

View File

@ -12,84 +12,119 @@ import Network.Wai.Internal
import Network.HTTP.Types
import qualified Network.Wai.Handler.Warp as Warp
import Data.IORef
import Data.Monoid (mappend)
import Control.Concurrent (forkIO, threadDelay)
import Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString as S
import Blaze.ByteString.Builder (fromByteString)
import Blaze.ByteString.Builder (fromByteString, Builder)
import qualified Blaze.ByteString.Builder as Blaze
#if WINDOWS
import Foreign
import Foreign.C.String
#else
import System.Cmd (rawSystem)
import System.Process (rawSystem)
#endif
import Data.Conduit.Zlib (decompressFlush, WindowBits (WindowBits))
import Data.Conduit.Blaze (builderToByteStringFlush)
import Data.Conduit
import qualified Data.Conduit.List as CL
import Data.Streaming.Blaze (newBlazeRecv, defaultStrategy)
ping :: IORef Bool -> Middleware
ping var app req
ping var app req sendResponse
| pathInfo req == ["_ping"] = do
liftIO $ writeIORef var True
return $ responseLBS status200 [] ""
| otherwise = do
res <- app req
sendResponse $ responseLBS status200 [] ""
| otherwise = app req $ \res -> do
let isHtml hs =
case lookup "content-type" hs of
Just ct -> "text/html" `S.isPrefixOf` ct
Nothing -> False
case res of
ResponseFile _ hs _ _
| not $ isHtml hs -> return res
ResponseBuilder _ hs _
| not $ isHtml hs -> return res
ResponseSource _ hs _
| not $ isHtml hs -> return res
_ -> do
let (s, hs, withBody) = responseToSource res
let (isEnc, headers') = fixHeaders id hs
let headers'' = filter (\(x, _) -> x /= "content-length") headers'
if isHtml $ responseHeaders res
then do
let (s, hs, withBody) = responseToStream res
(isEnc, headers') = fixHeaders id hs
headers'' = filter (\(x, _) -> x /= "content-length") headers'
{-
let fixEnc src =
if isEnc then
src $= decompressFlush (WindowBits 31)
else src
return $ ResponseSource s headers'' $ \f -> withBody $ \body -> f
$ fixEnc (body $= builderToByteStringFlush)
$= insideHead
$= CL.map (fmap fromByteString)
-}
withBody $ \body ->
sendResponse $ responseStream s headers'' $ \sendChunk flush ->
addInsideHead sendChunk flush $ \sendChunk' flush' -> do
(sendChunk'', flush'') <-
if isEnc
then decode sendChunk' flush'
else return (sendChunk', flush')
body sendChunk' flush'
{-
f -> withBody $ \body -> f
$ fixEnc (body $= builderToByteStringFlush)
$= insideHead
$= CL.map (fmap fromByteString)
-}
else sendResponse res
decode = error "decode"
toInsert :: S.ByteString
toInsert = "<script>setInterval(function(){var x;if(window.XMLHttpRequest){x=new XMLHttpRequest();}else{x=new ActiveXObject(\"Microsoft.XMLHTTP\");}x.open(\"GET\",\"/_ping\",false);x.send();},60000)</script>"
insideHead :: Conduit (Flush S.ByteString) IO (Flush S.ByteString)
insideHead =
loop' (S.empty, whole)
addInsideHead :: (Builder -> IO ())
-> IO ()
-> StreamingBody
-> IO ()
addInsideHead sendInner flushInner streamingBody = do
(blazeRecv, blazeFinish) <- newBlazeRecv defaultStrategy
ref <- newIORef $ Just (S.empty, whole)
streamingBody (inner blazeRecv ref) (flush blazeRecv ref)
state <- readIORef ref
mbs <- blazeFinish
held <- case mbs of
Nothing -> return state
Just bs -> push state bs
case state of
Nothing -> return ()
Just (held, _) -> sendInner $ fromByteString held `mappend` fromByteString toInsert
where
loop' state = await >>= maybe (close state) (push' state)
whole = "<head>"
push' state (Chunk x) = push state x
push' state Flush = yield Flush >> loop' state
push (held, atFront) x
flush blazeRecv ref = inner blazeRecv ref Blaze.flush
inner blazeRecv ref builder = do
state0 <- readIORef ref
popper <- blazeRecv builder
let loop state = do
bs <- popper
if S.null bs
then writeIORef ref state
else push state bs >>= loop
loop state0
push Nothing x = sendInner (fromByteString x) >> return Nothing
push (Just (held, atFront)) x
| atFront `S.isPrefixOf` x = do
let y = S.drop (S.length atFront) x
mapM_ (yield . Chunk) [held, atFront, toInsert, y]
CL.map id
sendInner $ fromByteString held
`mappend` fromByteString atFront
`mappend` fromByteString toInsert
`mappend` fromByteString y
return Nothing
| whole `S.isInfixOf` x = do
let (before, rest) = S.breakSubstring whole x
let after = S.drop (S.length whole) rest
mapM_ (yield . Chunk) [held, before, whole, toInsert, after]
CL.map id
sendInner $ fromByteString held
`mappend` fromByteString before
`mappend` fromByteString whole
`mappend` fromByteString toInsert
`mappend` fromByteString after
return Nothing
| x `S.isPrefixOf` atFront = do
let held' = held `S.append` x
atFront' = S.drop (S.length x) atFront
loop' (held', atFront')
return $ Just (held', atFront')
| otherwise = do
let (held', atFront', x') = getOverlap whole x
mapM_ (yield . Chunk) [held, x']
loop' (held', atFront')
close (held, _) = mapM_ yield [Chunk held, Chunk toInsert]
sendInner $ fromByteString held `mappend` fromByteString x'
return $ Just (held', atFront')
getOverlap :: S.ByteString -> S.ByteString -> (S.ByteString, S.ByteString, S.ByteString)
getOverlap whole x =
@ -138,11 +173,11 @@ runUrl = runUrlPort 4587
runUrlPort :: Int -> String -> Application -> IO ()
runUrlPort port url app = do
x <- newIORef True
_ <- forkIO $ Warp.runSettings Warp.defaultSettings
{ Warp.settingsPort = port
, Warp.settingsOnException = (\_ _ -> return ())
, Warp.settingsHost = "*4"
} $ ping x app
_ <- forkIO $ Warp.runSettings
( Warp.setPort port
$ Warp.setOnException (\_ _ -> return ())
$ Warp.setHost "*4" Warp.defaultSettings)
$ ping x app
launch port url
loop x