使用django-storages+App Engine+Python3.x时get_serving_url无提示失败
开发Django项目时使用django-storages对接Google Cloud Storage存储媒体文件,尝试通过get_serving_url提供文件服务时无提示失败,异常无日志文本。BlobKey生成正常,但调用images.get_serving_url(blobkey, secure_url=True)时抛出无错误文本的异常。
实现代码
storage_backends.py
class GoogleCloudMediaStorage(GoogleCloudStorage): """GoogleCloudStorage suitable for Django's Media files.""" def __init__(self, *args, **kwargs): if not settings.MEDIA_URL: raise Exception('MEDIA_URL has not been configured') kwargs['bucket_name'] = setting('GS_MEDIA_BUCKET_NAME') super(GoogleCloudMediaStorage, self).__init__(*args, **kwargs) # 此方法工作正常 def url(self, name): """.url that doesn't call Google.""" return urljoin(settings.MEDIA_URL, name) # 此方法暂不工作 def serving_url(self, name): logging.info('serving url called') if settings.DEBUG: return urljoin(settings.MEDIA_URL, name) else: # 应用的GCS存储桶及使用的文件夹结构 try: logging.info('trying to get serving_url') filename = settings.GS_MEDIA_BUCKET_NAME + '/' + name logging.info(filename) blobkey = blobstore.create_gs_key('/gs/' + filename) logging.info('This is a blobkey') logging.info(blobkey) image = images.get_serving_url(blobkey, secure_url=True) return image except Exception as e: logging.warn('didnt work') logging.warn(e) return urljoin(settings.MEDIA_URL, name)
main.py
from antiques_project.wsgi import application from google.appengine.api import wrap_wsgi_app app = wrap_wsgi_app(application)
app.yaml配置
app_engine_apis: true
已安装appengine-python-standard,尝试过多种BlobKey生成方式(带/不带桶名),secure_url设为False/True均无效。
补充错误追踪日志
Traceback (most recent call last): File "/layers/google.python.pip/pip/lib/python3.10/site-packages/google/appengine/api/images/__init__.py", line 2013, in get_serving_url_hook rpc.check_success() File "/layers/google.python.pip/pip/lib/python3.10/site-packages/google/appengine/api/apiproxy_stub_map.py", line 614, in check_success self.__rpc.CheckSuccess() File "/layers/google.python.pip/pip/lib/python3.10/site-packages/google/appengine/api/apiproxy_rpc.py", line 149, in CheckSuccess raise self.exception File "/layers/google.python.pip/pip/lib/python3.10/site-packages/google/appengine/runtime/default_api_stub.py", line 276, in _CaptureTrace f(**kwargs) File "/layers/google.python.pip/pip/lib/python3.10/site-packages/google/appengine/runtime/default_api_stub.py", line 269, in _SendRequest raise self._TranslateToError(parsed_response) File "/layers/google.python.pip/pip/lib/python3.10/site-packages/google/appengine/runtime/default_api_stub.py", line 138, in _TranslateToError raise apiproxy_errors.ApplicationError(response.application_error.code, google.appengine.runtime.apiproxy_errors.ApplicationError: ApplicationError: 1 During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/srv/config/storage_backends.py", line 50, in serving_url image = images.get_serving_url(blobkey, secure_url=True) File "/layers/google.python.pip/pip/lib/python3.10/site-packages/google/appengine/api/images/__init__.py", line 1911, in get_serving_url return rpc.get_result() File "/layers/google.python.pip/pip/lib/python3.10/site-packages/google/appengine/api/apiproxy_stub_map.py", line 648, in get_result return self.__get_result_hook(self) File "/layers/google.python.pip/pip/lib/python3.10/site-packages/google/appengine/api/images/__init__.py", line 2015, in get_serving_url_hook raise _ToImagesError(e, readable_blob_key) google.appengine.api.images.TransformationError
1. 检查服务账号权限
get_serving_url要求App Engine默认服务账号拥有Storage Object Viewer权限,确保该账号能正常访问GCS存储桶内的文件。
2. 验证BlobKey格式
生成BlobKey时必须严格遵循格式:/gs/[bucket-name]/[object-path],注意开头的斜杠不能省略,桶名和对象路径之间也不能多加分隔符。
3. 确认文件类型兼容性
images.get_serving_url仅支持图片格式(JPEG、PNG、GIF、BMP、TIFF、WebP等),如果尝试处理非图片文件会直接抛出TransformationError,先检查目标文件是否为有效图片。
4. 改用GCS签名URL替代
如果get_serving_url问题无法解决,可切换到GCS原生的签名URL方案,无需依赖App Engine Images API:
from google.cloud import storage from datetime import timedelta def serving_url(self, name): if settings.DEBUG: return urljoin(settings.MEDIA_URL, name) else: try: storage_client = storage.Client() bucket = storage_client.bucket(settings.GS_MEDIA_BUCKET_NAME) blob = bucket.blob(name) # 生成有效期1小时的签名URL url = blob.generate_signed_url( version="v4", expiration=timedelta(hours=1), method="GET", ) return url except Exception as e: logging.warn(f"生成签名URL失败: {e}") return urljoin(settings.MEDIA_URL, name)
使用该方案需确保服务账号拥有Storage Object Signer角色,或已配置正确的密钥文件。
5. 确认环境配置
检查app.yaml中app_engine_apis: true配置是否生效,且部署环境为App Engine标准环境(弹性环境对部分App Engine API支持有限)。
内容的提问来源于stack exchange,提问作者Gabriel Ficht

