Dan Loewenherz
Dan Loewenherz

Reputation: 11226

Haskell type throwing unexpected exception

I'm querying a remote API and am running into an issue where an exception is being thrown when I would not have expected it. I have inspected the types and it does not seem that there should be a problem binding the value I need to a variable.

I've aliased Network.HTTP.Client to HTTPClient, and import qualified Data.ByteString.Lazy as LBS to LBS.

Here's what my GHCi session looks like:

λ> manager <- HTTPClient.newManager HTTPClient.defaultManagerSettings
λ> request <- HTTPClient.parseUrl postsURL
λ> :t HTTPClient.httpLbs request manager
HTTPClient.httpLbs request manager
  :: IO (HTTPClient.Response LBS.ByteString)
λ> response <- HTTPClient.httpLbs request manager
*** Exception: StatusCodeException (Status {statusCode = 403, statusMessage = "Forbidden"}) [("Date","Thu, 21 Aug 2014 20:25:15 GMT"),("Server","Apache/2.2.22 (Ubuntu)"),("Vary","Accept-Encoding"),("Content-Encoding","gzip"),("Content-Length","239"),("Content-Type","text/html; charset=iso-8859-1"),("X-Response-Body-Start","<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>403 Forbidden</title>\n</head><body>\n<h1>Forbidden</h1>\n<p>You don't have permission to access /posts/all\non this server.</p>\n<hr>\n<address>Apache/2.2.22 (Ubuntu) Server at api.pinboard.in Port 80</address>\n</body></html>\n"),("X-Request-URL","GET http://api.pinboard.in:80/posts/all?format=json&auth_token=username:XXXXX")] (CJ {expose = []})

And then, of course:

λ> response

<interactive>:14:1: Not in scope: `response'

I would've thought that I could bind the result of httpLbs to response since it's wrapped in the IO monad, but it seems that somehow the exception that is raised prevents this from happening. I thought types were supposed to protect against scenarios like this, but maybe I am doing something wrong. Any help would be much appreciated.

Upvotes: 1

Views: 459

Answers (1)

Sibi
Sibi

Reputation: 48644

As a starting point for you, let me give an working example for you:

import           Network.HTTP.Conduit
import qualified Control.Exception as E
import           Network.HTTP.Types.Status
import           Data.ByteString.Lazy.Char8 (pack)

main :: IO ()
main = do
  let url = "http://www.google.com/does-not-exist.txt"
  res <- (simpleHttp url) `E.catch`
        (\(StatusCodeException s _ _) -> (return . pack . show . statusCode $ s))
  print res

In ghci:

λ> main
"404"

In the above example, I'm returing the status code 404 from the exception. You can follow the same method to do your custom logic processing for your Exception.

UPDATE: Method for working with httpLbs:

As suggested in the forum, you can wrap Maybe on top of Response and perform the exception handling:

import Network.HTTP.Client
import qualified Control.Exception as E
import Network.HTTP.Types.Status (statusCode)

main = do
  manager <- newManager defaultManagerSettings
  request <- parseUrl "http://www.google.com/does-not-exist.txt"
  resp <- (fmap Just $ httpLbs request manager) `E.catch` handleMyException
  print resp

handleMyException :: HttpException -> IO (Maybe a)
handleMyException (StatusCodeException s _ _) = if statusCode s == 404
                                                then putStrLn ((show . statusCode $ s) ++ " I'm sorry!") >> return Nothing
                                                else return Nothing

Sample demo:

λ> main
404 I'm sorry!
Nothing

I thought types were supposed to protect against scenarios like this, but maybe I am doing something wrong.

Types cannot protect you when you communicate with the real world. Doing IO activities like reading a file, connecting to a socket etc. depends upon lot of external factors which isn't under the control of Haskell (or any PL for that matter). So when things like that fail, Haskell cannot do anything. But the good thing is that Haskell makes it explicit that those are IO actions indicating that you are out of the pure domain.

Upvotes: 4

Related Questions