Google Cloud Pub/Sub

The Google Cloud client libraries over gRPC with TLS, with no credentials (AnonymousCredentials) and your identifier as the project.

Endpoint and credentials

Endpointpubsub.powerqueue.link:443, gRPC over TLS
Project idyour tenant identifier: <your-identifier>
Credentialsnone: AnonymousCredentials, or authentication turned off
Resource namesprojects/<your-identifier>/topics/<topic>, projects/<your-identifier>/subscriptions/<subscription>

Do not set PUBSUB_EMULATOR_HOST. The client libraries treat it as a local emulator and turn TLS off, which this endpoint does not accept. Set the endpoint in code as below, and make sure the client does not fetch Google credentials (it would fail without them, and they would be useless here).

Topics and subscriptions without setup

A pull names only a subscription, never its topic, so the public service uses a naming convention to create subscriptions on demand:

Topics and subscriptions created implicitly are deleted after an hour empty and unused.

Python (google-cloud-pubsub)

pip install google-cloud-pubsub. Publish, then receive with a streaming pull:

from concurrent.futures import TimeoutError

from google.api_core.client_options import ClientOptions
from google.auth.credentials import AnonymousCredentials
from google.cloud import pubsub_v1

PROJECT = "<your-identifier>"
options = ClientOptions(api_endpoint="pubsub.powerqueue.link:443")
publisher = pubsub_v1.PublisherClient(credentials=AnonymousCredentials(), client_options=options)
subscriber = pubsub_v1.SubscriberClient(credentials=AnonymousCredentials(), client_options=options)

topic = publisher.topic_path(PROJECT, "events")
for i in range(3):
    publisher.publish(topic, f"order {i}".encode(), origin="example").result()


def callback(message):
    print(message.data, message.attributes)
    message.ack()


streaming = subscriber.subscribe(subscriber.subscription_path(PROJECT, "events"), callback=callback)
with subscriber:
    try:
        streaming.result(timeout=10)
    except TimeoutError:
        streaming.cancel()
        streaming.result()

Creating entities explicitly works too, with any subscription settings within the limits. With clients made as above:

publisher.create_topic(name=publisher.topic_path(PROJECT, "reports"))
subscriber.create_subscription(
    name=subscriber.subscription_path(PROJECT, "reports-worker"),
    topic=publisher.topic_path(PROJECT, "reports"),
    ack_deadline_seconds=30,
)

Node.js (@google-cloud/pubsub)

npm install @google-cloud/pubsub @grpc/grpc-js. An endpoint outside googleapis.com makes the library assume an emulator, so set emulatorMode: false. TLS channel credentials in sslCreds stop it from looking for Google credentials, and naming the universeDomain stops it from probing for Google Cloud's metadata server:

import { PubSub } from "@google-cloud/pubsub";
import { credentials } from "@grpc/grpc-js";

const pubsub = new PubSub({
  projectId: "<your-identifier>",
  apiEndpoint: "pubsub.powerqueue.link:443",
  emulatorMode: false,
  universeDomain: "googleapis.com",
  sslCreds: credentials.createSsl(),
});

await pubsub.topic("events").publishMessage({ data: Buffer.from("hello") });

const subscription = pubsub.subscription("events");
subscription.on("message", (message) => {
  console.log(message.data.toString());
  message.ack();
});
setTimeout(() => subscription.close(), 10000);

Go (cloud.google.com/go/pubsub/v2)

go get cloud.google.com/go/pubsub/v2. option.WithoutAuthentication sends no credentials and keeps TLS:

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"cloud.google.com/go/pubsub/v2"
	"google.golang.org/api/option"
)

func main() {
	ctx := context.Background()
	client, err := pubsub.NewClient(ctx, "<your-identifier>",
		option.WithEndpoint("pubsub.powerqueue.link:443"),
		option.WithoutAuthentication(),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	publisher := client.Publisher("events")
	if _, err := publisher.Publish(ctx, &pubsub.Message{Data: []byte("hello")}).Get(ctx); err != nil {
		log.Fatal(err)
	}
	publisher.Stop()

	receiveCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()
	err = client.Subscriber("events").Receive(receiveCtx, func(_ context.Context, message *pubsub.Message) {
		fmt.Println(string(message.Data))
		message.Ack()
		cancel()
	})
	if err != nil {
		log.Fatal(err)
	}
}

Supported

Differences from Google Cloud Pub/Sub

Messages from other interfaces

Bodies sent through the other interfaces arrive as their bytes; a typed Service Bus body arrives as JSON with the attribute x-pq-body-encoding = json. Attributes are strings: numbers arrive as their decimal text, booleans as true or false, AMQP tables and arrays as JSON, and binary values (an SQS Binary attribute, an AMQP byte-array header) base64-encoded, their names listed, comma-separated, in x-pq-base64-attributes. Attribute names starting x-pq- cannot be published.

The full rules, with examples: Using one queue from several interfaces.

Errors

SituationgRPC status
Project is not a tenant identifierINVALID_ARGUMENT, with the identifier help text (Python: google.api_core.exceptions.InvalidArgument)
Too many new tenants from your address, or the service is fullRESOURCE_EXHAUSTED
More than 100 requests a second (after a burst of 200); each request on a streaming pull countsRESOURCE_EXHAUSTED (the libraries back off and retry)
More than 100 calls in progress from your address (an open streaming pull counts until it closes)RESOURCE_EXHAUSTED (the libraries back off and retry)
Message data plus attributes over 256 KiBINVALID_ARGUMENT
Topics, subscriptions, schemas, snapshots and IAM policies, with the tenant's other settings, taking more than 1 MiB together, as storedRESOURCE_EXHAUSTED
A 101st topic, subscription, schema or snapshot, explicit or implicitRESOURCE_EXHAUSTED
Publishing to a topic with a subscription holding 1,000 messages, or when the tenant holds 16 MiB of messagesRESOURCE_EXHAUSTED; the publisher retries it until its deadline
Message retention over 1 hourINVALID_ARGUMENT
Pulling an unknown subscription not named X~nameNOT_FOUND
Publishing to an unknown topic X when another protocol already has a queue called XFAILED_PRECONDITION, naming the clash (create the topic and a subscription explicitly, or use another name)

Each client address may have at most 100 calls in progress, over all its connections; each HTTP/2 connection carries at most 100 concurrent streams, and the libraries queue further calls until a stream is free.