diff --git a/Hack/Handler/FastCGI.hsc b/Hack/Handler/FastCGI.hsc
new file mode 100644
--- /dev/null
+++ b/Hack/Handler/FastCGI.hsc
@@ -0,0 +1,289 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE EmptyDataDecls #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Hack.Handler.FastCGI
+-- Copyright   :  (c) Bjorn Bringert 2004-2005, (c) Lemmih 2006
+-- License     :  BSD-style (see the file libraries/network/LICENSE)
+-- 
+-- Maintainer  :  michael@snoyman.com
+-- Stability   :  experimental
+-- Portability :  non-portable (uses FFI)
+--
+-- Interface for FastCGI <http://fastcgi.com/>, using the fcgiapp API.
+-- Totally ripped off by Michael Snoyman to work with Hack.
+--
+-----------------------------------------------------------------------------
+module Hack.Handler.FastCGI
+    (
+    -- * Single-threaded interface
+      runFastCGIorCGI
+    , runOneFastCGIorCGI
+    , runFastCGI
+    , runOneFastCGI
+    -- * Concurrent interface
+    , runFastCGIConcurrent
+    , runFastCGIConcurrent'
+    ) where
+
+import Control.Concurrent ( forkOS )
+import Control.Concurrent.MVar
+import Control.Concurrent.QSem
+import Control.OldException as Exception (catch, finally)
+import Control.Monad    ( liftM )
+import Data.Word (Word8)
+import Foreign          ( Ptr, castPtr, nullPtr, peekArray0 
+                        , alloca, mallocBytes, free, throwIfNeg_)
+import Foreign.C        ( CInt, CString, CStringLen
+                        , peekCString )
+import Foreign.Storable ( Storable (..) )
+import System.IO.Unsafe (unsafeInterleaveIO,unsafePerformIO)
+
+import qualified Hack
+import qualified Hack.Handler.CGI as CGI
+
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy.Char8 as Lazy
+#if __GLASGOW_HASKELL__ >= 608
+import qualified Data.ByteString.Internal as BSB
+import qualified Data.ByteString.Unsafe   as BSB
+#else
+import qualified Data.ByteString.Base as BSB
+#endif
+
+-- For debugging
+import Control.Concurrent ( myThreadId )
+import Prelude hiding     ( log, catch )
+import System.IO          ( hPutStrLn, stderr )
+
+#include <fcgiapp.h>
+
+------------------------------------------------------------------------
+
+data FCGX_Stream
+type StreamPtr = Ptr FCGX_Stream
+type Environ = Ptr CString
+
+------------------------------------------------------------------------
+
+foreign import ccall unsafe "fcgiapp.h FCGX_IsCGI" fcgx_isCGI
+    :: IO CInt
+
+foreign import ccall unsafe "fcgiapp.h FCGX_GetStr" fcgx_getStr
+    :: CString -> CInt -> StreamPtr -> IO CInt
+
+foreign import ccall unsafe "fcgiapp.h FCGX_PutStr" fcgx_putStr
+    :: CString -> CInt -> StreamPtr -> IO CInt
+
+foreign import ccall threadsafe "fcgiapp.h FCGX_Accept" fcgx_accept
+    :: Ptr StreamPtr -> Ptr StreamPtr -> Ptr StreamPtr -> Ptr Environ -> IO CInt
+foreign import ccall unsafe "fcgiapp.h FCGX_Finish" fcgx_finish
+    :: IO ()
+
+------------------------------------------------------------------------
+
+-- | Handle a single CGI request, or FastCGI requests in an infinite loop.
+--   This function only returns normally if it was a CGI request.
+--   This lets you use the same program
+--   as either a FastCGI or CGI program, depending on what the server 
+--   treats it as.
+runFastCGIorCGI :: Hack.Application -> IO ()
+runFastCGIorCGI f = do fcgi <- runOneFastCGIorCGI f
+                       if fcgi then runFastCGIorCGI f
+                               else return ()
+
+-- | Handle a single FastCGI or CGI request. This lets you use the same program
+--   as either a FastCGI or CGI program, depending on what the server 
+--   treats it as.
+runOneFastCGIorCGI :: Hack.Application
+                   -> IO Bool -- ^ True if it was a FastCGI request, 
+                              --   False if CGI.
+runOneFastCGIorCGI f =
+    do x <- fcgx_isCGI
+       if x /= 0 then CGI.run f >> return False
+                 else runOneFastCGI f >> return True
+
+-- | Handle FastCGI requests in an infinite loop.
+runFastCGI :: Hack.Application -> IO ()
+runFastCGI f = runOneFastCGI f >> runFastCGI f
+
+-- | Handle a single FastCGI request.
+runOneFastCGI :: Hack.Application -> IO ()
+runOneFastCGI f = do
+    alloca (\inp ->
+            alloca (\outp ->
+                    alloca (\errp ->
+                            alloca (\envp ->
+                                    oneRequest f inp outp errp envp))))
+
+oneRequest :: Hack.Application
+           -> Ptr StreamPtr
+           -> Ptr StreamPtr
+           -> Ptr StreamPtr
+           -> Ptr Environ
+           -> IO ()
+oneRequest f inp outp errp envp =
+    do
+    testReturn "FCGX_Accept" $ fcgx_accept inp outp errp envp
+    ins  <- peek inp
+    outs <- peek outp
+    errs <- peek errp
+    env  <- peek envp
+    handleRequest f ins outs errs env
+    fcgx_finish
+
+handleRequest :: Hack.Application
+              -> StreamPtr
+              -> StreamPtr
+              -> StreamPtr
+              -> Environ
+              -> IO ()
+handleRequest f ins outs _errs env =
+    do
+    vars <- environToTable env
+    input <- sRead ins
+    output <- CGI.helper vars input f
+    sPutStr outs output
+
+
+
+data FCGX_Request
+
+foreign import ccall unsafe "fcgiapp.h FCGX_Init" fcgx_init
+    :: IO CInt
+
+foreign import ccall unsafe "fcgiapp.h FCGX_InitRequest" fcgx_initrequest
+    :: Ptr FCGX_Request -> CInt -> CInt -> IO CInt
+
+foreign import ccall threadsafe "fcgiapp.h FCGX_Accept_r" fcgx_accept_r
+    :: Ptr FCGX_Request -> IO CInt
+
+foreign import ccall unsafe "fcgiapp.h FCGX_Finish_r" fcgx_finish_r
+    :: Ptr FCGX_Request -> IO ()
+
+-- | Like 'Network.CGI.runCGI', but uses the FastCGI interface
+--   and forks off a new thread (using 'forkOS') for every request.
+runFastCGIConcurrent :: Int -- ^ Max number of concurrent threads.
+                     -> Hack.Application -> IO ()
+runFastCGIConcurrent = runFastCGIConcurrent' forkOS
+
+runFastCGIConcurrent' :: (IO () -> IO a) -- ^ How to fork a request.
+                      -> Int             -- ^ Max number of concurrent threads.
+                      -> Hack.Application -> IO ()
+
+runFastCGIConcurrent' fork m f
+    = do qsem <- newQSem m
+         testReturn "FCGX_Init" $ fcgx_init
+         let loop = do waitQSem qsem
+                       reqp <- acceptRequest
+                       fork (oneRequestMT f reqp
+                             `finally`
+                            (finishRequest reqp >> signalQSem qsem))
+                       loop
+         loop `catch` \e -> log (show e)
+
+oneRequestMT :: Hack.Application -> Ptr FCGX_Request -> IO ()
+oneRequestMT app r = do
+     env    <- peekEnvp r
+     vars   <- environToTable env
+     ins    <- peekIn r
+     input  <- sRead ins
+     output <- CGI.helper vars input app
+     outs   <- peekOut r
+     sPutStr outs output
+--
+-- * FCGX_Reqest struct
+--
+
+acceptRequest :: IO (Ptr FCGX_Request)
+acceptRequest = do
+    reqp <- mallocBytes (#size FCGX_Request)
+    initAndAccept reqp
+    return reqp
+  where initAndAccept reqp = do
+          testReturn "FCGX_InitRequest" $ fcgx_initrequest reqp 0 0
+          testReturn "FCGX_Accept_r" $ fcgx_accept_r reqp
+
+finishRequest :: Ptr FCGX_Request -> IO ()
+finishRequest reqp = do
+                     fcgx_finish_r reqp
+                     free reqp
+
+peekIn, peekOut, _peekErr :: Ptr FCGX_Request -> IO (Ptr FCGX_Stream)
+peekIn  = (#peek FCGX_Request, in)
+peekOut = (#peek FCGX_Request, out)
+_peekErr = (#peek FCGX_Request, err)
+
+peekEnvp :: Ptr FCGX_Request -> IO Environ
+peekEnvp = (#peek FCGX_Request, envp)
+
+
+--
+-- * Stream IO
+--
+
+sPutStr :: StreamPtr -> Lazy.ByteString -> IO ()
+sPutStr h str =
+    mapM_ (flip BSB.unsafeUseAsCStringLen (fcgxPutCStringLen h)) (Lazy.toChunks str)
+
+fcgxPutCStringLen :: StreamPtr -> CStringLen -> IO ()
+fcgxPutCStringLen h (cs,len) =
+    testReturn "FCGX_PutStr" $ fcgx_putStr cs (fromIntegral len) h
+
+sRead :: StreamPtr -> IO Lazy.ByteString
+sRead h = buildByteString (fcgxGetBuf h) 4096
+
+fcgxGetBuf :: StreamPtr -> Ptr a -> Int -> IO Int
+fcgxGetBuf h p c =
+    liftM fromIntegral $ fcgx_getStr (castPtr p) (fromIntegral c) h
+
+--
+-- * ByteString utilities
+--
+
+-- | Data.ByteString.Lazy.hGetContentsN generalized to arbitrary 
+--   reading functions.
+buildByteString :: (Ptr Word8 -> Int -> IO Int) -> Int -> IO Lazy.ByteString
+buildByteString f k = lazyRead >>= return . Lazy.fromChunks
+  where
+    lazyRead = unsafeInterleaveIO $ do
+        ps <- BSB.createAndTrim k $ \p -> f p k
+        case BS.length ps of
+            0         -> return []
+            n | n < k -> return [ps]
+            _         -> do pss <- lazyRead
+                            return (ps : pss)
+
+--
+-- * Utilities
+--
+
+testReturn :: String -> IO CInt -> IO ()
+testReturn e = throwIfNeg_ (\n -> e ++ " failed with error code: "++ show n)
+
+environToTable :: Environ -> IO [(String,String)]
+environToTable arr =
+    do css <- peekArray0 nullPtr arr
+       ss <- mapM peekCString css
+       return $ map (splitBy '=') ss
+
+-- | Split a list at the first occurence of a marker.
+--   Do not include the marker in any of the resulting lists.
+--   If the marker does not occur in the list, the entire
+--   input with be in the first list.
+splitBy :: Eq a => a -> [a] -> ([a],[a])
+splitBy x xs = (y, drop 1 z)
+    where (y,z) = break (==x) xs
+
+--
+-- * Debugging
+--
+
+{-# NOINLINE logMutex #-}
+logMutex :: MVar ()
+logMutex = unsafePerformIO (newMVar ())
+
+log :: String -> IO ()
+log msg = do
+          t <- myThreadId
+          withMVar logMutex (const $ hPutStrLn stderr (show t ++ ": " ++ msg))
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+The following license covers this documentation, and the source code, except
+where otherwise indicated.
+
+Copyright 2008, Michael Snoyman. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,7 @@
+#!/usr/bin/env runhaskell
+
+> module Main where
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/hack-handler-fastcgi.cabal b/hack-handler-fastcgi.cabal
new file mode 100644
--- /dev/null
+++ b/hack-handler-fastcgi.cabal
@@ -0,0 +1,19 @@
+name:            hack-handler-fastcgi
+version:         0.0.0
+license:         BSD3
+license-file:    LICENSE
+author:          Michael Snoyman <michael@snoyman.com>
+maintainer:      Michael Snoyman <michael@snoyman.com>
+synopsis:        Hack handler direct to fastcgi
+category:        Web
+stability:       stable
+cabal-version:   >= 1.2
+build-type:      Simple
+homepage:        http://github.com/snoyberg/hack-handler-fastcgi/tree/master
+
+library
+    build-depends:   base, hack >= 2009.5.19, bytestring, hack-handler-cgi
+    exposed-modules: Hack.Handler.FastCGI
+    ghc-options:     -Wall
+    includes:        fcgiapp.h
+    extra-libraries: fcgi
