Écrire des fonctions Cloud Functions

Les fonctions Cloud peuvent être écrites dans les langages de programmation Node.js, Python, Go, Java, .NET, Ruby et PHP, et s'exécutent dans des environnements d'exécution propres à un langage. L'environnement d'exécution de Cloud Functions varie selon l'environnement d'exécution choisi. Les pages de présentation de l'environnement d'exécution fournissent des informations supplémentaires sur chaque environnement d'exécution :

Faites l'essai

Si vous débutez sur Google Cloud, créez un compte pour évaluer les performances de Cloud Functions en conditions réelles. Les nouveaux clients bénéficient également de 300 $ de crédits gratuits pour exécuter, tester et déployer des charges de travail.

Profiter d'un essai gratuit de Cloud Functions

Types de fonctions cloud

Il existe deux types distincts de fonctions Cloud Functions : les fonctions HTTP et les fonctions basées sur des événements. Les fonctions basées sur des événements peuvent être des fonctions d'arrière-plan ou des fonctions CloudEvent, selon l'environnement d'exécution Cloud Functions pour lesquelles elles sont écrites.

Fonctions HTTP

Les fonctions HTTP sont appelées à partir de requêtes HTTP standards. Ces requêtes HTTP attendent la réponse et sont compatibles avec les méthodes de requêtes HTTP courantes telles que GET, PUT, POST, DELETE et OPTIONS. Lors de l'utilisation des fonctions Cloud, un certificat TLS permettant d'appeler toutes les fonctions HTTP via une connexion sécurisée est automatiquement fourni.

Pour en savoir plus, consultez la section Écrire des fonctions HTTP.

Exemple :

Node.js

const escapeHtml = require('escape-html');

/**
 * HTTP Cloud Function.
 *
 * @param {Object} req Cloud Function request context.
 *                     More info: https://expressjs.com/en/api.html#req
 * @param {Object} res Cloud Function response context.
 *                     More info: https://expressjs.com/en/api.html#res
 */
exports.helloHttp = (req, res) => {
  res.send(`Hello ${escapeHtml(req.query.name || req.body.name || 'World')}!`);
};

Python

from flask import escape

def hello_http(request):
    """HTTP Cloud Function.
    Args:
        request (flask.Request): The request object.
        <https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
    """
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and 'name' in request_json:
        name = request_json['name']
    elif request_args and 'name' in request_args:
        name = request_args['name']
    else:
        name = 'World'
    return 'Hello {}!'.format(escape(name))

Go


// Package helloworld provides a set of Cloud Functions samples.
package helloworld

import (
	"encoding/json"
	"fmt"
	"html"
	"net/http"
)

