Escribe Cloud Functions

Las funciones de Cloud Functions se pueden escribir en lenguaje de programación Node.js, Python, Go, Java, .NET, Ruby, and PHP y se ejecutan en entornos de ejecución específicos del lenguaje. El entorno de ejecución de Cloud Functions varía según el tipo de entorno de ejecución que elijas. En las páginas de descripción general de los entornos de ejecución, se proporcionan más detalles sobre cada uno:

Pruébalo tú mismo

Si es la primera vez que usas Google Cloud, crea una cuenta para evaluar el rendimiento de Cloud Functions en situaciones reales. Los clientes nuevos también obtienen $300 en créditos gratuitos para ejecutar, probar y, además, implementar cargas de trabajo.

Probar Cloud Functions gratis

Tipos de Cloud Functions

Existen estos dos tipos de funciones de Cloud Functions: funciones de HTTP y funciones controladas por eventos. Las funciones controladas por eventos pueden ser funciones en segundo plano o funciones de CloudEvent, según el entorno de ejecución de Cloud Functions para el que se escriban.

Funciones de HTTP

Invocas funciones de HTTP a partir de solicitudes HTTP estándar. Estas solicitudes HTTP esperan la respuesta y admiten el manejo de métodos de solicitud HTTP comunes, como GET, PUT, POST, DELETE y OPTIONS. Cuando usas Cloud Functions, se aprovisiona de forma automática un certificado TLS, por lo que todas las funciones de HTTP pueden invocarse a través de una conexión segura.

Para obtener detalles, consulta Escribe funciones de HTTP.

Ejemplo:

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

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

Funciones controladas por eventos

Cloud Functions usa funciones controladas por eventos para controlar los eventos de tu infraestructura de nube, como los mensajes en un tema de Pub/Sub o los cambios en un bucket de Cloud Storage.

Cloud Functions admite estos dos subtipos de funciones controladas por eventos:

Como se explica a continuación, el subtipo que uses se regirá por el entorno de ejecución al que se segmente tu función.

Funciones en segundo plano

Las funciones controladas por eventos escritas para los entornos de ejecución de Cloud Functions basados en Node.js, Python, Go y Java se conocen como funciones en segundo plano. Consulta la sección sobre cómo escribir funciones en segundo plano para obtener más detalles.

Ejemplo:

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

Funciones de CloudEvent

Las funciones controladas por eventos escritas para los entornos de ejecución basados en .NET, Ruby y PHP se conocen como funciones de CloudEvent. Consulta la sección sobre cómo escribir funciones de CloudEvent para obtener más detalles.

Ejemplo:

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']);

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

Estructura código fuente

Con el fin de que Cloud Functions encuentre la definición de tu función, cada entorno de ejecución tiene requisitos de estructuración para el código fuente: En general, recomendamos dividir las bases de código grandes y multifuncionales en bases más pequeñas por función a fin de reducir la complejidad del código y los recuentos de dependencias por función.

Node.js

Para los entornos de ejecución de Node.js, el código fuente de tu función debe exportarse desde un módulo de Node.js, que Cloud Functions carga mediante una llamada require(). Para determinar qué módulo cargar, Cloud Functions usa el campo main en tu archivo package.json. Si no se especifica el campo main, Cloud Functions carga el código desde index.js.

Por ejemplo, las siguientes configuraciones de código fuente son válidas:

  • Un único archivo index.js ubicado en el directorio raíz de la función que exporta una o más funciones:

    .
    └── index.js
    
  • Un archivo index.js que importa código desde un archivo foo.js y, luego, exporta una o más funciones:

    .
    ├── index.js
    └── foo.js
    
  • Un archivo app.js que exporta una o más funciones, con un archivo package.json que contiene "main": "app.js":

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

Python

Para el entorno de ejecución de Python, el punto de entrada de tu función debe estar definido en un archivo de origen de Python llamado main.py ubicado en el directorio raíz de las funciones.

Por ejemplo, las siguientes configuraciones de código fuente son válidas:

  • Un único archivo main.py en el directorio raíz de la función que define una o más funciones:

    .
    └── main.py
    
  • Un archivo main.py con un archivo requirements.txt que especifica dependencias:

    .
    ├── main.py
    └── requirements.txt
    
  • Un archivo main.py que importa código desde una dependencia local:

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

Go

Para el entorno de ejecución de Go, tu función debe estar en un paquete de Go en la raíz de tu proyecto. Tu función no puede estar en package main. Los subpaquetes solo se admiten cuando se usan módulos de Go.

