Desempaquetado de la carga útil de las suscripciones push de Pub/Sub

Al crear tu sistema Pub/Sub, el desempaquetado de la carga útil puede ayudarte a conectarte a otros sistemas que no cumplan todos los requisitos del sistema de una implementación estándar de endpoint de envío de Pub/Sub.

Estos son algunos casos prácticos potenciales del deswrap de cargas útiles:

  • No quieres escribir código de análisis de mensajes específico de Pub/Sub para tus endpoints de inserción HTTP.
  • Prefieres recibir los metadatos de los mensajes de Pub/Sub como encabezados HTTP en lugar de recibirlos en el cuerpo de la solicitud HTTP POST.
  • Quieres enviar mensajes de Pub/Sub y excluir los metadatos de Pub/Sub, por ejemplo, al enviar datos a una API de terceros.

Cómo funciona el desempaquetado de la carga útil

El desempaquetado de la carga útil es una función que elimina de los mensajes de Pub/Sub todos los metadatos, excepto los datos del mensaje. Al enviar datos de mensajes sin procesar, los suscriptores pueden procesar el mensaje sin tener que cumplir ningún requisito del sistema de Pub/Sub.

  • Con el desempaquetado de la carga útil, los datos del mensaje se envían directamente como cuerpo HTTP.
  • Sin el desempaquetado de la carga útil, Pub/Sub envía un objeto JSON que contiene varios campos de metadatos de mensajes y un campo de datos de mensajes. En este caso, el JSON debe analizarse para recuperar los datos del mensaje y, a continuación, decodificarse en base64.

Escribir metadatos

Después de habilitar el desempaquetado de la carga útil, puedes usar la opción Escribir metadatos, que añade los metadatos de los mensajes que se hayan eliminado anteriormente al encabezado de la solicitud.

  • Escribir metadatos habilitado. Vuelve a añadir los metadatos del mensaje al encabezado de la solicitud. También proporciona los datos del mensaje sin procesar y decodificados.
  • Escritura de metadatos inhabilitada. Solo proporciona los datos sin procesar y decodificados del mensaje.

Los metadatos de escritura se exponen a través de Pub/Sub, el argumento --push-no-wrapper-write-metadata de la CLI de Google Cloud y la propiedad NoWrapper de la API. De forma predeterminada, este valor es nulo.

Antes de empezar

Ejemplo de mensajes envueltos y sin envolver

En los siguientes ejemplos se muestra la diferencia entre enviar un mensaje HTTP envuelto y uno sin envolver. En estos ejemplos, los datos del mensaje contienen la cadena {"status": "Hello there"}.

En este ejemplo, se crea una suscripción con la función de desempaquetado de la carga útil habilitada y se publica un mensaje en mytopic. Usa una clave de ordenación con el valor some-key y el tipo de contenido multimedia se declara como application/json.

gcloud pubsub topics publish mytopic
   --message='{"status": "Hello there"}'
   --ordering-key="some-key"
   --attribute "Content-Type=application/json"

En las siguientes secciones se muestra la diferencia entre un mensaje envuelto y uno sin envolver.

Mensaje envuelto

En el siguiente ejemplo se muestra un mensaje estándar envuelto de Pub/Sub. En este caso, no se ha habilitado el desempaquetado de la carga útil.

Publicar Recepción de endpoint de inserción
data="{"status": "Hello there"}"
ordering_key="some-key"
attributes=
  {
     {"Content-Type", "application/json"}
  }
Content-Length: 361
Content-Type: application/json
User-Agent: CloudPubSub-Google
Host: subscription-project.uc.r.appspot.com

{
  "message": {
      "attributes": {
          "Content-Type": "application/json"
      },
      "data": "eyJzdGF0dXMiOiAiSGVsbG8gdGhlcmUifQ==", //  Base64 - {"status": "Hello there"}
      "messageId": "2070443601311540",
      "message_id": "2070443601311540",
      "publishTime": "2021-02-26T19:13:55.749Z",
      "publish_time": "2021-02-26T19:13:55.749Z"
  },
  "subscription": "projects/myproject/..."
}

Mensaje sin envolver con metadatos de escritura inhabilitados

En el siguiente ejemplo se muestra un mensaje sin envolver con la opción de metadatos de escritura inhabilitada. En este caso, no se incluyen los encabezados x-goog-pubsub-* ni los atributos de mensaje.

