Scrivere funzioni Cloud

Cloud Functions può essere scritto in Node.js, Python, Go, Java, .NET, Ruby e PHP e i programmi vengono eseguiti in runtime specifici per i linguaggi. L'ambiente di esecuzione di Cloud Functions varia in base al runtime di tua scelta. Le pagine della panoramica del runtime forniscono ulteriori dettagli su ogni ambiente di runtime:

Provalo

Se non hai mai utilizzato Google Cloud, crea un account per valutare le prestazioni di Cloud Functions in scenari reali. I nuovi clienti ricevono anche 300 $ di crediti gratuiti per l'esecuzione, il test e il deployment dei carichi di lavoro.

Prova Cloud Functions gratuitamente

Tipi di funzioni di Cloud Functions

Esistono due diversi tipi di funzioni di Cloud Functions: funzioni HTTP e funzioni basate su eventi. Le funzioni basate su eventi possono essere di tipo funzioni in background o cloudEvent, a seconda del runtime di Cloud Functions per cui sono scritte.

Funzioni HTTP

Le chiamate HTTP vengono richiamate dalle richieste HTTP standard. Queste richieste HTTP attendono la risposta e il supporto della gestione di metodi di richiesta HTTP comuni, come GET, PUT, POST, DELETE e OPTIONS. Quando utilizzi Cloud Functions, viene eseguito automaticamente il provisioning di un certificato TLS, quindi tutte le funzioni HTTP possono essere richiamate tramite una connessione sicura.

Per maggiori dettagli, consulta la sezione Scrivere funzioni HTTP.

Esempio:

Node.js

const functions = require('@google-cloud/functions-framework');
const escapeHtml = require('escape-html');

