diff --git a/snap-core.cabal b/snap-core.cabal
--- a/snap-core.cabal
+++ b/snap-core.cabal
@@ -1,5 +1,5 @@
 name:           snap-core
-version:        0.2.11
+version:        0.2.12
 synopsis:       Snap: A Haskell Web Framework (Core)
 
 description:
@@ -98,29 +98,29 @@
   test/suite/Snap/Util/GZip/Tests.hs
 
 
-Flag debug
-  Description: Enable debug logging to stderr
-  Default: False
-
-Flag testsuite
-  Description: Are we running the testsuite? Causes arguments to \"debug\" to
-               be evaluated but not printed.
-  Default: False
-
 Flag portable
   Description: Compile in cross-platform mode. No platform-specific code or
                optimizations such as C routines will be used.
   Default: False
 
+
+Flag no-debug
+  Description: Disable any debug logging code. Without this flag, Snap will
+               test the DEBUG environment variable to decide whether to do
+               logging, and this introduces a tiny amount of overhead
+               (a call into a function pointer) because the calls to 'debug'
+               cannot be inlined. Users who want to squeeze out maximum
+               performance can set the no-debug flag to get a version of Snap
+               which has debug calls that should be inlined away.
+               
+  Default: False
+
+
 Library
   hs-source-dirs: src
 
-  if flag(debug)
-    cpp-options: -DDEBUG
-
-  if flag(testsuite)
-    cpp-options: -DDEBUG_TEST
-    build-depends: deepseq >= 1.1 && <1.2
+  if flag(no-debug)
+    cpp-options: -DNODEBUG
 
   if flag(portable) || os(windows)
     cpp-options: -DPORTABLE
@@ -150,6 +150,7 @@
     bytestring-nums,
     cereal >= 0.3 && < 0.4,
     containers,
+    deepseq >= 1.1 && <1.2,
     directory,
     dlist >= 0.5 && < 0.6,
     filepath,
@@ -173,16 +174,16 @@
   else
     ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
 
-Executable snap
-  if flag(testsuite)
-    cpp-options: -DDEBUG_TEST
-    build-depends: deepseq >= 1.1 && <1.2
 
+Executable snap
   hs-source-dirs: src
   main-is: Snap/Starter.hs
 
   other-modules: Snap.StarterTH
 
+  if flag(no-debug)
+    cpp-options: -DNODEBUG
+
   build-depends:
     attoparsec >= 0.8.1 && < 0.9,
     base >= 4 && < 5,
@@ -190,6 +191,7 @@
     bytestring-nums,
     cereal >= 0.3 && < 0.4,
     containers,
+    deepseq >= 1.1 && <1.2,
     directory,
     directory-tree,
     dlist >= 0.5 && < 0.6,
diff --git a/src/Snap/Internal/Debug.hs b/src/Snap/Internal/Debug.hs
--- a/src/Snap/Internal/Debug.hs
+++ b/src/Snap/Internal/Debug.hs
@@ -1,63 +1,102 @@
 -- | An internal Snap module for (optionally) printing debugging
--- messages. Normally 'debug' does nothing, but you can pass \"-fdebug\" to
--- @cabal install@ to build a @snap-core@ which debugs to stderr.
+-- messages. Normally 'debug' does nothing, but if you set @DEBUG=1@ in the
+-- environment you'll get debugging messages. We use 'unsafePerformIO' to make
+-- sure that the call to 'getEnv' is only made once.
 --
 -- /N.B./ this is an internal interface, please don't write external code that
 -- depends on it.
 
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# OPTIONS_GHC -fno-cse         #-}
 
-module Snap.Internal.Debug where
 
