Learn how to use task queues and the App Engine Image API to resize images.
Task queues execute code outside of direct user interaction, allowing tasks to happen in the background. This guide uses a Task Queue to perform tasks after adding an image to Cloud Storage. The tasks to be performed in the Task Queue are:
- Retrieve the image file that just got uploaded to Cloud Storage.
- Resize it to a thumbnail image using the Image API.
- Store the resulting thumbnail in Cloud Storage.
The App Engine Java 8 runtime also supports Java's native image manipulation classes such as AWT and Java2D.
Before you begin
Configure your development environment and create your App Engine project.
This guide uses the Apache Commons IOUtils library. To include the IOUtils library to your App Engine project:
Add to your
pom.xml
:<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.5</version> </dependency>
Importing libraries
The sample code provided with this guide uses the following imports:
import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.ImagesService;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.Transform;
import org.apache.commons.io.IOUtils;
Creating a task queue
While App Engine provides a default
task queue, you can create
different task queues for different types of work. For example, you can create
one task queue to resize images and another to update your app's database.
To add queues, create the queue.xml
file in your App Engine project's
WEB-INF
directory. A taskqueue must specify a name and an
execution rate:
<?xml version="1.0" encoding="UTF-8"?>
<queue-entries>
<queue>
<name>resize-image</name>
<rate>60/h</rate>
</queue>
</queue-entries>
This example queue, named resize-image
, defines a rate of execution
of 60 times per hour, or once a minute. To see the full list queue options see
the queue.xml
reference.
A task queue has two components, the task requester and a task handler. The requestor adds a task to the queue and sends it to the task handler.
Adding tasks to a queue
To add a task to a queue:
Create a task queue object using
QueueFactory.getQueue()
, making sure you specify the queue name defined in thequeue.xml
:Queue imageResizeQueue; // Taskqueue queue @Override public void init() throws ServletException { // Setup Cloud Storage service gcsService = GcsServiceFactory.createGcsService( new RetryParams.Builder() .initialRetryDelayMillis(10) .retryMaxAttempts(10) .totalRetryPeriodMillis(15000) .build()); // Initialize the queue object with the specific queue imageResizeQueue = QueueFactory.getQueue([QUEUE-NAME]); // Cloud SQL connection setup try { final String url = System.getProperty("cloudsql"); // Cloud SQL server URI try { conn = DriverManager.getConnection(url); // Connect to the database Statement createTable; // SQL statement // Batch SQL table creation commands createTable.addBatch(createContentTableSql); createTable.addBatch(createUserTableSql); createTable.addBatch(createImageTableSql); createTable.addBatch(createBlogPostImageTableSql); conn.createTable.executeBatch(); // Execute batch } catch (SQLException e) { throw new ServletException("Unable to connect to Cloud SQL", e); } } finally { // Nothing really to do here. } }
Add tasks to
Queue
object. As shown in the code sample,imageResizeQueue.add()
adds a task to theimageResizeQueue
object:try { // Add a queued task to create a thumbnail of the uploaded image imageResizeQueue.add( TaskOptions.Builder.withUrl("/tasks/imageresize").param("filename", filename)); }
Specify the URI of the task handler using
TaskOptions.Builder.withUrl()
, along with and any parameters sent to the handler.In this example, the URI is
/tasks/imageresize
and the parameter is a variable calledfilename
that contains the filename of the image to be processed.
Creating a task handler
Once you have added a task to the queue, the task handler mapped to the URI
/tasks/imageresize
will run. A task handler is a Java Servlet that attempts to
perform the task until it is successful.
In this example, the task handler does three tasks:
Retrieve the image specified by the caller from Cloud Storage.
Transform the image using the App Engine Image API, in this sample, to a thumbnail image.
Store the transformed image (thumbnail) in Cloud Storage.
To create the task handler:
Add an annotation that maps the handler to the URI
/tasks/imageresize
:@WebServlet(name = "imageResize", description = "Task queue handler", urlPatterns = "/tasks/imageresize") public class imageResize extends HttpServlet { // Task handler functionality }
Set up a connection to Cloud Storage as documented in the Using Cloud Storage guide and retrieve the image from Cloud Storage:
public void init() throws ServletException { // initiate GcsService GcsService gcsService = GcsServiceFactory.createGcsService( new RetryParams.Builder() .initialRetryDelayMillis(10) .retryMaxAttempts(10) .totalRetryPeriodMillis(15000) .build()); }
Handle the incoming Task Queue request, using the supplied file name to retrieve the image from Cloud Storage:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String filename = req.getParameter("filename"); // Get the filename passed from the task requestor GcsFilename gcsFile = new GcsFilename(bucket, filename); // Create a valid Cloud Storage filename GcsInputChannel readChannel = gcsService.openPrefetchingReadChannel(gcsFile, 0, BUFFER_SIZE); // Read the file from Cloud Storage
Use the
ImagesService
object to do the image resizing:// Get an instance of the ImagesService we can use to transform images. ImagesService imagesService = ImagesServiceFactory.getImagesService(); // Make an image directly from a byte array, and transform it. Image image = ImagesServiceFactory.makeImage(IOUtils.toByteArray(Channels.newInputStream(readChannel))); Transform resize = ImagesServiceFactory.makeResize(100, 50); // resize image to 100x50 Image resizedImage = imagesService.applyTransform(resize, image); // Write the transformed image back to a Cloud Storage object. gcsService.createOrReplace( new GcsFilename(bucket, "thumbnail_" + filename), new GcsFileOptions.Builder().acl("public-read").build(), ByteBuffer.wrap(resizedImage.getImageData()));
The above snippet uses the Image API
makeResize()
method to resize the image to a thumbnail. To do this, it reads the image from Cloud Storage into anInputChannel
and convert it to a ByteArray usingIOUtils.toByteArray()
.After applying the transformation, the new image has the string
thumbnail_
appended to its filename, permission set to be publicly readable and written to Cloud Storage.
Securing task handler URLs
You should secure tasks that perform sensitive operations such as modifying data so external users cannot call it directly. You can do this by restricting task access to App Engine administrators, which prevents users from accessing task URLs . Note that this restriction does not apply to task requests coming from your App Engine app.
In the current example, the task handlers have URLs in the /tasks/
folder.
To restrict access to the /tasks/
folder to App Engine administrators, add the
following to the project's web.xml
.
<security-constraint>
<web-resource-collection>
<web-resource-name>tasks</web-resource-name>
<url-pattern>/tasks/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
Removing a single task from a queue
To remove a single task from a queue, use deleteTask()
:
private void removeTask(Queue queue, String taskName) {
queue.deleteTask(taskName); // remove a task from a queue
}
Removing all tasks from a queue
To remove all tasks from a queue, use purge()
. The purge can take up to a
minute to remove all tasks in the queue.
private void purgeQueue(Queue queue) {
queue.purge(); // remove all tasks from a queue
}
It can take a minute to remove all tasks from a queue, so you should wait a few seconds before adding new tasks to the queue.
Deleting a task queue
To delete a task queue, remove the entry from the project's queue.xml
file and
redeploy.
Deploying to App Engine
You can deploy your app to App Engine using Maven.
Go to the root directory of your project and type:
mvn package appengine:deploy -Dapp.deploy.projectId=PROJECT_ID
Replace PROJECT_ID with the ID of your Google Cloud project. If
your pom.xml
file already
specifies your
project ID, you don't need to include the -Dapp.deploy.projectId
property in the
command you run.
After Maven deploys your app, open a web browser tab automatically at your new app by typing:
gcloud app browse
What's next
This guide shows how to use a task queue to create an image thumbnail and store it in Cloud Storage. It can be used with other storage services such as Cloud Datastore or Cloud SQL.