Funciones de HTTP

Utiliza funciones de HTTP cuando desees invocar tu función mediante solicitudes de HTTP(s). Para permitir la semántica de HTTP, las firmas de las funciones de HTTP aceptan argumentos específicos de HTTP.

Ejemplo de uso

En el siguiente ejemplo, se muestra cómo procesar una solicitud HTTP POST que contiene un parámetro name:

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.
            <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
        Returns:
            The response text, or any set of values that can be turned into a
            Response object using `make_response`
            <http://flask.pocoo.org/docs/1.0/api/#flask.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))
    }
    

El siguiente comando muestra cómo llamar a la función y pasarle un parámetro con curl:

    curl -X POST HTTP_TRIGGER_ENDPOINT -H "Content-Type:application/json"  -d '{"name":"Jane"}'
    

donde HTTP_TRIGGER_ENDPOINT es la URL de la función, que se obtiene cuando esta se implementa. Para obtener más información, consulta Activadores de HTTP.

Marcos de trabajo de HTTP

Para controlar HTTP, Cloud Functions utiliza un marco de trabajo de HTTP particular en cada entorno de ejecución:

Entorno de ejecución Marco de trabajo de HTTP
Node.js (6, 8, y 10) Express 4.17.1
Python Flask 1.0.2
Go Interfaz http.HandlerFunc estándar

Analiza solicitudes HTTP

El siguiente ejemplo muestra cómo leer las solicitudes HTTP en varios formatos:

Node.js

En Node.js, el cuerpo de la solicitud se analiza automáticamente según el encabezado content-type y se encuentra disponible mediante los argumentos de la función de HTTP.

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.
     */
    exports.helloContent = (req, res) => {
      let name;

      switch (req.get('content-type')) {
        // '{"name":"John"}'
        case 'application/json':
          ({name} = req.body);
          break;

        // 'John', stored in a Buffer
        case 'application/octet-stream':
          name = req.body.toString(); // Convert buffer to a string
          break;

        // 'John'
        case 'text/plain':
          name = req.body;
          break;

        // 'name=John' in the body of a POST request (not the URL)
        case 'application/x-www-form-urlencoded':
          ({name} = req.body);
          break;
      }

      res.status(200).send(`Hello ${escapeHtml(name || 'World')}!`);
    };

Python

from flask import escape

    def hello_content(request):
        """ Responds to an HTTP request using data from the request body parsed
        according to the "content-type" header.
        Args:
            request (flask.Request): The request object.
            <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
        Returns:
            The response text, or any set of values that can be turned into a
            Response object using `make_response`
            <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
        """
        content_type = request.headers['content-type']
        if content_type == 'application/json':
            request_json = request.get_json(silent=True)
            if request_json and 'name' in request_json:
                name = request_json['name']
            else:
                raise ValueError("JSON is invalid, or missing a 'name' property")
        elif content_type == 'application/octet-stream':
            name = request.data
        elif content_type == 'text/plain':
            name = request.data
        elif content_type == 'application/x-www-form-urlencoded':
            name = request.form.get('name')
        else:
            raise ValueError("Unknown content type: {}".format(content_type))
        return 'Hello {}!'.format(escape(name))

Go


    // Package http provides a set of HTTP Cloud Functions samples.
    package http

    import (
    	"encoding/json"
    	"fmt"
    	"html"
    	"io/ioutil"
    	"log"
    	"net/http"
    )

    // HelloContentType is an HTTP Cloud function.
    // It uses the Content-Type header to identify the request payload format.
    func HelloContentType(w http.ResponseWriter, r *http.Request) {
    	var name string

    	switch r.Header.Get("Content-Type") {
    	case "application/json":
    		var d struct {
    			Name string `json:"name"`
    		}
    		err := json.NewDecoder(r.Body).Decode(&d)
    		if err != nil {
    			log.Printf("error parsing application/json: %v", err)
    		} else {
    			name = d.Name
    		}
    	case "application/octet-stream":
    		body, err := ioutil.ReadAll(r.Body)
    		if err != nil {
    			log.Printf("error parsing application/octet-stream: %v", err)
    		} else {
    			name = string(body)
    		}
    	case "text/plain":
    		body, err := ioutil.ReadAll(r.Body)
    		if err != nil {
    			log.Printf("error parsing text/plain: %v", err)
    		} else {
    			name = string(body)
    		}
    	case "application/x-www-form-urlencoded":
    		if err := r.ParseForm(); err != nil {
    			log.Printf("error parsing application/x-www-form-urlencoded: %v", err)
    		} else {
    			name = r.FormValue("name")
    		}
    	}

    	if name == "" {
    		name = "World"
    	}

    	fmt.Fprintf(w, "Hello, %s!", html.EscapeString(name))
    }
    

