Introdução: filas de tarefas

Saiba como usar filas de tarefas e a API Image do App Engine para redimensionar imagens.

As filas de tarefas executam código fora da interação direta do utilizador, o que permite que as tarefas ocorram em segundo plano. Este guia usa uma fila de tarefas para realizar tarefas depois de adicionar uma imagem ao Cloud Storage. As tarefas a realizar na fila de tarefas são:

  1. Obtenha o ficheiro de imagem que acabou de ser carregado para o Cloud Storage.
  2. Redimensione-a para uma imagem de miniatura através da API Image.
  3. Armazenar a miniatura resultante no Cloud Storage.

O tempo de execução do Java 8 do App Engine também suporta classes de manipulação de imagens nativas do Java, como AWT e Java2D.

Antes de começar

  1. Configure o ambiente de programação e crie o projeto do App Engine.

  2. Este guia usa a biblioteca Apache Commons IOUtils. Para incluir a biblioteca IOUtils no seu projeto do App Engine:

    Adicione-o à sua pom.xml:

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

Importar bibliotecas

O exemplo de código fornecido com este guia usa as seguintes importações:

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;

Criar uma fila de tarefas

Embora o App Engine forneça uma fila de tarefas default, pode criar diferentes filas de tarefas para diferentes tipos de trabalho. Por exemplo, pode criar uma fila de tarefas para redimensionar imagens e outra para atualizar a base de dados da sua app.

Para adicionar filas, crie o ficheiro queue.xml no diretório WEB-INF do projeto do App Engine. Uma taskqueue tem de especificar um nome e uma taxa de execução:

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

Esta fila de exemplo, denominada resize-image, define uma taxa de execução de 60 vezes por hora ou uma vez por minuto. Para ver a lista completa de opções de fila, consulte a referência queue.xml.

Uma fila de tarefas tem dois componentes: o requerente de tarefas e um controlador de tarefas. O solicitador adiciona uma tarefa à fila e envia-a para o controlador de tarefas.

Adicionar tarefas a uma fila

Para adicionar uma tarefa a uma fila:

  1. Crie um objeto de fila de tarefas com QueueFactory.getQueue(), certificando-se de que especifica o nome da fila definido em 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. Adicione tarefas ao objeto Queue. Conforme mostrado no exemplo de código, imageResizeQueue.add() adiciona uma tarefa ao 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));
    }
    

    Especifique o URI do controlador de tarefas através de TaskOptions.Builder.withUrl(), juntamente com os parâmetros enviados para o controlador.

    Neste exemplo, o URI é /tasks/imageresize e o parâmetro é uma variável denominada filename que contém o nome do ficheiro da imagem a ser processada.

Criar um controlador de tarefas

Depois de adicionar uma tarefa à fila, o controlador de tarefas mapeado para o URI /tasks/imageresize é executado. Um controlador de tarefas é um servlet Java que tenta executar a tarefa até ter êxito.

Neste exemplo, o controlador de tarefas realiza três tarefas:

  • Obter a imagem especificada pelo autor da chamada a partir do Cloud Storage.

  • Transforme a imagem usando a API App Engine Image, neste exemplo, numa imagem em miniatura.

  • Armazenar a imagem transformada (miniatura) no Cloud Storage.

Para criar o controlador de tarefas:

  1. Adicione uma anotação que mapeie o controlador para o URI /tasks/imageresize:

     @WebServlet(name = "imageResize", description = "Task queue handler", urlPatterns = "/tasks/imageresize")
     public class imageResize extends HttpServlet {
    
       // Task handler functionality
    
     }
    
  2. Configure uma ligação ao Cloud Storage, conforme documentado no guia de utilização do Cloud Storage e obtenha a imagem do Cloud Storage:

     public void init() throws ServletException {
    
      // initiate GcsService
      GcsService gcsService =
        GcsServiceFactory.createGcsService(
            new RetryParams.Builder()
                .initialRetryDelayMillis(10)
                .retryMaxAttempts(10)
                .totalRetryPeriodMillis(15000)
                .build());
    }
    
  3. Processar o pedido da fila de tarefas recebido, usando o nome do ficheiro fornecido para obter a imagem do 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. Use o objeto ImagesService para redimensionar a imagem:

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

    O fragmento acima usa o método makeResize() da API Image para redimensionar a imagem para uma miniatura. Para o fazer, lê a imagem do Cloud Storage num InputChannel e converte-a num ByteArray através de IOUtils.toByteArray().

    Após aplicar a transformação, a nova imagem tem a string thumbnail_ anexada ao nome do ficheiro, a autorização definida como publicamente legível e escrita no Cloud Storage.

Proteger URLs de controladores de tarefas

Deve proteger as tarefas que realizam operações confidenciais, como a modificação de dados, para que os utilizadores externos não as possam chamar diretamente. Pode fazê-lo restringindo o acesso às tarefas aos administradores do App Engine, o que impede os utilizadores de acederem aos URLs das tarefas . Tenha em atenção que esta restrição não se aplica a pedidos de tarefas provenientes da sua app do App Engine.

No exemplo atual, os controladores de tarefas têm URLs na pasta /tasks/. Para restringir o acesso à pasta /tasks/ aos administradores do App Engine, adicione o seguinte ao ficheiro web.xml do projeto.

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

Remover uma única tarefa de uma fila

Para remover uma única tarefa de uma fila, use deleteTask():

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

Remover todas as tarefas de uma fila

Para remover todas as tarefas de uma fila, use purge(). A limpeza pode demorar até 1 minuto a remover todas as tarefas na fila.

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

A remoção de todas as tarefas de uma fila pode demorar um minuto, pelo que deve aguardar alguns segundos antes de adicionar novas tarefas à fila.

Eliminar uma fila de tarefas

Para eliminar uma fila de tarefas, remova a entrada do ficheiro queue.xml do projeto e volte a implementá-lo.

Implementação no App Engine

Pode implementar a sua app no App Engine através do Maven.

Aceda ao diretório raiz do seu projeto e escreva:

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

Substitua PROJECT_ID pelo ID do seu Google Cloud projeto. Se o seu ficheiro pom.xmlespecificar o seu ID do projeto, não precisa de incluir a propriedade -Dapp.deploy.projectId no comando que executar.

Depois de o Maven implementar a sua app, é aberto automaticamente um separador do navegador de Internet na nova app. Para tal, escreva:

gcloud app browse

O que se segue?

Este guia mostra como usar uma fila de tarefas para criar uma miniatura de imagem e armazená-la no Cloud Storage. Pode ser usado com outros serviços de armazenamento, como o Cloud Datastore ou o Cloud SQL.