on master help sign in
Working version
387ec9aMichael Snoyman
Network/Wai/Handler/DevelServer.hsnew file+257
@@ -0,0 +1,257 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+module Network.Wai.Handler.DevelServer (run) where
5+
6+import Language.Haskell.Interpreter
7+import Network.Wai
8+import qualified System.IO
9+
10+import Data.ByteString (ByteString)
11+import Data.ByteString.Lazy.Internal (defaultChunkSize)
12+import qualified Data.ByteString.Char8 as B
13+import qualified Data.ByteString.Lazy as L
14+import qualified Data.ByteString.Lazy.Char8 as L8
15+import Network
16+ ( listenOn, accept, sClose, PortID(PortNumber), Socket
17+ , withSocketsDo)
18+import Control.Exception (bracket, finally, Exception, throwIO)
19+import System.IO (Handle, hClose)
20+import Control.Concurrent (forkIO, threadDelay)
21+import Control.Monad (unless, forM)
22+import Data.Maybe (isJust, fromJust, fromMaybe)
23+
24+import Data.Typeable (Typeable)
25+
26+import Network.Socket.SendFile
27+import Control.Arrow (first)
28+import Control.Concurrent.MVar as M
29+import System.Directory (getModificationTime)
30+
31+type FunctionName = String
32+
33+run :: Port -> ModuleName -> FunctionName -> IO ()
34+run port modu func = do
35+ app <- M.newMVar $ loadingApp Nothing
36+ _ <- forkIO $ fillApp modu func app
37+ run' port app
38+
39+fillApp :: String -> String -> MVar Handler -> IO ()
40+fillApp modu func mapp =
41+ go Nothing []
42+ where
43+ go prevError prevFiles = do
44+ toReload <-
45+ if null prevFiles
46+ then return True
47+ else do
48+ times <- mapM (getModificationTime . fst) prevFiles
49+ return $ times /= map snd prevFiles
50+ (newError, newFiles) <-
51+ if toReload
52+ then reload prevError
53+ else return (prevError, prevFiles)
54+ threadDelay 1000000
55+ go newError newFiles
56+ reload prevError = do
57+ putStrLn "Attempting to interpret your app..."
58+ _ <- swapMVar mapp $ loadingApp prevError
59+ res <- theapp modu func
60+ case res of
61+ Left err -> do
62+ putStrLn $ "Compile failed: " ++ show err
63+ _ <- swapMVar mapp $ loadingApp $ Just err
64+ return (Just err, [])
65+ Right (app, files) -> do
66+ putStrLn "Interpreting success, new app loaded"
67+ _ <- swapMVar mapp app
68+ files' <- forM files $ \f -> do
69+ t <- getModificationTime f
70+ return (f, t)
71+ return (Nothing, files')
72+
73+
74+loadingApp :: Maybe InterpreterError -> Handler
75+loadingApp err f =
76+ f $ const $ return $ Response status200
77+ [ ("Content-Type", "text/plain")
78+ , ("Refresh", "1")
79+ ] $ ResponseLBS $ L8.pack $ toMessage err
80+ where
81+ toMessage Nothing = "Loading code changes, please wait"
82+ toMessage (Just err') = "Error loading code: " ++ show err'
83+
84+type Handler = (Application -> IO ()) -> IO ()
85+type MHandler = M.MVar Handler
86+
87+theapp :: String -> String -> IO (Either InterpreterError (Handler, [FilePath]))
88+theapp modu func =
89+ runInterpreter $ do
90+ loadModules [modu]
91+ mods <- getLoadedModules
92+ setImports ["Prelude", "Network.Wai", modu]
93+ app <- interpret func infer
94+ return (app, map toFile mods)
95+ where
96+ toFile s = map toSlash s ++ ".hs"
97+ toSlash '.' = '/'
98+ toSlash c = c
99+
100+run' :: Port -> MHandler -> IO ()
101+run' port = withSocketsDo .
102+ bracket
103+ (listenOn $ PortNumber $ fromIntegral port)
104+ sClose .
105+ serveConnections port
106+type Port = Int
107+
108+serveConnections :: Port -> MHandler -> Socket -> IO ()
109+serveConnections port app socket = do
110+ (conn, remoteHost', _) <- accept socket
111+ _ <- forkIO $ serveConnection port app conn remoteHost'
112+ serveConnections port app socket
113+
114+serveConnection :: Port -> MHandler -> Handle -> String -> IO ()
115+serveConnection port handler conn remoteHost' =
116+ finally
117+ serveConnection'
118+ (hClose conn)
119+ where
120+ serveConnection' = do
121+ env <- hParseRequest port conn remoteHost'
122+ handler' <- M.readMVar handler
123+ handler' $ \app -> do
124+ res <- app env
125+ sendResponse (httpVersion env) conn res
126+
127+hParseRequest :: Port -> Handle -> String -> IO Request
128+hParseRequest port conn remoteHost' = do
129+ headers' <- takeUntilBlank conn id
130+ parseRequest port headers' conn remoteHost'
131+
132+takeUntilBlank :: Handle
133+ -> ([ByteString] -> [ByteString])
134+ -> IO [ByteString]
135+takeUntilBlank h front = do
136+ l <- stripCR `fmap` B.hGetLine h
137+ if B.null l
138+ then return $ front []
139+ else takeUntilBlank h $ front . (:) l
140+
141+stripCR :: ByteString -> ByteString
142+stripCR bs
143+ | B.null bs = bs
144+ | B.last bs == '\r' = B.init bs
145+ | otherwise = bs
146+
147+data InvalidRequest =
148+ NotEnoughLines [String]
149+ | HostNotIncluded
150+ | BadFirstLine String
151+ | NonHttp
152+ deriving (Show, Typeable)
153+instance Exception InvalidRequest
154+
155+-- | Parse a set of header lines and body into a 'Request'.
156+parseRequest :: Port
157+ -> [ByteString]
158+ -> Handle
159+ -> String
160+ -> IO Request
161+parseRequest port lines' handle remoteHost' = do
162+ case lines' of
163+ (_:_:_) -> return ()
164+ _ -> throwIO $ NotEnoughLines $ map B.unpack lines'
165+ (method, rpath', gets, httpversion) <- parseFirst $ head lines'
166+ let rpath = '/' : case B.unpack rpath' of
167+ ('/':x) -> x
168+ _ -> B.unpack rpath'
169+ let heads = map (first mkCIByteString . parseHeaderNoAttr) $ tail lines'
170+ let host' = lookup "Host" heads
171+ unless (isJust host') $ throwIO HostNotIncluded
172+ let host = fromJust host'
173+ let len = fromMaybe 0 $ do
174+ bs <- lookup "Content-Length" heads
175+ let str = B.unpack bs
176+ case reads str of
177+ (x, _):_ -> Just x
178+ _ -> Nothing
179+ let (serverName', _) = B.break (== ':') host
180+ return $ Request
181+ { requestMethod = method
182+ , httpVersion = httpversion
183+ , pathInfo = B.pack rpath
184+ , queryString = gets
185+ , serverName = serverName'
186+ , serverPort = port
187+ , requestHeaders = heads
188+ , isSecure = False
189+ , requestBody = requestBodyHandle handle len
190+ , errorHandler = System.IO.hPutStr System.IO.stderr
191+ , remoteHost = B.pack remoteHost'
192+ }
193+
194+parseFirst :: ByteString
195+ -> IO (ByteString, ByteString, ByteString, HttpVersion)
196+parseFirst s = do
197+ let pieces = B.words s
198+ (method, query, http') <-
199+ case pieces of
200+ [x, y, z] -> return (x, y, z)
201+ _ -> throwIO $ BadFirstLine $ B.unpack s
202+ let (hfirst, hsecond) = B.splitAt 5 http'
203+ unless (hfirst == B.pack "HTTP/") $ throwIO NonHttp
204+ let (rpath, qstring) = B.break (== '?') query
205+ return (method, rpath, qstring, hsecond)
206+
207+sendResponse :: HttpVersion -> Handle -> Response -> IO ()
208+sendResponse httpversion h res = do
209+ B.hPut h $ B.pack "HTTP/"
210+ B.hPut h $ httpversion
211+ B.hPut h $ B.pack " "
212+ B.hPut h $ B.pack $ show $ statusCode $ status res
213+ B.hPut h $ B.pack " "
214+ B.hPut h $ statusMessage $ status res
215+ B.hPut h $ B.pack "\r\n"
216+ mapM_ putHeader $ responseHeaders res
217+ B.hPut h $ B.pack "\r\n"
218+ case responseBody res of
219+ ResponseFile fp -> unsafeSendFile h fp
220+ ResponseEnumerator (Enumerator enum) -> enum myPut h >> return ()
221+ ResponseLBS lbs -> L.hPut h lbs
222+ where
223+ myPut _ bs = do
224+ B.hPut h bs
225+ return (Right h)
226+ putHeader (x, y) = do
227+ B.hPut h $ ciOriginal x
228+ B.hPut h $ B.pack ": "
229+ B.hPut h y
230+ B.hPut h $ B.pack "\r\n"
231+
232+parseHeaderNoAttr :: ByteString -> (ByteString, ByteString)
233+parseHeaderNoAttr s =
234+ let (k, rest) = B.span (/= ':') s
235+ rest' = if not (B.null rest) &&
236+ B.head rest == ':' &&
237+ not (B.null $ B.tail rest) &&
238+ B.head (B.tail rest) == ' '
239+ then B.drop 2 rest
240+ else rest
241+ in (k, rest')
242+
243+requestBodyHandle :: Handle -> Int -> Source
244+requestBodyHandle h =
245+ requestBodyFunc go
246+ where
247+ go i = Just `fmap` B.hGet h (min i defaultChunkSize)
248+
249+requestBodyFunc :: (Int -> IO (Maybe B.ByteString)) -> Int -> Source
250+requestBodyFunc _ 0 = Source $ return Nothing
251+requestBodyFunc h len = Source $ do
252+ mbs <- h len
253+ case mbs of
254+ Nothing -> return Nothing
255+ Just bs -> do
256+ let newLen = len - B.length bs
257+ return $ Just (bs, requestBodyFunc h $ max 0 newLen)
wai-handler-devel.hsnew file+19
@@ -0,0 +1,19 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+import Network.Wai.Handler.DevelServer (run)
3+import System.Console.CmdArgs
4+
5+data Devel = Devel
6+ { port :: Int
7+ , moduleName :: String
8+ , function :: String
9+ }
10+ deriving (Show, Data, Typeable)
11+
12+main :: IO ()
13+main = do
14+ Devel p m f <- cmdArgs Devel
15+ { port = 3000 &= argPos 0 &= typ "PORT"
16+ , moduleName = "" &= argPos 1 &= typ "MODULE"
17+ , function = "" &= argPos 2 &= typ "FUNCTION"
18+ } &= summary "WAI development web server"
19+ run p m f