LoginSignup
5
5

More than 5 years have passed since last update.

HaskellでHTTPクライアントを書く その2

Last updated at Posted at 2014-08-26

前回の記事では、Network.HTTP.simpleHTTPを使ってHTTPクライアントを書きましたが、これだとプロキシー設定ができず、テキストデータしか取ってこれません。

それに対し、Network.Browserを使うと、プロキシー設定ができ、バイナリーデータも取ってこれます。

テキストデータはStringで、バイナリーデータはByteStringでとってくることにしたのですが、そうすると関数を2系統作らなければなりません。実際にNetwork.Browserを使ってダウンロードしてくる部分は2系統必要ですが、それを呼び出す関数を1つにまとめたいと考えました。

代数的データ型の直和を使って、StringでもByteStringでも、同じ関数名でダウンロードして保存できるようにするようにしてみました。

http_client.hs
{-# OPTIONS -Wall -Werror #-}

import Network.HTTP
import Network.HTTP.Proxy
import Network.Browser
import Data.Maybe (fromJust)
import Network.URI (parseURI,URI)
import Control.Applicative
import qualified Data.ByteString.Lazy as L
import Data.List
import Control.Exception

-- | ----------------------------------------------------
-- | プロキシーサーバ設定
proxyType :: Proxy
proxyType = NoProxy
-- proxyType = fromJust $ parseProxy "127.0.0.1:8080"

-- | ----------------------------------------------------
-- | バイナリ取得Request
binary_request :: String -> Request L.ByteString
binary_request uri = 
     Request {
       rqURI = fromJust $ parseURI uri,
       rqMethod = GET,
       rqHeaders = [],
       rqBody = L.empty }

-- | バイナリの取得action
binary_action :: 
  String -> BrowserAction (HandleStream L.ByteString) (URI, Response L.ByteString)
binary_action uri = 
  (setOutHandler (\_ -> return ())) >>
  (setProxy proxyType) >>
  (request $ binary_request uri)

-- | バイナリの取得browse
binary_browse :: String -> IO L.ByteString
binary_browse uri = do
  (_, res) <- browse $ binary_action uri
  return $ rspBody res
-- | ----------------------------------------------------
-- | テキスト取得Request
text_request :: String -> Request String
text_request uri = 
     Request {
       rqURI = fromJust $ parseURI uri,
       rqMethod = GET,
       rqHeaders = [],
       rqBody = "" }

-- | テキストの取得action
text_action :: 
  String -> BrowserAction (HandleStream String) (URI, Response String)
text_action uri = 
  (setOutHandler (\_ -> return ())) >>
  (setProxy proxyType) >>
  (request $ text_request uri)

-- | テキストの取得browse  do~return版
text_browse' :: String -> IO String
text_browse' uri = do
  (_, res) <- browse $ text_action uri
  return $ rspBody res

-- | テキストの取得browse  アプリカティブスタイル版
text_browse :: String -> IO String
text_browse uri = (rspBody . snd) <$> (browse $ text_action uri)

-- | ----------------------------------------------------
-- | ファイル名の拡張子が候補の中にあったらTrueを返す
foundSuffixIn :: [String] -> String -> Bool
foundSuffixIn suffixes filename =
  or $ map (f filename) suffixes
    where f a b = isSuffixOf b a

-- | リソースの長さを表示する
getLength :: String -> IO ()
getLength uri = 
  if foundSuffixIn [".png",".jpg",".jpeg"] uri 
    then do x <- binary_browse uri
            putStrLn ("bin uri:" ++ uri ++ " length:" ++ (show $ L.length x))
    else do x <- text_browse uri
            putStrLn ("txt uri:" ++ uri ++ " length:" ++ (show $ length x))

-- | ----------------------------------------------------
-- | 代数的データ型を使って、StringでもL.ByteStringでも
-- | 同じ関数名でダウンロードして保存できるようにする
data Resource = S String | B L.ByteString
     deriving (Eq, Show)

getResource :: String -> IO Resource
getResource uri = 
      if foundSuffixIn [".png",".jpg",".jpeg"] uri 
        then do x <- binary_browse uri
                return (B x)
--      else do x <- text_browse uri
--              return (S x)
        else (S <$> (text_browse uri))

saveResource :: Resource -> IO ()
saveResource (S text) = writeFile   "hoge.txt" text
saveResource (B bin)  = L.writeFile "hoge.dat" bin

-- | ----------------------------------------------------
-- | メインプログラム
png_uri :: String
png_uri = "http://www.google.com/intl/en_com/images/srpr/logo1w.png"

text_uri :: String
text_uri = "http://ntp-a1.nict.go.jp/cgi-bin/time"

main :: IO ()
main = catches (
         do getLength text_uri
            getLength png_uri
            getResource text_uri >>= saveResource
            getResource png_uri >>= saveResource
       )
       [Handler ((\e -> putStrLn ("IOException " ++ show e)) :: IOException -> IO ()),
        Handler ((\e -> putStrLn ("SomeException " ++ show e)) :: SomeException -> IO ())]

美しくないですが、プロキシーを使うかどうかはプログラムを書き換えて切り替えます。

実験用ソースコードなので、do~returnとかアプリカティブスタイルとか混ぜて書いてあります。


参考サイト
1. HaskellでHTTP -- Kenkov diary
http://d.hatena.ne.jp/kenkov/20110430/1304162021
プロキシーがある場合が説明されています。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5