Utiliser des API App Engine pour Cloud Storage

Ce document explique comment stocker et récupérer des données à l'aide de la bibliothèque cliente App Engine pour Cloud Storage. Nous partons du principe que vous savez également comment créer une application App Engine, comme décrit dans le guide de démarrage rapide de l'environnement standard Java 8 d'App Engine. Nous supposons également que vous comprenez les concepts de base de l'utilisation de Cloud Storage dans App Engine.

Télécharger la bibliothèque cliente App Engine pour Cloud Storage

Vous pouvez télécharger la bibliothèque à l'aide d'outils populaires comme Apache Maven, Apache Ivy ou Git, ou télécharger la bibliothèque manuellement à partir du dépôt Maven. Choisissez la méthode appropriée :

Git

Si Git est installé, vous pouvez cloner le dépôt GitHub de la bibliothèque cliente Google Cloud Storage comme suit :

git clone https://github.com/GoogleCloudPlatform/appengine-gcs-client.git

Maven

Les utilisateurs de Maven doivent inclure les éléments suivants dans le fichier pom.xml de leur application :

<dependency>
    <groupId>com.google.appengine.tools</groupId>
    <artifactId>appengine-gcs-client</artifactId>
    <version>0.8</version>
</dependency>

Ivy

Les utilisateurs d'Ivy doivent inclure les éléments suivants dans le fichier ivy.xml de leur application :

<dependency org="com.google.appengine.tools"
            name="appengine-gcs-client"
            rev="latest.integration" />

Téléchargement manuel

Consultez le dépôt Maven de la bibliothèque et téléchargez les derniers fichiers source, de classes et JavaDoc JAR :

De plus, vous devrez télécharger les dépendances suivantes et les inclure dans l'application :

LCA et bibliothèque cliente App Engine

Une application utilisant la bibliothèque cliente ne peut pas modifier la LCA du bucket, mais elle peut spécifier une LCA qui contrôle l'accès aux objets qu'elle crée. Les paramètres de LCA disponibles sont décrits dans la documentation de la classe FcsFileOptions.

Cloud Storage et sous-répertoires

La bibliothèque cliente App Engine pour Cloud Storage permet de fournir des délimiteurs de sous-répertoires lorsque vous créez un objet, mais il n'existe pas de véritables sous-répertoires dans Cloud Storage. Au lieu de cela, un sous-répertoire dans Cloud Storage fait partie du nom de fichier de l'objet.

Par exemple, vous pouvez supposer que la création d'un objet somewhere/over/the/rainbow.mp3 stocke le fichier rainbow.mp3 dans le sous-répertoire somewhere/over/the/. En réalité, le nom de l'objet est défini sur somewhere/over/the/rainbow.mp3.

Utiliser la bibliothèque cliente App Engine avec le serveur d'applications de développement

Vous pouvez employer la bibliothèque cliente avec le serveur de développement. Cependant, en l'absence d'émulation locale de Cloud Storage, toutes les requêtes de lecture et d'écriture de fichiers doivent être envoyées par Internet à un bucket Cloud Storage réel.

Pour utiliser la bibliothèque cliente avec le serveur d'applications de développement, exécutez dev_appserver.py avec l'option --default_gcs_bucket_name [BUCKET_NAME], en remplaçant [BUCKET_NAME] par le nom du bucket Cloud Storage que vous utilisez.

Cette option contrôle le bucket qui est renvoyé lorsque l'application appelle file.DefaultBucketName(ctx).

Lire et écrire dans Google Cloud Storage

Importations requises

L'extrait de code suivant illustre les importations dont vous avez besoin pour accéder à Cloud Storage via la bibliothèque cliente :

import com.google.appengine.tools.cloudstorage.GcsFileOptions;
import com.google.appengine.tools.cloudstorage.GcsFilename;
import com.google.appengine.tools.cloudstorage.GcsInputChannel;
import com.google.appengine.tools.cloudstorage.GcsOutputChannel;
import com.google.appengine.tools.cloudstorage.GcsService;
import com.google.appengine.tools.cloudstorage.GcsServiceFactory;
import com.google.appengine.tools.cloudstorage.RetryParams;

Spécifier le bucket Cloud Storage

L'extrait de code suivant illustre comment l'utilisateur peut spécifier un nom de bucket lorsqu'il cherche à écrire un fichier dans un bucket :

