diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,9 @@
 # Changelog for `proto-lens`
 
+## v0.5.1.0
+- Add `decodeMessageDelimitedH` to decode delimited messages from a file handle
+  (#61).
+
 ## v0.5.0.1
 - Bump the upper bound on `profunctors` to allow 5.4.
 - Bump the upper bound on `primitive` to allow 0.7.
diff --git a/proto-lens.cabal b/proto-lens.cabal
--- a/proto-lens.cabal
+++ b/proto-lens.cabal
@@ -1,11 +1,13 @@
--- This file has been generated from package.yaml by hpack version 0.28.2.
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.1.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 29ea431ef6a13fa63ce37199c52c0b46df4db87cdfa86c0241034a9d9c5aaa12
+-- hash: 383bc147ec1afaa52a971f87eb6ac95d25c572f183ae0ace7d63ea570da72c44
 
 name:           proto-lens
-version:        0.5.0.1
+version:        0.5.1.0
 synopsis:       A lens-based implementation of protocol buffers in Haskell.
 description:    The proto-lens library provides an API for protocol buffers using modern Haskell language and library patterns.  Specifically, it provides:
                 .
@@ -23,12 +25,11 @@
 license:        BSD3
 license-file:   LICENSE
 build-type:     Simple
-cabal-version:  >= 1.10
 extra-source-files:
     Changelog.md
 data-files:
-    proto-lens-imports/google/protobuf/compiler/plugin.proto
     proto-lens-imports/google/protobuf/descriptor.proto
+    proto-lens-imports/google/protobuf/compiler/plugin.proto
 
 source-repository head
   type: git
diff --git a/src/Data/ProtoLens/Encoding.hs b/src/Data/ProtoLens/Encoding.hs
--- a/src/Data/ProtoLens/Encoding.hs
+++ b/src/Data/ProtoLens/Encoding.hs
@@ -7,12 +7,17 @@
     -- ** Delimited messages
     buildMessageDelimited,
     parseMessageDelimited,
+    decodeMessageDelimitedH,
     ) where
 
+import System.IO (Handle)
+
 import Data.ProtoLens.Message (Message(..))
 import Data.ProtoLens.Encoding.Bytes (Parser, Builder)
 import qualified Data.ProtoLens.Encoding.Bytes as Bytes
 
+import Control.Monad.IO.Class (liftIO)
+import Control.Monad.Trans.Except (runExceptT, ExceptT(..))
 import qualified Data.ByteString as B
 import Data.Semigroup ((<>))
 
@@ -47,3 +52,10 @@
     len <- Bytes.getVarInt
     bytes <- Bytes.getBytes $ fromIntegral len
     either fail return $ decodeMessage bytes
+
+-- | Same as @decodeMessage@ but for delimited messages read through a Handle
+decodeMessageDelimitedH :: Message msg => Handle -> IO (Either String msg)
+decodeMessageDelimitedH h = runExceptT $
+    Bytes.getVarIntH h >>=
+    liftIO . B.hGet h . fromIntegral >>=
+    ExceptT . return . decodeMessage
diff --git a/src/Data/ProtoLens/Encoding/Bytes.hs b/src/Data/ProtoLens/Encoding/Bytes.hs
--- a/src/Data/ProtoLens/Encoding/Bytes.hs
+++ b/src/Data/ProtoLens/Encoding/Bytes.hs
@@ -9,6 +9,7 @@
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE LambdaCase #-}
 
 -- | Utility functions for parsing and encoding individual types.
 module Data.ProtoLens.Encoding.Bytes(
@@ -23,6 +24,7 @@
     putBytes,
     -- * Integral types
     getVarInt,
+    getVarIntH,
     putVarInt,
     getFixed32,
     getFixed64,
@@ -45,6 +47,8 @@
     foldMapBuilder,
     ) where
 
+import Control.Monad.IO.Class (liftIO)
+import Control.Monad.Trans.Except (throwE, ExceptT)
 import Data.Bits
 import Data.ByteString (ByteString)
 import Data.ByteString.Lazy.Builder as Builder
@@ -53,13 +57,16 @@
 import Data.Int (Int32, Int64)
 import Data.Monoid ((<>))
 import qualified Data.Vector.Generic as V
-import Data.Word (Word32, Word64)
+import Data.Word (Word8, Word32, Word64)
+import Foreign.Marshal (malloc, free)
+import Foreign.Storable (peek)
+import System.IO (Handle, hGetBuf)
 #if MIN_VERSION_base(4,11,0)
 import qualified GHC.Float as Float
 #else
 import Foreign.Ptr (castPtr)
 import Foreign.Marshal.Alloc (alloca)
-import Foreign.Storable (Storable, peek, poke)
+import Foreign.Storable (Storable, poke)
 import System.IO.Unsafe (unsafePerformIO)
 #endif
 
@@ -76,14 +83,43 @@
 -- VarInts are inherently unsigned; there are different ways of encoding
 -- negative numbers for int32/64 and sint32/64.
 getVarInt :: Parser Word64
-getVarInt = loop 1 0
+getVarInt = loopStart 0 1
   where
-    loop !s !n = do
-        b <- getWord8
-        let n' = n + s * fromIntegral (b .&. 127)
-        if (b .&. 128) == 0
-            then return $! n'
-            else loop (128*s) n'
+    loopStart !n !s = getWord8 >>= getVarIntLoopFinish loopStart n s
+
+-- Same as getVarInt but reads from a Handle
+getVarIntH :: Handle -> ExceptT String IO Word64
+getVarIntH h = do
+    buf <- liftIO malloc
+    let loopStart !n !s =
+          (liftIO $ hGetBuf h buf 1) >>=
+          \case
+            1 -> (liftIO $ peek buf) >>=
+                 getVarIntLoopFinish loopStart n s
+            _ -> throwE "Unexpected end of file"
+    res <- loopStart 0 1
+    liftIO $ free buf
+    return res
+
+getVarIntLoopFinish
+  :: (Monad m)
+  => (Word64 -> Word64 -> m Word64) -- "loop start" callback
+  -> Word64
+  -> Word64
+  -> Word8
+  -> m Word64
+getVarIntLoopFinish ls !n !s !b = do
+    let n' = decodeVarIntStep n s b
+    if testMsb b
+      then ls n' (128*s)
+      else return $! n'
+
+-- n -- result of previous step; s -- 128^{step index}; b -- step byte
+decodeVarIntStep :: Word64 -> Word64 -> Word8 -> Word64
+decodeVarIntStep n s b = n + s * fromIntegral (b .&. 127)
+
+testMsb :: Word8 -> Bool
+testMsb b = (b .&. 128) /= 0
 
 putVarInt :: Word64 -> Builder
 putVarInt n
