Search code examples
haskellhttp-conduit

Handling 404 via Maybe in http-conduit


Using http-conduit I want to download a HTTP URL implementing the following semantics (resulting in IO (Maybe LB.ByteString) ):

  1. If the HTTP response code is 2xx, return Just the response body
  2. If the HTTP response code is 404, return Nothing
  3. If the response code indicates a redirect, follow according to standard http-conduit settings
  4. For any other response code, throw a StatusCodeException.

How can I do that using httpLbs without any libraries beyond http-conduit and its dependencies?

Note: This question was answered in Q&A form and therefore intentionally does not show research effort.


Solution

  • This is possible by using a custom checkStatus similar to one of the offical examples.

    We will declare checkStatus200 which passes if the response status code is either 2xx or 404. If it doesn't pass, it calls the default checkStatus function to throw the appropriate exception.

    After calling httpLbs with a Request using checkStatus200, we can check the status code and return either Just the response code or Nothing.

    import Data.Conduit.Binary (sinkFile)
    import Network.HTTP.Types.Status (Status(..))
    import Network.HTTP.Conduit
    import qualified Data.Conduit as C
    import Network
    import Data.Default (def)
    import qualified Data.ByteString.Lazy as LB
    
    -- | @checkStatus@ implementation that accepts 
    --   2xx status codes and 404. Calls default implementation
    --   on other status codes (i.e. throws exception)
    checkStatus200 st@(Status sc _) rh cj =
        if (200 <= sc && sc < 300) || sc == 404
            then Nothing
            else (checkStatus def) st rh cj
    
    -- | Download a HTTP link, returning @Nothing@ on 404 status code
    downloadCatch404 :: String
                     -> IO (Maybe LB.ByteString)
    downloadCatch404 url = withSocketsDo $ do
        request <- parseUrl url
        let request' = request { checkStatus = checkStatus200 }
        res <- withManager $ httpLbs request'
        let status =  statusCode . responseStatus $ res
        -- Return Nothing if status code == 404
        return $ if status == 404
            then Nothing
            else Just $ responseBody res