使用GAE/Go实现使用TaskQueue的API

在之前的文章中,我抱怨说在Google App Engine中无法创建一个只返回响应并在另一进程中执行繁重处理的API。但是后来有人告诉我,可以通过使用TaskQueue来实现这一点。于是我决定亲自尝试一下。

源代码在这里

克大猩猩/ GAE API示例

不使用TaskQueue进行实施(NG模式)

之前的实现大致是这样的。
使用这种方法时,当在goroutine内部尝试使用context时,会发生无效的安全凭证(上下文已过期)错误。

package main

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

  "google.golang.org/appengine"
  "google.golang.org/appengine/log"
  "google.golang.org/appengine/urlfetch"

  "google.golang.org/appengine/taskqueue"
)

const url = "https://www.google.com/"

type Response struct {
  Status string `json:"status"`
  Message string `json:"message"`
}

func main() {
  http.HandleFunc("/back", helloBackground)
  appengine.Main()
}

// NGパターン
func helloBackground(w http.ResponseWriter, r *http.Request) {
  // URLにリクエストして返ってきたステータスをログに残すgoroutine
  go func() {
    ctx := appengine.NewContext(r) // App Engineのcontext作成
    client := urlfetch.Client(ctx) // contextを使ってhttp.Clientを作成
    resp, err := client.Get(url)
    if err != nil {
      log.Errorf(ctx, "TEST: helloBackground: %s", err)
      return
    }
    log.Infof(ctx, "'%s' returns %s", url, resp.Status)
  }()

  // レスポンス
  json.NewEncoder(w).Encode(Response{
    Status: "ok",
    Message: "processing in background.",
  })
}

用 TaskQueue 进行实现

使用TaskQueue修复它会变成这样。

package main

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

  "google.golang.org/appengine"
  "google.golang.org/appengine/log"
  "google.golang.org/appengine/urlfetch"

  "google.golang.org/appengine/taskqueue"
)

const url = "https://www.google.com/"

type Response struct {
  Status string `json:"status"`
  Message string `json:"message"`
}

func main() {
  http.HandleFunc("/back", helloBackground)
  http.HandleFunc("/back/worker", helloBackgroundWorker)
  appengine.Main()
}

// レスポンスだけ速攻で返して時間のかかる処理はTask Queueに渡すAPI(10分 or 24時間でタイムアウト)
func helloBackground(w http.ResponseWriter, r *http.Request) {
  ctx := appengine.NewContext(r) // App Engineのcontext作成

  // タスクをTaskQueueに投げる
  t := taskqueue.NewPOSTTask("/back/worker", map[string][]string{})
  if _, err := taskqueue.Add(ctx, t, ""); err != nil {
    http.Error(w, "internal server error", http.StatusInternalServerError)
    return
  }

  // レスポンス
  json.NewEncoder(w).Encode(Response{
    Status: "ok",
    Message: "processing in background.",
  })
}

// URLにリクエストして返ってきたステータスをログに残すworker
func helloBackgroundWorker(w http.ResponseWriter, r *http.Request) {
  ctx := appengine.NewContext(r) // App Engineのcontext作成
  client := urlfetch.Client(ctx) // contextを使ってhttp.Clientを作成
  resp, err := client.Get(url)
  if err != nil {
    log.Errorf(ctx, "TEST: helloBackground: %s", err)
    return
  }
  log.Infof(ctx, "'%s' returns %s", url, resp.Status)

  return
}

当你在taskqueue.NewPOSTTask的第二个参数中传递一个map作为参数时,可以将参数传递给TaskQueue。

这个地方的做法可以参考这篇文章。

GAE/Go任务队列之一~推送队列~ – 轮次

广告
将在 10 秒后关闭
bannerAds