Primeros pasos con las listas de tareas en cola

Aprende a usar las listas de tareas en cola y la API de imágenes de App Engine para cambiar el tamaño de las imágenes.

Las listas de tareas en cola ejecutan el código fuera de la interacción directa del usuario, lo cual permite que se realicen tareas en segundo plano. En esta guía se utiliza una lista de tareas en cola para realizar tareas después de agregar una imagen a Cloud Storage. Las tareas que se tienen que realizar en la lista de tareas en cola son las siguientes:

  1. Recupera el archivo de imagen que acabas de subir a Cloud Storage.
  2. Cambia el tamaño a una imagen en miniatura con la API de imágenes.
  3. Almacena la miniatura en Cloud Storage.

El entorno de ejecución de Java 8 en App Engine también admite las clases de manipulación de imágenes nativas de Java, como AWT y Java2D.

Antes de comenzar

  1. Configura tu entorno de desarrollo y crea tu proyecto de App Engine.

  2. En esta guía se usa la biblioteca Apache Commons IOUtils. Para incluir la biblioteca IOUtils en tu proyecto de App Engine, sigue estos pasos:

    Agrega a pom.xml:

    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.5</version>
    </dependency>
    

Importa bibliotecas

En el código de muestra proporcionado con esta guía, se usan las siguientes importaciones:

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;

Crea una lista de tareas en cola

Si bien App Engine proporciona una lista de tareas en cola default, puedes crear diferentes listas de tareas en cola para los distintos tipos de trabajo. Por ejemplo, puedes crear una lista de tareas en cola con el fin de cambiar el tamaño de las imágenes y otra para actualizar la base de datos de la app.

Para agregar colas, crea el archivo queue.xml en el directorio WEB-INF del proyecto de App Engine. Una taskqueue debe especificar un nombre y una frecuencia de ejecución:

<?xml version="1.0" encoding="UTF-8"?>
  <queue-entries>
    <queue>
      <name>resize-image</name>
      <rate>60/h</rate>
    </queue>
  </queue-entries>

Esta cola de ejemplo con el nombre resize-image define una frecuencia de ejecución de 60 veces por hora o una vez por minuto. Para ver las opciones de fila de lista completa, consulta la referencia queue.xml.

Una lista de tareas en cola tiene dos componentes: el solicitante de tareas y el controlador de tareas. El solicitante agrega una tarea a la cola y la envía al controlador de tareas.

Agrega tareas a una cola

Para agregar una tarea a una cola, haz lo siguiente:

  1. Crea un objeto de lista de tareas en cola mediante QueueFactory.getQueue() y asegúrate de especificar el nombre de la cola definido en queue.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.
      }
    
    }
    
  2. Agrega tareas al objeto Queue. Como se observa en la muestra de código, imageResizeQueue.add() agrega una tarea al objeto imageResizeQueue:

    try {
      // Add a queued task to create a thumbnail of the uploaded image
      imageResizeQueue.add(
          TaskOptions.Builder.withUrl("/tasks/imageresize").param("filename", filename));
    }
    

    Especifica el URI del controlador de tareas mediante TaskOptions.Builder.withUrl(), junto con cualquier parámetro enviado al controlador.

    En este ejemplo, el URI es /tasks/imageresize y el parámetro es una variable llamada filename que contiene el nombre del archivo de la imagen que se procesará.

Crea un controlador de tareas

Una vez que agregaste una tarea a la cola, el controlador de tareas asignado al URI /tasks/imageresize se ejecutará. Un controlador de tareas es un servlet de Java que intenta realizar la tarea hasta lograrlo.

En este ejemplo, el controlador de tareas realiza tres tareas:

  • Recupera la imagen especificada por el emisor desde Cloud Storage.

  • Transforma la imagen en este ejemplo en una imagen en miniatura con la API de App Engine Image.

  • Almacena la imagen transformada (imagen en miniatura) en Cloud Storage.

Para crear el controlador de tareas:

  1. Agrega una anotación que mapee el controlador al URI /tasks/imageresize:

     @WebServlet(name = "imageResize", description = "Task queue handler", urlPatterns = "/tasks/imageresize")
     public class imageResize extends HttpServlet {
    
       // Task handler functionality
    
     }
    
  2. Configura una conexión a Cloud Storage como se documenta en la guía Usa Cloud Storage y recupera la imagen desde Cloud Storage:

     public void init() throws ServletException {
    
      // initiate GcsService
      GcsService gcsService =
        GcsServiceFactory.createGcsService(
            new RetryParams.Builder()
                .initialRetryDelayMillis(10)
                .retryMaxAttempts(10)
                .totalRetryPeriodMillis(15000)
                .build());
    }
    
  3. Controla la solicitud entrante de la lista de tareas en cola con el nombre de archivo proporcionado para recuperar la imagen desde 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
    
  4. Usa el objeto ImagesService para cambiar el tamaño de la imagen:

    // 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()));
    

    El fragmento anterior usa el método makeResize() de la API de imágenes para cambiar el tamaño de la imagen a una miniatura. Para ello, lee la imagen desde Cloud Storage en InputChannel y la convierte en ByteArray mediante IOUtils.toByteArray().

    Después de aplicar la transformación, la imagen nueva tiene la string thumbnail_ anexada a su nombre de archivo, escrita en Cloud Storage y con permiso de lectura pública.

Protege las URL del controlador de tareas

Debes proteger las tareas que realizan operaciones sensibles, como modificar datos, para que usuarios externos no puedan emitirlas de manera directa. Para ello, restringe el acceso a las tareas a los administradores de App Engine, lo cual no permite que los usuarios accedan a las URL de tareas. Ten en cuenta que esta restricción no aplica a las solicitudes de tareas que provienen de la app de App Engine.

En el ejemplo actual, los controladores de tareas tienen URL en la carpeta /tasks/. Para restringir el acceso a la carpeta /tasks/ a los administradores de App Engine, agrega lo siguiente al web.xml del proyecto.

<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>

Quita una sola tarea de una cola

Para quitar una sola tarea de una cola, usa deleteTask():

private void removeTask(Queue queue, String taskName) {
  queue.deleteTask(taskName); // remove a task from a queue
}

Quita todas las tareas de una cola

Para quitar todas las tareas de una cola, usa purge(). La eliminación definitiva de las todas las tareas en la cola puede demorar hasta un minuto.

private void purgeQueue(Queue queue) {
  queue.purge(); // remove all tasks from a queue
}

La eliminación definitiva de todas las tareas de una cola puede demorar hasta un minuto, por lo que deberías esperar unos segundos antes de agregar nuevas tareas a la cola.

Borra una lista de tareas en cola

Para borrar una lista de tareas en cola, quita la entrada del archivo queue.xml del proyecto y vuelve a implementarla.

Implementa en App Engine

Puedes implementar tu aplicación en App Engine con Maven.

Ve al directorio raíz de tu proyecto y escribe lo siguiente:

mvn package appengine:deploy -Dapp.deploy.projectId=PROJECT_ID

Reemplaza PROJECT_ID por el ID del proyecto de Cloud. Si tu archivo pom.xml ya especifica tu ID del proyecto, no necesitas incluir la propiedad -Dapp.deploy.projectId en el comando que ejecutas.

Luego de que Maven implemente tu aplicación, escribe lo siguiente para abrir una pestaña del navegador web de forma automática en tu nueva aplicación:

gcloud app browse

Pasos siguientes

En esta guía se muestra cómo usar una lista de tareas en cola para crear una imagen en miniatura y almacenarla en Cloud Storage. Puede usarse con otros servicios de almacenamiento, como Cloud Datastore o Cloud SQL.