// HelloHTTP is an HTTP Cloud Function with a request parameter.
func HelloHTTP(w http.ResponseWriter, r *http.Request) {
	var d struct {
		Name string `json:"name"`
	}
	if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	if d.Name == "" {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	fmt.Fprintf(w, "Hello, %s!", html.EscapeString(d.Name))
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;

public class HelloHttp implements HttpFunction {
  private static final Logger logger = Logger.getLogger(HelloHttp.class.getName());

  private static final Gson gson = new Gson();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Check URL parameters for "name" field
    // "world" is the default value
    String name = request.getFirstQueryParameter("name").orElse("world");

    // Parse JSON request and check for "name" field
    try {
      JsonElement requestParsed = gson.fromJson(request.getReader(), JsonElement.class);
      JsonObject requestJson = null;

      if (requestParsed != null && requestParsed.isJsonObject()) {
        requestJson = requestParsed.getAsJsonObject();
      }

      if (requestJson != null && requestJson.has("name")) {
        name = requestJson.get("name").getAsString();
      }
    } catch (JsonParseException e) {
      logger.severe("Error parsing JSON: " + e.getMessage());
    }

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Hello %s!", name);
  }
}

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;

namespace HelloHttp
{
    public class Function : IHttpFunction
    {
        private readonly ILogger _logger;

        public Function(ILogger<Function> logger) =>
            _logger = logger;

        public async Task HandleAsync(HttpContext context)
        {
            HttpRequest request = context.Request;
            // Check URL parameters for "name" field
            // "world" is the default value
            string name = ((string) request.Query["name"]) ?? "world";

            // If there's a body, parse it as JSON and check for "name" field.
            using TextReader reader = new StreamReader(request.Body);
            string text = await reader.ReadToEndAsync();
            if (text.Length > 0)
            {
                try
                {
                    JsonElement json = JsonSerializer.Deserialize<JsonElement>(text);
                    if (json.TryGetProperty("name", out JsonElement nameElement) &&
                        nameElement.ValueKind == JsonValueKind.String)
                    {
                        name = nameElement.GetString();
                    }
                }
                catch (JsonException parseException)
                {
                    _logger.LogError(parseException, "Error parsing JSON request");
                }
            }

            await context.Response.WriteAsync($"Hello {name}!");
        }
    }
}

Ruby

require "functions_framework"
require "cgi"
require "json"

FunctionsFramework.http "hello_http" do |request|
  # The request parameter is a Rack::Request object.
  # See https://www.rubydoc.info/gems/rack/Rack/Request
  name = request.params["name"] ||
         (JSON.parse(request.body.read)["name"] rescue nil) ||
         "World"
  # Return the response body as a string.
  # You can also return a Rack::Response object, a Rack response array, or
  # a hash which will be JSON-encoded into a response.
  "Hello #{CGI.escape_html name}!"
end

PHP


use Psr\Http\Message\ServerRequestInterface;

function helloHttp(ServerRequestInterface $request): string
{
    $name = 'World';
    $body = $request->getBody()->getContents();
    if (!empty($body)) {
        $json = json_decode($body, true);
        if (json_last_error() != JSON_ERROR_NONE) {
            throw new RuntimeException(sprintf(
                'Could not parse body: %s',
                json_last_error_msg()
            ));
        }
        $name = $json['name'] ?? $name;
    }
    $queryString = $request->getQueryParams();
    $name = $queryString['name'] ?? $name;

    return sprintf('Hello, %s!', htmlspecialchars($name));
}

Fonctions basées sur les événements

Cloud Functions utilise des fonctions basées sur des événements pour gérer les événements de votre infrastructure Cloud, tels que des messages sur un sujet Pub/Sub ou des modifications dans un bucket Cloud Storage.

Cloud Functions accepte deux sous-types de fonctions basées sur des événements :

Comme expliqué ci-dessous, le sous-type que vous utilisez sera régi par l'environnement d'exécution ciblé par votre fonction.

Fonctions d'arrière-plan

Des fonctions basées sur des événements écrites pour les environnements d'exécution Cloud Functions Node.js , Python , Go et Java sont appelésfonctions d'arrière-plan. Pour en savoir plus, consultez la section Écrire des fonctions d'arrière-plan.

Exemple :

Node.js

/**
 * Background Cloud Function to be triggered by Pub/Sub.
 * This function is exported by index.js, and executed when
 * the trigger topic receives a message.
 *
 * @param {object} message The Pub/Sub message.
 * @param {object} context The event metadata.
 */
exports.helloPubSub = (message, context) => {
  const name = message.data
    ? Buffer.from(message.data, 'base64').toString()
    : 'World';

  console.log(`Hello, ${name}!`);
};

Python

def hello_pubsub(event, context):
    """Background Cloud Function to be triggered by Pub/Sub.
    Args:
         event (dict):  The dictionary with data specific to this type of
         event. The `data` field contains the PubsubMessage message. The
         `attributes` field will contain custom attributes if there are any.
         context (google.cloud.functions.Context): The Cloud Functions event
         metadata. The `event_id` field contains the Pub/Sub message ID. The
         `timestamp` field contains the publish time.
    """
    import base64

    print("""This Function was triggered by messageId {} published at {}
    """.format(context.event_id, context.timestamp))

    if 'data' in event:
        name = base64.b64decode(event['data']).decode('utf-8')
    else:
        name = 'World'
    print('Hello {}!'.format(name))

Go


// Package helloworld provides a set of Cloud Functions samples.
package helloworld

import (
	"context"
	"log"
)

// PubSubMessage is the payload of a Pub/Sub event.
// See the documentation for more details:
// https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
type PubSubMessage struct {
	Data []byte `json:"data"`
}

// HelloPubSub consumes a Pub/Sub message.
func HelloPubSub(ctx context.Context, m PubSubMessage) error {
	name := string(m.Data) // Automatically decoded from base64.
	if name == "" {
		name = "World"
	}
	log.Printf("Hello, %s!", name)
	return nil
}

Java


import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.Context;
import com.google.events.cloud.pubsub.v1.Message;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloPubSub implements BackgroundFunction<Message> {
  private static final Logger logger = Logger.getLogger(HelloPubSub.class.getName());

  @Override
  public void accept(Message message, Context context) {
    String name = "world";
    if (message != null && message.getData() != null) {
      name = new String(
          Base64.getDecoder().decode(message.getData().getBytes(StandardCharsets.UTF_8)),
          StandardCharsets.UTF_8);
    }
    logger.info(String.format("Hello %s!", name));
    return;
  }
}

Fonctions CloudEvent

Les fonctions basées sur des événements qui sont écrites pour les environnements d'exécution .NET, Ruby et PHP sont appelées des fonctions CloudEvent. Pour en savoir plus, consultez la page Écrire des fonctions CloudEvent.

Exemple :

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.PubSub.V1;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace HelloPubSub
{
    public class Function : ICloudEventFunction<MessagePublishedData>
    {
        private readonly ILogger _logger;

        public Function(ILogger<Function> logger) =>
            _logger = logger;

        public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken)
        {
            string nameFromMessage = data.Message?.TextData;
            string name = string.IsNullOrEmpty(nameFromMessage) ? "world" : nameFromMessage;
            _logger.LogInformation("Hello {name}", name);
            return Task.CompletedTask;
        }
    }
}

