Buffer HTTP requests with Cloud Tasks

1. Introduction

c6ac6ed05292f13e.png

Cloud Tasks is a fully managed queueing service for managing the execution, dispatch and delivery of a large number of tasks.

Cloud Tasks lets you separate out pieces of work, called tasks, that can be performed independently (eg. a task to update a database entry), outside of your main application flow, and send them off to be processed, asynchronously, using handlers that you create.

The offloaded task is added to a queue, which persists the task until it is successfully executed or gets a failure. Based on the configuration, the queue can also act as a dispatch flow control. You create and configure the queue which is then managed by the Cloud Tasks service. Once tasks are added, the queue dispatches the tasks and makes sure the workers reliably process them.

d59ffe8d34138c88.png

Some of the main features of Cloud Tasks are:

  • HTTP targets: Add tasks targeting any HTTP service running on Compute Engine, Google Kubernetes Engine, Cloud Run, Cloud Functions or on-premises systems in a secure fashion using industry standard OAuth/OIDC authentication.
  • Task deduplication: Tasks added multiple times will only be dispatched once.
  • Guaranteed delivery: Tasks are guaranteed to be delivered at-least-once and most tasks are delivered exactly once.
  • Rate and retry controls: Control the execution by setting the rate at which tasks are dispatched, the maximum number of attempts, and the minimum amount of time to wait between attempts.
  • Future scheduling: Control the time at which a task is run.

In this codelab, you will first learn how to create and use a regular Cloud Tasks queue for HTTP target tasks. Then, you'll learn how to use queue-level HTTP URI override and the new BufferTask API to more easily buffer HTTP requests with Cloud Tasks.

What you'll learn

  • How to create HTTP target tasks.
  • How to create HTTP target tasks with the new queue-level HTTP URI override.
  • How to change the pending tasks with the new queue-level HTTP URI override.
  • How to more easily buffer HTTP requests with the new BufferTask API.

2. Setup and Requirements

Self-paced environment setup

  1. Sign-in to the Google Cloud Console and create a new project or reuse an existing one. If you don't already have a Gmail or Google Workspace account, you must create one.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • The Project name is the display name for this project's participants. It is a character string not used by Google APIs. You can always update it.
  • The Project ID is unique across all Google Cloud projects and is immutable (cannot be changed after it has been set). The Cloud Console auto-generates a unique string; usually you don't care what it is. In most codelabs, you'll need to reference your Project ID (typically identified as PROJECT_ID). If you don't like the generated ID, you might generate another random one. Alternatively, you can try your own, and see if it's available. It can't be changed after this step and remains for the duration of the project.
  • For your information, there is a third value, a Project Number, which some APIs use. Learn more about all three of these values in the documentation.
  1. Next, you'll need to enable billing in the Cloud Console to use Cloud resources/APIs. Running through this codelab won't cost much, if anything at all. To shut down resources to avoid incurring billing beyond this tutorial, you can delete the resources you created or delete the project. New Google Cloud users are eligible for the $300 USD Free Trial program.

Start Cloud Shell

While Google Cloud can be operated remotely from your laptop, in this codelab you will be using Google Cloud Shell, a command line environment running in the Cloud.

From the Google Cloud Console, click the Cloud Shell icon on the top right toolbar:

55efc1aaa7a4d3ad.png

It should only take a few moments to provision and connect to the environment. When it is finished, you should see something like this:

7ffe5cbb04455448.png

This virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory, and runs on Google Cloud, greatly enhancing network performance and authentication. All of your work in this codelab can be done within a browser. You do not need to install anything.

3. Create a regular queue for HTTP target tasks

In this first step, you'll learn how to create a regular Cloud Tasks queue and add HTTP tasks to it to target a Cloud Run service.

d4f09a342c8eab.png

What are HTTP target tasks?

HTTP target tasks can target any HTTP service running on Compute Engine, Google Kubernetes Engine, Cloud Run, Cloud Functions or on-premises systems in a secure fashion using industry standard OAuth/OIDC authentication.

Deploy a Cloud Run service

First, make sure the required APIs are enabled:

gcloud services enable \
  cloudtasks.googleapis.com \
  run.googleapis.com

Deploy a Cloud Run service which will serve as the target of the HTTP tasks:

SERVICE1=hello1
REGION=us-central1

gcloud run deploy $SERVICE1 \
  --allow-unauthenticated \
  --image=gcr.io/cloudrun/hello \
  --region=$REGION

Create a Cloud Tasks queue

Create a regular Cloud Tasks queue:

QUEUE1=http-queue
LOCATION=us-central1

gcloud tasks queues create $QUEUE1 --location=$LOCATION

Pause the queue temporarily, so you can observe HTTP tasks as they are created:

gcloud tasks queues pause $QUEUE1 --location=$LOCATION

4. Create and test an HTTP task

In this step, you will create an HTTP task to target the queue you created earlier.

Create an HTTP task

You can create HTTP tasks using gcloud:

gcloud tasks create-http-task \
    --queue=$QUEUE1 \
    --location=$LOCATION \
    --url=$SERVICE1_URL \
    --method=GET

Optional: You can also create an HTTP task with client libraries. For example, you can check out the Program.cs for a C# sample where an HTTP request is wrapped into a Task and a TaskRequest before being sent to Cloud Tasks with a CloudTasksClient:

var taskRequest = new CreateTaskRequest
{
    Parent = new QueueName(projectId, location, queue).ToString(),
    Task = new Task
    {
        HttpRequest = new HttpRequest
        {
            HttpMethod = HttpMethod.Get,
            Url = url
        }
    }
};

var client = CloudTasksClient.Create();
var response = client.CreateTask(taskRequest);

You can run it as follows to create and add the task to the queue:

dotnet run $PROJECT_ID $LOCATION $QUEUE1 $SERVICE1_URL

Test the HTTP task

At this point, the task is created but it's not executed yet, as the queue is paused. You can verify this by listing the queues:

gcloud tasks queues list --location=$LOCATION

You should see the queue in PAUSED state:

QUEUE_NAME  STATE
http-queue  PAUSED

Resume the queue:

gcloud tasks queues resume $QUEUE --location=$LOCATION

Check the logs of the Cloud Run service:

gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=$SERVICE1" --limit 1

You should see that the Cloud Run service received an HTTP GET request from Cloud Tasks:

httpRequest:
  latency: 0.227597158s
  protocol: HTTP/1.1
  remoteIp: 35.243.23.192
  requestMethod: GET
  requestSize: '415'
  requestUrl: https://hello1-idcwffc3yq-uc.a.run.app/
  responseSize: '5510'
  serverIp: 216.239.32.53
  status: 200
  userAgent: Google-Cloud-Tasks

5. Create a queue with a routing configuration

In this step, you'll learn how to create a Cloud Tasks queue with a routing configuration to add an HTTP URI override using the Queue-level Task Routing Configuration feature. You'll then add HTTP tasks to it to target the first Cloud Run service and observe that the routing configuration overrides the URI to route tasks to the second Cloud Run service.

5d1ec61a933f77.png

What is Queue-level Task Routing Configuration?

Queue-level Task Routing Configuration changes the HTTP task routing for the entire queue for all pending and new tasks. This allows easier creation of tasks as the HTTP target need not be set at the task level and it shifts more control to the service provider as they are able to set the target of all task in a queue (eg. route traffic to a different backend if the original backend is down).

The following configuration can be set at the queue-level:

  • Headers: Queue-level headers when specified at the queue-level, will upsert headers for all tasks in the queue.
  • HTTP Method: HTTP method when specified at the queue-level, will override the HTTP method for all tasks in the queue.
  • Target URI: Host, path, query, port, scheme (HTTP or HTTPS) can be individually overridden.
  • Authorization: OIDC/OAuth config when specified at the queue-level will override the task-level OIDC/OAuth config.

Deploy a second Cloud Run service

Deploy a second Cloud Run service which will serve as the target of the HTTP URI override later:

SERVICE2=hello2
REGION=us-central1

gcloud run deploy $SERVICE2 \
  --allow-unauthenticated \
  --image=gcr.io/cloudrun/hello \
  --region=$REGION

Save the host of the service URL for later:

SERVICE2_URL=$(gcloud run services describe $SERVICE2 --region $REGION --format 'value(status.url)')
SERVICE2_HOST=$(echo $SERVICE2_URL | sed 's,http[s]*://,,g')

Create a Cloud Tasks queue with a routing configuration

Create a queue with a routing configuration with HTTP URI override to the second Cloud Run service.

QUEUE2=http-queue-uri-override

gcloud beta tasks queues create $QUEUE2 \
  --http-uri-override=host:$SERVICE2_HOST \
  --location=$LOCATION

Note that the URI override refers to the second Cloud Run service. Any HTTP task added to the queue will have its original URI host overridden. You can see the queue configuration:

gcloud beta tasks queues describe $QUEUE2 --location=$LOCATION