function uploadFile() {
  var bucket = document.forms["putFile"]["bucket"].value;
  var filename = document.forms["putFile"]["fileName"].value;
  if (bucket == null || bucket == "" || filename == null || filename == "") {
    alert("Both Bucket and FileName are required");
    return false;
  } else {
    var postData = document.forms["putFile"]["content"].value;
    document.getElementById("content").value = null;

    var request = new XMLHttpRequest();
    request.open("POST", "/gcs/" + bucket + "/" + filename, false);
    request.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
    request.send(postData);
  }
}

Cet extrait de code ajoute l'élément /gcs/ requis pour les noms de bucket et de fichier fournis par l'utilisateur.

Écrire dans Cloud Storage

Pour écrire un fichier dans Cloud Storage :

@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  GcsFileOptions instance = GcsFileOptions.getDefaultInstance();
  GcsFilename fileName = getFileName(req);
  GcsOutputChannel outputChannel;
  outputChannel = gcsService.createOrReplace(fileName, instance);
  copy(req.getInputStream(), Channels.newOutputStream(outputChannel));
}

Cet exemple de code écrit un nouveau fichier dans Cloud Storage ou, s'il existe déjà un fichier portant le même nom, l'écrase. Cela est utile car une fois qu'un fichier est écrit dans Cloud Storage, vous ne pouvez plus le modifier. Pour modifier le fichier, vous devez modifier votre propre copie, puis écraser l’ancien fichier.

Lecture à partir de Cloud Storage

Pour lire un fichier à partir de Cloud Storage :

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  GcsFilename fileName = getFileName(req);
  if (SERVE_USING_BLOBSTORE_API) {
    BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
    BlobKey blobKey = blobstoreService.createGsBlobKey(
        "/gs/" + fileName.getBucketName() + "/" + fileName.getObjectName());
    blobstoreService.serve(blobKey, resp);
  } else {
    GcsInputChannel readChannel = gcsService.openPrefetchingReadChannel(fileName, 0, BUFFER_SIZE);
    copy(Channels.newInputStream(readChannel), resp.getOutputStream());
  }
}

Sur la ligne contenant gcsService.openPrefetchingReadChannel, notez l'utilisation de la prérécupération. Cette méthode consiste à mettre les données en mémoire tampon et à les récupérer avant que cela ne soit effectivement nécessaire, afin d'éviter tout blocage lors de l'appel en lecture.

Modifier des objets dans Cloud Storage

Pour modifier un objet existant dans Cloud Storage, écrivez l'objet modifié avec le même nom de fichier que l'original.

Supprimer des objets de Cloud Storage

La procédure suivante indique comment supprimer des objets dans une application qui stocke le nom d'objet Cloud Storage complet (nom de fichier) dans une base de données Cloud SQL :

final String bucket = "CLOUD-STORAGE-BUCKET"; // Cloud Storage bucket name
Map<String, String[]> userData = req.getParameterMap();

String[] image = userData.get("id"); // Grab the encoded image ID
String decodedId = new String(Base64.getUrlDecoder().decode(image[0])); // decode the image ID
int imageId = Integer.parseInt(decodedId);

// Grab the filename and build out a Cloud Storage filepath in preparation for deletion
try (PreparedStatement statementDeletePost = conn.prepareStatement(imageFilenameSql)) {
  statementDeletePost.setInt(1, imageId); // cast String to Int
  ResultSet rs = statementDeletePost.executeQuery(); // remove image record
  rs.next(); // move the cursor

  GcsFilename filename = new GcsFilename(bucket, rs.getString("filename"));
  if (gcsService.delete(filename)) {

    // Remove all records of image use in the blog
    // Use of foreign keys with cascading deletes will cause removal from blogpostImages table
    PreparedStatement statementDeleteImageRecord = conn.prepareStatement(deleteSql);
    statementDeleteImageRecord.setInt(1, imageId);
    statementDeleteImageRecord.executeUpdate();

    final String confirmation =
        "Image ID "
            + imageId
            + " has been deleted and record of its use in blog posts have been removed.";

    req.setAttribute("confirmation", confirmation);
    req.getRequestDispatcher("/confirm.jsp").forward(req, resp);
  } else {
    final String confirmation = "File marked for deletion does not exist.";

    req.setAttribute("confirmation", confirmation);
    req.getRequestDispatcher("/confirm.jsp").forward(req, resp);
  }
} catch (SQLException e) {
  throw new ServletException("SQL error", e);
}

Le code ci-dessus décode l'ID d'image encodé en Base64 et récupère le nom de fichier d'une image identifiée par son image_id dans la table images. Le nom de fichier est converti en nom de fichier Cloud Storage valide à l'aide de GcsFilename.

Le fichier est supprimé du bucket à l'aide de gcsService.delete. Enfin, l'enregistrement de son utilisation dans la table blogpostImage est supprimé.

Étape suivante