dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[GAE][django]Google APIs Client Library for Pythonをdjangoで使う (4)

前回 書いたコード、しばらく経ってからアクセスすると AccessTokenRefreshError なるエラーが発生することがわかった。

ERROR    2015-03-13 15:12:12,692 base.py:210] Internal Server Error: /task_manager/
Traceback (most recent call last):
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/django-1.5/django/core/handlers/base.py", line 113, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/django-1.5/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/django-1.5/django/views/generic/base.py", line 86, in dispatch
    return handler(request, *args, **kwargs)
  File "/Users/yama/workspace/GAE/django_apps/utils/login.py", line 25, in check_login
    return handler_method(self, request, *args)
  File "/Users/yama/workspace/GAE/django_apps/task_manager/views.py", line 54, in get
    tasks = service.tasks().list(tasklist='@default').execute(http=http)
  File "/Users/yama/workspace/GAE/django_apps/libs/oauth2client/util.py", line 132, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/Users/yama/workspace/GAE/django_apps/libs/apiclient/http.py", line 716, in execute
    body=self.body, headers=self.headers)
  File "/Users/yama/workspace/GAE/django_apps/libs/oauth2client/util.py", line 132, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/Users/yama/workspace/GAE/django_apps/libs/oauth2client/client.py", line 494, in new_request
    self._refresh(request_orig)
  File "/Users/yama/workspace/GAE/django_apps/libs/oauth2client/client.py", line 663, in _refresh
    self._do_refresh_request(http_request)
  File "/Users/yama/workspace/GAE/django_apps/libs/oauth2client/client.py", line 710, in _do_refresh_request
    raise AccessTokenRefreshError(error_msg)
AccessTokenRefreshError: invalid_grant

f:id:dackdive:20150314012044p:plain

どうやら OAuth2.0 のアクセストークンの有効期限が切れていた時に当該エラーを投げている様子。
また、エラーログを見る限りエラーを raise しているのは

tasks = service.tasks().list(tasklist='@default').execute(http=http)

という行。

これを回避するには、こちらのサイト

http://www.sw-engineering-candies.com/blog-1/how-to-get-user-information-with-oauth2-in-a-google-appengine-python-application

のようにエラーを raise する可能性があるところで補足してやる方法もあるみたいだけど、
こちらの公式のサンプル

https://developers.google.com/api-client-library/python/auth/web-app#example

によると、oauth2client.client.OAuth2Credentials クラス(これは StorageByKeyName() から get したオブジェクト)には
そのへんの有効期限を判定するための access_token_expired というプロパティがあるらしい。

実際のコードを見てみる。

oauth2client/client.py

@property
def access_token_expired(self):
  """True if the credential is expired or invalid.
                                                                      
  If the token_expiry isn't set, we assume the token doesn't expire.
  """
  if self.invalid:
    return True
                                                                      
  if not self.token_expiry:
    return False
                                                                      
  now = datetime.datetime.utcnow()
  if now >= self.token_expiry:
    logger.info('access_token is expired. Now: %s, token_expiry: %s',
                now, self.token_expiry)
    return True
  return False

内部で self.invalid もやってる様子。
というわけで、既存の credentials.invalid をこっちのプロパティで置き換えてみた。

before

if credentials is None or credentials.invalid:  # --- before ---
    FLOW.params['state'] = xsrfutil.generate_token(
            settings.SECRET_KEY, user)
    authorize_url = FLOW.step1_get_authorize_url()
    logging.info(authorize_url)
    return HttpResponseRedirect(authorize_url)
else:
    (Tasks API を使って処理)

after

if credentials is None or credentials.access_token_expired:  # --- after ---
    FLOW.params['state'] = xsrfutil.generate_token(
            settings.SECRET_KEY, user)
    authorize_url = FLOW.step1_get_authorize_url()
    logging.info(authorize_url)
    return HttpResponseRedirect(authorize_url)
else:
    (Tasks API を使って処理)

しばらくこれで様子見。

TODO

アクセストークンの有効期限ってどこかに明記されてるのかな。。。

リファレンス

OAuth2 のフローについて(日本語) http://g-master.appspot.com/article/30001/50001/50002.html