読者です 読者をやめる 読者になる 読者になる

Keep on moving

あんまりまとまってないことを書きますよ

pyuvを使って http-clientを書いてみる

python libuv

どうもこんばんは。差し迫ったなにかがあるときって、別のなにかをやりたくなるときってありますよね。
近頃良く遊んでるpyuvでhttpクライアントを作ってみました。

ソースコード

エラー処理が抜けてます。

import sys
import socket
import urlparse
import pyuv

loop, clients = None, []

def on_close(tcp_handle):
    tcp_handle.data["file"].close()

def on_read(tcp_handle, data, error):
    if data is None:
        tcp_handle.close(on_close)
        return
    if not data.strip():
        return
    if tcp_handle.data['header'] == '':
        pos = data.find(b'\r\n\r\n')
        if pos > -1:
            tcp_handle.data['header'] = data[0:pos]
            tcp_handle.data["file"].write(data[pos+4:])
    else:
        tcp_handle.data["file"].write(data)

def on_write(tcp_handle, status):
    tcp_handle.start_read(on_read)

def on_connection(tcp_handle, status):
    print("[connect]")
    req = 'GET %s HTTP/1.0\nHost: %s\n\n' % (tcp_handle.data["url"].path,tcp_handle.data["url"].hostname)
    tcp_handle.write(req, on_write)

def getaddrinfo_cb_maker(index):
  ''' urlとipの関係を維持するためにindexで関連づけする '''
  def getaddrinfo_cb(resolver, status, result):
    parsed = resolver.data["urls"][index]
    l = pyuv.Loop.default_loop()
    client = pyuv.TCP(l)
    client.data={'url': parsed,
                 'header': b'',
                 'file': open(parsed.path.split('/')[-1], 'wb')}
    ip = result[0][-1][0]
    client.connect((ip, 80),on_connection)
    l.run()
  return getaddrinfo_cb

def main(arg):
    loop = pyuv.Loop.default_loop()
    resolver = pyuv.dns.DNSResolver(loop)
    resolver.data = {"urls":[]}
    idx = 0
    for url in arg:
        parsed = urlparse.urlparse(url)
        resolver.data["urls"].append(parsed)
        resolver.getaddrinfo(getaddrinfo_cb_maker(idx), parsed.hostname, 80)
        idx += 1
    loop.run()

if __name__ == "__main__":
    print("PyUV version %s" % pyuv.__version__)
    # python wget2.py url url ...
    main(sys.argv[1:])
    print("Stopped!")

実行

こんなかんじでファイルを2つ同時にダウンロードできます。
ダウンロード後にmd5sumコマンドを実行してファイルが正しいかを調べられると本当はいいのかも。。

$ time python wget2.py http://pypi.python.org/packages/source/D/Django/Django-1.3.1.tar.gz http://pypi.python.org/packages/source/i/ipython/ipython-0.12.tar.gz#md5=0199b62aa65986726b46247db3cb06cf
PyUV version 0.2.0
82.94.164.168
82.94.164.168
[connect]
GET /packages/source/i/ipython/ipython-0.12.tar.gz HTTP/1.0
Host: pypi.python.org


[connect]
GET /packages/source/D/Django/Django-1.3.1.tar.gz HTTP/1.0
Host: pypi.python.org


Stopped!

real	0m30.623s
user	0m0.200s
sys	0m0.992s

所感

コールバックが多すぎなので、コードの見通しが悪いのが気になる。
コードの再利用はわりと難しいな。
Twistedとかgeventだとどう書けるのか、とても興味があります。