Primeros pasos: colas de tareas

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

Las colas de tareas ejecutan código fuera de la interacción directa de los usuarios, lo que permite que las tareas se realicen en segundo plano. En esta guía se usa una cola de tareas para realizar tareas después de añadir una imagen a Cloud Storage. Las tareas que se deben realizar en la cola de tareas son las siguientes:

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

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

Antes de empezar

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

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

    Añade lo siguiente a tu pom.xml:

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

Importar bibliotecas

El código de ejemplo que se proporciona en esta guía usa 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;

Crear una cola de tareas

Aunque App Engine proporciona una defaultcola de tareas, puedes crear diferentes colas de tareas para distintos tipos de trabajo. Por ejemplo, puedes crear una cola de tareas para cambiar el tamaño de las imágenes y otra para actualizar la base de datos de tu aplicación.

Para añadir colas, crea el archivo queue.xml en el directorio WEB-INF de tu proyecto de App Engine. Una cola de tareas debe especificar un nombre y una tasa 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, llamada resize-image, define una frecuencia de ejecución de 60 veces por hora, es decir, una vez por minuto. Para ver la lista completa de opciones de la cola, consulta la referencia de queue.xml.

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

Añadir tareas a una cola

Para añadir una tarea a una cola, sigue estos pasos:

  1. Crea un objeto de cola de tareas con QueueFactory.getQueue(). 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. Añade tareas al objeto Queue. Como se muestra en el código de ejemplo, imageResizeQueue.add() añade 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 los parámetros que se envíen al controlador.

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

Crear un controlador de tareas

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

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

  • Obtiene la imagen especificada por la persona que llama de Cloud Storage.

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

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

Para crear el controlador de tareas, sigue estos pasos:

  1. Añade una anotación que asigne 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 describe en la guía sobre cómo usar Cloud Storage y recupera la imagen de Cloud Storage:

     public void init() throws ServletException {
    
      // initiate GcsService
      GcsService gcsService =
        GcsServiceFactory.createGcsService(
            new RetryParams.Builder()
                .initialRetryDelayMillis(10)
                .retryMaxAttempts(10)
                .totalRetryPeriodMillis(15000)
                .build());
    }
    
  3. Gestiona la solicitud de la cola de tareas entrante. Para ello, usa el nombre de archivo proporcionado para obtener la imagen de 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 Image para cambiar el tamaño de la imagen a una miniatura. Para ello, lee la imagen de Cloud Storage en un InputChannel y la convierte en un ByteArray mediante IOUtils.toByteArray().

    Después de aplicar la transformación, la nueva imagen tiene la cadena thumbnail_ añadida a su nombre de archivo, el permiso se ha configurado para que se pueda leer públicamente y se ha escrito en Cloud Storage.

Proteger las URLs de los controladores de tareas

Debes proteger las tareas que realicen operaciones sensibles, como modificar datos, para que los usuarios externos no puedan llamarlas directamente. Para ello, puedes restringir el acceso a las tareas a los administradores de App Engine, lo que impide que los usuarios accedan a las URLs de las tareas . Ten en cuenta que esta restricción no se aplica a las solicitudes de tareas procedentes de tu aplicación de App Engine.

En el ejemplo actual, los controladores de tareas tienen URLs en la carpeta /tasks/. Para restringir el acceso a la carpeta /tasks/ a los administradores de App Engine, añade lo siguiente al archivo 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>

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

Eliminar todas las tareas de una cola

Para quitar todas las tareas de una cola, usa purge(). La purga puede tardar hasta un minuto en eliminar todas las tareas de la cola.

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

Puede tardar un minuto en eliminar todas las tareas de una cola, por lo que debes esperar unos segundos antes de añadir nuevas tareas a la cola.

Eliminar una cola de tareas

Para eliminar una cola de tareas, quita la entrada del archivo queue.xml del proyecto y vuelve a implementar.

Desplegar en App Engine

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

Sustituye PROJECT_ID por el ID de tu Google Cloud proyecto. Si tu archivo pom.xml ya especifica tu ID de proyecto, no es necesario que incluyas la propiedad -Dapp.deploy.projectId en el comando que ejecutes.

Una vez que Maven haya desplegado tu aplicación, se abrirá automáticamente una pestaña del navegador web con tu nueva aplicación. Para ello, escribe lo siguiente:

gcloud app browse

Siguientes pasos

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