ביטול העטיפה של מטען ייעודי (payload) במינויים מסוג Pub/Sub push

כשמפתחים מערכת Pub/Sub, אפשר להשתמש בפריסת מטען ייעודי כדי להתחבר למערכות אחרות שלא עומדות בכל דרישות המערכת של הטמעה רגילה של נקודת קצה מסוג Pub/Sub push.

הנה כמה תרחישי שימוש אפשריים בפירוק של מטען ייעודי (payload):

  • אתם לא רוצים לכתוב קוד לניתוח הודעות שספציפי ל-Pub/Sub עבור נקודות הקצה של HTTP push.
  • אתם מעדיפים לקבל את המטא-נתונים של הודעות Pub/Sub ככותרות HTTP במקום המטא-נתונים בגוף ה-HTTP POST.
  • אתם רוצים לשלוח הודעות Pub/Sub ולהחריג את המטא-נתונים של Pub/Sub, למשל כששולחים נתונים ל-API של צד שלישי.

איך מתבצעת פריסת המטען הייעודי (payload)

התכונה 'פריקת מטען ייעודי' מסירה מהודעות Pub/Sub את כל המטא-נתונים של ההודעה, למעט נתוני ההודעה. כששולחים נתוני הודעות גולמיים, המנויים יכולים לעבד את ההודעה בלי להקפיד על דרישות המערכת של Pub/Sub.

  • במקרה של פריסת מטען ייעודי (payload), נתוני ההודעה מועברים ישירות כגוף ה-HTTP.
  • בלי פריסת המטען הייעודי, מערכת Pub/Sub מעבירה אובייקט JSON שמכיל כמה שדות של מטא-נתונים של ההודעה ושדה של נתוני ההודעה. במקרה הזה, צריך לנתח את ה-JSON כדי לאחזר את נתוני ההודעה, ואז לפענח את הקידוד base64.

כתיבת מטא-נתונים

אחרי שמפעילים את האפשרות להסרת העטיפה של מטען ייעודי, אפשר להשתמש באפשרות write metadata (כתיבת מטא-נתונים) שמוסיפה את המטא-נתונים של ההודעה שהוסרו קודם לrequest header.

  • האפשרות 'כתיבת מטא-נתונים' מופעלת. מוסיפים את המטא-נתונים של ההודעה בחזרה לכותרת של הבקשה. היא גם מספקת את נתוני ההודעה הגולמיים והמפוענחים.
  • הכתיבה של המטא-נתונים מושבתת. הכלי מספק רק את הנתונים הגולמיים של ההודעה המפוענחת.

האפשרות לכתוב מטא-נתונים מוצגת דרך Pub/Sub, הארגומנט --push-no-wrapper-write-metadata של Google Cloud CLI והמאפיין NoWrapper של ה-API. כברירת מחדל, הערך הזה הוא null.

לפני שמתחילים

דוגמה להודעות עטופות ולא עטופות

בדוגמאות הבאות אפשר לראות את ההבדל בין שליחת הודעת HTTP עטופה לבין שליחת הודעת HTTP לא עטופה. בדוגמאות האלה, נתוני ההודעה מכילים את המחרוזת {"status": "Hello there"}.

בדוגמה הזו, נוצר מינוי עם התכונה payload unwrapping (פריקת מטען ייעודי) מופעלת, והודעה מתפרסמת ב-mytopic. הוא משתמש במפתח הזמנה עם ערך של some-key וסוג המדיה מוצהר כ-application/json.

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

בקטעים הבאים מוצג ההבדל בין הודעה עטופה להודעה לא עטופה.

הודעה עם גלישת טקסט

בדוגמה הבאה מוצגת הודעה רגילה של Pub/Sub שעטופה ב-CloudEvents. במקרה הזה, ביטול העטיפה של מטען הייעודי לא מופעל.

פרסום נקודת הקצה (endpoint) של הודעת ה-Push מקבלת
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/..."
}

הודעה לא עטופה עם השבתה של כתיבת מטא-נתונים

בדוגמה הבאה מוצגת הודעה לא עטופה עם האפשרות 'כתיבת מטא-נתונים' מושבתת. במקרה כזה, x-goog-pubsub-* הכותרות ומאפייני ההודעה לא נכללים.

פרסום נקודת הקצה (endpoint) של הודעת ה-Push מקבלת
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"}

הודעה לא עטופה עם מטא-נתונים של כתיבה מופעלים

בדוגמה הבאה מוצגת הודעה לא עטופה עם האפשרות 'כתיבת מטא-נתונים' מופעלת. במקרה כזה, הכותרות x-goog-pubsub-* ומאפייני ההודעה נכללים.

פרסום נקודת הקצה (endpoint) של הודעת ה-Push מקבלת
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"}

הגדרת פריסת מטען ייעודי (payload)

אפשר להפעיל את האפשרות 'שליחת הודעות בדחיפה עם פתיחת המטען הייעודי (payload) של המינוי' באמצעות הדף Subscription Details במסוף Google Cloud , Google Cloud CLI או ספריות הלקוח.