You should see that the httpTarget has an uriOverride pointing to the host of the second service:

httpTarget:
  uriOverride:
    host: hello2-idcwffc3yq-uc.a.run.app
    pathOverride: {}
    queryOverride: {}
...

Pause the queue temporarily, so you can observe HTTP tasks as they are created:

gcloud tasks queues pause $QUEUE2 --location=$LOCATION

6. Create and test an HTTP task for the queue with the routing configuration

In this step, you will create an HTTP task to target the first service and observe that its URI is overridden by the queue to point to the second service.

Create an HTTP task

Create an HTTP task with the first service's URL:

gcloud tasks create-http-task \
    --queue=$QUEUE2 \
    --location=$LOCATION \
    --url=$SERVICE1_URL \
    --method=GET

Test the HTTP task

Resume the queue:

gcloud tasks queues resume $QUEUE2 --location=$LOCATION

You should see that the second (not the first) Cloud Run service received an HTTP GET request from Cloud Tasks due to the override:

gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=$SERVICE2" --limit 1
---
httpRequest:
  latency: 0.228982142s
  protocol: HTTP/1.1
  remoteIp: 35.187.132.84
  requestMethod: GET
  requestSize: '426'
  requestUrl: https://hello2-idcwffc3yq-uc.a.run.app/
  responseSize: '5510'
  serverIp: 216.239.34.53
  status: 200
  userAgent: Google-Cloud-Tasks

7. Change the pending tasks with the routing configuration

You can also use the routing configuration to change the HTTP URI of all pending tasks in a queue. This is useful if the backend service goes down and you want to quickly route to another service. Let's see how this works in this step.

Pause the queue again:

gcloud tasks queues pause $QUEUE2 --location=$LOCATION

Create an HTTP task with google.com as the task URL:

gcloud tasks create-http-task \
    --queue=$QUEUE2 \
    --location=$LOCATION \
    --url=https://www.google.com \
    --method=GET

The task is in pending state as the queue is paused.

Now, update the HTTP URI override to point to the first service. This will override the pending task's host from google.com to the first service's host:

SERVICE1_URL=$(gcloud run services describe $SERVICE1 --region $REGION --format 'value(status.url)')
SERVICE1_HOST=$(echo $SERVICE1_URL | sed 's,http[s]*://,,g')

gcloud beta tasks queues update $QUEUE2 \
  --http-uri-override=host:$SERVICE1_HOST \
  --location=$LOCATION

Resume the queue:

gcloud tasks queues resume $QUEUE2 --location=$LOCATION

You should see that the first Cloud Run service received an HTTP GET request from Cloud Tasks due to the override (instead of google.com):

gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=$SERVICE1" --limit 1
---
httpRequest:
  latency: 0.228982142s
  protocol: HTTP/1.1
  remoteIp: 35.187.132.84
  requestMethod: GET
  requestSize: '426'
  requestUrl: https://hello1-idcwffc3yq-uc.a.run.app/
  responseSize: '5510'
  serverIp: 216.239.34.53
  status: 200
  userAgent: Google-Cloud-Tasks

8. Create a queue for BufferTask API

Normally, you create tasks by using the Tasks API either from gcloud or Tasks client libraries. This burdens the applications to wrap HTTP requests into Tasks using client libraries and it also creates a dependency between applications and the Tasks client libraries.

In this step, you'll see how to take advantage of the queue-level HTTP URI override and the new BufferTask API to create HTTP target tasks more easily by simply sending an HTTP request. Any application that can send HTTP requests can now create HTTP target tasks.

b1606516297fc4b6.png

What is the BufferTask API?

The CreateTask API is the old way of creating Tasks and requires the client to send in a Task object to the API with all the required fields set.

BufferTask API is a new feature that allows the users to create an HTTP Task without the need to provide any Task Configuration (HTTP URL, headers, authorization), allowing you to simply send a message or the body of your request to the Buffer API.

This enables easier integration with services as Cloud Tasks can now be deployed in front of your service without needing any code changes on the client side. Any arbitrary HTTP request sent to the BufferTask API will be wrapped as a Task object and delivered to the destination set at the queue-level.

To use the BufferTask API, the queue needs to have the Target URI configuration set, or in other words, the Queue-level Routing Configuration feature is a prerequisite for using the BufferTask API.

Create a Cloud Tasks queue with routing configuration

Create a queue with a routing configuration pointing to the first service we deployed in the previous step:

SERVICE1=hello1
SERVICE1_URL=$(gcloud run services describe $SERVICE1 --region $REGION --format 'value(status.url)')
SERVICE1_HOST=$(echo $SERVICE1_URL | sed 's,http[s]*://,,g')
QUEUE3=http-queue-uri-override-buffer

gcloud beta tasks queues create $QUEUE3 \
  --http-uri-override=host:$SERVICE1_HOST \
  --location=$LOCATION

Pause the queue temporarily, so you can observe HTTP tasks as they are created:

gcloud tasks queues pause $QUEUE3 --location=$LOCATION

9. Buffer HTTP requests with BufferTask API

In this step, you will buffer simple HTTP GET or POST requests with BufferTask API. Under the covers, Cloud Tasks will wrap these HTTP requests into HTTP tasks with the default routing configuration settings of the queue.

First, login to get an access token and set some variables:

gcloud auth application-default login
ACCESS_TOKEN=$(gcloud auth application-default print-access-token)
PROJECT_ID=$(gcloud config get-value project)
TASKS_QUEUES_API="https://cloudtasks.googleapis.com/v2beta3/projects/$PROJECT_ID/locations/$LOCATION/queues"

Create an HTTP task

Create an HTTP task with BufferTask API. Notice how it's a simple a HTTP GET request without the need for creating a Task:

curl -X GET "$TASKS_QUEUES_API/$QUEUE3/tasks:buffer" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Create another HTTP task with HTTP POST and a body:

curl -X POST "$TASKS_QUEUES_API/$QUEUE3/tasks:buffer" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d "{'message': 'Hello World'}"

Optional: You can also create an HTTP task with client libraries. For example, you can check out the Program.cs for a C# sample where an HTTP GET request is sent directly to the BufferTask API without having to wrap it into a Task or needing the client-library for Cloud Tasks:

var BufferTaskApiUrl = $"https://cloudtasks.googleapis.com/v2beta3/projects/{ProjectId}/locations/{Location}/queues/{Queue}/tasks:buffer";

using (var client = new HttpClient())
{
    client.DefaultRequestHeaders.Add("Authorization", $"Bearer {AccessToken}");
    var response = await client.GetAsync(BufferTaskApiUrl);
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Response: {content}");
}

You can run it as follows:

dotnet run $PROJECT_ID $LOCATION $QUEUE3 $ACCESS_TOKEN

BufferTask API takes care of creating a Task out of the HTTP requests and adds the URL from the queue's routing configuration settings for the URI.

Test the HTTP task

Resume the queue:

gcloud tasks queues resume $QUEUE3 --location=$LOCATION

You should see that the Cloud Run service received an HTTP GET and POST requests from Cloud Tasks:

gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=$SERVICE1" --limit 4
---
httpRequest:
  latency: 0.002279292s
  protocol: HTTP/1.1
  remoteIp: 35.243.23.42
  requestMethod: POST
  requestSize: '777'
  requestUrl: https://hello1-idcwffc3yq-uc.a.run.app/
  responseSize: '5450'
  serverIp: 216.239.32.53
  status: 200
  userAgent: Google-Cloud-Tasks
...
httpRequest:
  latency: 0.228982142s
  protocol: HTTP/1.1
  remoteIp: 35.187.132.84
  requestMethod: GET
  requestSize: '426'
  requestUrl: https://hello1-idcwffc3yq-uc.a.run.app/
  responseSize: '5510'
  serverIp: 216.239.34.53
  status: 200
  userAgent: Google-Cloud-Tasks

10. Congratulations

Congratulations, you finished the codelab!

As a follow up, you can try the Cloud Tasks as a buffer between Pub/Sub and Cloud Run to see a real-world example of how these new features of Cloud Tasks can help to easily create a buffer queue between services.

Cleanup (Optional)

To avoid incurring charges, it's a good idea to clean up resources.

If you don't need the project, you can simply delete the project:

gcloud projects delete $PROJECT_ID

If you need the project, you can delete resources individually.

Delete the Cloud Run services:

gcloud run services delete $SERVICE1 --region $REGION
gcloud run services delete $SERVICE2 --region $REGION

Delete the Cloud Tasks queues:

gcloud tasks queues delete $QUEUE1 --location=$LOCATION
gcloud tasks queues delete $QUEUE2 --location=$LOCATION
gcloud tasks queues delete $QUEUE3 --location=$LOCATION

What we've covered

  • How to create HTTP target tasks.
  • How to create HTTP target tasks with the new queue-level HTTP URI override.
  • How to change the pending tasks with the new queue-level HTTP URI override.
  • How to more easily buffer HTTP requests with the new BufferTask API.