Ruby

require "functions_framework"
require "base64"

FunctionsFramework.cloud_event "hello_pubsub" do |event|
  # The event parameter is a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  name = Base64.decode64 event.data["message"]["data"] rescue "World"

  # A cloud_event function does not return a response, but you can log messages
  # or cause side effects such as sending additional events.
  logger.info "Hello, #{name}!"
end

PHP


use Google\CloudFunctions\CloudEvent;

function helloworldPubsub(CloudEvent $event): void
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');

    $cloudEventData = $event->getData();
    $pubSubData = base64_decode($cloudEventData['message']['data']);

    if ($pubSubData) {
        $name = htmlspecialchars($pubSubData);
    } else {
        $name = 'World';
    }

    $result = 'Hello, ' . $name . '!';
    fwrite($log, $result . PHP_EOL);
}

Structurer le code source

Pour que Cloud Functions trouve la définition de votre fonction, chaque environnement d'exécution a certaines exigences concernant la structuration de votre code source. En général, nous vous recommandons de diviser les codebases multifonctions volumineux en plusieurs petits codebases par fonction pour réduire la complexité du code et le nombre de dépendances par fonction.

Node.js

Dans l'environnement d'exécution Node.js, le code source de votre fonction doit être exporté depuis un module Node.js, que Cloud Functions charge à l'aide d'un appel require(). Pour déterminer le module à charger, Cloud Functions utilise le champ main de votre fichier package.json. Si le champ main n'est pas spécifié, Cloud Functions charge le code à partir de index.js.

Par exemple, les configurations de code source suivantes sont valides :

  • Un seul fichier index.js situé dans le répertoire racine de votre fonction qui exporte une ou plusieurs fonctions :

    .
    └── index.js
    
  • Un fichier index.js qui importe le code à partir d'un fichier foo.js, puis exporte une ou plusieurs fonctions :

    .
    ├── index.js
    └── foo.js
    
  • Un fichier app.js qui exporte une ou plusieurs fonctions, accompagné d'un fichier package.json contenant "main": "app.js" :

    .
    ├── app.js
    └── package.json
    

Python

Dans l'environnement d'exécution Python, le point d'entrée de votre fonction doit être défini dans un fichier source Python situé à la racine de votre projet et nommé main.py.

Par exemple, les configurations de code source suivantes sont valides :

  • Un seul fichier main.py dans le répertoire racine de votre fonction, qui définit une ou plusieurs fonctions :

    .
    └── main.py
    
  • Un fichier main.py accompagné d'un fichier requirements.txt qui spécifie des dépendances :

    .
    ├── main.py
    └── requirements.txt
    
  • Un fichier main.py qui importe le code à partir d'une dépendance locale :

    .
    ├── main.py
    └── mylocalpackage/
        ├── __init__.py
        └── myscript.py
    

Go

Dans l'environnement d'exécution Go, votre fonction doit se trouver dans un package Go à la racine de votre projet. Votre fonction ne peut pas se trouver dans package main. Les sous-packages ne sont compatibles que lors de l'utilisation de modules Go.

Par exemple, les configurations de code source suivantes sont valides :

  • Un package à la racine du projet qui exporte une ou plusieurs fonctions :

    .
    └── function.go
    
  • Un package à la racine du projet qui importe le code d'un sous-package et exporte une ou plusieurs fonctions :

    .
    ├── function.go
    ├── go.mod
    └── shared/
        └── shared.go
    
  • Un package à la racine de votre projet avec un sous-répertoire qui définit un package main :

    .
    ├── cmd/
    |   └── main.go
    └── function.go
    

Java

Dans l'environnement d'exécution Java, vous devez créer un répertoire de fonction de premier niveau contenant un sous-répertoire src/main/java/ et un fichier pom.xml. Nous vous recommandons de placer les tests dans un sous-répertoire src/test/java/.

.
├── pom.xml
└── src/
    ├── main/
    |   └── java/
    |       └── MyFunction.java
    └── test
        └── java/
            └── MyFunctionTest.java

Si votre fichier .java déclare un package (par exemple, functions), votre hiérarchie de répertoires se présente comme suit :

.
├── pom.xml
└── src/
    ├── main/
    |   └── java/
    |       └── functions/
    |               └── MyFunction.java
    └── test/
        └── java/
                └── functions/
                    └── MyFunctionTest.java

