I have an Azure Function that receives an HTTP Request containing JSON data. The function is supposed to filter the incoming JSON type and, upon identifying the specified JSON, extract certain values from it, creating a new JSON, and sending it in a POST request to another Azure Function. However, the error 'Unexpected end of request content' sometimes appears in the Azure Logs. I've added several Try-Except blocks to try to capture this error, but so far, no success.
@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!")
Here is the type of JSON that I expect to receive:
{ "key": "03c38d18-d12f-4b5f-841c-afab52fe33c5", "data": { "our_number": 142, "paid_amount": 6676.38, "payment_bank": 104, "bank_slip_key": "03c38d18-d12f-4b5f-841c-afab52fe33c5", "payment_method": 2, "payment_origin": 3, "paid_in": { "name": "QI TECH", "code_number": "329", "ispb": "32402502" }, "occurrence_type": "payment_notice", "occurrence_feedback": "confirmed", "occurrence_sequence": 0, "requester_profile_code": "329-09-0001-0082162", "registration_institution": "qi_scd", "cnab_file_occurrence_order": 1, "registration_institution_occurrence_date": "2021-04-19" }, "status": "payment_notice", "webhook_type": "bank_slip.status_change", "event_datetime": "2021-04-19 20:04:06" }
But I can also receive various types of JSON, some larger, some smaller. During the testing phase, I sent all kinds of JSON, and when it should capture errors, the code did so effectively.
Instead of directly using req.get_json()
, try using req.get_body()
and then parsing it as JSON. This way, you can explicitly handle any exceptions that might occur during parsing.
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
exception and improve the handling of the request body.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)
It will catch the IncompleteRead
exception that might occur while reading the request content and handle it gracefully by returning a 400 Bad Request response.
Result:
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)
Reference: