diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,8 @@
 # Revision history for binary-parsec
 
-## 0.1.0.0  -- YYYY-mm-dd
+## 0.2.0.0  -- 2016-09-21
 
-* First version. Released on an unsuspecting world.
+* Add `endOfLine` combinator.
+* Add http request parsing benchmark.
+* Fix a numeric parser bug.
+* Fix wrong documents.
diff --git a/Data/Binary/Parser.hs b/Data/Binary/Parser.hs
--- a/Data/Binary/Parser.hs
+++ b/Data/Binary/Parser.hs
@@ -33,6 +33,7 @@
 -- count == replicateM
 -- atEnd == isEmpty
 -- take == getByteString
+-- many1 == some
 -- @
 --
 -- For fast byte set operations, please use <http://hackage.haskell.org/package/charset charset>
@@ -164,11 +165,11 @@
         some_p = liftM2' (:) p many_p
 {-# INLINE many' #-}
 
--- | @many1' p@ applies the action @p@ /one/ or more times. Returns a
+-- | @some' p@ applies the action @p@ /one/ or more times. Returns a
 -- list of the returned values of @p@. The value returned by @p@ is
 -- forced to WHNF.
 --
--- >  word  = many1' letter
+-- >  word  = some' letter
 some' :: (MonadPlus m) => m a -> m [a]
 some' p = liftM2' (:) p (many' p)
 {-# INLINE some' #-}
diff --git a/Data/Binary/Parser/Char8.hs b/Data/Binary/Parser/Char8.hs
--- a/Data/Binary/Parser/Char8.hs
+++ b/Data/Binary/Parser/Char8.hs
@@ -76,7 +76,7 @@
 anyChar = w2c <$> BG.getWord8
 {-# INLINE anyChar #-}
 
--- | The parser @skip p@ succeeds for any char for which the predicate @p@ returns 'True'.
+-- | The parser @skipChar p@ succeeds for any char for which the predicate @p@ returns 'True'.
 --
 skipChar :: (Char -> Bool) -> Get ()
 skipChar p = W.skipWord8 (p . w2c)
diff --git a/Data/Binary/Parser/Numeric.hs b/Data/Binary/Parser/Numeric.hs
--- a/Data/Binary/Parser/Numeric.hs
+++ b/Data/Binary/Parser/Numeric.hs
@@ -112,20 +112,20 @@
 -- Examples with behaviour identical to 'read', if you feed an empty
 -- continuation to the first result:
 --
--- >double "3"     == Done 3.0 ""
--- >double "3.1"   == Done 3.1 ""
--- >double "3e4"   == Done 30000.0 ""
--- >double "3.1e4" == Done 31000.0, ""
+-- >runGetOrFail double "3"     == Right ("",1,3.0)
+-- >runGetOrFail double "3.1"   == Right ("",3,3.1)
+-- >runGetOrFail double "3e4"   == Right ("",3,30000.0)
+-- >runGetOrFail double "3.1e4" == Right ("",5,31000.0)
 --
 -- Examples with behaviour identical to 'read':
 --
--- >double ".3"    == Fail "input does not start with a digit"
--- >double "e3"    == Fail "input does not start with a digit"
+-- >runGetOrFail double ".3"    == Left (".3",0,"takeWhile1")
+-- >runGetOrFail double "e3"    == Left ("e3",0,"takeWhile1")
 --
 -- Examples of differences from 'read':
 --
--- >double "3.foo" == Done 3.0 ".foo"
--- >double "3e"    == Done 3.0 "e"
+-- >runGetOrFail double "3.foo" == Right (".foo",1,3.0)
+-- >runGetOrFail double "3e"    == Right ("e",1,3.0)
 --
 -- This function does not accept string representations of \"NaN\" or
 -- \"Infinity\".
@@ -154,7 +154,7 @@
                    intPart' = intPart * (10 ^ B.length fracDigits)
                    fracPart = LexInt.readDecimal_ fracDigits
                parseE (intPart' + fracPart) e'
-           ) <|> (pure (Sci.scientific intPart 0))
+           ) <|> (parseE intPart 0)
 
     if sign /= MINUS then return $! h sci else return $! h (negate sci)
   where
diff --git a/Data/Binary/Parser/Word8.hs b/Data/Binary/Parser/Word8.hs
--- a/Data/Binary/Parser/Word8.hs
+++ b/Data/Binary/Parser/Word8.hs
@@ -107,7 +107,7 @@
 anyWord8 = getWord8
 {-# INLINE anyWord8 #-}
 
--- | The parser @skip p@ succeeds for any byte for which the predicate @p@ returns 'True'.
+-- | The parser @skipWord8 p@ succeeds for any byte for which the predicate @p@ returns 'True'.
 --
 skipWord8 :: (Word8 -> Bool) -> Get ()
 skipWord8 p = do
@@ -148,7 +148,7 @@
         put rest
         if B.null rest
         then do
-            e <- isEmpty
+            e <- isEmpty -- isEmpty will draw input here
             if e then return acc' else go acc'
         else return acc'
 {-# INLINE takeTill #-}
@@ -295,3 +295,15 @@
 isEndOfLine :: Word8 -> Bool
 isEndOfLine w = w == 13 || w == 10
 {-# INLINE isEndOfLine #-}
+
+--------------------------------------------------------------------------------
+
+-- | Match either a single newline byte @\'\\n\'@, or a carriage
+-- return followed by a newline byte @\"\\r\\n\"@.
+endOfLine :: Get ()
+endOfLine = do
+    w <- getWord8
+    case w of
+        10 -> return ()
+        13 -> word8 10
+        _  -> fail "endOfLine"
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -32,7 +32,27 @@
 ---------
 
 ```
-Benchmark criterion: RUNNING...
+benchmarking http-req/attoparsec
+time                 2.240 μs   (2.216 μs .. 2.264 μs)
+                     0.998 R²   (0.996 R² .. 0.999 R²)
+mean                 2.320 μs   (2.251 μs .. 2.702 μs)
+std dev              417.9 ns   (60.72 ns .. 1.012 μs)
+variance introduced by outliers: 96% (severely inflated)
+
+benchmarking http-req/binary-parsers
+time                 1.552 μs   (1.533 μs .. 1.577 μs)
+                     0.980 R²   (0.942 R² .. 0.999 R²)
+mean                 1.673 μs   (1.549 μs .. 1.984 μs)
+std dev              583.2 ns   (53.70 ns .. 1.115 μs)
+variance introduced by outliers: 99% (severely inflated)
+
+benchmarking http-req/warp
+time                 903.0 ns   (894.7 ns .. 911.2 ns)
+                     0.999 R²   (0.998 R² .. 0.999 R²)
+mean                 906.6 ns   (896.2 ns .. 918.4 ns)
+std dev              36.38 ns   (28.49 ns .. 51.58 ns)
+variance introduced by outliers: 56% (severely inflated)
+
 benchmarking attoparsec/buffer-builder
 time                 4.025 ms   (3.965 ms .. 4.097 ms)
                      0.998 R²   (0.997 R² .. 0.999 R²)
diff --git a/bench/Aeson.hs b/bench/Aeson.hs
--- a/bench/Aeson.hs
+++ b/bench/Aeson.hs
@@ -40,6 +40,7 @@
 import qualified Data.ByteString.Unsafe as B
 import qualified Data.HashMap.Strict as H
 import Criterion.Main
+import Common (pathTo)
 
 #define BACKSLASH 92
 #define CLOSE_CURLY 125
@@ -56,13 +57,6 @@
 #define C_f 102
 #define C_n 110
 #define C_t 116
-
-pathTo :: String -> IO FilePath
-pathTo wat = do
-  exists <- doesDirectoryExist "bench"
-  return $ if exists
-           then "bench" </> wat
-           else wat
 
 data Result a = Error String
               | Success a
diff --git a/bench/AesonBP.hs b/bench/AesonBP.hs
--- a/bench/AesonBP.hs
+++ b/bench/AesonBP.hs
@@ -33,14 +33,12 @@
 import qualified Data.Attoparsec.Zepto as Z
 import Data.Binary.Get (Get)
 import qualified Data.Binary.Parser as BP
-import qualified Data.Binary.Get as BG
-import qualified Data.Binary.Parser.Word8 as BP
-import qualified Data.Binary.Parser.Numeric as BP
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as L
 import qualified Data.ByteString.Unsafe as B
 import qualified Data.HashMap.Strict as H
 import Criterion.Main
+import Common (pathTo)
 
 #define BACKSLASH 92
 #define CLOSE_CURLY 125
@@ -58,13 +56,6 @@
 #define C_f 102
 #define C_n 110
 #define C_t 116
-
-pathTo :: String -> IO FilePath
-pathTo wat = do
-  exists <- doesDirectoryExist "bench"
-  return $ if exists
-           then "bench" </> wat
-           else wat
 
 data Result a = Error String
               | Success a
diff --git a/bench/Bench.hs b/bench/Bench.hs
--- a/bench/Bench.hs
+++ b/bench/Bench.hs
@@ -5,10 +5,14 @@
 import Criterion.Main
 import qualified Aeson
 import qualified AesonBP
+import qualified HttpReq
 import Data.List
 
 main :: IO ()
 main = do
+    http <- HttpReq.headers
+    defaultMain http
+
     aeson <- Aeson.aeson
     aesonbp <- AesonBP.aeson
     aesonLazy <- Aeson.aesonLazy
diff --git a/bench/Common.hs b/bench/Common.hs
new file mode 100644
--- /dev/null
+++ b/bench/Common.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE CPP #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module Common (
+      chunksOf
+    , pathTo
+    , rechunkBS
+    , rechunkT
+    ) where
+
+import Control.DeepSeq (NFData(rnf))
+import System.Directory (doesDirectoryExist)
+import System.FilePath ((</>))
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+
+#if !MIN_VERSION_bytestring(0,10,0)
+import Data.ByteString.Internal (ByteString(..))
+
+instance NFData ByteString where
+    rnf (PS _ _ _) = ()
+#endif
+
+chunksOf :: Int -> [a] -> [[a]]
+chunksOf k = go
+  where go xs = case splitAt k xs of
+                  ([],_)  -> []
+                  (y, ys) -> y : go ys
+
+rechunkBS :: Int -> B.ByteString -> BL.ByteString
+rechunkBS n = BL.fromChunks . map B.pack . chunksOf n . B.unpack
+
+rechunkT :: Int -> T.Text -> TL.Text
+rechunkT n = TL.fromChunks . map T.pack . chunksOf n . T.unpack
+
+pathTo :: String -> IO FilePath
+pathTo wat = do
+  exists <- doesDirectoryExist "bench"
+  return $ if exists
+           then "bench" </> wat
+           else wat
diff --git a/bench/HttpReq.hs b/bench/HttpReq.hs
new file mode 100644
--- /dev/null
+++ b/bench/HttpReq.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE OverloadedStrings #-}
+module HttpReq (headers) where
+
+import Common (pathTo, rechunkBS)
+import Control.Applicative
+import Criterion.Main (bench, bgroup, nf, nfIO)
+import Control.DeepSeq (NFData(..))
+import Criterion.Types (Benchmark)
+import Network.Wai.Handler.Warp.RequestHeader (parseHeaderLines)
+import qualified Data.Attoparsec.ByteString as AP
+import qualified Data.Attoparsec.ByteString.Char8 as APC
+import qualified Data.ByteString.Char8 as BC
+import qualified Data.Binary.Parser as BP
+import qualified Data.Binary.Parser.Char8 as BPC
+import Network.HTTP.Types.Version (HttpVersion, http11)
+
+headers :: IO [Benchmark]
+headers = do
+  req <- BC.readFile =<< pathTo "http-request.txt"
+  return [
+        bench "http-req/attoparsec" $ nf (AP.parseOnly attoRequest) req
+      , bench "http-req/binary-parsers" $ nf (BP.parseOnly bpRequest) req
+      , bench "http-req/warp" $ nfIO (parseHeaderLines (BC.lines req))
+      ]
+
+--------------------------------------------------------------------------------
+
+instance NFData HttpVersion where
+    rnf !_ = ()
+
+attoHeader = do
+  name <- APC.takeWhile1 (APC.inClass "a-zA-Z0-9_-") <* APC.char ':' <* APC.skipSpace
+  body <- attoBodyLine
+  return (name, body)
+
+attoBodyLine = APC.takeTill (\c -> c == '\r' || c == '\n') <* APC.endOfLine
+
+attoReqLine = do
+  m <- (APC.takeTill APC.isSpace <* APC.char ' ')
+  (p,q) <- BC.break (=='?') <$> (APC.takeTill APC.isSpace <* APC.char ' ')
+  v <- attoHttpVersion
+  return (m,p,q,v)
+
+attoHttpVersion = http11 <$ APC.string "HTTP/1.1"
+
+attoRequest = (,) <$> (attoReqLine <* APC.endOfLine) <*> attoManyHeader
+
+attoManyHeader = do
+  c <- APC.peekChar'
+  if c == '\r' || c == '\n'
+    then return []
+
+    else (:) <$> attoHeader <*> attoManyHeader
+
+--------------------------------------------------------------------------------
+
+bpHeader = do
+  name <- BPC.takeWhile1 isHeaderChar <* BPC.char ':' <* BP.skipSpaces
+  body <- bpBodyLine
+  return (name, body)
+  where
+    isHeaderChar c = ('a' <= c && c <= 'z')
+                  || ('A' <= c && c <= 'Z')
+                  || ('0' <= c && c <= '0')
+                  || c == '_' || c == '-'
+
+bpBodyLine = BPC.takeTill (\c -> c == '\r' || c == '\n') <* BP.satisfy BP.isEndOfLine
+
+bpReqLine = do
+  m <- (BPC.takeTill BPC.isSpace <* BPC.char ' ')
+  (p,q) <- BC.break (=='?') <$> (BPC.takeTill BPC.isSpace <* BPC.char ' ')
+  v <- bpHttpVersion
+  return (m,p,q,v)
+
+bpHttpVersion = http11 <$ BP.string "HTTP/1.1"
+
+bpRequest = (,) <$> (bpReqLine <* BP.satisfy BP.isEndOfLine) <*> bpManyHeader
+
+bpManyHeader = do
+  c <- BPC.peek
+  if c == '\r' || c == '\n'
+    then return []
+    else (:) <$> bpHeader <*> bpManyHeader
diff --git a/bench/Network/Wai/Handler/Warp/ReadInt.hs b/bench/Network/Wai/Handler/Warp/ReadInt.hs
new file mode 100644
--- /dev/null
+++ b/bench/Network/Wai/Handler/Warp/ReadInt.hs
@@ -0,0 +1,67 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE BangPatterns #-}
+
+-- Copyright     : Erik de Castro Lopo <erikd@mega-nerd.com>
+-- License       : BSD3
+
+module Network.Wai.Handler.Warp.ReadInt (
+    readInt
+  , readInt64
+  ) where
+
+-- This function lives in its own file because the MagicHash pragma interacts
+-- poorly with the CPP pragma.
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as S
+import Data.Int (Int64)
+import GHC.Prim
+import GHC.Types
+import GHC.Word
+
+{-# INLINE readInt #-}
+readInt :: Integral a => ByteString -> a
+readInt bs = fromIntegral $ readInt64 bs
+
+-- This function is used to parse the Content-Length field of HTTP headers and
+-- is a performance hot spot. It should only be replaced with something
+-- significantly and provably faster.
+--
+-- It needs to be able work correctly on 32 bit CPUs for file sizes > 2G so we
+-- use Int64 here and then make a generic 'readInt' that allows conversion to
+-- Int and Integer.
+
+{-# NOINLINE readInt64 #-}
+readInt64 :: ByteString -> Int64
+readInt64 bs = S.foldl' (\ !i !c -> i * 10 + fromIntegral (mhDigitToInt c)) 0
+             $ S.takeWhile isDigit bs
+
+data Table = Table !Addr#
+
+{-# NOINLINE mhDigitToInt #-}
+mhDigitToInt :: Word8 -> Int
+mhDigitToInt (W8# i) = I# (word2Int# (indexWord8OffAddr# addr (word2Int# i)))
+  where
+    !(Table addr) = table
+    table :: Table
+    table = Table
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"#
+
+isDigit :: Word8 -> Bool
+isDigit w = w >= 48 && w <= 57
diff --git a/bench/Network/Wai/Handler/Warp/RequestHeader.hs b/bench/Network/Wai/Handler/Warp/RequestHeader.hs
new file mode 100644
--- /dev/null
+++ b/bench/Network/Wai/Handler/Warp/RequestHeader.hs
@@ -0,0 +1,172 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE BangPatterns #-}
+
+module Network.Wai.Handler.Warp.RequestHeader (
+      parseHeaderLines
+    , parseByteRanges
+    ) where
+
+import Control.Exception (Exception, throwIO)
+import Control.Monad (when)
+import Data.Typeable (Typeable)
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Char8 as B (unpack, readInteger)
+import Data.ByteString.Internal (ByteString(..), memchr)
+import qualified Data.CaseInsensitive as CI
+import Data.Word (Word8)
+import Foreign.ForeignPtr (withForeignPtr)
+import Foreign.Ptr (Ptr, plusPtr, minusPtr, nullPtr)
+import Foreign.Storable (peek)
+import qualified Network.HTTP.Types as H
+-- import Network.Wai.Handler.Warp.Types
+import qualified Network.HTTP.Types.Header as HH
+-- $setup
+-- >>> :set -XOverloadedStrings
+
+data InvalidRequest = NotEnoughLines [String]
+                    | BadFirstLine String
+                    | NonHttp
+                    | IncompleteHeaders
+                    | ConnectionClosedByPeer
+                    | OverLargeHeader
+                    deriving (Eq, Typeable, Show)
+
+instance Exception InvalidRequest
+
+----------------------------------------------------------------
+
+parseHeaderLines :: [ByteString]
+                 -> IO (H.Method
+                       ,ByteString  --  Path
+                       ,ByteString  --  Path, parsed
+                       ,ByteString  --  Query
+                       ,H.HttpVersion
+                       ,H.RequestHeaders
+                       )
+parseHeaderLines [] = throwIO $ NotEnoughLines []
+parseHeaderLines (firstLine:otherLines) = do
+    (method, path', query, httpversion) <- parseRequestLine firstLine
+    let path = H.extractPath path'
+        hdr = map parseHeader otherLines
+    return (method, path', path, query, httpversion, hdr)
+
+----------------------------------------------------------------
+
+-- |
+--
+-- >>> parseRequestLine "GET / HTTP/1.1"
+-- ("GET","/","",HTTP/1.1)
+-- >>> parseRequestLine "POST /cgi/search.cgi?key=foo HTTP/1.0"
+-- ("POST","/cgi/search.cgi","?key=foo",HTTP/1.0)
+-- >>> parseRequestLine "GET "
+-- *** Exception: Warp: Invalid first line of request: "GET "
+-- >>> parseRequestLine "GET /NotHTTP UNKNOWN/1.1"
+-- *** Exception: Warp: Request line specified a non-HTTP request
+parseRequestLine :: ByteString
+                 -> IO (H.Method
+                       ,ByteString -- Path
+                       ,ByteString -- Query
+                       ,H.HttpVersion)
+parseRequestLine requestLine@(PS fptr off len) = withForeignPtr fptr $ \ptr -> do
+    when (len < 14) $ throwIO baderr
+    let methodptr = ptr `plusPtr` off
+        limptr = methodptr `plusPtr` len
+        lim0 = fromIntegral len
+
+    pathptr0 <- memchr methodptr 32 lim0 -- ' '
+    when (pathptr0 == nullPtr || (limptr `minusPtr` pathptr0) < 11) $
+        throwIO baderr
+    let pathptr = pathptr0 `plusPtr` 1
+        lim1 = fromIntegral (limptr `minusPtr` pathptr0)
+
+    httpptr0 <- memchr pathptr 32 lim1 -- ' '
+    when (httpptr0 == nullPtr || (limptr `minusPtr` httpptr0) < 9) $
+        throwIO baderr
+    let httpptr = httpptr0 `plusPtr` 1
+        lim2 = fromIntegral (httpptr0 `minusPtr` pathptr)
+
+    checkHTTP httpptr
+    !hv <- httpVersion httpptr
+    queryptr <- memchr pathptr 63 lim2 -- '?'
+
+    let !method = bs ptr methodptr pathptr0
+        !path
+          | queryptr == nullPtr = bs ptr pathptr httpptr0
+          | otherwise           = bs ptr pathptr queryptr
+        !query
+          | queryptr == nullPtr = S.empty
+          | otherwise           = bs ptr queryptr httpptr0
+
+    return (method,path,query,hv)
+  where
+    baderr = BadFirstLine $ B.unpack requestLine
+    check :: Ptr Word8 -> Int -> Word8 -> IO ()
+    check p n w = do
+        w0 <- peek $ p `plusPtr` n
+        when (w0 /= w) $ throwIO NonHttp
+    checkHTTP httpptr = do
+        check httpptr 0 72 -- 'H'
+        check httpptr 1 84 -- 'T'
+        check httpptr 2 84 -- 'T'
+        check httpptr 3 80 -- 'P'
+        check httpptr 4 47 -- '/'
+        check httpptr 6 46 -- '.'
+    httpVersion httpptr = do
+        major <- peek $ httpptr `plusPtr` 5
+        minor <- peek $ httpptr `plusPtr` 7
+        return $ if major == (49 :: Word8) && minor == (49 :: Word8) then
+            H.http11
+          else
+            H.http10
+    bs ptr p0 p1 = PS fptr o l
+      where
+        o = p0 `minusPtr` ptr
+        l = p1 `minusPtr` p0
+
+----------------------------------------------------------------
+
+-- |
+--
+-- >>> parseHeader "Content-Length:47"
+-- ("Content-Length","47")
+-- >>> parseHeader "Accept-Ranges: bytes"
+-- ("Accept-Ranges","bytes")
+-- >>> parseHeader "Host:  example.com:8080"
+-- ("Host","example.com:8080")
+-- >>> parseHeader "NoSemiColon"
+-- ("NoSemiColon","")
+
+parseHeader :: ByteString -> H.Header
+parseHeader s =
+    let (k, rest) = S.break (== 58) s -- ':'
+        rest' = S.dropWhile (\c -> c == 32 || c == 9) $ S.drop 1 rest
+     in (CI.mk k, rest')
+
+parseByteRanges :: S.ByteString -> Maybe HH.ByteRanges
+parseByteRanges bs1 = do
+    bs2 <- stripPrefix "bytes=" bs1
+    (r, bs3) <- range bs2
+    ranges (r:) bs3
+  where
+    range bs2 =
+        case stripPrefix "-" bs2 of
+            Just bs3 -> do
+                (i, bs4) <- B.readInteger bs3
+                Just (HH.ByteRangeSuffix i, bs4)
+            Nothing -> do
+                (i, bs3) <- B.readInteger bs2
+                bs4 <- stripPrefix "-" bs3
+                case B.readInteger bs4 of
+                    Nothing -> Just (HH.ByteRangeFrom i, bs4)
+                    Just (j, bs5) -> Just (HH.ByteRangeFromTo i j, bs5)
+    ranges front bs3 =
+        case stripPrefix "," bs3 of
+            Nothing -> Just (front [])
+            Just bs4 -> do
+                (r, bs5) <- range bs4
+                ranges (front . (r:)) bs5
+
+    stripPrefix x y
+        | x `S.isPrefixOf` y = Just (S.drop (S.length x) y)
+        | otherwise = Nothing
diff --git a/bench/http-request.txt b/bench/http-request.txt
new file mode 100644
--- /dev/null
+++ b/bench/http-request.txt
@@ -0,0 +1,9 @@
+GET / HTTP/1.1
+Host: twitter.com
+Accept: text/html, application/xhtml+xml, application/xml; q=0.9, image/webp, */*; q=0.8
+Accept-Encoding: gzip,deflate,sdch
+Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
+Cache-Control: max-age=0
+Cookie: guest_id=v1%3A139; _twitter_sess=BAh7CSIKZmxhc2hJQz-e1e1; __utma=43838368.452555194.1399611824.1; __utmb=43838368; __utmc=43838368; __utmz=1399611824.1.1.utmcsr=(direct)|utmcmd=(none)
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.86 Safari/537.36
+
diff --git a/binary-parsers.cabal b/binary-parsers.cabal
--- a/binary-parsers.cabal
+++ b/binary-parsers.cabal
@@ -1,5 +1,5 @@
 name:                binary-parsers
-version:             0.1.0.0
+version:             0.2.0.0
 synopsis:            Extends binary with parsec/attoparsec style parsing combinators.
 description:         Extends binary with parsec/attoparsec style parsing combinators.
 license:             BSD3
@@ -13,6 +13,7 @@
                      , README.md
                      , bench/json-data/*.json
                      , tests/json-data/*.json
+                     , bench/http-request.txt
 
 cabal-version:       >=1.10
 homepage:            https://github.com/winterland1989/binary-parsers
@@ -40,6 +41,11 @@
 benchmark criterion
     other-modules:       Aeson
                     ,    AesonBP
+                    ,    Common
+                    ,    HttpReq
+                    ,    Network.Wai.Handler.Warp.ReadInt
+                    ,    Network.Wai.Handler.Warp.RequestHeader
+
     build-depends:      base
                     ,   deepseq
                     ,   bytestring
@@ -53,6 +59,8 @@
                     ,   directory
                     ,   filepath
                     ,   unordered-containers
+                    ,   http-types
+                    ,   case-insensitive
 
     default-language: Haskell2010
     hs-source-dirs:   bench
diff --git a/tests/AesonBP.hs b/tests/AesonBP.hs
--- a/tests/AesonBP.hs
+++ b/tests/AesonBP.hs
@@ -28,9 +28,6 @@
 import qualified Data.Attoparsec.Zepto as Z
 import Data.Binary.Get (Get)
 import qualified Data.Binary.Parser as BP
-import qualified Data.Binary.Get as BG
-import qualified Data.Binary.Parser.Word8 as BP
-import qualified Data.Binary.Parser.Numeric as BP
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as L
 import qualified Data.ByteString.Unsafe as B
diff --git a/tests/JSON.hs b/tests/JSON.hs
--- a/tests/JSON.hs
+++ b/tests/JSON.hs
@@ -6,7 +6,7 @@
 import Test.Tasty.HUnit
 import Test.Tasty (TestTree)
 import System.Directory (getDirectoryContents, doesDirectoryExist)
-import System.FilePath ((</>), dropExtension)
+import System.FilePath ((</>))
 import qualified Aeson as A
 import qualified Data.Attoparsec.ByteString as A
 import qualified AesonBP as B
diff --git a/tests/QC/ByteString.hs b/tests/QC/ByteString.hs
--- a/tests/QC/ByteString.hs
+++ b/tests/QC/ByteString.hs
@@ -126,7 +126,7 @@
 
 endOfLine :: L.ByteString -> Property
 endOfLine s =
-  case (parseBS (P.satisfy P.isEndOfLine) s, L8.uncons s) of
+  case (parseBS P.endOfLine s, L8.uncons s) of
     (Nothing, mcs) -> maybe (property True) (expectFailure . eol) mcs
     (Just _,  mcs) -> maybe (property False) eol mcs
   where eol (c,s') = c === '\n' .||.
@@ -137,6 +137,11 @@
   where p = P.scan k $ \ n _ ->
             if n > 0 then let !n' = n - 1 in Just n' else Nothing
 
+decimal :: Integer -> Property
+decimal d =
+    let dBS = BB.toLazyByteString $ BB.integerDec d
+    in parseBS (P.signed P.decimal) dBS === Just d
+
 double :: Double -> Property
 double d =
     let dBS = BB.toLazyByteString $ BB.doubleDec d
@@ -170,6 +175,7 @@
     , testProperty "takeWhile1" takeWhile1
     , testProperty "takeWhile1_empty" takeWhile1_empty
     , testProperty "word8" word8
+    , testProperty "decimal" decimal
     , testProperty "double" double
     , testProperty "scientific" scientific
   ]
diff --git a/tests/QC/Common.hs b/tests/QC/Common.hs
--- a/tests/QC/Common.hs
+++ b/tests/QC/Common.hs
@@ -17,7 +17,6 @@
 #endif
 import Data.Char (isAlpha)
 import Test.QuickCheck
-import Test.QuickCheck.Unicode (shrinkChar, string)
 import Test.QuickCheck.Instances ()
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as BL