Por ejemplo, las siguientes configuraciones de código fuente son válidas:

  • Un paquete en la raíz de tu proyecto que exporta una o más funciones:

    .
    └── function.go
    
  • Un paquete en la raíz de tu proyecto que importa código desde un subpaquete y exporta una o más funciones:

    .
    ├── function.go
    ├── go.mod
    └── shared/
        └── shared.go
    
  • Un paquete en la raíz de tu proyecto con un subdirectorio que define un package main:

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

Java

Para el entorno de ejecución de Java, debes crear un directorio de funciones de nivel superior que contenga un subdirectorio src/main/java/ y un archivo pom.xml. Recomendamos colocar pruebas en un subdirectorio src/test/java/.

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

Si el archivo .java declara un paquete (por ejemplo, functions), tu jerarquía de directorios se vería de la siguiente manera:

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

Si tu función se define en un paquete específico, como la mayoría de las funciones de Java, se debe especificar como parte de un valor --entry-point en el momento de la implementación.

Agrupa varias funciones

Si estás pensando en agrupar varias funciones en un solo proyecto, ten en cuenta que cada función puede terminar compartiendo el mismo conjunto de dependencias. Sin embargo, es posible que algunas de las funciones no necesiten todas las dependencias compartidas.

Recomendamos colocar cada función en su propio directorio de nivel superior, como se muestra antes, con su propio subdirectorio src/main/java y archivo pom.xml. Este enfoque minimiza la cantidad de dependencias necesarias para una función específica, lo que, a su vez, reduce la cantidad de memoria que necesita tu función.

Además, las funciones separadas facilitan la especificación de una sola función cuando se ejecutan funciones de forma local a través de Functions Framework. Esto puede ser útil para el desarrollo y las pruebas locales.

C#

Para el entorno de ejecución de .NET, puedes estructurar los proyectos como lo harías con cualquier otro código fuente de .NET.

Cuando usas las plantillas para crear una función de C#, se crean los siguientes archivos en el mismo nivel del sistema de archivos:

  • Un archivo de origen de función llamado Function.cs
  • Un archivo de proyecto con una extensión .csproj

Por ejemplo:

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

El mismo patrón se aplica cuando usas las plantillas para crear funciones de F# y Visual Basic. En el caso de F#, el nombre del archivo es Function.fs, y el archivo de proyecto tiene la extensión .fsproj. En el caso de Visual Basic, el nombre del archivo es CloudFunction.vb y el archivo de proyecto tiene la extensión .vbproj.

Sin embargo, estos patrones son solo convenciones, no requisitos. Puedes estructurar el código como lo harías con cualquier otro proyecto normal de C#, F# o Visual Basic, que puede contener varios archivos de origen, recursos, etcétera.

Además, si implementas una sola función por proyecto, puedes comenzar a alojar esa función sin especificar su nombre, lo que puede facilitar la depuración y el desarrollo local.

Ruby

Para el entorno de ejecución de Ruby, el punto de entrada de tu función debe definirse en un archivo de origen de Ruby llamado app.rb que se ubica en el directorio raíz de la función. Además, las dependencias, incluido al menos la gema functions_framework, deben aparecer en un Gemfile y el archivo Gemfile.lock asociado también ubicado en el directorio raíz de la función. Tu función puede incluir archivos Ruby adicionales en el directorio raíz o subdirectorios.

Por ejemplo, las siguientes configuraciones de código fuente son válidas:

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

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

PHP

Para el entorno de ejecución de PHP, debes crear un directorio de funciones de nivel superior que tenga un archivo index.php que contenga la implementación de tu función.

El archivo composer.json declara dependencias. Cuando ejecutas el comando composer require google/cloud-functions-framework como se describe en Especifica dependencias en PHP, se crea un directorio vendor/ en tu directorio de código de función que contiene las dependencias. El archivo composer.lock bloquea las dependencias de tu función, lo que significa que la función retiene un registro exacto de las versiones de dependencia que se instalaron. Si vuelves a ejecutar composer install en el futuro, usarás las versiones de dependencia especificadas en el archivo composer.lock.

Recomendamos colocar pruebas en un directorio test/.

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

Especifica dependencias

Especificarás las dependencias de tu función de manera idiomática según el entorno de ejecución que usas. Para obtener más detalles, consulta la página que corresponda entre las siguientes opciones:

Nombra Cloud Functions

Cloud Functions tiene una propiedad “name” que se configura en el momento de la implementación, y una vez configurada, no se puede cambiar. El nombre de una función se usa como su identificador y debe ser único dentro de una región. Consulta la documentación de implementación para obtener más detalles.

Próximos pasos

Pruébalo tú mismo

Si es la primera vez que usas Google Cloud, crea una cuenta para evaluar el rendimiento de Cloud Functions en situaciones reales. Los clientes nuevos también obtienen $300 en créditos gratuitos para ejecutar, probar y, además, implementar cargas de trabajo.

Probar Cloud Functions gratis