Publicar Recepción de endpoint de inserción
data="{"status": "Hello there"}"
ordering_key="some-key"
attributes=
  {
     {"Content-Type", "application/json"}
  }
Content-Length: 25
User-Agent: CloudPubSub-Google
Host: subscription-project.uc.r.appspot.com

{"status": "Hello there"}

Mensaje sin envolver con metadatos de escritura habilitados

En el siguiente ejemplo se muestra un mensaje sin envolver con la opción de metadatos de escritura habilitada. En este caso, se incluyen los encabezados x-goog-pubsub-* y los atributos del mensaje.

Publicar Recepción de endpoint de inserción
data="{"status": "Hello there"}"
ordering_key="some-key"
attributes=
  {
     {"Content-Type", "application/json"}
  }
x-goog-pubsub-subscription-name: "projects/myproject/..."
x-goog-pubsub-message-id: "2070443601311540"
x-goog-pubsub-publish-time: "2021-02-26T19:13:55.749Z"
x-goog-pubsub-ordering-key: "some-key"
Content-Type: application/json
Content-Length: 12
User-Agent: CloudPubSub-Google
Host: subscription-project.uc.r.appspot.com

{"status": "Hello there"}

Configurar la retirada de envoltorio de la carga útil

Puedes habilitar el envío push de desencapsulado de carga útil para una suscripción mediante la página Detalles de la suscripción de la consola Google Cloud , la CLI de Google Cloud o las bibliotecas de cliente.

Consola

  1. En la Google Cloud consola, ve a la página Suscripciones.

    Abrir suscripciones de Pub/Sub

  2. Haz clic en Crear suscripción.

  3. En el campo ID de suscripción, introduce un nombre.

    Para obtener información sobre cómo asignar un nombre a una suscripción, consulta las directrices para asignar un nombre a un tema o a una suscripción.

  4. Selecciona un tema en el menú desplegable. La suscripción recibe mensajes del tema.

  5. En Tipo de envío, selecciona Envío.

  6. Para habilitar el desempaquetado de la carga útil, selecciona Habilitar desempaquetado de la carga útil.

  7. (Opcional) Para conservar los metadatos de los mensajes en la cabecera de la solicitud, selecciona Escribir metadatos. Debe habilitar esta opción para definir un encabezado Content-Type en sus mensajes.

  8. Especifica una URL de endpoint.

  9. Mantén el resto de los valores predeterminados.

  10. Haz clic en Crear.

gcloud

Para configurar una suscripción con el desempaquetado de la carga útil que incluya cabeceras HTTP estándar, ejecuta el siguiente comando gcloud pubsub subscriptions create:

gcloud pubsub subscriptions create SUBSCRIPTION \
  --topic TOPIC \
  --push-endpoint=PUSH_ENDPOINT \
  --push-no-wrapper

Haz los cambios siguientes:

  • SUBSCRIPTION: el nombre o el ID de tu suscripción de extracción.
  • TOPIC: el ID del tema.
  • PUSH_ENDPOINT: URL que se va a usar como endpoint de esta suscripción. Por ejemplo, https://myproject.appspot.com/myhandler
  • --push-no-wrapper: entrega los datos del mensaje directamente como cuerpo HTTP.

Para configurar una suscripción con el desempaquetado de la carga útil y controlar el uso de los encabezados x-goog-pubsub-*, ejecuta el siguiente comando:

gcloud pubsub subscriptions create SUBSCRIPTION \
  --topic TOPIC \
  --push-endpoint=PUSH_ENDPOINT \
  --push-no-wrapper \
  --push-no-wrapper-write-metadata
  • --push-no-wrapper-write-metadata: si es true, escribe los metadatos del mensaje de Pub/Sub en los encabezados x-goog-pubsub-<KEY>:<VAL> de la solicitud HTTP. Escribe los atributos del mensaje de Pub/Sub en los encabezados <KEY>:<VAL> de la solicitud HTTP.

Python

Antes de probar este ejemplo, sigue las instrucciones de configuración de Python que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Python de Pub/Sub.

from google.cloud import pubsub_v1

# TODO(developer)
# project_id = "your-project-id"
# topic_id = "your-topic-id"
# subscription_id = "your-subscription-id"
# endpoint = "https://my-test-project.appspot.com/push"

publisher = pubsub_v1.PublisherClient()
subscriber = pubsub_v1.SubscriberClient()
topic_path = publisher.topic_path(project_id, topic_id)
subscription_path = subscriber.subscription_path(project_id, subscription_id)

