Utiliser Cloud Storage avec Node.js

Cette partie du tutoriel consacré à l'application Node.js Bookshelf décrit comment l'exemple d'application stocke des images dans Cloud Storage.

Ce tutoriel comporte plusieurs pages. Pour le suivre depuis le début et consulter les instructions relatives à la configuration, accédez à la page consacrée à l'application Bookshelf Node.js.

Créer un bucket Cloud Storage

Les instructions suivantes expliquent comment créer un bucket Cloud Storage. Les buckets sont les conteneurs de base dans lesquels sont stockées vos données dans Cloud Storage.

  1. Dans votre fenêtre de terminal, créez un bucket Cloud Storage. Remplacez YOUR_BUCKET_NAME par le nom de votre bucket :

    gsutil mb gs://YOUR_BUCKET_NAME
    
  2. Pour afficher les images importées dans l'application Bookshelf, définissez la liste de contrôle d'accès (LCA) par défaut du bucket sur public-read :

    gsutil defacl set public-read gs://YOUR_BUCKET_NAME
    

Configurer les paramètres

Dans le répertoire nodejs-getting-started/3-binary-data, créez un fichier config.json avec le contenu suivant :

{
  "CLOUD_BUCKET": "[YOUR_BUCKET_NAME]"
}

Remplacez [YOUR_BUCKET_NAME] par le nom de votre bucket Cloud Storage.

Installer les dépendances

Installez les dépendances dans le répertoire nodejs-getting-started/3-binary-data en utilisant npm :

npm install

Exécuter l'application sur votre machine locale

  1. Démarrez un serveur Web local à l'aide de npm :

    npm start
    
  2. Dans votre navigateur Web, saisissez l'adresse suivante :

    http://localhost:8080

Vous pouvez à présent consulter les pages Web de l'application, ajouter des livres avec des images de couverture, et modifier et supprimer des livres.

Déployer l'application dans l'environnement standard App Engine

  1. Déployez l'exemple d'application à partir du répertoire nodejs-getting-started/3-binary-data :

    gcloud app deploy
    
  2. Dans votre navigateur Web, saisissez cette adresse :

    https://[YOUR_PROJECT_ID].appspot.com
    

    Remplacez [YOUR_PROJECT_ID] par l'ID de votre projet Google Cloud.

Si vous mettez à jour votre application, vous déployez la version à jour en saisissant la même commande que celle utilisée pour le premier déploiement. Le nouveau déploiement crée une version de votre application, qui est alors définie comme version par défaut. Les anciennes versions de votre application sont conservées. Par défaut, l'environnement standard App Engine retombe à 0 instance en l'absence de trafic entrant vers une version. Les versions inutilisées ne devraient donc rien coûter. Cependant, toutes ces versions de l'application restent des ressources facturables.

Reportez-vous à la section Effectuer un nettoyage de la dernière étape de ce tutoriel pour plus d'informations sur le nettoyage des ressources facturables, y compris les versions d'application autres que celles par défaut.

Structure de l'application

Exemple de structure de données binaires

L'exemple d'application utilise Cloud Storage pour stocker des données binaires (des images, dans le cas présent) tout en recourant à une base de données structurée pour stocker les informations des livres (Datastore ou Cloud SQL).

Comprendre le code

Cette section décrit le code de l'application et son fonctionnement.

Gérer les importations des utilisateurs

Pour permettre aux utilisateurs d'importer des images, vous devez modifier le formulaire d'ajout ou de modification afin d'autoriser les transferts de fichiers. Définissez enctype sur multipart/form-data et ajoutez un champ pour l'image :

