Use multiple paths in Cloud Functions, Python and Flask

guillaume blaquiere
Google Cloud - Community
3 min readJun 8, 2021

Function as a Service, or FaaS, has been a cornerstone in app development. Popularized by AWS Lambda service, all the major Cloud Providers offer their version, with different features. And they also extend this principle to containers, with Cloud Run on Google Cloud for example.

On Google Cloud, Cloud Functions is the FaaS service and, to use it, you have to enforce a predefined code structure. In Python, to handle HTTP requests, the function signature is the following

def my_function(request):
...
return "response", 200 #http code

FaaS common issues

When you deploy a function, it’s “only one function”, to achieve only one task. This opinionated design allows a better scalability and separation of concern.

The result of this choice can be seen in the available URL to call the function

https://<region>-<projectID>.cloudfunctions.net/<functionName>

Only one path is suitable. No subpath configuration, no path routing definition.

However, sometimes, you would like to do more, even if it’s not a good pattern, you need (or want) to use REST API definition to access to resources, by using path parameters.
For example: /<functionName>/user/<userId>

Flask routing and Cloud Functions limitation

Flask framework proposes an idiomatic way to achieve this. You define your Flask App, your route, and that’s all (you can find that in any tutorial/getting started)

from flask import Flask, request

app = Flask("internal")

@app.route('/user/<string:id>', methods=['GET', 'POST'])
def users(id):
<do something>
print(id)
return id, 200

Sadly, with Cloud Functions, and even if Cloud Functions is based on Flask request definition, you can’t because you don’t manage the Flask root app, only the Cloud Functions endpoint, that’s all!!

To provide a better understanding, the implementation is similar to that

############### MANAGED BY GOOGLE ####################from flask import Flask, request

app = Flask("google_managed")

@app.route('/my_function', methods=['GET', 'POST'])
def common_cloud_functions_function():
return my_function(request)
############### MANAGED AND PROVIDED BY YOU ####################
def my_function(request):
...
return "response", 200 #http code

How to perform path based routing without managing the Flask server?

Manual Flask invocation

Flask is a framework that performs 2 main tasks

  • Perform routing based on URL map
  • Listen and serve a webserver

The principle is to reuse only the routing part, without the listen and serve; this part is performed by the Cloud Functions runtime.
The main interest of this solution is the capacity to reuse the Flask idiomatic path rule definition, and to keep an environment familiar to the developers.

So, when a request comes in, we have to:

  • Create a new context
  • Copy the request’s main values (data, headers, path,…)
  • Dispatch and process the request

So, let’s code that !

from flask import Flask, request#Define an internal Flask app
app = Flask("internal")
#Define the internal path, idiomatic Flask definition
@app.route('/user/<string:id>', methods=['GET', 'POST'])
def users(id):
print(id)
return id, 200
#Comply with Cloud Functions code structure for entry point
def my_function(request):
#Create a new app context for the internal app
internal_ctx = app.test_request_context(path=request.full_path,
method=request.method)

#Copy main request data from original request
#According to your context, parts can be missing. Adapt here!
internal_ctx.request.data = request.data
internal_ctx.request.headers = request.headers

#Activate the context
internal_ctx.push()
#Dispatch the request to the internal app and get the result
return_value = app.full_dispatch_request()
#Offload the context
internal_ctx.pop()

#Return the result of the internal app routing and processing
return return_value

The Cloud functions deploys as usual and offer the same endpoint. However, this time, you can request your new paths. Try this request

curl https://<region>-<projectID>.cloudfunctions.net/<functionName>/user/123# The return is 123

Unlock Cloud Functions limitation….. or not!!

This solution allows you to unlock the routing capacity of Cloud Functions and to do more than a single purpose endpoint. You can gain in consistency and efficiency (less functions to deploy)

However, keep in mind that’s a workaround, even a hack, and Cloud Functions aren’t designed for this purpose.

If you want to create an API backend, to handle concurrent requests on the same instance, to have a developer friendly environment (for test, packaging, portability,…), Cloud Run is a much more suitable product for that!

Disclaimer: I’m not a Python developer and some code parts could be improved to be more idiomatic. Don’t hesitate to comment to improve these code samples!

--

--

Google Cloud - Community
Google Cloud - Community

Published in Google Cloud - Community

A collection of technical articles and blogs published or curated by Google Cloud Developer Advocates. The views expressed are those of the authors and don't necessarily reflect those of Google.

guillaume blaquiere
guillaume blaquiere

Written by guillaume blaquiere

GDE cloud platform, Group Data Architect @Carrefour, speaker, writer and polyglot developer, Google Cloud platform 3x certified, serverless addict and Go fan.