A Google Cloud Function can be called in response to multiple types of events, and of the most useful events to use is an HTTP request. This allows you to make a GET
, POST
or other HTTP request to a URL provided by your Cloud Function, and have it run in response to the given request.
When you activate your Cloud Function via HTTP, you can also optionally return a response, much like a HTTP server would. In the simplest examples, this is usually just a string:
def hello(request):
return "Hello, World!"
This creates an HTTP response that looks like this:
HTTP/1.0 200 OK
Content-Length: 13
Content-Type: text/html; charset=utf-8
Hello, World!
You'll notice here that the Content-Type
is text/html
, but the response is not HTML at all, it's just plaintext. What if we wanted to add some formatting?
Adding some formatting
Let's say we wanted to make part of our response bold. We could do this by adding some HTML tags:
def hello(request):
return "Hello, <b>World!</b>"
Which would give us the following response:
HTTP/1.0 200 OK
Content-Length: 20
Content-Type: text/html; charset=utf-8
Hello, <b>World!</b>
This would probably render correctly in our browsers, but only because modern browsers have an amazing propensity for dealing with invalid HTML. To make this actually a valid HTML response, we would need to change our function to be:
def hello(request):
return """
<!DOCTYPE html>
<head>
<title>Saying Hello</title>
</head>
<body>
Hello, <b>World!</b>
</body>
"""
This is starting to get a bit long, so perhaps we would make it a separate variable:
INDEX_TEMPLATE = """
<!DOCTYPE html>
<head>
<title>Saying Hello</title>
</head>
<body>
Hello, <b>World!</b>
</body>
"""
def hello(request):
return INDEX_TEMPLATE
Passing variables and controlling flow
If we want to pass variables into our template, we could just do it with .format
:
INDEX_TEMPLATE = """
<!DOCTYPE html>
<head>
<title>Saying Hello</title>
</head>
<body>
Hello, <b>{place}!</b>
</body>
"""
def hello(request):
return INDEX_TEMPLATE.format(place="World")
What if we had multiple places we wanted to say hello to, on separate lines?
def hello(request):
return INDEX_TEMPLATE.format(places=["World", "dev.to"])
At this point, our "homemade" template would start break down, and we'd need to start getting creative in order to make it continue to work.
Inline templates with Cloud Functions
Instead, let's use some existing tools. Since under the hood Cloud Functions is using Flask, this means that the render_template_string
function is available to us. This lets us render a Jinja2 template in a similar way as our custom solution, but also lets us do loops, if statements, etc:
from flask import render_template_string
INDEX_TEMPLATE = """
<!DOCTYPE html>
<head>
<title>Saying Hello</title>
</head>
<body>
{% for place in places %}
Hello, <b>{{ place }}!</b><br>
{% endfor %}
</body>
"""
def hello(request):
return render_template_string(
INDEX_TEMPLATE, places=["World", "dev.to"]
)
This would produce a response such as:
HTTP/1.0 200 OK
Content-Length: 140
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
<head>
<title>Saying Hello</title>
</head>
<body>
Hello, <b>World!</b><br>
Hello, <b>dev.to!</b><br>
</body>
Templates directory
Finally, perhaps your template is quite long and you don't want it to exist in the same file as your function, you can put it in a directory called templates
and use the render_template
function instead.
Your source files should look something like this:
├── main.py
├── requirements.txt
└── templates
└── hello.html
With hello.html
being the same as our previous example:
<!DOCTYPE html>
<head>
<title>Saying Hello</title>
</head>
<body>
{% for place in places %}
Hello, <b>{{ place }}!</b><br>
{% endfor %}
</body>
And the function configured like this:
from flask import render_template
def hello(request):
return render_template('hello.html', places=["World", "dev.to"])
And that's it! From here, you can use all of Jinja2's features that you normally would in any other template.