Python 2.7转3.9 GAE应用RPCFailedError问题求助
Python 3.9 GAE迁移:memcache RPCFailedError 问题排查与解决思路
问题背景
团队将Python 2.7的App Engine应用迁移至Python 3.9后,使用旧版memcache捆绑服务时,调用任意memcache方法均触发异常:
RPCFailedError: Attempted RPC call without active security ticket
已尝试多种方案但均无效。
当前应用配置
Bottle应用初始化代码
app = bottle.default_app() app.config.update({'catchall': False}) app = google.appengine.api.wrap_wsgi_app(app, use_legacy_context_mode=True, use_deferred=False)
NDB上下文处理(基于Stack Overflow建议)
为适配大量NDB使用场景,通过Bottle类的__call__方法包裹请求周期:
def __call__(self, environ, start_response): """ Each instance of :class:'Bottle' is a WSGI application. """ with self.ndb_client.context(): return self.wsgi(environ, start_response)
核心疑问
DefaultApiStub.ShouldUseRequestSecurityTicketForThread()是否由GAE底层自动设为True,还是需手动调用DefaultApiStub.SetUseRequestSecurityTicketForThread(True)开启?- 若该函数返回True,ticket实例化依赖的
HTTP_X_APPENGINE_API_TICKET环境变量或NDB上下文GAE头,是由底层自动设置还是需开发者自行管理?
已尝试的无效方案
- 确保所有代码在同一线程运行,本地与云环境均无改善
- 尝试替换memcache,但引发其他问题,且旧版
deferred服务也出现相同异常,故保留原memcache - 尝试Chromium补丁及其他公开修改方案,均未解决问题
解决思路与验证方案
1. 调整上下文初始化顺序
当前先通过wrap_wsgi_app包装应用,再创建NDB上下文的顺序可能导致安全票据未正确继承。建议调整为:在NDB上下文内调用wrap_wsgi_app包装后的WSGI应用,或确保GAE legacy上下文先完成初始化。
修改__call__方法示例:
def __call__(self, environ, start_response): # 先让wrap_wsgi_app处理GAE上下文,再启动NDB上下文 wrapped_wsgi = google.appengine.api.wrap_wsgi_app(self.wsgi, use_legacy_context_mode=True, use_deferred=False) with self.ndb_client.context(): return wrapped_wsgi(environ, start_response)
2. 手动注入安全票据
针对疑问1:GAE运行时理论上会通过wrap_wsgi_app自动设置ShouldUseRequestSecurityTicketForThread()为True,但如果请求流程绕过该逻辑,需手动触发。可在请求开始时显式设置开关并注入票据:
from google.appengine.api import apiproxy_stub_map from google.appengine.api.default_api_stub import DefaultApiStub def __call__(self, environ, start_response): # 手动开启安全票据线程开关 DefaultApiStub.SetUseRequestSecurityTicketForThread(True) # 从WSGI环境提取票据并设置到memcache stub ticket = environ.get('HTTP_X_APPENGINE_API_TICKET') if ticket: stub = apiproxy_stub_map.apiproxy.GetStub('memcache') if isinstance(stub, DefaultApiStub): stub.SetRequestSecurityTicketForThread(ticket) with self.ndb_client.context(): return self.wsgi(environ, start_response)
3. 关联NDB上下文与GAE头信息
针对疑问2:HTTP_X_APPENGINE_API_TICKET由GAE底层自动注入到WSGI环境的environ中,但NDB上下文可能未自动关联这些头。尝试在创建NDB上下文时显式传递GAE头:
def __call__(self, environ, start_response): gae_headers = { 'X-Appengine-Api-Ticket': environ.get('HTTP_X_APPENGINE_API_TICKET'), 'X-Appengine-Request-Id': environ.get('HTTP_X_APPENGINE_REQUEST_ID') } # 传递gae_headers到NDB上下文 with self.ndb_client.context(gae_headers=gae_headers): return self.wsgi(environ, start_response)
4. 本地开发环境特殊处理
本地开发服务器的安全票据逻辑与生产环境不同,可尝试禁用本地的票据检查:
import os from google.appengine.api.default_api_stub import DefaultApiStub def __call__(self, environ, start_response): if os.environ.get('GAE_ENV') == 'localdev': # 本地开发环境关闭安全票据检查 DefaultApiStub.SetUseRequestSecurityTicketForThread(False) with self.ndb_client.context(): return self.wsgi(environ, start_response)
内容的提问来源于stack exchange,提问作者Tony Tullemans
相关产品推荐
相关产品推荐