המסוף

  1. נכנסים לדף Subscriptions במסוף Google Cloud .

    הרשמות פתוחות ל-Pub/Sub

  2. לוחצים על יצירת מינוי.

  3. כותבים שם בשדה Subscription ID.

    מידע על מתן שם למינוי זמין במאמר הנחיות למתן שם לנושא או למינוי.

  4. בוחרים נושא מהתפריט הנפתח. המנוי מקבל הודעות מהנושא.

  5. בקטע סוג המסירה, בוחרים באפשרות דחיפה.

  6. כדי להפעיל את ביטול העטיפה של המטען הייעודי (payload), בוחרים באפשרות הפעלה של ביטול העטיפה של המטען הייעודי.

  7. (אופציונלי) כדי לשמור את המטא-נתונים של ההודעות ב-request header, בוחרים באפשרות Write metadata (כתיבת מטא-נתונים). כדי להגדיר כותרת Content-Type להודעות, צריך להפעיל את האפשרות הזו.

  8. מציינים כתובת URL של נקודת קצה.

  9. משאירים את כל שאר ערכי ברירת המחדל.

  10. לוחצים על יצירה.

gcloud

כדי להגדיר מינוי עם פתיחת עטיפה של מטען ייעודי (payload) שכולל כותרות HTTP רגילות, מריצים את הפקודה הבאה של gcloud pubsub subscriptions create:

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

מחליפים את מה שכתוב בשדות הבאים:

  • SUBSCRIPTION: השם או המזהה של מינוי הדחיפה.
  • TOPIC: מזהה הנושא.
  • PUSH_ENDPOINT: כתובת ה-URL שתשמש כנקודת הקצה של המינוי הזה. לדוגמה, https://myproject.appspot.com/myhandler.
  • --push-no-wrapper: מעביר את נתוני ההודעה ישירות כגוף ה-HTTP.

כדי להגדיר מינוי עם פריקת מטען (payload) ולשלוט בשימוש בכותרות x-goog-pubsub-*, מריצים את הפקודה הבאה:

gcloud pubsub subscriptions create SUBSCRIPTION \
  --topic TOPIC \
  --push-endpoint=PUSH_ENDPOINT \
  --push-no-wrapper \
  --push-no-wrapper-write-metadata
  • --push-no-wrapper-write-metadata: אם הערך הוא true, המטא-נתונים של הודעת Pub/Sub נכתבים בכותרות x-goog-pubsub-<KEY>:<VAL> של בקשת ה-HTTP. כותב את מאפייני ההודעה ב-Pub/Sub לכותרות <KEY>:<VAL> של בקשת ה-HTTP.

Python

לפני שמנסים את הדוגמה הזו, צריך לפעול לפי הוראות ההגדרה של Python במאמר תחילת העבודה המהירה: שימוש בספריות לקוח. מידע נוסף מופיע במאמרי העזרה של ה-API בשפת Python של 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

לפני שמנסים את הדוגמה הזו, צריך לפעול לפי הוראות ההגדרה של Java במאמר תחילת העבודה: שימוש בספריות לקוח. מידע נוסף מופיע במאמרי העזרה של Pub/Sub Java API.

/*
 * 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++‎

לפני שמנסים את הדוגמה הזו, צריך לפעול לפי הוראות ההגדרה של C++‎ במאמר תחילת העבודה המהירה: שימוש בספריות לקוח. מידע נוסף זמין במאמרי העזרה של Pub/Sub C++ API.

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 Pub/Sub ‏ (v2). אם אתם עדיין משתמשים בספרייה v1, כדאי לעיין במדריך להעברה לגרסה v2. כדי לראות רשימה של דוגמאות קוד מגרסה 1, אפשר לעיין ב דוגמאות הקוד שהוצאו משימוש.

לפני שמנסים את הדוגמה הזו, צריך לפעול לפי הוראות ההגדרה של Go במאמר מדריך למתחילים: שימוש בספריות לקוח. מידע נוסף מופיע במאמרי העזרה של Pub/Sub Go API.

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

לפני שמנסים את הדוגמה הזו, צריך לפעול לפי הוראות ההגדרה של Node.js במאמר תחילת העבודה: שימוש בספריות לקוח. מידע נוסף מופיע במאמרי העזרה של Pub/Sub Node.js API.

/**
 * 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

לפני שמנסים את הדוגמה הזו, צריך לפעול לפי הוראות ההגדרה של Node.js במאמר תחילת העבודה: שימוש בספריות לקוח. מידע נוסף מופיע במאמרי העזרה של Pub/Sub Node.js API.

/**
 * 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.`);
}

הגדרת כותרת מסוג תוכן בהודעה

אחרי שמפעילים את ביטול העטיפה של מטען הייעודי (payload), מערכת Pub/Sub לא מגדירה באופן אוטומטי שדה כותרת של סוג מדיה בבקשה. אם לא מגדירים באופן מפורש שדה כותרת Content-Type, שרת האינטרנט שמטפל בבקשה עשוי להגדיר ערך ברירת מחדל של application/octet-stream או לפרש את הבקשה בצורה לא צפויה.

אם אתם צריכים כותרת Content-Type, אתם צריכים להצהיר עליה באופן מפורש בזמן הפרסום של כל הודעה שמתפרסמת. כדי לעשות את זה, קודם צריך להפעיל את האפשרות כתיבת מטא-נתונים. התוצאה של הפעלת האפשרות כתיבת מטא-נתונים מוצגת בדוגמאות שסופקו.

המאמרים הבאים