Reputation: 11226
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
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