wheatandcatの開発ブログ

React Nativeで開発しているペペロミア & memoirの技術系記事を投稿してます

GAE、Cloud Tasks、Cloud Functionの組み合わせでPush通知の送信を作成

memoirでPush通知の実装を行ったので、その構成について紹介

Pull Request

使用する技術

Push通知はAPIと同期させる実行する必要が無かったので、以下の手順で実行させる - GAEからCloud Tasksのタスクを作成 → Cloud TasksでCloud Functionsの実行を管理 → Cloud FunctionsでPush通知を送信

f:id:wheatandcat:20210702002148p:plain:w480

実装

まず、Push通知を送信する Cloud Functions を作成

memoir-notification/function.go

package memoirnotification

import (
    "encoding/json"
    "net/http"

    expo "github.com/oliveroneill/exponent-server-sdk-golang/sdk"
)

type NotificationRequest struct {
    Token     []string `json:"token"`
    Title     string   `json:"title"`
    Body      string   `json:"body"`
    URLScheme string   `json:"urlScheme"`
}

func SendNotification(w http.ResponseWriter, r *http.Request) {
    param := NotificationRequest{}

    if err := json.NewDecoder(r.Body).Decode(&param); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    to := []expo.ExponentPushToken{}
    for _, token := range param.Token {
        pushToken, err := expo.NewExponentPushToken(token)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        to = append(to, pushToken)

    }

    client := expo.NewPushClient(nil)

    _, err := client.Publish(
        &expo.PushMessage{
            To:       to,
            Body:     param.Body,
            Data:     map[string]string{"urlScheme": param.URLScheme},
            Sound:    "default",
            Title:    param.Title,
            Priority: expo.DefaultPriority,
        },
    )

    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

Push通知は以下のExpo NotificationのGoのSDKを使用

github.com

実装はシンプルにNotificationのトークンメッセージ内容メッセージのパラメータをRequestパラメータに設定してPostするのみです。

こちらを以下のコマンドデプロイして、Cloud Functionsに関しては完了

$ gcloud functions deploy SendNotification --runtime go113 --trigger-http --region asia-northeast1

次にCloud Tasksの設定していきます。以下のコマンドでタスクのキューを作成

 $ gcloud tasks queues create sendNotification

以下のコマンドで作成タスクの情報を確認

$ gcloud tasks queues describe sendNotification

以下みたいに表示されます

rateLimits:
  maxBurstSize: 100
  maxConcurrentDispatches: 1000
  maxDispatchesPerSecond: 500.0
retryConfig:
  maxAttempts: 100
  maxBackoff: 3600s
  maxDoublings: 16
  minBackoff: 0.100s
state: RUNNING

設定はこちらから確認できます。

cloud.google.com

デフォルトだと、maxAttempts(最大試行回数): 100回、maxBackoff(リトライ間隔の最小の時間):0.1秒、maxBackoff(リトライ間隔の最大の時間):1時間、maxDoublings(最大倍数回数): 16回と、結構手厚い設定になっているので、今回のPush通知は必須の処理では無いので、以下のコマンドで設定を変更しました。

$ gcloud tasks queues update sendNotification --max-attempts 6 --min-backoff 5s --max-doublings 3

これでCloud Tasksの設定も完了です。 次はGAEからタスクを作成を実装していきます。

client/task/task.go

package task

import (
    "context"
    "encoding/json"
    "fmt"
    "os"

    cloudtasks "cloud.google.com/go/cloudtasks/apiv2"
    taskspb "google.golang.org/genproto/googleapis/cloud/tasks/v2"
)

type NotificationRequest struct {
    Token     []string `json:"token"`
    Title     string   `json:"title"`
    Body      string   `json:"body"`
    URLScheme string   `json:"urlScheme"`
}

type HTTPTaskInterface interface {
    PushNotification(r NotificationRequest) (*taskspb.Task, error)
}

type HTTPTask struct {
    ProjectID  string
    LocationID string
    QueueID    string
    URL        string
}

func NewNotificationTask() HTTPTaskInterface {
    return &HTTPTask{
        ProjectID:  os.Getenv("GCP_PROJECT_ID"),
        LocationID: os.Getenv("GCP_LOCATION_ID"),
        QueueID:    os.Getenv("NOTIFICATION_QUEUE_ID"),
        URL:        os.Getenv("NOTIFICATION_URL"),
    }

}

func (t *HTTPTask) PushNotification(r NotificationRequest) (*taskspb.Task, error) {
    ctx := context.Background()
    client, err := cloudtasks.NewClient(ctx)
    if err != nil {
        return nil, fmt.Errorf("NewClient: %v", err)
    }
    defer client.Close()

    body, err := json.Marshal(r)
    if err != nil {
        return nil, err
    }

    // Build the Task queue path.
    queuePath := fmt.Sprintf("projects/%s/locations/%s/queues/%s", t.ProjectID, t.LocationID, t.QueueID)

    req := &taskspb.CreateTaskRequest{
        Parent: queuePath,
        Task: &taskspb.Task{
            MessageType: &taskspb.Task_HttpRequest{
                HttpRequest: &taskspb.HttpRequest{
                    HttpMethod: taskspb.HttpMethod_POST,
                    Url:        t.URL,
                    Body:       []byte(body),
                },
            },
        },
    }

    createdTask, err := client.CreateTask(ctx, req)
    if err != nil {
        return nil, fmt.Errorf("cloudtasks.CreateTask: %v", err)
    }

    return createdTask, nil
}

まず、Cloud Tasksで使用するGCPの情報を設定します。(URLには 上で作成したCloud FunctionsのURLを設定)

 return &HTTPTask{
        ProjectID:  os.Getenv("GCP_PROJECT_ID"),
        LocationID: os.Getenv("GCP_LOCATION_ID"),
        QueueID:    os.Getenv("NOTIFICATION_QUEUE_ID"),
        URL:        os.Getenv("NOTIFICATION_URL"),
    }

そして、以下でタスクを作成しています。

 req := &taskspb.CreateTaskRequest{
        Parent: queuePath,
        Task: &taskspb.Task{
            MessageType: &taskspb.Task_HttpRequest{
                HttpRequest: &taskspb.HttpRequest{
                    HttpMethod: taskspb.HttpMethod_POST,
                    Url:        t.URL,
                    Body:       []byte(body),
                },
            },
        },
    }

    createdTask, err := client.CreateTask(ctx, req)
    if err != nil {
        return nil, fmt.Errorf("cloudtasks.CreateTask: %v", err)
    }

これにより、タスクが作成されPush通知が送信されるようになります。 以下、動作確認の動画です

www.youtube.com