urllibでベーシック認証が必要なページを取得する方法

urllib.FancyURLopenerを利用して、認証が必要なページへアクセスするとプロンプトが表示されユーザ名とパスワードを聞いてくる。プロンプトでユーザとパスワード指定すれば取得できるが、これだと自動化できない。
このプロンプトはFancyURLopenerのprompt_user_passwdメソッドのデフォルト動作なので、prompt_user_passwdを書き換えてやれば良い。prompt_user_passwdはhostとrealmの引数をとり、ユーザ名とパスワードのタプルを返すのメソッドなので、以下のソースのように書き換える。今回、ユーザ名/パスワードは固定。

import urllib
opener = urllib.FancyURLopener()
opener.prompt_user_passwd = lambda host , realm : ( user , passw )
opener.open( url )

これで、ベーシック認証が必要なページの取得が出来る。lambda便利だなぁとか思ってたら、認証に失敗するページにアクセスすると、Process is terminated due to StackOverflowException.となって落ちるIronPython(IPCE-r5)。IronPython(IPCE-r5)固有の問題かと思いきやpython 2.4でも落ちる。python 2.4の場合は以下。

  File "D:\Program Files\Python24\lib\urllib.py", line 190, in open
    return getattr(self, name)(url)
  File "D:\Program Files\Python24\lib\urllib.py", line 322, in open_http
    return self.http_error(url, fp, errcode, errmsg, headers)
  File "D:\Program Files\Python24\lib\urllib.py", line 335, in http_error
    result = method(url, fp, errcode, errmsg, headers)
  File "D:\Program Files\Python24\lib\urllib.py", line 644, in http_error_401
    return getattr(self,name)(url, realm)
  File "D:\Program Files\Python24\lib\urllib.py", line 657, in retry_http_basic_auth
    return self.open(newurl)
  File "D:\Program Files\Python24\lib\urllib.py", line 190, in open
    return getattr(self, name)(url)
  File "D:\Program Files\Python24\lib\urllib.py", line 322, in open_http
    return self.http_error(url, fp, errcode, errmsg, headers)
  File "D:\Program Files\Python24\lib\urllib.py", line 335, in http_error
    result = method(url, fp, errcode, errmsg, headers)
  File "D:\Program Files\Python24\lib\urllib.py", line 644, in http_error_401
    return getattr(self,name)(url, realm)
  File "D:\Program Files\Python24\lib\urllib.py", line 657, in retry_http_basic_auth
    return self.open(newurl)
  File "D:\Program Files\Python24\lib\urllib.py", line 190, in open
    return getattr(self, name)(url)
  File "D:\Program Files\Python24\lib\urllib.py", line 316, in open_http
    errcode, errmsg, headers = h.getreply()
  File "D:\Program Files\Python24\lib\httplib.py", line 1137, in getreply
    response = self._conn.getresponse()
  File "D:\Program Files\Python24\lib\httplib.py", line 866, in getresponse
    response.begin()
  File "D:\Program Files\Python24\lib\httplib.py", line 365, in begin
    self.msg = HTTPMessage(self.fp, 0)
  File "D:\Program Files\Python24\lib\mimetools.py", line 16, in __init__
    rfc822.Message.__init__(self, fp, seekable)
  File "D:\Program Files\Python24\lib\rfc822.py", line 106, in __init__
    self.readheaders()
  File "D:\Program Files\Python24\lib\httplib.py", line 221, in readheaders
    line = self.fp.readline()
  File "D:\Program Files\Python24\lib\socket.py", line 313, in readline
    assert data == ""
RuntimeError: maximum recursion depth exceeded in cmp

self.open(newurl)あたりで延々同じ再帰ルートを辿ってRuntimeError: maximum recursion depth exceededになる。ソースを追ってみると、prompt_user_passwd()が(None,None)のタプルを返さない限り、永久に再起する。うーん。
ということは、今回はユーザ名/パスワードは固定で良いので、2回目から(None,None)を返す関数を作れば良いのね。散々lambdaで作れないかと悩んだすえ無理っぽいので、普通に関数定義する方法に。ガクリ。

	def user_passwd( host , realm , f = [1] ) :
		if f[0] != 0 :
			f[0] = 0
			return ( user , passw )
		return ( None , None )
		
	opener.prompt_user_passwd = user_passwd

第3パラメタのfはC言語の関数内static変数のようなもの。これがないと再帰ループ中の2回目の呼び出しとか判定できない。あんまりスマートな方法ではないんだけど、グローバル汚染したりこれだけのためにクラス定義するのも面倒なので妥協。ほかに上手いやり方ありそうだけどなぁ。
yield使う邪道な方法もあるけどw

	def user_passwd2( host , realm ) :
		yield ( user , passw )
		return ( None , None )