on master help sign in
Add 'wai-handler-launch/' from commit 'bf2844de5e2612a6c2ce962a91664da203b8aaf6'

git-subtree-dir: wai-handler-launch git-subtree-mainline: 745eba8d6381d66f4e01f6845aa26b3f0df25425 git-subtree-split: bf2844de5e2612a6c2ce962a91664da203b8aaf6

commit027a09e3e819d3188222fc87ceffa268b9a8e32f authorMichael Snoyman <michael@snoyman.com> dateFri 22 Jul 2011, 16:52 (15 years ago) parent745eba8bf2844d
9 files changed+251
wai-handler-launch/.gitignore+2 wai-handler-launch/LICENSE+30 wai-handler-launch/Network/Wai/Handler/Launch.hs+148 wai-handler-launch/README wai-handler-launch/Setup.hs+2 wai-handler-launch/runtests.hs+22 wai-handler-launch/test.hs+7 wai-handler-launch/wai-handler-launch.cabal+33 wai-handler-launch/windows.c+7
wai-handler-launch/.gitignorenew file+2
@@ -0,0 +1,2 @@
1+dist/
2+*.swp
wai-handler-launch/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c)2011, Michael Snoyman
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Michael Snoyman nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
wai-handler-launch/Network/Wai/Handler/Launch.hsnew file+148
@@ -0,0 +1,148 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE ForeignFunctionInterface #-}
3+{-# LANGUAGE CPP #-}
4+module Network.Wai.Handler.Launch (run) where
5+
6+import Network.Wai
7+import Network.HTTP.Types
8+import qualified Network.Wai.Handler.Warp as Warp
9+import Data.IORef
10+import Control.Concurrent
11+import Foreign
12+import Foreign.C.String
13+import Control.Monad.IO.Class (liftIO)
14+import qualified Data.ByteString as S
15+import Data.Enumerator (($$), enumList, joinI, Enumeratee, Stream (..), Iteratee (..), Step (..))
16+import Blaze.ByteString.Builder (fromByteString)
17+#if !WINDOWS
18+import System.Cmd (rawSystem)
19+#endif
20+import Codec.Zlib.Enum (ungzip)
21+import Blaze.ByteString.Builder.Enumerator (builderToByteString)
22+import qualified Data.Enumerator.List as EL
23+import Control.Monad.Trans.Class (lift)
24+
25+ping :: IORef Bool -> Middleware
26+ping var app req
27+ | pathInfo req == ["_ping"] = do
28+ liftIO $ writeIORef var True
29+ return $ responseLBS status200 [] ""
30+ | otherwise = do
31+ res <- app req
32+ let isHtml hs =
33+ case lookup "content-type" hs of
34+ Just ct -> "text/html" `S.isPrefixOf` ct
35+ Nothing -> False
36+ case res of
37+ ResponseFile _ hs _ _
38+ | not $ isHtml hs -> return res
39+ ResponseBuilder _ hs _
40+ | not $ isHtml hs -> return res
41+ _ -> do
42+ let renum = responseEnumerator res
43+ return $ ResponseEnumerator $ \f -> renum $ \status headers ->
44+ if isHtml headers
45+ then do
46+ let (isEnc, headers') = fixHeaders id headers
47+ let headers'' = filter (\(x, _) -> x /= "content-length") headers'
48+ let fixEnc x =
49+ if isEnc
50+ then joinI $ ungzip $$ x
51+ else x
52+ joinI $ builderToByteString $$ fixEnc $ joinI $ insideHead "<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>" $$ joinI $ EL.map fromByteString $$ f status headers''
53+ else f status headers
54+
55+insideHead :: S.ByteString -> Enumeratee S.ByteString S.ByteString IO a
56+insideHead toInsert =
57+ go "" whole
58+ where
59+ whole = "<head>"
60+ go :: S.ByteString -> S.ByteString -> Step S.ByteString IO a -> Iteratee S.ByteString IO (Step S.ByteString IO a)
61+ go held atFront step = do
62+ mx <- EL.head
63+ case mx of
64+ Nothing -> feedDone $ Chunks [held, toInsert]
65+ Just x
66+ | atFront `S.isPrefixOf` x -> do
67+ let y = S.drop (S.length atFront) x
68+ let stream = Chunks [held, atFront, toInsert, y]
69+ feedDone stream
70+ | whole `S.isInfixOf` x -> do
71+ let (before, rest) = S.breakSubstring whole x
72+ let after = S.drop (S.length whole) rest
73+ feedDone $ Chunks [held, before, whole, toInsert, after]
74+ | x `S.isPrefixOf` atFront -> go
75+ (held `S.append` x)
76+ (S.drop (S.length x) atFront)
77+ step
78+ | otherwise -> do
79+ let (held', atFront', x') = getOverlap whole x
80+ feedCont held' atFront' $ Chunks [held, x']
81+ where
82+ --feedDone :: Stream S.ByteString -> Iteratee S.ByteString IO (Step S.ByteString IO a)
83+ feedDone stream =
84+ case step of
85+ Continue k -> do
86+ step' <- lift $ runIteratee $ k stream
87+ EL.map id step'
88+ Yield b s -> return $ Yield b s
89+ Error e -> return $ Error e
90+
91+ --feedCont :: Monad m => S.ByteString -> S.ByteString -> Stream S.ByteString -> Iteratee S.ByteString m (Step S.ByteString m a)
92+ feedCont held atFront stream = do
93+ case step of
94+ Continue k -> do
95+ step' <- lift $ runIteratee $ k stream
96+ go held atFront step'
97+ Yield b s -> return $ Yield b s
98+ Error e -> return $ Error e
99+
100+getOverlap :: S.ByteString -> S.ByteString -> (S.ByteString, S.ByteString, S.ByteString)
101+getOverlap whole x =
102+ go whole
103+ where
104+ go piece
105+ | S.null piece = ("", whole, x)
106+ | piece `S.isSuffixOf` x =
107+ let x' = S.take (S.length x - S.length piece) x
108+ atFront = S.drop (S.length piece) whole
109+ in (piece, atFront, x')
110+ | otherwise = go $ S.init piece
111+
112+fixHeaders front [] = (False, front [])
113+fixHeaders front (("content-encoding", "gzip"):rest) = (True, front rest)
114+fixHeaders front (x:xs) = fixHeaders (front . (:) x) xs
115+
116+#if WINDOWS
117+foreign import ccall "launch"
118+ launch :: IO ()
119+#else
120+launch :: IO ()
121+launch = forkIO (rawSystem
122+#if MAC
123+ "open"
124+#else
125+ "xdg-open"
126+#endif
127+ ["http://127.0.0.1:4587/"] >> return ()) >> return ()
128+#endif
129+
130+run :: Application -> IO ()
131+run app = do
132+ x <- newIORef True
133+ forkIO $ Warp.runSettings Warp.defaultSettings
134+ { Warp.settingsPort = 4587
135+ , Warp.settingsOnException = const $ return ()
136+ , Warp.settingsHost = "127.0.0.1"
137+ } $ ping x app
138+ launch
139+ loop x
140+
141+loop :: IORef Bool -> IO ()
142+loop x = do
143+ let seconds = 120
144+ threadDelay $ 1000000 * seconds
145+ b <- readIORef x
146+ if b
147+ then writeIORef x False >> loop x
148+ else return ()
wai-handler-launch/READMEnew file

No changes to the content.

wai-handler-launch/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
wai-handler-launch/runtests.hsnew file+22
@@ -0,0 +1,22 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+import Network.Wai.Handler.Launch (insideHead)
3+import Test.Hspec
4+import Test.Hspec.HUnit
5+import Data.Enumerator hiding (map)
6+import Test.HUnit
7+import qualified Data.ByteString as S
8+
9+main :: IO ()
10+main = hspec $ describe "insideHead"
11+ [ it "handles single chunks" $ helper "bar<head>foobaz" ["bar<head>baz"]
12+ , it "handles single chunks, no <head>" $ helper "barfoo" ["bar"]
13+ , it "handles whole chunks" $ helper "bar<head>foobaz" ["bar", "<head>", "baz"]
14+ , it "handles split chunks" $ helper "bar<head>foobaz" ["bar", "<he", "ad>", "baz"]
15+ , it "handles many chunks" $ helper "bar<head>foobaz1234567890" ["bar", "<he", "ad>", "baz", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
16+ , it "handles many pieces" $ helper "bar<head>foobaz" $ map S.singleton $ S.unpack "bar<head>baz"
17+ ]
18+
19+helper :: S.ByteString -> [S.ByteString] -> Assertion
20+helper res pieces = do
21+ x <- run_ $ enumList 1 pieces $$ joinI $ insideHead "foo" $$ consume
22+ S.concat x @?= res
wai-handler-launch/test.hsnew file+7
@@ -0,0 +1,7 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+import Network.Wai
3+import Network.HTTP.Types
4+import Network.Wai.Handler.Launch
5+import Network.Wai.Middleware.Gzip
6+
7+main = run $ gzip True $ \_ -> return $ responseLBS status200 [("Content-Type", "text/html; charset=utf8")] "<html><head></head><body>HELLO THERE"
wai-handler-launch/wai-handler-launch.cabalnew file+33
@@ -0,0 +1,33 @@
1+Name: wai-handler-launch
2+Version: 0.0.1
3+Synopsis: Launch a web app in the default browser.
4+Description: This handles cross-platform launching and inserts Javascript code to ping the server. When the server no longer receives pings, it shuts down.
5+License: BSD3
6+License-file: LICENSE
7+Author: Michael Snoyman
8+Maintainer: michael@snoyman.com
9+Category: Web
10+Build-type: Simple
11+Cabal-version: >=1.2
12+
13+Library
14+ Exposed-modules: Network.Wai.Handler.Launch
15+ build-depends: base >= 4 && < 5
16+ , wai >= 0.4 && < 0.5
17+ , warp >= 0.4 && < 0.5
18+ , http-types >= 0.6 && < 0.7
19+ , transformers >= 0.2 && < 0.3
20+ , bytestring >= 0.9 && < 1
21+ , blaze-builder >= 0.2 && < 0.4
22+ , enumerator >= 0.4 && < 0.5
23+ , blaze-builder-enumerator>= 0.2 && < 0.3
24+ , zlib-enum >= 0.2 && < 0.3
25+ if os(windows)
26+ c-sources: windows.c
27+ cpp-options: -DWINDOWS
28+ extra-libraries: Shell32
29+ else
30+ if os(darwin)
31+ cpp-options: -DMAC
32+ else
33+ build-depends: process >= 1.0 && < 1.2
wai-handler-launch/windows.cnew file+7
@@ -0,0 +1,7 @@
1+#include <windows.h>
2+#include <shellapi.h>
3+
4+void launch(void)
5+{
6+ ShellExecute(NULL, "open", "http://127.0.0.1:4587/", NULL, NULL, SW_SHOWNORMAL);
7+}