block content
  h3 #{action} book
  form(method="POST", enctype="multipart/form-data")
    .form-group
      label(for="title") Title
      input.form-control(type="text", name="title", id="title", value=book.title)
    .form-group
      label(for="author") Author
      input.form-control(type="text", name="author", id="author", value=book.author)
    .form-group
      label(for="publishedDate") Date Published
      input.form-control(type="text", name="publishedDate", id="publishedDate", value=book.publishedDate)
    .form-group
      label(for="description") Description
      input.form-control(type="text", name="description", id="description", value=book.description)
    .form-group
      label(for="image") Cover Image
      input.form-control(type="file", name="image", id="image")
    .form-group.hidden
      label(for="imageUrl") Cover Image URL
      input.form-control(type="text", name="imageUrl", id="imageUrl", value=book.imageUrl)
    button.btn.btn-success(type="submit") Save

L'application utilise le middleware Multer pour gérer l'analyse des requêtes de transfert de fichiers. Elle stocke temporairement les fichiers transférés dans la mémoire, ce qui leur permet d'être transférés directement vers Cloud Storage :

const Multer = require('multer');
const multer = Multer({
  storage: Multer.MemoryStorage,
  limits: {
    fileSize: 5 * 1024 * 1024, // no larger than 5mb
  },
});

Importer des fichiers dans Cloud Storage

Ensuite, l'application utilise le middleware sendUploadToGCS pour gérer le transfert des fichiers en mémoire dans Cloud Storage :

function sendUploadToGCS(req, res, next) {
  if (!req.file) {
    return next();
  }

  const gcsname = Date.now() + req.file.originalname;
  const file = bucket.file(gcsname);

  const stream = file.createWriteStream({
    metadata: {
      contentType: req.file.mimetype,
    },
    resumable: false,
  });

  stream.on('error', err => {
    req.file.cloudStorageError = err;
    next(err);
  });

  stream.on('finish', () => {
    req.file.cloudStorageObject = gcsname;
    file.makePublic().then(() => {
      req.file.cloudStoragePublicUrl = getPublicUrl(gcsname);
      next();
    });
  });

  stream.end(req.file.buffer);
}

Le middleware vérifie chaque fichier de la requête et le transfère vers Cloud Storage via un flux accessible en écriture standard. Une fois transférés, les fichiers sont rendus publics et le middleware définit la propriété cloudStoragePublicUrl sur le fichier. L'URL publique diffuse l'image directement depuis Cloud Storage :

function getPublicUrl(filename) {
  return `https://storage.googleapis.com/${CLOUD_BUCKET}/${filename}`;
}

L'application utilise ensuite la propriété suivante pour enregistrer l'URL publique de l'image dans la base de données :

router.post(
  '/add',
  images.multer.single('image'),
  images.sendUploadToGCS,
  (req, res, next) => {
    let data = req.body;

    // Was an image uploaded? If so, we'll use its public URL
    // in cloud storage.
    if (req.file && req.file.cloudStoragePublicUrl) {
      data.imageUrl = req.file.cloudStoragePublicUrl;
    }

    // Save the data to the database.
    model.create(data, (err, savedData) => {
      if (err) {
        next(err);
        return;
      }
      res.redirect(`${req.baseUrl}/${savedData.id}`);
    });
  }
);

Diffuser des images depuis Cloud Storage

Puisque l'application dispose de l'URL publique de l'image, elle peut facilement la diffuser, La diffusion directe depuis Cloud Storage s'avère utile, car les requêtes exploitent l'infrastructure de diffusion mondiale de Google. Ainsi, l'application n'a pas besoin de répondre aux requêtes concernant les images, ce qui libère des cycles de processeur pour d'autres requêtes.

block content
  h3 Book
    small

  .btn-group
    a(href=`/books/${book.id}/edit`, class='btn btn-primary btn-sm')
      i.glyphicon.glyphicon-edit
      span  Edit book
    a(href=`/books/${book.id}/delete`, class='btn btn-danger btn-sm')
      i.glyphicon.glyphicon-trash
      span  Delete book

  .media
    .media-left
      img(src=book.imageUrl || "http://placekitten.com/g/128/192")
    .media-body
      h4= book.title
        |  
        small= book.publishedDate
      h5 By #{book.author||'unknown'}
      p= book.description