You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cloud Function返回400 Bad Request问题排查求助

问题描述

我写了一段HTTP触发的Cloud Function代码,用来提取GCS中指定zip文件的内容并上传回GCS。代码如下:

import functions_framework
import os
import tempfile
import zipfile
from google.cloud import storage

def extract_zip(event):
    """Cloud Function to extract contents of a zip file uploaded to GCS."""
    
    # Specify your GCS bucket name
    bucket_name = "deepcare-dataset"

    print(bucket_name)

    # Extract information from the event
    file_name = event["name"]
    file_path = f"gs://{bucket_name}/{file_name}"
    print(file_path)

    # Set up a temporary directory to extract files
    temp_dir = tempfile.mkdtemp()

    # Create a storage client
    storage_client = storage.Client()

    print('Downloading...')
    # Download the zip file from GCS
    blob = storage_client.get_bucket(bucket_name).get_blob(file_name)
    download_path = os.path.join(temp_dir, file_name)
    blob.download_to_filename(download_path)

    print('Extracting...')
    # Extract the contents of the zip file
    with zipfile.ZipFile(download_path, 'r') as zip_ref:
        zip_ref.extractall(temp_dir)

    print('Uploading...')
    # Upload the extracted files back to GCS
    for extracted_file in os.listdir(temp_dir):
        extracted_file_path = os.path.join(temp_dir, extracted_file)
        destination_blob_name = f"{file_name[:-4]}/{extracted_file}"

        # Upload the extracted file to GCS
        storage_client.get_bucket(bucket_name).blob(destination_blob_name).upload_from_filename(extracted_file_path)
        print(f'Uploaded for:', destination_blob_name)

    # Clean up the temporary directory
    os.remove(download_path)
    os.rmdir(temp_dir)

    print(f"Extraction and upload completed for {file_name}")


@functions_framework.http
def hello_http(request):
    """HTTP Cloud Function.
    Args:
        request (flask.Request): The request object.
        <https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
    """
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and 'name' in request_json:
        name = request_json['name']
        if name == 'myzip.zip':
            extract_zip(request_args)
    elif request_args and 'name' in request_args:
        name = request_args['name']
        if name == 'myzip.zip':
            extract_zip(request_args)
    else:
        name = 'World'
    return 'Hello {}!'.format(name)

当POST请求体为{"name":"myzip.zip"}时,Cloud Function返回400 Bad Request;但请求体为{"name":"foo"}时返回200 OK。日志仅显示警告,无明确错误原因:

{
  "insertId": "656b0fa30002f03d0c947a91",
  "httpRequest": {
    "requestMethod": "POST",
    "requestUrl": "https://asia-southeast2-***.cloudfunctions.net/function-1",
    "requestSize": "464",
    "status": 400,
    "responseSize": "819",
    "userAgent": "PostmanRuntime/7.34.0",
    "remoteIp": "104.28.254.47",
    "serverIp": "216.239.36.54",
    "latency": "0.005019129s",
    "protocol": "HTTP/1.1"
  },
  "resource": {
    "type": "cloud_run_revision",
    "labels": {
      "revision_name": "function-1-00001-qid",
      "location": "asia-southeast2",
      "service_name": "function-1",
      "project_id": "***",
      "configuration_name": "function-1"
    }
  },
  "timestamp": "2023-12-02T11:06:11.180972Z",
  "severity": "WARNING",
  "labels": {
    "goog-managed-by": "cloudfunctions",
    "instanceId": "0087599d42c23459cbb07b0680f42627a501194e4825c912c5119b703c25894fe2d5caf7a1bbe0ce42006b2c6f646d2cd6ec68f6f5fb2ad91fa39a17e6eaa78789"
  },
  "logName": "projects/***/logs/run.googleapis.com%2Frequests",
  "trace": "projects/***/traces/98c21d5c3145fa7af9b5bc5f2d53b523",
  "receiveTimestamp": "2023-12-02T11:06:11.194091644Z",
  "spanId": "14357157746819527854",
  "traceSampled": true
}

怀疑extract_zip函数出错,但无法确定400响应来源,需要调试排查思路。


调试排查思路
  • 添加错误捕获与详细日志:在extract_zip函数外层包裹try-except块,捕获所有异常并打印异常类型、错误信息及堆栈跟踪;同时在hello_http调用extract_zip的位置也增加异常处理,避免未捕获异常直接触发HTTP错误。示例:
    def extract_zip(event):
        try:
            # 原函数代码
        except Exception as e:
            print(f"extract_zip错误: {type(e).__name__} - {str(e)}")
            import traceback
            traceback.print_exc()
            raise
    
    # 在hello_http中调用时:
    if name == 'myzip.zip':
        try:
            extract_zip(request_json if request_json else request_args)
        except Exception as e:
            print(f"调用extract_zip失败: {e}")
    
  • 修复参数传递错误:当前代码中,当请求体是JSON格式时,错误地将request_args(URL参数对象)传入extract_zip,但此时request_args为空,会触发KeyError。应改为传递request_json:
    if request_json and 'name' in request_json:
        name = request_json['name']
        if name == 'myzip.zip':
            extract_zip(request_json)
    
  • 验证GCS权限:确认Cloud Function的服务账号拥有目标GCS存储桶的storage.objects.get(下载权限)和storage.objects.create(上传权限),可临时添加Storage Object Admin角色测试,排除权限问题。
  • 修复临时目录清理逻辑:os.rmdir(temp_dir)只能删除空目录,解压后目录内存在提取的文件,会触发OSError。改用shutil.rmtree(temp_dir)递归删除目录及内容:
    import shutil
    # 替换原清理代码
    shutil.rmtree(temp_dir)
    
  • 查看完整函数日志:不要仅查看请求日志,前往Cloud Function的函数日志页面,这里会包含print语句输出和异常堆栈信息,可直接定位错误点。
  • 本地模拟测试:使用functions-framework在本地运行函数,模拟POST请求,直接观察控制台输出的错误信息,本地测试时可使用GCS密钥文件复现完整流程。

内容的提问来源于stack exchange,提问作者Muhammad Ikhwan Perwira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 19:29:51