/**
 * Responds to an HTTP request using data from the request body parsed according
 * to the "content-type" header.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
functions.http('helloHttp', (req, res) => {
  res.send(`Hello ${escapeHtml(req.query.name || req.body.name || 'World')}!`);
});

Python

from flask import escape
import functions_framework

@functions_framework.http
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

<?php

use Google\CloudFunctions\FunctionsFramework;
use Psr\Http\Message\ServerRequestInterface;

// Register the function with Functions Framework.
// This enables omitting the `FUNCTIONS_SIGNATURE_TYPE=http` environment
// variable when deploying. The `FUNCTION_TARGET` environment variable should
// match the first parameter.
FunctionsFramework::http('helloHttp', 'helloHttp');

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));
}

Funzioni basate su eventi

Cloud Functions utilizza funzioni basate su eventi per gestire gli eventi provenienti dalla tua infrastruttura Cloud, come i messaggi su un argomento Pub/Sub o le modifiche apportate a un bucket Cloud Storage.

Cloud Functions supporta due sottotipi di funzioni basate su eventi:

Come spiegato di seguito, il sottotipo utilizzato sarà regolato dal runtime scelto come target dalla funzione.

Funzioni in background

Le funzioni basate su eventi scritte per i runtime Node.js, Python, Go e Java sono note come funzioni in background. Per ulteriori informazioni, consulta la sezione Scrivere funzioni in background.

Esempio:

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 `@type` field maps to
                         `type.googleapis.com/google.pubsub.v1.PubsubMessage`.
                        The `data` field maps to the PubsubMessage data
                        in a base64-encoded string. The `attributes` field maps
                        to the PubsubMessage attributes if any is present.
         context (google.cloud.functions.Context): Metadata of triggering event
                        including `event_id` which maps to the PubsubMessage
                        messageId, `timestamp` which maps to the PubsubMessage
                        publishTime, `event_type` which maps to
                        `google.pubsub.topic.publish`, and `resource` which is
                        a dictionary that describes the service API endpoint
                        pubsub.googleapis.com, the triggering topic's name, and
                        the triggering event type
                        `type.googleapis.com/google.pubsub.v1.PubsubMessage`.
    Returns:
        None. The output is written to Cloud Logging.
    """
    import base64

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

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

Funzioni CloudEvent

Le funzioni basate su eventi scritte per i runtime .NET, Ruby e PHP sono note come funzioni CloudEvent. Per ulteriori dettagli, consulta la sezione Scrivere funzioni CloudEvent.

Esempio:

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 CloudEvents\V1\CloudEventInterface;
use Google\CloudFunctions\FunctionsFramework;

// Register the function with Functions Framework.
// This enables omitting the `FUNCTIONS_SIGNATURE_TYPE=cloudevent` environment
// variable when deploying. The `FUNCTION_TARGET` environment variable should
// match the first parameter.
FunctionsFramework::cloudEvent('helloworldPubsub', 'helloworldPubsub');

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

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

    $name = $pubSubData ? htmlspecialchars($pubSubData) : 'World';
    fwrite($log, "Hello, $name!" . PHP_EOL);
}

Strutturazione del codice sorgente

Affinché Cloud Functions possa trovare la definizione della tua funzione, ogni runtime ha requisiti di strutturazione per il codice sorgente. In generale, consigliamo di suddividere il codebase di grandi dimensioni e multifunzione in funzioni più piccole per ridurre la complessità del codice e il numero di dipendenze per funzione.

Node.js

Per i runtime Node.js, il codice sorgente della funzione deve essere esportato da un modulo Node.js, che Cloud Functions carica tramite una chiamata require(). Per determinare quale modulo caricare, Cloud Functions utilizza il campo main nel file package.json. Se il campo main non è specificato, Cloud Functions carica il codice da index.js.

Ad esempio, le seguenti configurazioni del codice sorgente sono valide:

  • Un singolo index.js situato nella directory radice della funzione che esporta una o più funzioni:

    .
    └── index.js
    
  • Un file index.js che importa il codice da un file foo.js e poi esporta una o più funzioni:

    .
    ├── index.js
    └── foo.js
    
  • Un file app.js che esporta una o più funzioni, con un file package.json che contiene "main": "app.js":

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

Python

Per il runtime Python, il punto di ingresso della tua funzione deve essere definito in un file di origine Python denominato main.py che si trova nella directory radice delle funzioni.

Ad esempio, le seguenti configurazioni del codice sorgente sono valide:

  • Un singolo file main.py nella directory radice della funzione che definisce una o più funzioni:

    .
    └── main.py
    
  • Un file main.py con un file requirements.txt che specifica le dipendenze:

    .
    ├── main.py
    └── requirements.txt
    
  • Un file main.py che importa il codice da una dipendenza locale:

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

Go

Per il runtime Go, la funzione deve trovarsi in un pacchetto Go nella directory principale del progetto. La tua funzione non può essere in package main. I sottopacchetti sono supportati solo quando si utilizzano i moduli Go.

Ad esempio, le seguenti configurazioni del codice sorgente sono valide:

  • Un pacchetto nella directory principale del progetto che esporta una o più funzioni:

    .
    └── function.go
    
  • Un pacchetto nella directory principale del progetto che consente di importare il codice da un sottopacchetto e di esportare una o più funzioni:

    .
    ├── function.go
    ├── go.mod
    └── shared/
        └── shared.go
    
  • Un pacchetto nella directory principale del progetto con una sottodirectory che definisce un elemento package main:

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

Java

Per il runtime Java, devi creare una directory di funzione di primo livello contenente una sottodirectory src/main/java/ e un file pom.xml. Consigliamo di inserire i test in una sottodirectory src/test/java/.

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

Se il file .java dichiara un pacchetto (ad esempio, functions), la gerarchia della directory sarà simile a questa:

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

Se la tua funzione viene definita in un pacchetto specifico come la maggior parte delle funzioni Java, deve essere specificata come parte del valore di --entry-point al tempo di deployment.

Raggruppamento di più funzioni

Se stai pensando di raggruppare più funzioni in un singolo progetto, tieni presente che ogni funzione potrebbe finire per condividere lo stesso insieme di dipendenze. Tuttavia, alcune funzioni potrebbero non richiedere tutte le dipendenze condivise.

Consigliamo di inserire ogni funzione nella propria directory di primo livello, come mostrato sopra, con la propria sottodirectory src/main/java e il proprio file pom.xml. Questo approccio riduce al minimo il numero di dipendenze necessarie per una determinata funzione, il che a sua volta riduce la quantità di memoria necessaria alla funzione.

Inoltre, funzioni separate semplificano la specifica di una singola funzione durante l'esecuzione di funzioni localmente tramite il framework delle funzioni. Questo può essere utile per lo sviluppo e i test locali.

C#

Per il runtime .NET, puoi strutturare i progetti come faresti con qualsiasi altro codice sorgente .NET.

Quando utilizzi i modelli per creare una funzione C#, vengono creati i seguenti file allo stesso livello del file system:

  • Un file di origine della funzione denominato Function.cs.
  • Un file di progetto con estensione .csproj.

Ad esempio:

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

Lo stesso pattern viene applicato quando utilizzi i modelli per creare le funzioni F# e Visual Basic. Per F#, il nome del file è Function.fs e il file del progetto ha l'estensione .fsproj. Per Visual Basic, il nome del file è CloudFunction.vb e il file del progetto ha l'estensione .vbproj.

Tuttavia, questi pattern sono solo convenzioni, non requisiti. Puoi strutturare il codice come faresti con qualsiasi altro progetto C#, F# o Visual Basic standard, che può contenere più file di origine, risorse e così via.

Inoltre, se implementi una sola funzione per progetto, puoi avviare l'hosting di tale funzione senza specificarne il nome, il che può semplificare lo sviluppo locale e il debug.

Ruby

Per il runtime Ruby, il punto di ingresso della tua funzione deve essere definito in un file di origine Ruby chiamato app.rb nella directory principale della funzione. Inoltre, le dipendenze, inclusa almeno la gemma functions_framework, devono essere elencate in un file Gemfile associato con Gemfile.lock, che si trova anche nella directory principale della funzione. La tua funzione può includere ulteriori file Ruby nella directory radice o nelle sottodirectory.

Ad esempio, le seguenti configurazioni del codice sorgente sono valide:

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

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

PHP

Per il runtime PHP, devi creare una directory di funzioni di primo livello con un file index.php contenente l'implementazione della funzione.

Il file composer.json dichiara le dipendenze. Quando esegui il comando composer require google/cloud-functions-framework come descritto in Specifica delle dipendenze in PHP, viene creata una directory vendor/ nella directory del codice della funzione che contiene le dipendenze. Il file composer.lock blocca le dipendenze per la funzione, il che significa che la funzione conserva un record esatto delle versioni delle dipendenze che sono state installate. Se dovessi eseguire di nuovo composer install in futuro, utilizzeresti le versioni delle dipendenze specificate nel file composer.lock.

Consigliamo di inserire i test in una directory test/.

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

Specifica delle dipendenze

Puoi specificare le dipendenze della funzione in modo idiomatico in base al tempo di esecuzione in uso. Per ulteriori dettagli, consulta la pagina appropriata:

Denominazione delle Funzioni Cloud

Cloud Functions ha una proprietà "name" che viene impostata al momento del deployment e, una volta impostata, non può essere modificata. Il nome di una funzione viene utilizzato come identificatore e deve essere univoco all'interno di un'area geografica. Per informazioni dettagliate, consulta la documentazione di implementazione.

Passaggi successivi

Provalo

Se non hai mai utilizzato Google Cloud, crea un account per valutare le prestazioni di Cloud Functions in scenari reali. I nuovi clienti ricevono anche 300 $ di crediti gratuiti per l'esecuzione, il test e il deployment dei carichi di lavoro.

Prova Cloud Functions gratuitamente