Pub/Sub メッセージの作成と応答

Cloud Pub/Sub は、信頼できる多対多の非同期メッセージングをアプリケーション間で行います。パブリッシャー アプリケーションはメッセージをトピックに送信し、その他のアプリケーションはそのトピックにサブスクライブしてメッセージを受信できます。

このドキュメントでは、Google Cloud クライアント ライブラリを使用して、Java 8 アプリで Cloud Pub/Sub メッセージを送受信する方法について説明します。

要件

  • App Engine の Java 8 用「Hello, World!」の説明に沿って環境とプロジェクトを設定し、App Engine での Java 8 アプリの構造を理解します。
  • このドキュメントで説明しているサンプルアプリを実行する際に必要となるため、プロジェクト ID をメモして保存しておきます。

サンプルアプリのクローン作成

サンプルアプリをローカルマシンにコピーし、pubsub ディレクトリに移動します。

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

トピックとサブスクリプションの作成

トピックとサブスクリプションを作成します。Pub/Sub サーバーがリクエストを送信するエンドポイントも指定します。

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

YOUR_TOKEN は、ランダムなシークレット トークンで置き換えます。push エンドポイントがこれを使用してリクエストを検証します。

環境変数の設定

appengine-web.xml ファイルを編集して、トピックと検証トークンの各環境変数を設定します。

<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <threadsafe>true</threadsafe>
  <runtime>java8</runtime>

  <env-variables>
    <env-var name="PUBSUB_TOPIC" value="your-topic" />
    <env-var name="PUBSUB_VERIFICATION_TOKEN" value="your-verification-token" />
  </env-variables>
</appengine-web-app>

コードのレビュー

サンプルアプリでは、Google Cloud クライアント ライブラリを使用しています。

このサンプルアプリでは、 appengine-web.xml ファイルに設定された値を使用して環境変数を構成します。push リクエスト ハンドラは、これらの値を使用して、Pub/Sub からのリクエストで、信頼できる送信元から送信されていることを確認します。

String pubsubVerificationToken = System.getenv("PUBSUB_VERIFICATION_TOKEN");

このサンプルアプリでは、メッセージを格納する Cloud Datastore データベース インスタンスを保守します。サーブレット PubSubPush は、push されたメッセージを受信して、messageRepository データベース インスタンスに追加します。

@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(102);
    } catch (Exception e) {
      resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
  }

  private Message getMessage(HttpServletRequest request) throws IOException {
    String requestBody = request.getReader().lines().collect(Collectors.joining("\n"));
    JsonElement jsonRoot = jsonParser.parse(requestBody);
    String messageStr = jsonRoot.getAsJsonObject().get("message").toString();
    Message message = gson.fromJson(messageStr, Message.class);
    // decode from base64
    String decoded = decode(message.getData());
    message.setData(decoded);
    return message;
  }

  private String decode(String data) {
    return new String(Base64.getDecoder().decode(data));
  }

  private final Gson gson = new Gson();
  private final JsonParser jsonParser = new JsonParser();
  private MessageRepository messageRepository;

  PubSubPush(MessageRepository messageRepository) {
    this.messageRepository = messageRepository;
  }

  public PubSubPush() {
    this.messageRepository = MessageRepositoryImpl.getInstance();
  }
}

PubSubPublish サーブレットは App Engine ウェブアプリと連携して新しいメッセージを公開し、受信したメッセージを表示します。

/*
 * Copyright 2017 Google Inc.
 *
 * 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 com.example.appengine.pubsub;

import com.google.cloud.ServiceOptions;
import com.google.cloud.pubsub.v1.Publisher;
import com.google.protobuf.ByteString;
import com.google.pubsub.v1.ProjectTopicName;
import com.google.pubsub.v1.PubsubMessage;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpStatus;

@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) {
        ProjectTopicName topicName =
            ProjectTopicName.newBuilder()
                .setProject(ServiceOptions.getDefaultProjectId())
                .setTopic(topicId)
                .build();
        publisher = Publisher.newBuilder(topicName).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());
    }
  }

  private Publisher publisher;

  public PubSubPublish() {}

  PubSubPublish(Publisher publisher) {
    this.publisher = publisher;
  }
}

サンプルをローカルで実行する

ローカルで実行する場合、Google Cloud API を使用するための認証を行うには、Cloud SDK を利用します。前提条件に記載されているとおりに環境を設定していれば、この認証を行う gcloud init コマンドはすでに実行されています。

mvn clean package

次に、アプリケーションを起動する前に環境変数を設定します。

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

プッシュ通知のシミュレート

アプリケーションは、ローカルでメッセージを送信できますが、push メッセージを受信することはできません。push メッセージをシミュレートするには、ローカルのプッシュ通知エンドポイントに HTTP リクエストを送信します。このサンプルには、sample_message.json というファイルが含まれています。

curl または httpie クライアントを使用して、HTTP POST リクエストを送信できます。

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

または

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

レスポンス:

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

リクエストの完了後、localhost:8080 を更新すると、受信メッセージのリストにメッセージが表示されます。

App Engine 上で実行する

gcloud コマンドライン ツールを使用してデモアプリを App Engine にデプロイするには、pom.xml が配置されているディレクトリから次のコマンドを実行します。

mvn appengine:deploy

これで、https://[YOUR_PROJECT_ID].appspot.com のアプリケーションにアクセスできます。フォームを使用してメッセージを送信できますが、アプリケーションのどのインスタンスが通知を受信するかはわかりません。複数のメッセージを送信してページを更新すると、受信メッセージを確認できます。

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Java 8 の App Engine スタンダード環境