Administra solicitudes de CORS

El uso compartido de recursos multiorigen (CORS) es una forma de permitir que las aplicaciones que se ejecutan en un dominio accedan al contenido de otro dominio, por ejemplo, cuando yourdomain.com realiza solicitudes a region-project.cloudfunctions.net/yourfunction.

Si CORS no está configurado correctamente, es probable que recibas errores similares a los siguientes:

    XMLHttpRequest cannot load https://region-project.cloudfunctions.net/function.
    No 'Access-Control-Allow-Origin' header is present on the requested resource.
    Origin 'http://yourdomain.com' is therefore not allowed access.
    

CORS consiste en dos solicitudes:

  • Una solicitud de comprobación previa de OPTIONS
  • Una solicitud principal que sigue a la solicitud de OPTIONS

La solicitud de comprobación previa contiene encabezados que indican qué método (Access-Control-Request-Method) y qué encabezados adicionales (Access-Control-Request-Headers) se enviarán en la solicitud principal, así como el origen de la solicitud principal (Origin).

Para administrar una solicitud de comprobación previa, debes establecer los encabezados Access-Control-Allow-* apropiados a fin de que coincidan con las solicitudes que deseas aceptar:

Node.js

/**
     * HTTP function that supports CORS requests.
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.corsEnabledFunction = (req, res) => {
      // Set CORS headers for preflight requests
      // Allows GETs from any origin with the Content-Type header
      // and caches preflight response for 3600s

      res.set('Access-Control-Allow-Origin', '*');

      if (req.method === 'OPTIONS') {
        // Send response to OPTIONS requests
        res.set('Access-Control-Allow-Methods', 'GET');
        res.set('Access-Control-Allow-Headers', 'Content-Type');
        res.set('Access-Control-Max-Age', '3600');
        res.status(204).send('');
      } else {
        res.send('Hello World!');
      }
    };

Python

def cors_enabled_function(request):
        # For more information about CORS and CORS preflight requests, see
        # https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
        # for more information.

        # Set CORS headers for the preflight request
        if request.method == 'OPTIONS':
            # Allows GET requests from any origin with the Content-Type
            # header and caches preflight response for an 3600s
            headers = {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'GET',
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Max-Age': '3600'
            }

            return ('', 204, headers)

        # Set CORS headers for the main request
        headers = {
            'Access-Control-Allow-Origin': '*'
        }

        return ('Hello World!', 200, headers)

Go


    // Package http provides a set of HTTP Cloud Functions samples.
    package http

    import (
    	"fmt"
    	"net/http"
    )

    // CORSEnabledFunction is an example of setting CORS headers.
    // For more information about CORS and CORS preflight requests, see
    // https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request.
    func CORSEnabledFunction(w http.ResponseWriter, r *http.Request) {
    	// Set CORS headers for the preflight request
    	if r.Method == http.MethodOptions {
    		w.Header().Set("Access-Control-Allow-Origin", "*")
    		w.Header().Set("Access-Control-Allow-Methods", "POST")
    		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    		w.Header().Set("Access-Control-Max-Age", "3600")
    		w.WriteHeader(http.StatusNoContent)
    		return
    	}
    	// Set CORS headers for the main request.
    	w.Header().Set("Access-Control-Allow-Origin", "*")
    	fmt.Fprint(w, "Hello, World!")
    }
    

De manera alternativa, puedes usar una biblioteca de terceros para que administre CORS por ti.

Autenticación y CORS

Si planeas enviar una solicitud con un encabezado Authorization, debes seguir estos pasos:

  1. Agregar el encabezado Authorization a Access-Control-Allow-Headers.
  2. Configurar el encabezado Access-Control-Allow-Credentials como true.
  3. Establecer un origen específico en Access-Control-Allow-Origin (no se aceptan comodines).

Node.js

/**
     * HTTP function that supports CORS requests with credentials.
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.corsEnabledFunctionAuth = (req, res) => {
      // Set CORS headers for preflight requests
      // Allows GETs from origin https://mydomain.com with Authorization header

      res.set('Access-Control-Allow-Origin', 'https://mydomain.com');
      res.set('Access-Control-Allow-Credentials', 'true');

      if (req.method === 'OPTIONS') {
        // Send response to OPTIONS requests
        res.set('Access-Control-Allow-Methods', 'GET');
        res.set('Access-Control-Allow-Headers', 'Authorization');
        res.set('Access-Control-Max-Age', '3600');
        res.status(204).send('');
      } else {
        res.send('Hello World!');
      }
    };

Python

def cors_enabled_function_auth(request):
        # For more information about CORS and CORS preflight requests, see
        # https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
        # for more information.

        # Set CORS headers for preflight requests
        if request.method == 'OPTIONS':
            # Allows GET requests from origin https://mydomain.com with
            # Authorization header
            headers = {
                'Access-Control-Allow-Origin': 'https://mydomain.com',
                'Access-Control-Allow-Methods': 'GET',
                'Access-Control-Allow-Headers': 'Authorization',
                'Access-Control-Max-Age': '3600',
                'Access-Control-Allow-Credentials': 'true'
            }
            return ('', 204, headers)

        # Set CORS headers for main requests
        headers = {
            'Access-Control-Allow-Origin': 'https://mydomain.com',
            'Access-Control-Allow-Credentials': 'true'
        }

        return ('Hello World!', 200, headers)

Go


    // Package http provides a set of HTTP Cloud Functions samples.
    package http

    import (
    	"fmt"
    	"net/http"
    )

    // CORSEnabledFunctionAuth is an example of setting CORS headers with
    // authentication enabled.
    // For more information about CORS and CORS preflight requests, see
    // https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request.
    func CORSEnabledFunctionAuth(w http.ResponseWriter, r *http.Request) {
    	// Set CORS headers for the preflight request
    	if r.Method == http.MethodOptions {
    		w.Header().Set("Access-Control-Allow-Credentials", "true")
    		w.Header().Set("Access-Control-Allow-Headers", "Authorization")
    		w.Header().Set("Access-Control-Allow-Methods", "POST")
    		w.Header().Set("Access-Control-Allow-Origin", "https://example.com")
    		w.Header().Set("Access-Control-Max-Age", "3600")
    		w.WriteHeader(http.StatusNoContent)
    		return
    	}
    	// Set CORS headers for the main request.
    	w.Header().Set("Access-Control-Allow-Credentials", "true")
    	w.Header().Set("Access-Control-Allow-Origin", "https://example.com")
    	fmt.Fprint(w, "Hello World!")
    }
    

Aloja en el mismo dominio

En lugar de implementar CORS, podrías alojar tu sitio web y tus funciones en el mismo dominio. Puesto que las solicitudes ahora provendrían del mismo origen, CORS no se aplicará. Esta acción simplifica tu código de manera considerable.

La manera más sencilla de hacerlo es mediante la integración de Firebase Hosting a Google Cloud Functions.

Usa Cloud Endpoints para administrar CORS

Puedes implementar un proxy de Cloud Endpoints y habilitar CORS.

Si deseas contar con capacidades de autenticación, también puedes habilitar la validación del token de ID de Google, lo que validará estos mismos tokens de autenticación.

Controla métodos de HTTP

Las funciones de HTTP aceptan todos los métodos de HTTP. En el siguiente ejemplo, se muestra cómo realizar diferentes acciones según el método de HTTP recibido (por ejemplo, GET y PUT):

Node.js

/**
     * Responds to a GET request with "Hello World!". Forbids a PUT request.
     *
     * @example
     * gcloud functions call helloHttp
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.helloHttp = (req, res) => {
      switch (req.method) {
        case 'GET':
          res.status(200).send('Hello World!');
          break;
        case 'PUT':
          res.status(403).send('Forbidden!');
          break;
        default:
          res.status(405).send({error: 'Something blew up!'});
          break;
      }
    };

Python

def hello_method(request):
        """ Responds to a GET request with "Hello world!". Forbids a PUT request.
        Args:
            request (flask.Request): The request object.
            <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
        Returns:
            The response text, or any set of values that can be turned into a
             Response object using `make_response`
            <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
        """
        from flask import abort

        if request.method == 'GET':
            return 'Hello World!'
        elif request.method == 'PUT':
            return abort(403)
        else:
            return abort(405)

Go


    // Package http provides a set of HTTP Cloud Functions samples.
    package http

    import (
    	"fmt"
    	"net/http"
    )

    // HelloHTTPMethod is an HTTP Cloud function.
    // It uses the request method to differentiate the response.
    func HelloHTTPMethod(w http.ResponseWriter, r *http.Request) {
    	switch r.Method {
    	case http.MethodGet:
    		fmt.Fprint(w, "Hello World!")
    	case http.MethodPut:
    		http.Error(w, "403 - Forbidden", http.StatusForbidden)
    	default:
    		http.Error(w, "405 - Method Not Allowed", http.StatusMethodNotAllowed)
    	}
    }
    

Administra tipos de contenido

Para Node.js, Cloud Functions analiza los tipos de contenido del cuerpo de la solicitud de application/json y application/x-www-form-urlencoded como se muestra arriba. Los tipos de contenido de texto sin formato (text/plain) se pasan como strings con UTF-8 como codificación predeterminada (o una codificación personalizada proporcionada en el encabezado content-type).

Se puede acceder a otros tipos de contenido mediante la inspección del argumento de tu función de HTTP. Los métodos para hacerlo varían según el lenguaje.

En el siguiente ejemplo, se maneja una solicitud con un tipo de contenido de text/xml:

Node.js

La propiedad rawBody contiene bytes sin analizar del cuerpo de la solicitud.

/**
     * Parses a document of type 'text/xml'
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.parseXML = (req, res) => {
      // Convert the request to a Buffer and a string
      // Use whichever one is accepted by your XML parser
      const data = req.rawBody;
      const xmlData = data.toString();

      const {parseString} = require('xml2js');

      parseString(xmlData, (err, result) => {
        if (err) {
          console.error(err);
          res.status(500).end();
          return;
        }
        res.send(result);
      });
    };

Python

import json
    import xmltodict

    def parse_xml(request):
        """ Parses a document of type 'text/xml'
        Args:
            request (flask.Request): The request object.
        Returns:
            The response text, or any set of values that can be turned into a
             Response object using `make_response`
            <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
        """
        data = xmltodict.parse(request.data)
        return json.dumps(data, indent=2)

Go


    // Package http provides a set of HTTP Cloud Functions samples.
    package http

    import (
    	"encoding/xml"
    	"fmt"
    	"html"
    	"io/ioutil"
    	"net/http"
    )

    // ParseXML is an example of parsing a text/xml request.
    func ParseXML(w http.ResponseWriter, r *http.Request) {
    	var d struct {
    		Name string
    	}
    	b, err := ioutil.ReadAll(r.Body)
    	if err != nil {
    		http.Error(w, "Could not read request", http.StatusBadRequest)
    	}
    	if err := xml.Unmarshal(b, &d); err != nil {
    		http.Error(w, "Could not parse request", http.StatusBadRequest)
    	}
    	if d.Name == "" {
    		d.Name = "World"
    	}
    	fmt.Fprintf(w, "Hello, %v!", html.EscapeString(d.Name))
    }
    

Datos multiparte

En el siguiente ejemplo, se muestra cómo procesar datos con un tipo de contenido multipart/form-data. Según el lenguaje que hayas elegido, es posible que debas usar una biblioteca de análisis.

Node.js

/**
     * Parses a 'multipart/form-data' upload request
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    const path = require('path');
    const os = require('os');
    const fs = require('fs');

    // Node.js doesn't have a built-in multipart/form-data parsing library.
    // Instead, we can use the 'busboy' library from NPM to parse these requests.
    const Busboy = require('busboy');

    exports.uploadFile = (req, res) => {
      if (req.method !== 'POST') {
        // Return a "method not allowed" error
        return res.status(405).end();
      }
      const busboy = new Busboy({headers: req.headers});
      const tmpdir = os.tmpdir();

      // This object will accumulate all the fields, keyed by their name
      const fields = {};

      // This object will accumulate all the uploaded files, keyed by their name.
      const uploads = {};

      // This code will process each non-file field in the form.
      busboy.on('field', (fieldname, val) => {
        // TODO(developer): Process submitted field values here
        console.log(`Processed field ${fieldname}: ${val}.`);
        fields[fieldname] = val;
      });

      const fileWrites = [];

      // This code will process each file uploaded.
      busboy.on('file', (fieldname, file, filename) => {
        // Note: os.tmpdir() points to an in-memory file system on GCF
        // Thus, any files in it must fit in the instance's memory.
        console.log(`Processed file ${filename}`);
        const filepath = path.join(tmpdir, filename);
        uploads[fieldname] = filepath;

        const writeStream = fs.createWriteStream(filepath);
        file.pipe(writeStream);

        // File was processed by Busboy; wait for it to be written to disk.
        const promise = new Promise((resolve, reject) => {
          file.on('end', () => {
            writeStream.end();
          });
          writeStream.on('finish', resolve);
          writeStream.on('error', reject);
        });
        fileWrites.push(promise);
      });

      // Triggered once all uploaded files are processed by Busboy.
      // We still need to wait for the disk writes (saves) to complete.
      busboy.on('finish', async () => {
        await Promise.all(fileWrites);

        // TODO(developer): Process saved files here
        for (const file of uploads) {
          fs.unlinkSync(file);
        }
        res.send();
      });

      busboy.end(req.rawBody);
    };

Python

import os
    import tempfile
    from werkzeug.utils import secure_filename

    # Helper function that computes the filepath to save files to
    def get_file_path(filename):
        # Note: tempfile.gettempdir() points to an in-memory file system
        # on GCF. Thus, any files in it must fit in the instance's memory.
        file_name = secure_filename(filename)
        return os.path.join(tempfile.gettempdir(), file_name)

    def parse_multipart(request):
        """ Parses a 'multipart/form-data' upload request
        Args:
            request (flask.Request): The request object.
        Returns:
            The response text, or any set of values that can be turned into a
             Response object using `make_response`
            <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
        """

        # This code will process each non-file field in the form
        fields = {}
        data = request.form.to_dict()
        for field in data:
            fields[field] = data[field]
            print('Processed field: %s' % field)

        # This code will process each file uploaded
        files = request.files.to_dict()
        for file_name, file in files.items():
            file.save(get_file_path(file_name))
            print('Processed file: %s' % file_name)

        # Clear temporary directory
        for file_name in files:
            file_path = get_file_path(file_name)
            os.remove(file_path)

        return "Done!"

Go


    // Package http provides a set of HTTP Cloud Functions samples.
    package http

    import (
    	"fmt"
    	"log"
    	"net/http"
    )

    // UploadFile processes a 'multipart/form-data' upload request.
    func UploadFile(w http.ResponseWriter, r *http.Request) {
    	const maxMemory = 2 * 1024 * 1024 // 2 megabytes.

    	// ParseMultipartForm parses a request body as multipart/form-data.
    	// The whole request body is parsed and up to a total of maxMemory bytes of
    	// its file parts are stored in memory, with the remainder stored on
    	// disk in temporary files.
    	if err := r.ParseMultipartForm(maxMemory); err != nil {
    		http.Error(w, "Unable to parse form", http.StatusBadRequest)
    		log.Printf("Error parsing form: %v", err)
    		return
    	}

    	// Be sure to remove all temporary files after your function is finished.
    	defer func() {
    		if err := r.MultipartForm.RemoveAll(); err != nil {
    			http.Error(w, "Error cleaning up form files", http.StatusInternalServerError)
    			log.Printf("Error cleaning up form files: %v", err)
    		}
    	}()

    	// r.MultipartForm.File contains *multipart.FileHeader values for every
    	// file in the form. You can access the file contents using
    	// *multipart.FileHeader's Open method.
    	for _, headers := range r.MultipartForm.File {
    		for _, h := range headers {
    			fmt.Fprintf(w, "File uploaded: %q (%v bytes)", h.Filename, h.Size)
    			// Use h.Open() to read the contents of the file.
    		}
    	}

    }
    
.

Sube archivos a través de Cloud Storage

Un caso práctico común de Cloud Functions es el procesamiento de archivos. Para archivos grandes o aquellos que requieren un almacenamiento continuo más allá del alcance de una sola solicitud, puedes usar Cloud Storage como un punto de entrada para tus cargas de archivos. Para lograrlo, debes generar una URL firmada que proporcione acceso de escritura temporal al depósito de Cloud Storage.

Si usas Cloud Functions directamente, genera una URL firmada con la biblioteca cliente de Cloud Storage.

El proceso para subir archivos a una función de Cloud Functions con Cloud Storage consta de tres pasos que se describen a continuación:

  1. Los clientes llaman a una función de Cloud Functions directamente para recuperar la URL firmada.

  2. Luego, los clientes envían los datos del archivo a la URL firmada a través de la solicitud HTTP PUT.

  3. Se activa una segunda función de Cloud Functions mediante la mutación en el depósito de Storage para procesar aún más el archivo.

Puedes ver el siguiente ejemplo de uso de la biblioteca cliente de Cloud Storage para generar una URL firmada.

.

Cloud Functions tiene una "credencial de aplicación predeterminada" que, por lo general, no incluye el permiso iam.serviceAccounts.signBlob. Para habilitar este permiso, primero será necesario que te asegures de que la cuenta de servicio de tu función tiene la función apropiada. Puedes hacer esto con Cloud Console o la herramienta de línea de comandos de gcloud:

Console

Para asegurarte de que la cuenta de servicio de tu función tiene la función apropiada, puedes modificar directamente las funciones de IAM de una cuenta con los siguientes pasos:

  1. Ve a Google Cloud Console:

    Ir a Google Cloud Console

  2. Selecciona la cuenta correcta y elige Editor > Cuentas de servicio > Creador de token de cuenta de servicio.

gcloud

Para asegurarte de que la cuenta de servicio de la función tiene la función apropiada, ejecuta el siguiente comando. La función predefinida serviceAccountTokenCreator tiene el permiso iam.serviceAccounts.signBlob que necesitas:

gcloud projects add-iam-policy-binding YOUR_PROJECT \
    --member serviceAccount:YOUR_SERVICE_ACCOUNT --role roles/iam.serviceAccountTokenCreator

Puedes determinar qué cuenta de servicio usan tus funciones mediante Cloud Console o la herramienta de línea de comandos de gcloud:

Console

Sigue estos pasos para determinar la cuenta de servicio que usan tus funciones con Cloud Console:

  1. Ve a Google Cloud Console:

    Ir a Google Cloud Console

  2. En la lista, selecciona la función que quieres inspeccionar.

Puedes ver la cuenta de servicio en la página de detalles de la función.

gcloud

Para determinar la cuenta de servicio que usan tus funciones, ejecuta el siguiente comando y busca la propiedad serviceAccountEmail:

gcloud beta functions describe YOUR_FUNCTION_NAME

A continuación, te mostramos un ejemplo de cómo generar una URL firmada que supone que el cliente enviará el depósito deseado y el nombre del archivo en el cuerpo de la solicitud:

Node.js

const {Storage} = require('@google-cloud/storage');
    const storage = new Storage();

    /**
     * HTTP function that generates a signed URL
     * The signed URL can be used to upload files to Google Cloud Storage (GCS)
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.getSignedUrl = (req, res) => {
      if (req.method !== 'POST') {
        // Return a "method not allowed" error
        return res.status(405).end();
      }
      // TODO(developer) check that the user is authorized to upload

      // Get a reference to the destination file in GCS
      const file = storage.bucket(req.body.bucket).file(req.body.filename);

      // Create a temporary upload URL
      const expiresAtMs = Date.now() + 300000; // Link expires in 5 minutes
      const config = {
        action: 'write',
        expires: expiresAtMs,
        contentType: req.body.contentType,
      };

      file.getSignedUrl(config, (err, url) => {
        if (err) {
          console.error(err);
          res.status(500).end();
          return;
        }
        res.send(url);
      });
    };

Python

from datetime import datetime, timedelta
    from flask import abort
    from google.cloud import storage
    storage_client = storage.Client()

    def get_signed_url(request):
        if request.method != 'POST':
            return abort(405)

        request_json = request.get_json()

        # Get a reference to the destination file in GCS
        bucket_name = request_json['bucket']
        file_name = request_json['filename']
        file = storage_client.bucket(bucket_name).blob(file_name)

        # Create a temporary upload URL
        expires_at_ms = datetime.now() + timedelta(seconds=30)
        url = file.generate_signed_url(expires_at_ms,
                                       content_type=request_json['contentType'])

        return url

Cuando el cliente sube un archivo a la URL firmada, puedes activar una segunda función desde esta mutación si quieres tomar medidas adicionales para la carga. Consulta el instructivo de Cloud Storage para obtener más información sobre cómo activar una Cloud Function después de los cambios a un depósito de Cloud Storage.

Próximos pasos