Create App Engine task handlers
Stay organized with collections
Save and categorize content based on your preferences.
This page demonstrates how to create an App Engine task handler, the
worker code that handles an App Engine task. The Cloud Tasks
queue sends HTTP requests to your task handler. Upon successful completion of
processing, the handler must send an HTTP status code between 200
and 299
back to the queue. Any other value indicates the task has failed and the queue
retries the task.
App Engine Task Queue requests are sent from the IP address 0.1.0.2
.
Also refer to the
IP range for requests sent to the App Engine environment.
C#
publicclassStartup
{
publicStartup(IConfigurationconfiguration)
{
Configuration=configuration;
}
publicIConfigurationConfiguration{get;}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
publicvoidConfigureServices(IServiceCollectionservices)
{
services.AddLogging(builder=>builder.AddDebug());
services.AddRouting();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv,ILoggerFactoryloggerFactory)
{
varlogger=loggerFactory.CreateLogger("testStackdriverLogging");
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// Configure error reporting service.
app.UseExceptionHandler("/Home/Error");
}
varrouteBuilder=newRouteBuilder(app);
routeBuilder.MapPost("log_payload",context=>
{
// Log the request payload
varreader=newStreamReader(context.Request.Body);
vartask=reader.ReadToEnd();
logger.LogInformation($"Received task with payload: {task}");
returncontext.Response.WriteAsync($"Printed task payload: {task}");
});
routeBuilder.MapGet("hello",context=>
{
// Basic index to verify app is serving
returncontext.Response.WriteAsync("Hello, world!");
});
routeBuilder.MapGet("_ah/health",context=>
{
// Respond to GAE health-checks
returncontext.Response.WriteAsync("OK");
});
routeBuilder.MapGet("/",context=>
{
returncontext.Response.WriteAsync("Hello, world!");
});
varroutes=routeBuilder.Build();
app.UseRouter(routes);
}
}
Go
// Sample task_handler is an App Engine app demonstrating Cloud Tasks handling.
packagemain
import(
"fmt"
"io"
"log"
"net/http"
"os"
)
funcmain(){
// Allow confirmation the task handling service is running.
http.HandleFunc("/",indexHandler)
// Handle all tasks.
http.HandleFunc("/task_handler",taskHandler)
port:=os.Getenv("PORT")
ifport==""{
port="8080"
log.Printf("Defaulting to port %s",port)
}
log.Printf("Listening on port %s",port)
iferr:=http.ListenAndServe(":"+port,nil);err!=nil{
log.Fatal(err)
}
}
// indexHandler responds to requests with our greeting.
funcindexHandler(whttp.ResponseWriter,r*http.Request){
ifr.URL.Path!="/"{
http.NotFound(w,r)
return
}
fmt.Fprint(w,"Hello, World!")
}
// taskHandler processes task requests.
functaskHandler(whttp.ResponseWriter,r*http.Request){
taskName:=r.Header.Get("X-Appengine-Taskname")
iftaskName==""{
// You may use the presence of the X-Appengine-Taskname header to validate
// the request comes from Cloud Tasks.
log.Println("Invalid Task: No X-Appengine-Taskname request header found")
http.Error(w,"Bad Request - Invalid Task",http.StatusBadRequest)
return
}
// Pull useful headers from Task request.
queueName:=r.Header.Get("X-Appengine-Queuename")
// Extract the request body for further task details.
body,err:=io.ReadAll(r.Body)
iferr!=nil{
log.Printf("ReadAll: %v",err)
http.Error(w,"Internal Error",http.StatusInternalServerError)
return
}
// Log & output details of the task.
output:=fmt.Sprintf("Completed task: task queue(%s), task name(%s), payload(%s)",
queueName,
taskName,
string(body),
)
log.Println(output)
// Set a non-2xx status code to indicate a failure in task processing that should be retried.
// For example, http.Error(w, "Internal Server Error: Task Processing", http.StatusInternalServerError)
fmt.Fprintln(w,output)
}
Java
@WebServlet(
name="Tasks",
description="Create Cloud Task",
urlPatterns="/tasks/create"
)
publicclass TaskServletextendsHttpServlet{
privatestaticLoggerlog=Logger.getLogger(TaskServlet.class.getName());
@Override
publicvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsIOException{
log.info("Received task request: "+req.getServletPath());
Stringbody=req.getReader()
.lines()
.reduce("",(accumulator,actual)->accumulator+actual);
if(!body.isEmpty()){
log.info("Request payload: "+body);
Stringoutput=String.format("Received task with payload %s",body);
resp.getOutputStream().write(output.getBytes());
log.info("Sending response: "+output);
resp.setStatus(HttpServletResponse.SC_OK);
}else{
log.warning("Null payload received in request to "+req.getServletPath());
}
}
}
Node.js
constexpress=require('express');
constapp=express();
app.enable('trust proxy');
// Set the Content-Type of the Cloud Task to ensure compatibility
// By default, the Content-Type header of the Task request is set to "application/octet-stream"
// see https://cloud.google.com/tasks/docs/reference/rest/v2beta3/projects.locations.queues.tasks#AppEngineHttpRequest
app.use(express.text());
app.get('/',(req,res)=>{
// Basic index to verify app is serving
res.send('Hello, World!').end();
});
app.post('/log_payload',(req,res)=>{
// Log the request payload
console.log(`Received task with payload: ${req.body}`);
res.send(`Printed task payload: ${req.body}`).end();
});
app.get('*',(req,res)=>{
res.send('OK').end();
});
constPORT=process.env.PORT||8080;
app.listen(PORT,()=>{
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
PHP
require __DIR__ . '/vendor/autoload.php';
use Google\Cloud\Logging\LoggingClient;
// Create the logging client.
$logging = new LoggingClient();
// Create a PSR-3-compatible logger.
$logger = $logging->psrLogger('app', ['batchEnabled' => true]);
// Front-controller to route requests.
switch (@parse_url($_SERVER['REQUEST_URI'])['path']) {
case '/':
print "Hello, World!\n";
break;
case '/task_handler':
// Taskname and Queuename are two of several useful Cloud Tasks headers available on the request.
$taskName = $_SERVER['HTTP_X_APPENGINE_TASKNAME'] ?? '';
$queueName = $_SERVER['HTTP_X_APPENGINE_QUEUENAME'] ?? '';
try {
handle_task(
$queueName,
$taskName,
file_get_contents('php://input')
);
} catch (Exception $e) {
http_response_code(400);
exit($e->getMessage());
}
break;
default:
http_response_code(404);
exit('Not Found');
}
/**
* Process a Cloud Tasks HTTP Request.
*
* @param string $queueName provides the name of the queue which dispatched the task.
* @param string $taskName provides the identifier of the task.
* @param string $body The task details from the HTTP request.
*/
function handle_task($queueName, $taskName, $body = '')
{
global $logger;
if (empty($taskName)) {
// You may use the presence of the X-Appengine-Taskname header to validate
// the request comes from Cloud Tasks.
$logger->warning('Invalid Task: No X-Appengine-Taskname request header found');
throw new Exception('Bad Request - Invalid Task');
}
$output = sprintf('Completed task: task queue(%s), task name(%s), payload(%s)', $queueName, $taskName, $body);
$logger->info($output);
// Set a non-2xx status code to indicate a failure in task processing that should be retried.
// For example, http_response_code(500) to indicate a server error.
print $output;
}
Python
fromflaskimport Flask, render_template, request
app = Flask(__name__)
@app.route("/example_task_handler", methods=["POST"])
defexample_task_handler():
"""Log the request payload."""
payload = request.get_data(as_text=True) or "(empty payload)"
print(f"Received task with payload: {payload}")
return render_template("index.html", payload=payload)
Ruby
require"sinatra"
require"json"
get"/"do
# Basic index to verify app is serving
"Hello World!"
end
post"/log_payload"do
data=request.body.read
# Log the request payload
puts"Received task with payload: #{data}"
"Printed task payload: #{data}"
end
Timeouts
App Engine tasks have specific timeouts that depend on the scaling type of the service that's running them.
For worker services running in the standard environment:
- Automatic scaling: task processing must finish in 10 minutes.
- Manual and basic scaling: requests can run up to 24 hours.
For worker services running in the flex environment: all types have a 60 minute timeout.
If your handler misses the deadline, the queue assumes the task failed and retries it.
Reading App Engine task request headers
Requests sent to your App Engine handler by a Cloud Tasks queue have special headers, which contain task-specific information your handler might want to use.
These headers are set internally. If any of these headers are present in an external user request to your app, they are replaced by the internal ones — except for requests from logged in administrators of the application, who are allowed to set headers for testing purposes.
App Engine task requests always contain the following headers:
Header | Description |
---|---|
X-AppEngine-QueueName |
The name of the queue. |
X-AppEngine-TaskName |
The "short" name of the task, or, if no name was specified at creation, a unique system-generated id. This is the my-task-id value in the complete task name; for example, task_name = projects/my-project-id/locations/my-location/queues/my-queue-id/tasks/my-task-id . |
X-AppEngine-TaskRetryCount |
The number of times that the task has been retried. For the first attempt, this value is 0 . This number includes attempts where the task failed due to a lack of available instances and never reached the execution phase. |
X-AppEngine-TaskExecutionCount |
The number of times that the task has executed and received a response from the handler. Since Cloud Tasks deletes the task once a successful response has been received, all previous handler responses are failures. This number does not include failures due to a lack of available instances. Note that X-AppEngine-TaskExecutionCount can be equal to X-AppEngine-TaskRetryCount if it is updated before an execution is attempted. |
X-AppEngine-TaskETA |
The schedule time of the task, specified in seconds since January 1st 1970. |
If your request handler finds any of the headers listed previously, it can assume that the request is a Cloud Tasks request.
In addition, requests from Cloud Tasks might contain the following headers:
Header | Description |
---|---|
X-AppEngine-TaskPreviousResponse |
The HTTP response code from the previous retry. |
X-AppEngine-TaskRetryReason |
The reason for retrying the task. |
X-AppEngine-FailFast |
Indicates that a task fails immediately if an existing instance is not available. |
Target routing
In App Engine tasks, both the queue and the task handler run within the same Google Cloud project. Traffic is encrypted during transport and never leaves Google datacenters. You cannot explicitly set the protocol (for example, HTTP or HTTPS). The request to the handler, however, will appear to have used the HTTP protocol.
Tasks can be dispatched to secure task handlers, unsecure task handlers, and in
supported runtimes, URIs restricted with
login: admin
.
Because tasks are not run as any user, they cannot be dispatched to URIs
restricted with login: required
.
Task dispatches also don't follow redirects.
What's next
- Learn more about tasks in the RPC API reference.
- Learn more about tasks in the REST API reference.