A Go Task Queue Example

This example creates an app that displays an HTML form. You enter a string into the dialog box and click Add. The app counts the number of times that you enter any string in this way.

The app does these things:

  • When you click Add, the form uses an HTTP POSTrequest to send the string to the app which is running on App Engine. There the app bundles the string into a task and sends it to the default queue.
  • The queue forwards the task to an included task handler, mapped to the URL /worker, which asynchronously writes the string to a datastore.
  • Sending an HTTP GET request displays a list of the strings you have entered and the number of times you haveAdded each string, either by typing it or by clicking on it in the dropdown box.

To deploy this app to App Engine:

  1. Copy the following into a file named queue.yaml. This changes the rate at which tasks will be processed from the default 5 per second to 3 per second.

    queue:
    - name: default
      rate: 3/s
    
  2. In the same directory, copy the following into a file named as you like (ending in .go). This is the application code, including the task handler.

    
    package counter
    
    import (
    	"html/template"
    	"net/http"
    
    	"google.golang.org/appengine"
    	"google.golang.org/appengine/datastore"
    	"google.golang.org/appengine/log"
    	"google.golang.org/appengine/taskqueue"
    )
    
    func init() {
    	http.HandleFunc("/", handler)
    	http.HandleFunc("/worker", worker)
    }
    
    type Counter struct {
    	Name  string
    	Count int
    }
    
    func handler(w http.ResponseWriter, r *http.Request) {
    	ctx := appengine.NewContext(r)
    	if name := r.FormValue("name"); name != "" {
    		t := taskqueue.NewPOSTTask("/worker", map[string][]string{"name": {name}})
    		if _, err := taskqueue.Add(ctx, t, ""); err != nil {
    			http.Error(w, err.Error(), http.StatusInternalServerError)
    			return
    		}
    	}
    	q := datastore.NewQuery("Counter")
    	var counters []Counter
    	if _, err := q.GetAll(ctx, &counters); err != nil {
    		http.Error(w, err.Error(), http.StatusInternalServerError)
    		return
    	}
    	if err := handlerTemplate.Execute(w, counters); err != nil {
    		http.Error(w, err.Error(), http.StatusInternalServerError)
    		return
    	}
    	// OK
    }
    
    func worker(w http.ResponseWriter, r *http.Request) {
    	ctx := appengine.NewContext(r)
    	name := r.FormValue("name")
    	key := datastore.NewKey(ctx, "Counter", name, 0, nil)
    	var counter Counter
    	if err := datastore.Get(ctx, key, &counter); err == datastore.ErrNoSuchEntity {
    		counter.Name = name
    	} else if err != nil {
    		log.Errorf(ctx, "%v", err)
    		return
    	}
    	counter.Count++
    	if _, err := datastore.Put(ctx, key, &counter); err != nil {
    		log.Errorf(ctx, "%v", err)
    	}
    }
    
    var handlerTemplate = template.Must(template.New("handler").Parse(handlerHTML))
    
    const handlerHTML = `
    {{range .}}
    <p>{{.Name}}: {{.Count}}</p>
    {{end}}
    <p>Start a new counter:</p>
    <form action="/" method="POST">
    <input type="text" name="name">
    <input type="submit" value="Add">
    </form>
    `
    
  3. In the same directory, copy the following into a file named app.yaml. This configures your application for App Engine:

    runtime: go
    api_version: go1
    
    handlers:
    - url: /worker/.*
      script: _go_app
      login: admin
    - url: /.*
      script: _go_app
  4. Make sure you have a Google Cloud Platform project with an App Engine app prepared and that you have initialized and configured the gcloud command for that project.

  5. Use the gcloud app deploy command to deploy the app to App Engine.

  6. See the app in action by using the gcloud app browse command.