Writing and Responding to Pub/Sub Messages

Google Cloud Pub/Sub provides reliable, many-to-many, asynchronous messaging between applications. Publisher applications can send messages to a topic, and other applications can subscribe to that topic to receive the messages.

This document describes how to use the Google Cloud Client Library to send and receive Google Cloud Pub/Sub messages in an app running in the flexible environment.

Prerequisites

Follow the instructions in "Hello, World!" for Java on App Engine to set up your environment and project, and to understand how App Engine Java apps are structured. Write down and save your project ID, because you will need it to run the sample application described in this document.

Clone the sample app

Copy the sample apps to your local machine, and navigate to the pubsub directory:

git clone https://github.com/GoogleCloudPlatform/java-docs-samples
cd java-docs-samples/flexible/pubsub

Create a topic and subscription

Create a topic and subscription, which includes specifying the endpoint to which the Pub/Sub server should send requests:

gcloud beta pubsub topics create YOUR_TOPIC_NAME
gcloud beta pubsub subscriptions create YOUR_SUBSCRIPTION_NAME \
    --topic YOUR_TOPIC_NAME \
    --push-endpoint \
    https://YOUR_APP_ID.appspot.com/pubsub/push?token=YOUR_TOKEN \
    --ack-deadline 10

Replace YOUR_TOKEN with a secret random token. The push endpoint uses this to verify requests.

Edit app.yaml

Edit app.yaml to set the environment variables for your topic and verification token:

env_variables:
  PUBSUB_TOPIC: <your-topic-name>
  PUBSUB_VERIFICATION_TOKEN: <your-verification-token>

Code review

The sample app uses the Google Cloud Client Libraries.

The sample app uses the values you set in the app.yaml file to configure environment variables.

The publish endpoint /pubsub/publish sends a string payload as a message to the topic configured in app.yaml

@WebServlet(name = "Publish with PubSub", value = "/pubsub/publish")
public class PubSubPublish extends HttpServlet {

  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException {
    Publisher publisher = this.publisher;
    try {
      String topicId = System.getenv("PUBSUB_TOPIC");
      // create a publisher on the topic
      if (publisher == null) {
        publisher = Publisher.defaultBuilder(
            TopicName.create(ServiceOptions.getDefaultProjectId(), topicId))
            .build();
      }
      // construct a pubsub message from the payload
      final String payload = req.getParameter("payload");
      PubsubMessage pubsubMessage =
          PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(payload)).build();

      publisher.publish(pubsubMessage);
      // redirect to home page
      resp.sendRedirect("/");
    } catch (Exception e) {
      resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage());
    }
  }

The push endpoint /pubsub/push uses these values to confirm that the request came from Pub/Sub and originated from a trusted source. These messages are then stored in Datastore under the kind messages.

@WebServlet(value = "/pubsub/push")
public class PubSubPush extends HttpServlet {

  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException {
    String pubsubVerificationToken = System.getenv("PUBSUB_VERIFICATION_TOKEN");
    // Do not process message if request token does not match pubsubVerificationToken
    if (req.getParameter("token").compareTo(pubsubVerificationToken) != 0) {
      resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
      return;
    }
    // parse message object from "message" field in the request body json
    // decode message data from base64
    Message message = getMessage(req);
    try {
      messageRepository.save(message);
      // 200, 201, 204, 102 status codes are interpreted as success by the Pub/Sub system
      resp.setStatus(HttpServletResponse.SC_OK);
    } catch (Exception e) {
      resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
  }

Run the sample locally

When running locally, you can use the Google Cloud SDK to provide authentication to use Google Cloud APIs. Assuming you set up your environment as described in Prerequisites, you have already run the gcloud init command, which provides this authentication.

mvn clean package

Then set environment variables before starting your application:

export PUBSUB_VERIFICATION_TOKEN=[your-verification-token]
export PUBSUB_TOPIC=[your-topic]
mvn jetty:run

Simulate push notifications

The application can send messages locally, but it is not able to receive push messages locally. You can, however, simulate a push message by making an HTTP request to the local push notification endpoint. The sample includes the file sample_message.json. You can use curl or httpie to send a HTTP POSTrequest:

curl -H "Content-Type: application/json" -i --data @sample_message.json ":8080/pubsub/push?token=[your-token]"

Or

http POST ":8080/pubsub/push?token=[your-token]" < sample_message.json

Response:

HTTP/1.1 200 OK
Date: Wed, 26 Apr 2017 00:03:28 GMT
Content-Length: 0
Server: Jetty(9.3.8.v20160314)

After the request completes, you can refresh localhost:8080 and see the message in the list of received messages.

Run on App Engine

To deploy the demo app to App Engine by using the gcloud command-line tool, you run the following command from the directory where your app.yaml is located:

mvn appengine:deploy
You can now access the application at https://[your-proj-id].appspot.com. You can use the form to submit messages, but there's no guarantee of which instance of your application will receive the notification. You can send multiple messages and refresh the page to see the received message.

Send feedback about...

App Engine flexible environment for Java docs