QiitaへのHaskellタグ記事初投稿です。よろしくお願いします。Qiitaはソースコードの色付けがきれいですね。
Haskellで、Network.HTTPとText.ParserCombinators.Parsecを使って、HTTPクライアントを書いてみました。
もっとも簡単なレスポンスを返すHTTPサーバとして、日本標準時を選びました。こちら
http://ntp-a1.nict.go.jp/cgi-bin/time
にアクセスすると、こういうフォーマット
"Sat Aug 23 22:38:19 2014 JST\n"
でレスポンスが返ってきます。それをパースしてみます。
ネットワークから受信しようとすると例外を投げる可能性があります。例外が投げられなかったら、パースしますが、パースも失敗する可能性があります。結果をEitherで受け取り、caseで場合分けします。
パーサーはパターンの再帰こそありませんが、パーサーを組み合わせて別のパーサー作ってあります。
逐次処理すればいい処理は、do~returnではなく、アプリカティブスタイルで書いてあります。Parsecとファンクターとアプリカティブとモナドとネットワークと例外のお勉強にどうぞ。
実行結果
C:>ghci --version
The Glorious Glasgow Haskell Compilation System, version 7.8.3
C:>runghc nict.hs
Time "Sun" "Aug" 24 "18:29:48 2014 JST\n"
Haskell Platform 2014.2.0.0 (GHC 7.8.3)のデフォルトインストールだけで、Windows 7, Mac OS 10.9.4 の両方で同じソースで動作しました。
ソースコード
nict.hs
{-# OPTIONS -Wall -Werror #-}
-- | nict.hs
import Network.HTTP
import Text.ParserCombinators.Parsec
import Control.Applicative hiding ((<|>),many)
import Control.Exception
-- | 日本標準時 http://www2.nict.go.jp/aeri/sts/tsp/PubNtp/http.html
url :: String
url = "http://ntp-a1.nict.go.jp/cgi-bin/time"
-- url = "aaaa/bbbb"
-- url = "ntp-a1.nict.go.jp/cgi-bin/time"
-- url = "https://ntp-a1.nict.go.jp/cgi-bin/time"
-- | こういうフォーマットで返ってくる
-- | "Sat Aug 23 22:38:19 2014 JST\n"
-- | 曜日 月 日 と 残り に分ける
type TailString = String
data TimeFormat = Time String String Int TailString
deriving Show
-- | Parser 3種類
int' :: Parser Int
int' = do n <- many1 digit
return $ read n
int :: Parser Int
int = read <$> (many1 digit)
remains :: Parser String
remains = many1 $ space <|> letter <|> digit <|> oneOf ":\n"
timeParser :: Parser TimeFormat
timeParser = (Time <$> (many letter <* space))
<*> (many letter <* space)
<*> (int <* space)
<*> (remains)
-- | 文字列をパースする
parseTimeParser :: String -> Either ParseError TimeFormat
parseTimeParser = parse timeParser ""
-- | HTTPで時刻を取得する。これは例外を投げる可能性がある
getTime :: IO String
getTime = (Network.HTTP.simpleHTTP (getRequest url) >>= getResponseBody)
-- | main' 例外をまとめて受け取る
main' :: IO ()
main' = catch (do nictString <- getTime
case (parseTimeParser nictString) of
Left err_msg -> print err_msg
Right x -> print x
)
((\_ -> putStrLn "SomeException") :: SomeException -> IO ())
-- | main 例外を別々に受け取る
main :: IO ()
main = catches (do nictString <- getTime
case (parseTimeParser nictString) of
Left err_msg -> print err_msg
Right x -> print x
)
[Handler ((\e -> putStrLn ("IOException " ++ show e)) :: IOException -> IO ()),
Handler ((\e -> putStrLn ("SomeException " ++ show e)) :: SomeException -> IO ())]
疑問
- SomeExceptionと書けば例外を全部まとめて受け取れるみたいですが、SomeExceptionではなく、具体的で必要十分な例外の種類を並べるにはどうすればいいのでしょう? Javaみたいにコンパイラに教えてもらう方法はないのでしょうか?
参考サイト
- 日本標準時
http://www2.nict.go.jp/aeri/sts/tsp/PubNtp/http.html
こちらのサイトにゆくと、時分秒のほかJSONフォーマットなど何種類かのフォーマットで日本標準時を返してくれます。 - Applicativeのススメ -- あどけない話
http://d.hatena.ne.jp/kazu-yamamoto/20101211/1292021817
いずれのページもとても勉強になります。 - Haskell での例外処理 -- あどけない話
http://d.hatena.ne.jp/kazu-yamamoto/20120604/1338802792 - Haskellでの例外処理(その2) -- あどけない話
http://d.hatena.ne.jp/kazu-yamamoto/20120605/1338871044 - HaskellでHTTP -- Kenkov diary
http://d.hatena.ne.jp/kenkov/20110430/1304162021
プロキシーがある場合が説明されています。 - HTTP client in Haskell
http://lbolla.info/blog/2011/09/26/http-client-in-haskell
いろいろな書き方が勉強になります。