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

風柳メモ

ソフトウェア・プログラミング関連の覚書が中心。

Python 2.5でCookieJar()が誤動作する場合があるのに対するパッチ

新しいPythonだときっと直ってるんでしょうけれど……

はてなボトルの質問を Quitteer/Quetter/Q&Aなう に流してみる - 風柳メモでも少し触れましたが、Google App Engine の開発に Python 2.5.4 を使っていて、これの CookieJar()が、Set-Cookieのパースに失敗して誤動作する(どうも、カンマ(", ")区切りで複数のCookieが入っていると対応できない)という現象に出会って往生しました。
新バージョンを探して入れたりするのが面倒だったこともあり*1、強引にパッチを当てて対応しましたので、メモ書き。

class patchedCookieJar(CookieJar):
  def __init__(self, *args, **kwargs):
    self.re_is_setcookie = re.compile(u'^set-cookie2?$',re.I)
    self.re_header_name = re.compile(u'^(.*?):\s*')
    self.re_cookie_sep = re.compile(u'([^a-z]),\s*')
    CookieJar.__init__(self, *args, **kwargs)
  
  def make_cookies(self,response,request):
    msg = response.info()
    msg.org_getallmatchingheaders = msg.getallmatchingheaders
    def _getallmatchingheaders(name):
      if not self.re_is_setcookie.search(name):
        return msg.org_getallmatchingheaders(name)
      s_list = msg.org_getallmatchingheaders(name)
      t_list = []
      for _val in s_list:
        _val = self.re_header_name.sub(r'',_val)
        t_list += [name+': ' +_v for _v in self.re_cookie_sep.sub(r'\1\n',_val).split('\n') if _v]
      return t_list
    msg.getallmatchingheaders = _getallmatchingheaders
    response.info = lambda: msg
    return CookieJar.make_cookies(self,response,request)

CookieJar()のサブクラスを作って、make_cookie()を上書きし、getallmatchingheaders() の処理を無理やり書換えているだけです。
これで、例えば

policy = DefaultCookiePolicy(rfc2965=True,netscape=True)
cjar = CookieJar(policy=policy)
cjar_handler = urllib2.HTTPCookieProcessor(cjar)
url_opener = urllib2.build_opener(cjar_handler)

のようなコードなら、CookieJarをpatchedCookieJarに置換して、

policy = DefaultCookiePolicy(rfc2965=True,netscape=True)
cjar = patchedCookieJar(policy=policy)  # ← cjar = CookieJar(policy=policy)
cjar_handler = urllib2.HTTPCookieProcessor(cjar)
url_opener = urllib2.build_opener(cjar_handler)

のようにすれば、とりあえずカンマ区切りのSet-Cookieを受信した場合でも、きちんと複数のCookieが保存されるようになりました。

とりあえずこれで凌いでますが、正式な対応方法をご存じの方は教えて下さい。

*1:もしかしたら、本番環境ではちゃんと動いたりしてね…