-import           Control.Monad.Trans
-
-#ifdef DEBUG_TEST
-import           Control.DeepSeq
-
-debug :: (MonadIO m) => String -> m ()
-debug !s = let !s' = rnf s in return $! s' `deepseq` ()
-{-# INLINE debug #-}
-
-debugErrno :: (MonadIO m) => String -> m ()
-debugErrno !s = let !s' = rnf s in return $! s' `deepseq` ()
-
-#elif defined(DEBUG)
+module Snap.Internal.Debug where
 
 ------------------------------------------------------------------------------
 import           Control.Concurrent
+import           Control.DeepSeq
+import           Control.Exception
+import           Control.Monad.Trans
+import           Data.Char
 import           Data.List
 import           Data.Maybe
 import           Foreign.C.Error
+import           System.Environment
 import           System.IO
 import           System.IO.Unsafe
 import           Text.Printf
 ------------------------------------------------------------------------------
 
+debug, debugErrno :: forall m . (MonadIO m => String -> m ())
 
+
+#ifndef NODEBUG
+
+{-# NOINLINE debug #-}
+debug = let !x = unsafePerformIO $! do
+            !e <- try $ getEnv "DEBUG"
+            
+            !f <- either (\(_::SomeException) -> return debugIgnore)
+                         (\x -> if x == "1" || x == "on"
+                                  then return debugOn
+                                  else if x == "testsuite"
+                                         then return debugSeq
+                                         else return debugIgnore)
+                         (fmap (map toLower) e)
+            return $! f
+        in x
+
+
+{-# NOINLINE debugErrno #-}
+debugErrno = let !x = unsafePerformIO $ do
+                 e <- try $ getEnv "DEBUG"
+                 
+                 !f <- either (\(_::SomeException) -> return debugErrnoIgnore)
+                              (\x -> if x == "1" || x == "on"
+                                       then return debugErrnoOn
+                                       else if x == "testsuite"
+                                              then return debugErrnoSeq
+                                              else return debugErrnoIgnore)
+                              (fmap (map toLower) e)
+                 return $! f
+             in x
+
+
 ------------------------------------------------------------------------------
+debugSeq :: (MonadIO m) => String -> m ()
+debugSeq !s = let !s' = rnf s in return $! s' `deepseq` ()
+{-# NOINLINE debugSeq #-}
+
+debugErrnoSeq :: (MonadIO m) => String -> m ()
+debugErrnoSeq !s = let !s' = rnf s in return $! s' `deepseq` ()
+{-# NOINLINE debugErrnoSeq #-}
+
+------------------------------------------------------------------------------
 _debugMVar :: MVar ()
 _debugMVar = unsafePerformIO $ newMVar ()
 {-# NOINLINE _debugMVar #-}
 
+
 ------------------------------------------------------------------------------
-debug :: (MonadIO m) => String -> m ()
-debug s = liftIO $ withMVar _debugMVar $ \_ -> do
-              tid <- myThreadId
-              hPutStrLn stderr $ s' tid
-              hFlush stderr
+debugOn :: (MonadIO m) => String -> m ()
+debugOn s = liftIO $ withMVar _debugMVar $ \_ -> do
+                tid <- myThreadId
+                hPutStrLn stderr $ s' tid
+                hFlush stderr
   where
     chop x = let y = fromMaybe x $ stripPrefix "ThreadId " x
              in printf "%8s" y
 
     s' t   = "[" ++ chop (show t) ++ "] " ++ s
 
-{-# INLINE debug #-}
+{-# NOINLINE debugOn #-}
 
 
 ------------------------------------------------------------------------------
-debugErrno :: (MonadIO m) => String -> m ()
-debugErrno loc = liftIO $ do
+debugErrnoOn :: (MonadIO m) => String -> m ()
+debugErrnoOn loc = liftIO $ do
     err <- getErrno
     let ex = errnoToIOError loc err Nothing Nothing
     debug $ show ex
@@ -65,13 +104,20 @@
 
 #else
 
-------------------------------------------------------------------------------
-debug :: (MonadIO m) => String -> m ()
-debug _ = return ()
+debug      = debugIgnore
 {-# INLINE debug #-}
 
-debugErrno :: (MonadIO m) => String -> m ()
-debugErrno _ = return ()
-------------------------------------------------------------------------------
+debugErrno = debugErrnoIgnore
+{-# INLINE debugErrno #-}
 
 #endif
+
+------------------------------------------------------------------------------
+debugIgnore :: (MonadIO m) => String -> m ()
+debugIgnore _ = return ()
+{-# INLINE debugIgnore #-}
+
+debugErrnoIgnore :: (MonadIO m) => String -> m ()
+debugErrnoIgnore _ = return ()
+{-# INLINE debugErrnoIgnore #-}
+------------------------------------------------------------------------------
diff --git a/src/Snap/Internal/Http/Types.hs b/src/Snap/Internal/Http/Types.hs
--- a/src/Snap/Internal/Http/Types.hs
+++ b/src/Snap/Internal/Http/Types.hs
@@ -168,6 +168,7 @@
 -- request type
 ------------------------------------------------------------------------------
 
+-- | An existential wrapper for the 'Enumerator' type
 data SomeEnumerator = SomeEnumerator (forall a . Enumerator a)
 
 
@@ -364,20 +365,24 @@
 ------------------------------------------------------------------------------
 -- | Represents an HTTP response.
 data Response = Response
-    { rspHeaders       :: Headers
-    , rspHttpVersion   :: !HttpVersion
+    { rspHeaders            :: Headers
+    , rspHttpVersion        :: !HttpVersion
 
       -- | We will need to inspect the content length no matter what, and
       --   looking up \"content-length\" in the headers and parsing the number
       --   out of the text will be too expensive.
-    , rspContentLength :: !(Maybe Int64)
-    , rspBody          :: ResponseBody
+    , rspContentLength      :: !(Maybe Int64)
+    , rspBody               :: ResponseBody
 
       -- | Returns the HTTP status code.
-    , rspStatus        :: !Int
+    , rspStatus             :: !Int
 
       -- | Returns the HTTP status explanation string.
-    , rspStatusReason  :: !ByteString
+    , rspStatusReason       :: !ByteString
+
+      -- | If true, we are transforming the request body with
+      -- 'transformRequestBody'
+    , rspTransformingRqBody :: !Bool
     }
 
 
@@ -447,7 +452,8 @@
 
 -- | An empty 'Response'.
 emptyResponse       :: Response
-emptyResponse       = Response Map.empty (1,1) Nothing (Enum return) 200 "OK"
+emptyResponse       = Response Map.empty (1,1) Nothing (Enum return) 200
+                               "OK" False
 
 
 ------------------------------------------------------------------------------
diff --git a/src/Snap/Internal/Iteratee/Debug.hs b/src/Snap/Internal/Iteratee/Debug.hs
--- a/src/Snap/Internal/Iteratee/Debug.hs
+++ b/src/Snap/Internal/Iteratee/Debug.hs
@@ -48,8 +48,12 @@
 iterateeDebugWrapper :: String -> Iteratee IO a -> Iteratee IO a
 iterateeDebugWrapper name iter = IterateeG f
   where
-    f c@(EOF _) = do
+    f c@(EOF Nothing) = do
         debug $ name ++ ": got EOF: " ++ show c
+        runIter iter c
+
+    f c@(EOF (Just e)) = do
+        debug $ name ++ ": got EOF **error**: " ++ show c
         runIter iter c
 
     f c@(Chunk _) = do
diff --git a/src/Snap/Internal/Types.hs b/src/Snap/Internal/Types.hs
--- a/src/Snap/Internal/Types.hs
+++ b/src/Snap/Internal/Types.hs
@@ -27,6 +27,8 @@
 ------------------------------------------------------------------------------
 import           Snap.Iteratee hiding (Enumerator)
 import           Snap.Internal.Http.Types
+import           Snap.Internal.Debug
+import           Snap.Internal.Iteratee.Debug
 
 
 ------------------------------------------------------------------------------
@@ -199,32 +201,42 @@
 
 
 ------------------------------------------------------------------------------
--- | Detaches the request body's 'Enumerator' from the 'Request' and
--- returns it. You would want to use this if you needed to send the
--- HTTP request body (transformed or otherwise) through to the output
--- in O(1) space. (Examples: transcoding, \"echo\", etc)
+-- | Normally Snap is careful to ensure that the request body is fully consumed
+-- after your web handler runs, but before the 'Response' enumerator is
+-- streamed out the socket. If you want to transform the request body into some
+-- output in O(1) space, you should use this function.
 --
--- Normally Snap is careful to ensure that the request body is fully
--- consumed after your web handler runs; this function is marked
--- \"unsafe\" because it breaks this guarantee and leaves the
--- responsibility up to you. If you don't fully consume the
--- 'Enumerator' you get here, the next HTTP request in the pipeline
--- (if any) will misparse. Be careful with exception handlers.
-unsafeDetachRequestBody :: Snap (Enumerator a)
-unsafeDetachRequestBody = do
+-- Note that upon calling this function, response processing finishes early as
+-- if you called 'finishWith'. Make sure you set any content types, headers,
+-- cookies, etc. before you call this function.
+--
+transformRequestBody :: (forall a . Enumerator a)
+                         -- ^ the output 'Iteratee' is passed to this
+                         -- 'Enumerator', and then the resulting 'Iteratee' is
+                         -- fed the request body stream. Your 'Enumerator' is
+                         -- responsible for transforming the input.
+                     -> Snap ()
+transformRequestBody trans = do
     req <- getRequest
     let ioref = rqBody req
     senum <- liftIO $ readIORef ioref
     let (SomeEnumerator enum) = senum
     liftIO $ writeIORef ioref
                (SomeEnumerator $ return . Iter.joinI . Iter.take 0)
-    return enum
 
+    origRsp <- getResponse
+    let rsp = setResponseBody
+                (\writeEnd -> do
+                     i <- trans writeEnd
+                     enum $ iterateeDebugWrapper "transformRequestBody" i)
+                $ origRsp { rspTransformingRqBody = True }
+    finishWith rsp
 
+
 ------------------------------------------------------------------------------
 -- | Short-circuits a 'Snap' monad action early, storing the given
 -- 'Response' value in its state.
-finishWith :: Response -> Snap ()
+finishWith :: Response -> Snap a
 finishWith = Snap . return . Just . Left
 {-# INLINE finishWith #-}
 
@@ -385,10 +397,15 @@
 -- 'Response' object stored in a 'Snap' monad. Note that the target URL is not
 -- validated in any way.
 redirect' :: ByteString -> Int -> Snap ()
-redirect' target status =
+redirect' target status = do
+    r <- getResponse
+
     finishWith
         $ setResponseCode status
-        $ setHeader "Location" target emptyResponse
+        $ setContentLength 0
+        $ modifyResponseBody (const $ enumBS "")
+        $ setHeader "Location" target r
+
 {-# INLINE redirect' #-}
 
 
diff --git a/src/Snap/Iteratee.hs b/src/Snap/Iteratee.hs
--- a/src/Snap/Iteratee.hs
+++ b/src/Snap/Iteratee.hs
@@ -71,7 +71,6 @@
 import           Control.Exception (SomeException)
 import           System.IO.Posix.MMap
 import           System.PosixCompat.Files
-import           System.Posix.Types
 #endif
 
 ------------------------------------------------------------------------------
@@ -195,11 +194,7 @@
 -- socket) it'll get changed out from underneath you, breaking referential
 -- transparency. Use with caution!
 --
--- The IORef returned can be set to True to "cancel" buffering. We added this
--- so that transfer-encoding: chunked (which needs its own buffer and therefore
--- doesn't need /its/ output buffered) can switch the outer buffer off.
---
-unsafeBufferIteratee :: Iteratee IO a -> IO (Iteratee IO a, IORef Bool)
+unsafeBufferIteratee :: Iteratee IO a -> IO (Iteratee IO a)
 unsafeBufferIteratee iter = do
     buf <- mkIterateeBuffer
     unsafeBufferIterateeWithBuffer buf iter
@@ -214,28 +209,17 @@
 --
 -- This version accepts a buffer created by 'mkIterateeBuffer'.
 --
--- The IORef returned can be set to True to "cancel" buffering. We added this
--- so that transfer-encoding: chunked (which needs its own buffer and therefore
--- doesn't need /its/ output buffered) can switch the outer buffer off.
---
 unsafeBufferIterateeWithBuffer :: ForeignPtr CChar
                                -> Iteratee IO a
-                               -> IO (Iteratee IO a, IORef Bool)
+                               -> IO (Iteratee IO a)
 unsafeBufferIterateeWithBuffer buf iteratee = do
-    esc <- newIORef False
-    return $! (start esc iteratee, esc)
+    return $! start iteratee
 
   where
-    start esc iter = IterateeG $! checkRef esc iter
+    start iter = IterateeG $! f 0 iter
     go bytesSoFar iter =
         {-# SCC "unsafeBufferIteratee/go" #-}
         IterateeG $! f bytesSoFar iter
-
-    checkRef esc iter ch = do
-        quit <- readIORef esc
-        if quit
-          then runIter iter ch
-          else f 0 iter ch
 
     sendBuf n iter =
         {-# SCC "unsafeBufferIteratee/sendBuf" #-}
diff --git a/src/Snap/Starter.hs b/src/Snap/Starter.hs
--- a/src/Snap/Starter.hs
+++ b/src/Snap/Starter.hs
@@ -48,7 +48,7 @@
           else writeFile f c
     insertProjName c = T.unpack $ T.replace
                            (T.pack "projname")
-                           (T.pack projName) c
+                           (T.pack $ filter (/='_') projName) c
 
 ------------------------------------------------------------------------------
 initProject :: [String] -> IO ()
diff --git a/src/Snap/Types.hs b/src/Snap/Types.hs
--- a/src/Snap/Types.hs
+++ b/src/Snap/Types.hs
@@ -37,10 +37,11 @@
     -- ** Logging
   , logError
 
-    -- ** Grabbing request bodies
+    -- ** Grabbing/transforming request bodies
   , runRequestBody
   , getRequestBody
-  , unsafeDetachRequestBody
+  , transformRequestBody
+
     -- * HTTP Datatypes and Functions
     -- $httpDoc
     --
@@ -107,6 +108,7 @@
 
     -- * Iteratee
   , Enumerator
+  , SomeEnumerator(..)
 
     -- * HTTP utilities
   , formatHttpTime
diff --git a/src/Snap/Util/GZip.hs b/src/Snap/Util/GZip.hs
--- a/src/Snap/Util/GZip.hs
+++ b/src/Snap/Util/GZip.hs
@@ -141,6 +141,7 @@
 gzipCompression ce = modifyResponse f
   where
     f = setHeader "Content-Encoding" ce .
+        setHeader "Vary" "Accept-Encoding" .
         clearContentLength .
         modifyResponseBody gcompress
 
@@ -150,6 +151,7 @@
 compressCompression ce = modifyResponse f
   where
     f = setHeader "Content-Encoding" ce .
+        setHeader "Vary" "Accept-Encoding" .
         clearContentLength .
         modifyResponseBody ccompress
 
diff --git a/test/runTestsAndCoverage.sh b/test/runTestsAndCoverage.sh
--- a/test/runTestsAndCoverage.sh
+++ b/test/runTestsAndCoverage.sh
@@ -2,6 +2,7 @@
 
 set -e
 
+export DEBUG=testsuite
 SUITE=./dist/build/testsuite/testsuite
 
 export LC_ALL=C
diff --git a/test/snap-core-testsuite.cabal b/test/snap-core-testsuite.cabal
--- a/test/snap-core-testsuite.cabal
+++ b/test/snap-core-testsuite.cabal
@@ -3,15 +3,6 @@
 build-type:     Simple
 cabal-version:  >= 1.6
 
-Flag debug
-  Description: Enable debug logging to stderr
-  Default: False
-
-Flag testsuite
-  Description: Are we running the testsuite? Causes arguments to \"debug\" to
-               be evaluated but not printed.
-  Default: True
-
 Flag portable
   Description: Compile in cross-platform mode. No platform-specific code or
                optimizations such as C routines will be used.
@@ -20,12 +11,6 @@
 Executable testsuite
   hs-source-dirs:  ../src suite
   main-is:         TestSuite.hs
-
-  if flag(debug)
-    cpp-options: -DDEBUG
-
-  if flag(testsuite)
-    cpp-options: -DDEBUG_TEST
 
   if flag(portable) || os(windows)
     cpp-options: -DPORTABLE
diff --git a/test/suite/Snap/Iteratee/Tests.hs b/test/suite/Snap/Iteratee/Tests.hs
--- a/test/suite/Snap/Iteratee/Tests.hs
+++ b/test/suite/Snap/Iteratee/Tests.hs
@@ -197,8 +197,8 @@
 
 bufferAndRun :: Iteratee IO a -> L.ByteString -> IO a
 bufferAndRun ii s = do
-    (i,_) <- unsafeBufferIteratee ii
-    iter  <- enumLBS s i
+    i    <- unsafeBufferIteratee ii
+    iter <- enumLBS s i
     run iter
 
 
@@ -219,7 +219,7 @@
 testUnsafeBuffer2 = testCase "testUnsafeBuffer2" prop
   where
     prop = do
-        (i,_) <- unsafeBufferIteratee $ drop 4 >> copyingStream2stream
+        i <- unsafeBufferIteratee $ drop 4 >> copyingStream2stream
 
         s <- enumLBS "abcdefgh" i >>= run >>= return . fromWrap
         H.assertEqual "s == 'efgh'" "efgh" s
@@ -244,13 +244,13 @@
                     monadicIO $ forAllM arbitrary prop
   where
     prop s = do
-        (i,_) <- liftQ $
-                 unsafeBufferIteratee (copyingStream2stream >> throwErr (Err "foo"))
+        i  <- liftQ $
+              unsafeBufferIteratee (copyingStream2stream >> throwErr (Err "foo"))
         i' <- liftQ $ enumLBS s i
         expectException $ run i'
 
-        (j,_) <- liftQ $
-                 unsafeBufferIteratee (throwErr (Err "foo") >> copyingStream2stream)
+        j  <- liftQ $
+              unsafeBufferIteratee (throwErr (Err "foo") >> copyingStream2stream)
         j' <- liftQ $ enumLBS s j
         expectException $ run j'
         
diff --git a/test/suite/Snap/Types/Tests.hs b/test/suite/Snap/Types/Tests.hs
--- a/test/suite/Snap/Types/Tests.hs
+++ b/test/suite/Snap/Types/Tests.hs
@@ -219,13 +219,10 @@
     assertEqual "rq body" "zazzle" v1
     assertEqual "rq body 2" "" v2
 
-    _ <- goBody $ g mvar1 mvar2
-    w1 <- takeMVar mvar1
-    w2 <- takeMVar mvar2
-
-    assertEqual "rq body" "zazzle" w1
-    assertEqual "rq body 2" "" w2
+    (_,rsp) <- goBody g
+    bd      <- getBody rsp
 
+    assertEqual "detached rq body" "zazzle" bd
 
 
   where
@@ -233,11 +230,7 @@
         getRequestBody >>= liftIO . putMVar mvar1
         getRequestBody >>= liftIO . putMVar mvar2
 
-    g mvar1 mvar2 = do
-        enum <- unsafeDetachRequestBody
-        bs <- liftM fromWrap (liftIO $ enum stream2stream >>= run)
-        liftIO $ putMVar mvar1 bs
-        getRequestBody >>= liftIO . putMVar mvar2
+    g = transformRequestBody return
 
 
 testTrivials :: Test
