diff --git a/http-kit.cabal b/http-kit.cabal
--- a/http-kit.cabal
+++ b/http-kit.cabal
@@ -1,5 +1,5 @@
 name:             http-kit
-version:          0.3.0
+version:          0.4.0
 synopsis:         A low-level HTTP library
 description:      A low-level HTTP library that can be used to build more
                   sophisticated applications on top of it.
diff --git a/src/Network/HTTP/Toolkit.hs b/src/Network/HTTP/Toolkit.hs
--- a/src/Network/HTTP/Toolkit.hs
+++ b/src/Network/HTTP/Toolkit.hs
@@ -1,6 +1,15 @@
 module Network.HTTP.Toolkit (
+-- * Exceptions
+-- |
+-- * All functions that consume input fail with `UnexpectedEndOfInput` if the
+--   input ends before the function can completely successfully.
+--
+-- * All cases where a function may fail with an exception other than
+--   @UnexpectedEndOfInput@ are documented thoroughly on a per function level.
+--
+  ToolkitError(..)
 -- * Connection
-  Connection
+, Connection
 , makeConnection
 , connectionFromHandle
 
@@ -22,9 +31,6 @@
 , BodyReader
 , sendBody
 , consumeBody
-
--- * Error type
-, ToolkitError(..)
 ) where
 
 import           Network.HTTP.Toolkit.Body
diff --git a/src/Network/HTTP/Toolkit/Body.hs b/src/Network/HTTP/Toolkit/Body.hs
--- a/src/Network/HTTP/Toolkit/Body.hs
+++ b/src/Network/HTTP/Toolkit/Body.hs
@@ -124,8 +124,9 @@
     if done
       then return ""
       else do
-        xs <- connectionRead c
-        when (B.null xs) $ writeIORef ref True
+        xs <- connectionRead c `catchOnly` UnexpectedEndOfInput $ do
+          writeIORef ref True
+          return ""
         return xs
 
 -- | Create a reader for bodies with a specified length.
@@ -158,32 +159,36 @@
 makeChunkedReader :: Connection -> IO BodyReader
 makeChunkedReader conn = do
   ref <- newIORef (More 0 Data)
-  return $ do
-    c <- readIORef ref
-    case c of
-      More 0 Data -> do
-        (n, xs) <- readChunkSize conn
-        writeIORef ref (More n Extension)
-        return xs
-      More n Extension -> do
-        bs <- connectionRead conn
-        case breakOnNewline bs of
-          ("", _) ->
-            if n > 0
-              then do
-                handleChunkData ref (n + 3) bs
-              else do
-                writeIORef ref Trailer
-                connectionUnread conn bs
-                readTrailer ref
-          (xs, ys) -> do
-            connectionUnread conn ys
-            return xs
-      More n Data -> do
-        connectionRead conn >>= handleChunkData ref n
-      Trailer -> readTrailer ref
-      Done -> return ""
+  return $ go ref `catchOnly` UnexpectedEndOfInput $ do
+    writeIORef ref Done
+    return ""
   where
+    go ref = do
+      c <- readIORef ref
+      case c of
+        More 0 Data -> do
+          (n, xs) <- readChunkSize conn
+          writeIORef ref (More n Extension)
+          return xs
+        More n Extension -> do
+          bs <- connectionRead conn
+          case breakOnNewline bs of
+            ("", _) ->
+              if n > 0
+                then do
+                  handleChunkData ref (n + 3) bs
+                else do
+                  writeIORef ref Trailer
+                  connectionUnread conn bs
+                  readTrailer ref
+            (xs, ys) -> do
+              connectionUnread conn ys
+              return xs
+        More n Data -> do
+          connectionRead conn >>= handleChunkData ref n
+        Trailer -> readTrailer ref
+        Done -> return ""
+
     handleChunkData :: IORef State -> Int -> ByteString -> IO ByteString
     handleChunkData ref n bs = do
       let (xs, ys) = B.splitAt n bs
diff --git a/src/Network/HTTP/Toolkit/Connection.hs b/src/Network/HTTP/Toolkit/Connection.hs
--- a/src/Network/HTTP/Toolkit/Connection.hs
+++ b/src/Network/HTTP/Toolkit/Connection.hs
@@ -8,12 +8,15 @@
 ) where
 
 import           Prelude hiding (read)
-import           Control.Monad (join, unless)
+import           Control.Monad (join, when, unless)
+import           Control.Exception
 import           System.IO (Handle)
 import           Data.IORef
 import           Data.ByteString (ByteString)
 import qualified Data.ByteString as B
 
+import           Network.HTTP.Toolkit.Error
+
 -- | An abstract connection type that allows to read and unread input.
 data Connection = Connection {
   _read :: IO ByteString
@@ -37,7 +40,10 @@
 
 -- | Read some input.
 connectionRead :: Connection -> IO ByteString
-connectionRead = _read
+connectionRead c = do
+  bs <- _read c
+  when (B.null bs) $ throwIO UnexpectedEndOfInput
+  return bs
 
 -- | Push back some input.  The pushed back input will be returned by a later
 -- call to `connectionRead`.
diff --git a/src/Network/HTTP/Toolkit/Error.hs b/src/Network/HTTP/Toolkit/Error.hs
--- a/src/Network/HTTP/Toolkit/Error.hs
+++ b/src/Network/HTTP/Toolkit/Error.hs
@@ -2,12 +2,15 @@
 module Network.HTTP.Toolkit.Error where
 
 import           Data.Typeable
+import           Control.Monad (guard)
 import           Control.Exception
 import           Data.ByteString (ByteString)
 
 data ToolkitError =
+    -- | The input ended unpexpectedly while reading a message.
+    UnexpectedEndOfInput
     -- | The request-line of the message is malformed.
-    InvalidRequestLine ByteString
+  | InvalidRequestLine ByteString
     -- | The status-line of the message is malformed.
   | InvalidStatusLine ByteString
     -- | A header field is malformed.
@@ -23,3 +26,6 @@
   deriving (Eq, Show, Typeable)
 
 instance Exception ToolkitError
+
+catchOnly :: (Eq e, Exception e) => IO a -> e -> IO a -> IO a
+(action `catchOnly` e) handler = catchJust (guard . (== e)) action $ \() -> handler
