Cloud Run: Why does python flask return 400 Bad request? But locally everything is fine

Issue

I have a simple flask application. And I need to run it on Cloud Run with enabled option "Manage authorized users with Cloud IAM."

app.py

from flask import Flask

api_app = Flask(__name__)

endpoints.py

from app import api_app

@api_app.route("/create", methods=["POST"])
def api_create():
    # logic

main.py

from app import api_app
from endpoints import *    


if __name__ == "__main__":
    api_app.run(host="0.0.0.0", port=8080)

And it works if i run it locally. It also works well if run in docker. When I upload the application image to Cloud Run, there are no deployment errors. But when I try to call the endpoint I get an error even though everything is working fine locally.

Request example:

import urllib
import google.auth.transport.requests
import google.oauth2.id_token
import requests
import os

os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "test.json"

audience="https://test-service-*****.a.run.app"

req = urllib.request.Request(audience)
auth_req = google.auth.transport.requests.Request()
token = google.oauth2.id_token.fetch_id_token(auth_req, audience)

response = requests.post(f"{audience}/create", data={
    "text": "cool text"
}, headers={
    "Authorization": f"Bearer {token}"
})

Response:

<!doctype html>
<html lang=en>
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>The browser (or proxy) sent a request that this server could not understand.</p>

In Cloud Run logs I have only one warning log
POST 400 820b 3ms python-requests/2.26.0 https://test-service-*****.a.run.app/create

Dockerfile:

FROM python:3.8-slim-buster

WORKDIR /alpha
ENV PYTHONUNBUFFERED True

COPY . .

RUN pip install --no-cache-dir -r requirements.txt

CMD exec gunicorn --bind :8080 --workers 1 --threads 8 --timeout 0 main:api_app

Why can’t I reach the application endpoint in Cloud Run?

Solution

So I did some testing, and the only thing I did was remove the port binding from the Dockerfile CMD exec gunicorn and from the main.py. Note that the dundermain thingy is not needed as gunicorn takes care of that.

After that it worked as expected.

Note that I did not set it up as a private endpoint as I was to lazy to jump through the hoops for that, and the response code you get back is not a 403.

Note that I also made sure that /create returned something. I’m going to assume that you did the same.


EDIT

I’ve made the CR endpoint private, and created a serviceaccount with the role "Cloud Run Invoker", created a JSON key and saved it as test.json

I’ve also added some logic to print out the token, and to decode the first part. Your output should look similar to mine.


EDIT 2

In the end it ended up Flask not being able to deal with the incoming data. There’s a very nice answer here which explains how to get the data from all the different ways it can get POSTed

FROM python:3.8-slim-buster

WORKDIR /alpha
ENV PYTHONUNBUFFERED True

COPY . .

RUN pip install --no-cache-dir -r requirements.txt

CMD exec gunicorn  --workers 1 --threads 8 --timeout 0 main:api_app

endpoints.py

from app import api_app
from flask import request


@api_app.route("/create", methods=["POST"])
def api_create():
    data = request.data
    return f"{data}"

main.py

from app import api_app
from endpoints import *    

perform_request.py

import urllib
import requests
import google.auth.transport.requests
import google.oauth2.id_token
import os
import base64


os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "test.json"
target_uri = "https://pasta*****.a.run.app"


req = urllib.request.Request(target_uri)
auth_req = google.auth.transport.requests.Request()
token = google.oauth2.id_token.fetch_id_token(auth_req, target_uri)

print(token, "\n")
print(base64.b64decode(f"""{token.split(".")[0]}==""".encode("utf-8")), "\n")


response = requests.post(
    f"{target_uri}/create",
    data={"text": "cool text"},
    headers={"Authorization": f"Bearer {token}"},
)

print(response.text)

output

eyJhbGciOiJSUzI1NiIsIm[SNIPPED]

b'{"alg":"RS256","kid":"713fd68c9[SNIPPED]","typ":"JWT"}' 

b'text=cool+text'

Answered By – Edo Akse

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published