no_wrapper = pubsub_v1.types.PushConfig.NoWrapper(write_metadata=True)
push_config = pubsub_v1.types.PushConfig(
    push_endpoint=endpoint, no_wrapper=no_wrapper
)

# Wrap the subscriber in a 'with' block to automatically call close() to
# close the underlying gRPC channel when done.
with subscriber:
    subscription = subscriber.create_subscription(
        request={
            "name": subscription_path,
            "topic": topic_path,
            "push_config": push_config,
        }
    )

print(f"Push no wrapper subscription created: {subscription}.")
print(f"Endpoint for subscription is: {endpoint}")
print(f"No wrapper configuration for subscription is: {no_wrapper}")

Java

Antes de probar este ejemplo, sigue las instrucciones de configuración de Java que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Java de Pub/Sub.

/*
 * Copyright 2016 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package pubsub;


import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
import com.google.pubsub.v1.PushConfig;
import com.google.pubsub.v1.PushConfig.NoWrapper;
import com.google.pubsub.v1.Subscription;
import com.google.pubsub.v1.SubscriptionName;
import com.google.pubsub.v1.TopicName;
import java.io.IOException;

public class CreateUnwrappedPushSubscriptionExample {
  public static void main(String... args) throws Exception {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "your-project-id";
    String subscriptionId = "your-subscription-id";
    String topicId = "your-topic-id";
    String pushEndpoint = "https://my-test-project.appspot.com/push";

    createPushSubscriptionExample(projectId, subscriptionId, topicId, pushEndpoint);
  }

  public static void createPushSubscriptionExample(
      String projectId, String subscriptionId, String topicId, String pushEndpoint)
      throws IOException {
    try (SubscriptionAdminClient subscriptionAdminClient = SubscriptionAdminClient.create()) {
      TopicName topicName = TopicName.of(projectId, topicId);
      SubscriptionName subscriptionName = SubscriptionName.of(projectId, subscriptionId);
      NoWrapper noWrapper =
          NoWrapper.newBuilder()
              // Determines if message metadata is added to the HTTP headers of
              // the delivered message.
              .setWriteMetadata(true)
              .build();
      PushConfig pushConfig =
          PushConfig.newBuilder().setPushEndpoint(pushEndpoint).setNoWrapper(noWrapper).build();

      // Create a push subscription with default acknowledgement deadline of 10 seconds.
      // Messages not successfully acknowledged within 10 seconds will get resent by the server.
      Subscription subscription =
          subscriptionAdminClient.createSubscription(subscriptionName, topicName, pushConfig, 10);
      System.out.println("Created push subscription: " + subscription.getName());
    }
  }
}

C++

Antes de probar este ejemplo, sigue las instrucciones de configuración de C++ que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de C++ de Pub/Sub.

namespace pubsub = ::google::cloud::pubsub;
namespace pubsub_admin = ::google::cloud::pubsub_admin;
[](pubsub_admin::SubscriptionAdminClient client,
   std::string const& project_id, std::string const& topic_id,
   std::string const& subscription_id, std::string const& endpoint) {
  google::pubsub::v1::Subscription request;
  request.set_name(
      pubsub::Subscription(project_id, subscription_id).FullName());
  request.set_topic(pubsub::Topic(project_id, topic_id).FullName());
  request.mutable_push_config()->set_push_endpoint(endpoint);
  request.mutable_push_config()->mutable_no_wrapper()->set_write_metadata(
      true);
  auto sub = client.CreateSubscription(request);
  if (sub.status().code() == google::cloud::StatusCode::kAlreadyExists) {
    std::cout << "The subscription already exists\n";
    return;
  }
  if (!sub) throw std::move(sub).status();

  std::cout << "The subscription was successfully created: "
            << sub->DebugString() << "\n";
}

Go

En el siguiente ejemplo se usa la versión principal de la biblioteca de cliente de Pub/Sub de Go (v2). Si sigues usando la biblioteca v1, consulta la guía de migración a la versión 2. Para ver una lista de ejemplos de código de la versión 1, consulta los ejemplos de código obsoletos.

Antes de probar este ejemplo, sigue las instrucciones de configuración de Go que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API Go de Pub/Sub.

import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/pubsub/v2"
	"cloud.google.com/go/pubsub/v2/apiv1/pubsubpb"
)

// createPushNoWrapperSubscription creates a push subscription where messages are delivered in the HTTP body.
func createPushNoWrapperSubscription(w io.Writer, projectID, topic, subscription, endpoint string) error {
	// projectID := "my-project-id"
	// topic := "projects/my-project-id/topics/my-topic"
	// subscription := "projects/my-project/subscriptions/my-sub"
	// endpoint := "https://my-test-project.appspot.com/push"
	ctx := context.Background()
	client, err := pubsub.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("pubsub.NewClient: %w", err)
	}
	defer client.Close()
	pbSubscription := &pubsubpb.Subscription{
		Name:               subscription,
		Topic:              topic,
		AckDeadlineSeconds: 10,
		PushConfig: &pubsubpb.PushConfig{
			PushEndpoint: endpoint,
			Wrapper: &pubsubpb.PushConfig_NoWrapper_{
				NoWrapper: &pubsubpb.PushConfig_NoWrapper{
					// Determines if message metadata is added to the HTTP headers of
					// the delivered message.
					WriteMetadata: true,
				},
			},
		},
	}
	sub, err := client.SubscriptionAdminClient.CreateSubscription(ctx, pbSubscription)
	if err != nil {
		return fmt.Errorf("CreateSubscription: %w", err)
	}
	fmt.Fprintf(w, "Created push no wrapper subscription: %v\n", sub)
	return nil
}

Node.js

Antes de probar este ejemplo, sigue las instrucciones de configuración de Node.js que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Node.js de Pub/Sub.

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const pushEndpoint = 'YOUR_ENDPOINT_URL';
// const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID';
// const subscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID';

// Imports the Google Cloud client library
const {PubSub} = require('@google-cloud/pubsub');

// Creates a client; cache this for further use
const pubSubClient = new PubSub();

async function createPushSubscriptionNoWrapper(
  pushEndpoint,
  topicNameOrId,
  subscriptionNameOrId,
) {
  const options = {
    pushConfig: {
      // Set to an HTTPS endpoint of your choice. If necessary, register
      // (authorize) the domain on which the server is hosted.
      pushEndpoint,
      // When true, writes the Pub/Sub message metadata to
      // `x-goog-pubsub-<KEY>:<VAL>` headers of the HTTP request. Writes the
      // Pub/Sub message attributes to `<KEY>:<VAL>` headers of the HTTP request.
      noWrapper: {
        writeMetadata: true,
      },
    },
  };

  await pubSubClient
    .topic(topicNameOrId)
    .createSubscription(subscriptionNameOrId, options);
  console.log(`Subscription ${subscriptionNameOrId} created.`);
}

Node.js

Antes de probar este ejemplo, sigue las instrucciones de configuración de Node.js que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Node.js de Pub/Sub.

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const pushEndpoint = 'YOUR_ENDPOINT_URL';
// const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID';
// const subscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID';

// Imports the Google Cloud client library
import {PubSub, CreateSubscriptionOptions} from '@google-cloud/pubsub';

// Creates a client; cache this for further use
const pubSubClient = new PubSub();

async function createPushSubscriptionNoWrapper(
  pushEndpoint: string,
  topicNameOrId: string,
  subscriptionNameOrId: string,
) {
  const options: CreateSubscriptionOptions = {
    pushConfig: {
      // Set to an HTTPS endpoint of your choice. If necessary, register
      // (authorize) the domain on which the server is hosted.
      pushEndpoint,
      // When true, writes the Pub/Sub message metadata to
      // `x-goog-pubsub-<KEY>:<VAL>` headers of the HTTP request. Writes the
      // Pub/Sub message attributes to `<KEY>:<VAL>` headers of the HTTP request.
      noWrapper: {
        writeMetadata: true,
      },
    },
  };

  await pubSubClient
    .topic(topicNameOrId)
    .createSubscription(subscriptionNameOrId, options);
  console.log(`Subscription ${subscriptionNameOrId} created.`);
}

Definir un encabezado Content-Type en el mensaje

Después de habilitar el desempaquetado de la carga útil, Pub/Sub no define automáticamente un campo de encabezado de tipo de medio en tu solicitud. Si no defines explícitamente un campo de encabezado Content-Type, es posible que el servidor web que procese tu solicitud asigne un valor predeterminado de application/octet-stream o interprete la solicitud de forma inesperada.

Si necesitas un encabezado Content-Type, asegúrate de declararlo explícitamente en el momento de la publicación de cada mensaje. Para ello, primero debes habilitar Escribir metadatos. El resultado de habilitar Escribir metadatos se muestra en los ejemplos proporcionados.

Siguientes pasos