I have a serverless pipeline on Google Cloud. It consists of three total steps:
- A video uploader that sends videos to Google Cloud Storage. Working fine.
- An eventarc + pub/sub trigger that fires whenever a video is uploaded to GCS to a cloud run application called 'post processor'.
- The post processor extracts some information regarding this file and sends a API request to my Django API, to create a history f this recording to my database.
The post processor is implemented using functions_framework.cloud_event. I also created some logs so I could debug it in Google Cloud Logging. Here's one example as follows:
2025年09月24日 14:53:05,135 INFO main:
RECEIVED DATA FROM EVENTARC (POST-PROCESSING):
Original Filename=7614_2025_09_23_16_46_17.mp4, Serial=7614,
UTC=2025年09月23日T16:46:17+00:00, Duration=0s, Frames=0
It received all the information correctly... Generated a payload from it and posted a request to my Django API. Payload sent is this:
{
"raw_file": null,
"raw_file_gcp": "https://storage.cloud.google.com/homolog-videos/7614_2025_09_23_16_46_17.mp4",
"motion_file": null,
"event_at": "2025年09月23日T16:46:17+00:00",
"camera": 7614,
"frames_count": 0,
"duration_seconds": 0,
"event_end_at": "2025年09月23日T16:46:17+00:00",
"size_mb": 0
}
However, when seeing the request on my Django API, it receives no payload at all and the requests comes as a GET method instead of the POST method. For example, the request above arrived like this: [GET] /api/recordings/ | From: 34.34.234.34 | Payload: {}
This unintentionally triggers a list endpoint instead of creating a record....
I reproduced my production settings locally, (DEBUG=False, ENVIRONMENT="production", etc) and kept the same Post Processor code and library versions.
I used NGrok to process videos sent to GCS, instead of CloudRun, and to my surprise, locally, when outside of Google Cloud Services (Cloud Run) the API correctly received the POST request with the correct JSON...
Same exact code as in cloud run, library versions, etc... Behaved as expected with no conversion to GET.
The libraries i'm using are as follows:
functions-framework==3.5.0
cloudevents==1.11.0
cachetools==6.1.0
pytz==2025.1
requests==2.32.5
It's a small script that only performs this post to the API.
What i'm not getting is what could be causing requests.post made from Cloud Run to result in a GET with a empty body conversion in my Django REST Framework...?
Is there a setting in my ingress.yaml setting I should do? A rule, host definition, etc, there that could be causing this issue?
Edit 1
Below is a minimal code example showing how the post processor works, that is activated whenever a GCS upload is finished.
import functions_framework
from cloudevents.http import CloudEvent
# Triggered by a Cloud Storage "object finalized" event
@functions_framework.cloud_event
def hello_gcs(cloud_event: CloudEvent):
data = cloud_event.data
name = data["name"] # e.g. "7614_2025_09_23_16_46_17.mp4"
# ...parse metadata and compute (serial, date, duration, frames, filename)...
serial, date, duration, frames, filename = process_video(name)
create_recording(serial, date, duration, frames, filename)
return "OK"
Where the payload is built and the API call is made:
def create_recording(serial_cam, date, duration, frames, filename):
API_URL = os.environ.get("API_URL")
API_TOKEN = os.environ.get("API_TOKEN")
api = WrapperAPI(API_URL, API_TOKEN)
# Build payload
payload = {
"raw_file": None,
"raw_file_gcp": f"https://storage.cloud.google.com/{BUCKET}/{filename}",
# ... etc ... A normal payload, nothing fancy.
}
# Perform the POST
recording = api.create(resource="recordings", json=payload, hide_error_msg=False)
API wrapper (uses requests.post, not GET.):
import requests
class WrapperAPI:
def __init__(self, base_url, token):
self.url = base_url.rstrip("/")
self.headers = {"Authorization": token, "Content-Type": "application/json"}
def create(self, resource, json, hide_error_msg=False):
try:
url = f"{self.url}/{resource}/"
resp = requests.post(url, json=json, headers=self.headers) # <-- POST
resp.raise_for_status()
return resp.json()
# ... etc ... Exception treatment... No magic here. Just the normal exception treatment you would expect.
Edit 2
We did some investigation and found some similar threads that have some similar problems. Tho they were not in GKE contexts...
When Python’s requests client followed it, it converted the method to GET and dropped the body—explaining exactly why the backend saw a GET with empty payload.
- Python Requests library post interpreted by Django as GET for some reason
- Python Requests POST doing a GET?
The question was exact 'Python Requests library post interpreted by Django as GET for some reason' and we had exactly the same problem. But we only found this after @shiro answer. We actually found it because of it. I wanted to link it here in case anyone comes across this.
1 Answer 1
Your POST became a GET because of an unhandled HTTP redirect.
Your GKE ingress redirected your insecure http:// request to the secure https:// URL. Following this redirect, your requests client automatically changed the method from POST to GET, which is standard, expected web behavior.
You may try to fix the API_URL in your Cloud Run environment variable to use https:// from the start. This prevents the redirect and ensures your POST arrives as intended.
To reliably trace this, inspect the response.history attribute in your Cloud Run client code. This will show the exact redirect that occurred.
2 Comments
Explore related questions
See similar questions with these tags.
POSTand GKE is receiving aGET. Absent evidence, the conclusion is that you're making aGET(and receiving aGET). Please include a minimal repro including the Cloud Run source code and details of how the GKE service is exposed.create_recordingdirectly) against a debug server locally, containerized locally and deploy to a local Kubernetes cluster and every time the server receivesPOST'sPOST's