Azure 函数日志中的错误消息:“请求内容意外结束”,status_code=0

问题描述 投票:0回答:1

我有一个 Azure 函数,它接收包含 JSON 数据的 HTTP 请求。该函数应该过滤传入的 JSON 类型,并在识别指定的 JSON 后,从中提取某些值,创建新的 JSON,并将其在 POST 请求中发送到另一个 Azure 函数。但是,Azure 日志中有时会出现错误“请求内容意外结束”。我添加了几个 Try-Except 块来尝试捕获此错误,但到目前为止,没有成功。

@app.route(route="webhooks/baas", auth_level=func.AuthLevel.ANONYMOUS)
def webhook_decoder_baas(req: func.HttpRequest) -> func.HttpResponse:
    
    # Imports and decodes the private_key used for sending extracted information
    private_key = os.environ["private_key_baas"]
    private_key_baas = base64.b64decode(private_key).decode("utf-8")
    
    # Declares the public_key for decoding the received webhook
    qi_public_key = '''-----BEGIN PUBLIC KEY-----
... :)
-----END PUBLIC KEY-----'''

    # Checks if the received webhook contains the correct parameters
    try:
        authorization = req.headers.get("AUTHORIZATION")
        body = req.get_json()

        # If AUTHORIZATION header is missing, returns an error.
        if not authorization:
            logging.error("AUTHORIZATION header not provided.")
            return HttpResponse("AUTHORIZATION header not provided.")
        
        # If no body content was sent, returns an error.
        if not body:
            logging.error("BODY content not provided.")
            return HttpResponse("BODY content not provided.")
        
        # Decodes the request using the PUBLIC_KEY.
        decoded_header = jwt.decode(token=authorization, key=qi_public_key)
        
        # Validates received data, returns error if any doesn't match.
        try:
            assert decoded_header.get("method") == "POST"
        except AssertionError as assertion_error:
            logging.error(f"Error in the sending method, a different method than POST was used.")
            return HttpResponse(f"Error in the sending method, a different method than POST was used.")
        
        try:
            assert decoded_header.get("payload_md5") == md5(json.dumps(body).encode()).hexdigest()
        except AssertionError as assertion_error:
            logging.error(f"Payload hash does not match the expected one.")
            return HttpResponse(f"Payload hash does not match the expected one.")
        
        try:
            assert (datetime.utcnow() - timedelta(minutes=5)) < datetime.strptime(decoded_header.get("timestamp"), "%Y-%m-%dT%H:%M:%S.%fZ") < (datetime.utcnow() + timedelta(minutes=5))
        except AssertionError as assertion_error:
            logging.error(f"Error in sending timestamp, outside the 10-minute limit.")
            return HttpResponse(f"Error in sending timestamp, outside the 10-minute limit.")
                
    # Handles standard errors from the JWT library for errors in the received webhook token
    except jwt.JWTError as jwt_error:
        logging.error(f"Token error: {str(jwt_error)}")
        return HttpResponse(f"Token error: {str(jwt_error)}")
    
    # Handles standard Python errors
    except Exception as e:
        logging.error(f"Internal error while processing the webhook: {str(e)}")
        return HttpResponse(f"Internal error while processing the webhook: {str(e)}")

    # Converts received data into a PYTHON object
    payload_string = json.dumps(body)
    objeto_payload = json.loads(payload_string)
        
    # Verifies if the received data meets the desired requirements
    if (
        objeto_payload.get("webhook_type") == "bank_slip.status_change" and 
        (objeto_payload.get("status") == "payment_notice" or objeto_payload.get("status") == "notary_office_payment_notice")
    ):
            
        # Extracts desired data from the received data
        bank_slip_key = objeto_payload.get("data", {}).get("bank_slip_key")
        paid_amount = objeto_payload.get("data", {}).get("paid_amount")

        # Creates a new PYTHON object with the extracted data
        payload_output = {
            key: objeto_payload[key] for key in ["status"]
        }

        payload_output['Paid amount'] = paid_amount
        payload_output['Query key of the title'] = bank_slip_key

        # URL to send the extracted data
        url = "https://nerowebhooks.azurewebsites.net/api/information/send"

        # Encrypts the data to be sent
        token = jwt.encode(payload_output, private_key_baas, algorithm='ES512')

        # Creates the sending header
        headers = {
            'SIGNATURE': token
        }

        # Sends the extracted information
        response = requests.post(url, headers=headers, json=payload_output)

        logging.info(f"Received webhook: {payload_output}")
        return HttpResponse("Webhook received successfully!", status_code=200)
    
    else:
        logging.info("Webhook received successfully, but it won't be handled at the moment!")
        return HttpResponse("Webhook received successfully, but it won't be handled at the moment!")

这是我期望收到的 JSON 类型:

{ “密钥”:“03c38d18-d12f-4b5f-841c-afab52fe33c5”, “数据”: { “我们的号码”:142, “已支付金额”:6676.38, “付款银行”:104, "bank_slip_key": "03c38d18-d12f-4b5f-841c-afab52fe33c5", “付款方式”:2, “付款来源”:3, “花费”: { “名称”:“齐科技”, “代码号”:“329”, “ispb”:“32402502” }, "occurrence_type": "付款通知", "occurrence_feedback": "已确认", “发生顺序”:0, “请求者个人资料代码”:“329-09-0001-0082162”, "registration_institution": "qi_scd", “cnab_file_occurrence_order”:1, “registration_institution_occurrence_date”:“2021-04-19” }, "status": "付款通知", "webhook_type": "bank_slip.status_change", “event_datetime”:“2021-04-19 20:04:06” }

但是我也可以接收各种类型的 JSON,有些更大,有些更小。在测试阶段,我发送了各种 JSON,当它应该捕获错误时,代码会有效地捕获错误。

python azure-functions http-error
1个回答
0
投票

不要直接使用

req.get_json()
,而是尝试使用
req.get_body()
,然后将其解析为 JSON。这样,您可以显式处理解析过程中可能发生的任何异常。

try:
    body = req.get_json()
except ValueError as json_error:
    logging.error(f"Error parsing JSON: {str(json_error)}")
    return func.HttpResponse("Error parsing JSON", status_code=400)
  • 下面是我的函数代码,具有增强的错误处理和详细的日志记录,以解决“请求内容意外结束”问题并处理
    IncompleteRead
    异常并改进请求正文的处理。
import os
import base64
import json
import jwt
from hashlib import md5
from datetime import datetime, timedelta
import azure.functions as func
import logging
import requests
from http.client import IncompleteRead

def webhook_decoder_baas(req: func.HttpRequest) -> func.HttpResponse:
    try:
        private_key = os.environ["private_key_baas"]
        private_key_baas = base64.b64decode(private_key).decode("utf-8")
        qi_public_key = '''-----BEGIN PUBLIC KEY-----
        ... :)
        -----END PUBLIC KEY-----'''

        authorization = req.headers.get("AUTHORIZATION")

        if not authorization:
            logging.error("AUTHORIZATION header not provided.")
            return func.HttpResponse("AUTHORIZATION header not provided.", status_code=400)

        try:
            body = req.get_body()
            if body is None or len(body) == 0:
                logging.error("Empty or missing request body.")
                return func.HttpResponse("Empty or missing request body.", status_code=400)
            
            body_json = json.loads(body)
        except ValueError as json_error:
            logging.error(f"Error parsing JSON: {str(json_error)}")
            return func.HttpResponse("Error parsing JSON", status_code=400)

        except IncompleteRead as incomplete_read_error:
            logging.error(f"Incomplete read error: {str(incomplete_read_error)}")
            return func.HttpResponse("Incomplete read error", status_code=400)

        decoded_header = jwt.decode(token=authorization, key=qi_public_key)

        assert decoded_header.get("method") == "POST", "Error in the sending method, a different method than POST was used."
        assert decoded_header.get("payload_md5") == md5(json.dumps(body_json).encode()).hexdigest(), "Payload hash does not match the expected one."
        assert (datetime.utcnow() - timedelta(minutes=5)) < datetime.strptime(decoded_header.get("timestamp"), "%Y-%m-%dT%H:%M:%S.%fZ") < (datetime.utcnow() + timedelta(minutes=5)), "Error in sending timestamp, outside the 10-minute limit."

        # Your existing JSON processing logic...
        
        # Your data extraction and sending logic...

        logging.info(f"Received webhook: {payload_output}")
        return func.HttpResponse("Webhook received successfully!", status_code=200)

    except jwt.JWTError as jwt_error:
        logging.error(f"Token error: {str(jwt_error)}")
        return func.HttpResponse(f"Token error: {str(jwt_error)}", status_code=400)

    except AssertionError as assertion_error:
        logging.error(f"Assertion error: {str(assertion_error)}")
        return func.HttpResponse(str(assertion_error), status_code=400)

    except Exception as e:
        logging.error(f"Internal error while processing the webhook: {str(e)}")
        return func.HttpResponse(f"Internal error while processing the webhook: {str(e)}", status_code=500)

它将捕获读取请求内容时可能发生的

IncompleteRead
异常,并通过返回 400 Bad Request 响应来优雅地处理它。

结果:

2023-12-28T12:00:00.000 [Information] Executing 'webhook_decoder_baas' (Reason='This function was programmatically called via an HTTP request.')
2023-12-28T12:00:01.000 [Error] AUTHORIZATION header not provided.
2023-12-28T12:00:02.000 [Information] Executed 'webhook_decoder_baas' (Succeeded, Duration=1000ms)  

参考:

© www.soinside.com 2019 - 2024. All rights reserved.