Si votre fonction est définie dans un package spécifique comme la plupart des fonctions Java, il doit être spécifié dans votre valeur --entry-point au moment du déploiement.

Regrouper plusieurs fonctions

Si vous envisagez de regrouper plusieurs fonctions dans un seul projet, sachez qu'il est possible qu'elles partagent le même ensemble de dépendances. Toutefois, certaines fonctions ne nécessitent pas l'ensemble des dépendances partagées.

Comme indiqué ci-dessus, nous vous recommandons de placer chaque fonction dans son propre répertoire de premier niveau, avec un sous-répertoire src/main/java et un fichier pom.xml dédiés. Cette approche minimise le nombre de dépendances requises par une fonction particulière et réduit la quantité de mémoire dont votre fonction a besoin.

En outre, des fonctions distinctes facilitent la spécification d'une fonction lors de l'exécution locale via le framework des fonctions. Cela peut être utile lors du développement et des tests locaux.

C#

Pour l'environnement d'exécution .NET, vous pouvez structurer vos projets comme vous le feriez pour tout autre code source .NET.

Lorsque vous utilisez des modèles pour créer une fonction C#, les fichiers suivants sont créés au même niveau dans le système de fichiers :

  • Un fichier source de fonction appelé Function.cs
  • Un fichier de projet avec l'extension .csproj

Exemple :

.
├── Function.cs
└── HelloHttp.csproj

Le même schéma s'applique lorsque vous utilisez des modèles pour créer des fonctions F# et Visual Basic. Pour F#, le nom du fichier est Function.fs et le fichier de projet porte l'extension .fsproj. Pour Visual Basic, le nom du fichier est CloudFunction.vb et le fichier de projet porte l'extension .vbproj.

Toutefois, ces schémas ne sont que des conventions, et non des exigences. Vous pouvez structurer votre code comme vous le feriez avec tout autre projet standard C#, F# ou Visual Basic, qui peut contenir plusieurs fichiers sources, des ressources, etc.

En outre, si vous ne mettez en œuvre qu'une seule fonction par projet, vous pouvez commencer à l'héberger sans spécifier son nom, ce qui peut faciliter le développement local et le débogage.

Ruby

Dans l'environnement d'exécution Ruby, le point d'entrée de votre fonction doit être défini dans un fichier source Ruby nommé app.rb situé dans le répertoire racine de la fonction. En outre, les dépendances, y compris au moins le gem functions_framework, doivent être répertoriées dans un fichier Gemfile et dans le fichier Gemfile.lock associé situé également dans le répertoire racine de la fonction. Votre fonction peut inclure des fichiers Ruby supplémentaires dans le répertoire racine ou les sous-répertoires.

Par exemple, les configurations de code source suivantes sont valides :

.
├── Gemfile
├── Gemfile.lock
└── app.rb

.
├── Gemfile
├── Gemfile.lock
├── app.rb
└── lib/
    └── my_class.rb

PHP

Pour l'environnement d'exécution PHP, vous devez créer un répertoire de fonction de premier niveau contenant un fichier index.php, qui stocke la mise en œuvre de votre fonction.

Le fichier composer.json déclare les dépendances. Lorsque vous exécutez la commande composer require google/cloud-functions-framework, comme décrit dans la section Spécifier des dépendances en PHP, elle crée un répertoire vendor/ dans le répertoire de votre code de fonction, qui contient les dépendances. Le fichier composer.lock verrouille les dépendances de votre fonction, ce qui signifie que votre fonction conserve un enregistrement fidèle des versions de dépendance qui ont été installées. Si vous deviez à nouveau exécuter composer install à l'avenir, vous utiliserez alors les versions de dépendance spécifiées dans le fichier composer.lock.

Nous vous recommandons de placer les tests dans un répertoire test/.

.
├── index.php
├── composer.json
├── composer.lock
├── vendor/
└── test/

Spécifier des dépendances

Vous spécifiez les dépendances de votre fonction de manière idiomatique en fonction de l'environnement d'exécution que vous utilisez. Pour plus d'informations, consultez la page appropriée :

Nommer les fonctions cloud

Les fonctions Cloud Functions ont une propriété "name" définie au moment du déploiement et, une fois définie, celle-ci ne peut plus être modifiée. Le nom d'une fonction est utilisé comme identifiant et doit être unique dans une région. Consultez la documentation sur le déploiement pour en savoir plus.

Étapes suivantes

Faites l'essai

Si vous débutez sur Google Cloud, créez un compte pour évaluer les performances de Cloud Functions en conditions réelles. Les nouveaux clients bénéficient également de 300 $ de crédits gratuits pour exécuter, tester et déployer des charges de travail.

Profiter d'un essai gratuit de Cloud Functions