10

I've got a push task queue in a Go App Engine application. When we try to enqueue tasks in testing for whatever reason the tasks always return 404.

Our app.yaml:

runtime: go
api_version: go1.9

handlers:
 - url: /worker/.*
   script: _go_app
   login: admin
 - url: /.*
   script: _go_app

The actual task invocation:

func Handler(w http.ResponseWriter, r *http.Request) {
    ctx := appengine.NewContext(r)
    t := taskqueue.NewPOSTTask("/worker", map[string][]string{"key": {"val"}})
    _, err := taskqueue.Add(ctx, t, "")
    if err != nil {
        log.Errorf(ctx, "Failed to add task");
    }
    fmt.Fprintf(w, "Success");
}

A still-incomplete handler, but it exists!

func Worker(w http.ResponseWriter, r *http.Request) {
    ctx := appengine.NewContext(r)
    log.Infof(ctx, "Worker succeeded")
}

and finally, proof that we actually added the path to our router:

func init() {
    http.HandleFunc("/", Handler)
    http.HandleFunc("/worker", Worker)
}

When we actually run tests, we always get the following logging output:

INFO     2018-05-03 09:51:11,794 module.py:846] default: "POST /worker HTTP/1.1" 404 19
WARNING  2018-05-03 09:51:11,794 taskqueue_stub.py:2149] Task � failed to execute. This task will retry in 0.100 seconds
INFO     2018-05-03 09:51:11,897 module.py:846] default: "POST /worker HTTP/1.1" 404 19
WARNING  2018-05-03 09:51:11,897 taskqueue_stub.py:2149] Task � failed to execute. This task will retry in 0.200 seconds
INFO     2018-05-03 09:51:12,101 module.py:846] default: "POST /worker HTTP/1.1" 404 19
WARNING  2018-05-03 09:51:12,101 taskqueue_stub.py:2149] Task � failed to execute. This task will retry in 0.400 seconds

Note that the /worker endpoint returns 302 when I try to ping it via an API client like Paw, so the route seems to have been configured correctly. The 404 only arises when I try to run things in a test.

Why is this returning 404? I've tried running tests around the example push queue in their documentation have run into the same issue there - is there some sort of missing configuration flag I'm failing to pass to goapp?

I've pushed up a GitHub repo with a minimal replicable example here

Venantius
  • 2,471
  • 2
  • 28
  • 36
  • Did you register your `mux`? How? Please show that code. – icza May 03 '18 at 11:38
  • Also `api_version` must be `go1`, this is not the Go's version. – icza May 03 '18 at 11:41
  • `go1.9` is a supported `api_version`, but it's currently on beta support. Refer to https://cloud.google.com/appengine/docs/standard/go/config/appref#api_version – Venantius May 03 '18 at 12:35
  • Added code showing where `mux` is registered. – Venantius May 03 '18 at 12:35
  • Do your other handlers work? If you don't have other handlers, please add one for testing purposes. – icza May 03 '18 at 13:03
  • All of my normal HTTP handlers work fine - I've got an SPA frontend that proxies to them and haven't had any issues so far. I didn't include the code for them because I haven't had any issues with them and didn't want to introduce any confusion. They're added to the same mux the same way. – Venantius May 03 '18 at 13:14
  • Also added an extra note - just tried out the endpoint via Paw and got a 302, indicating at the very least that the `app.yaml` configuration is correct. – Venantius May 03 '18 at 13:23
  • What is the Return URL for the 302 response? – Tarun Lalwani May 06 '18 at 10:19
  • `http://localhost:8080/_ah/login?continue=http%3A//localhost%3A8080/worker/settle` – Venantius May 06 '18 at 10:26
  • And is that a 404 locally? If so that may be your problem – Tarun Lalwani May 06 '18 at 16:29
  • @TarunLalwani sorry, that URL happens to be incorrect because I was tweaking the routes, but yes - regardless of what's specified, it's correct, because that URL is auto-generated by App Engine when you set `login: admin` in `app.yaml` on a particular route. – Venantius May 06 '18 at 17:09

2 Answers2

4

The order for running goapp is top-bottom but you need to be specific in your app.yaml. In your case this will works:

package main

import (
    "fmt"
    "net/http"

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

func Handler(w http.ResponseWriter, r *http.Request) {
    ctx := appengine.NewContext(r)
    t := taskqueue.NewPOSTTask("/worker", map[string][]string{"key": {"val"}})
    _, err := taskqueue.Add(ctx, t, "")
    if err != nil {
        log.Errorf(ctx, "Failed to add task")
    }
    fmt.Fprintf(w, "Success")
}

func Worker(w http.ResponseWriter, r *http.Request) {
    ctx := appengine.NewContext(r)
    log.Infof(ctx, "Worker succeeded")
}

func init() {
    http.HandleFunc("/", Handler)
    http.HandleFunc("/worker", Worker)
}

For this you need to map url like:

runtime: go
api_version: go1.9

handlers:
 - url: /worker
   script: _go_app
   login: admin
 - url: /.*
   script: _go_app

The result is:

enter image description here

See that worker is running two times. This is ocurring because GET /favicon.ico is entering in GET /.* mapping. So this is only details for you!

UPDATE (05/14/2018): In your test you use aetest.NewInstance(), that runs dev_appserver.py in ioutil.TempDir("", "appengine-aetest"), that writes your own main.go and app.yaml. See above in instance_vm.go:

i.appDir, err = ioutil.TempDir("", "appengine-aetest")
if err != nil {
    return err
}
defer func() {
    if err != nil {
        os.RemoveAll(i.appDir)
    }
}()
err = os.Mkdir(filepath.Join(i.appDir, "app"), 0755)
if err != nil {
    return err
}
err = ioutil.WriteFile(filepath.Join(i.appDir, "app", "app.yaml"), []byte(i.appYAML()), 0644)
  if err != nil {
    return err
}
err = ioutil.WriteFile(filepath.Join(i.appDir, "app", "stubapp.go"), []byte(appSource), 0644)
if err != nil {
    return err
}

//... others codes

const appYAMLTemplate = `
application: %s
version: 1
runtime: go
api_version: go1
vm: true
handlers:
- url: /.*
  script: _go_app
`

const appSource = `
package main
import "google.golang.org/appengine"
func main() { appengine.Main() }
`

So you need to create your own server-instance. This is a way:

//out buffer
var out bytes.Buffer
//start server
c := exec.Command("goapp", "serve") //default port=8080, adminPort=8000
c.Stdout = &out
c.Stderr = &out
c.Start()
defer c.Process.Kill()
//delay to wait server is completed
time.Sleep(10 * time.Second)

//... others codes

//quit server
quitReq, err := http.NewRequest("GET", "http://localhost:8000/quit", nil)
_, err := client.Do(quitReq)
if err != nil {
    fmt.Errorf("GET /quit handler error: %v", err)
}

To test your Handler function do:

//create request (testing Handler func)
req, err := http.NewRequest("GET", "http://localhost:8080/", nil)
if err != nil {
    t.Fatal(err.Error())
}
//do GET
client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
    t.Error(err)
}
defer resp.Body.Close()
//delay to wait for the worker to execute
time.Sleep(10 * time.Second)

To retrieve the result and test it:

//read response
b, _ := ioutil.ReadAll(resp.Body)
resp_content := string(b)
//checking
if !strings.Contains(resp_content, "Handler Success") {
    t.Errorf("Handler not working")
}
//log server content
logserver := out.String()
if !strings.Contains(logserver, "Worker succeeded") {
    t.Errorf("Worker not working")
}
//log response
t.Logf(logserver)

The result is:

screenshot - test result

UPDATE: Link for Github: https://github.com/ag-studies/go-appengine-sample

Hope this help!!

Aristofanio Garcia
  • 1,103
  • 8
  • 13
  • Can you provide a GitHub repository with a working example? The code you've provided still has the same error on my machine, though we could have written our test differently. – Venantius May 12 '18 at 16:30
  • Updated with link for github. – Aristofanio Garcia May 12 '18 at 22:04
  • That repo doesn't seem to have a test file. This question is specifically about running tests. Were your logs from tests or just from running a dev server? – Venantius May 12 '18 at 22:33
3

Your handlers pattern is /worker/.* but you are issuing tasks to /worker.

You should do 1 of the following:

  1. Either change pattern to /worker.* or just /worker
  2. Or issue request(s) to /worker/ or /worker/some-task-name.
Alexander Trakhimenok
  • 6,019
  • 2
  • 27
  • 52
  • Did you register your task handler? Can you provide code for this? – Alexander Trakhimenok May 08 '18 at 10:43
  • All of the code for registering handlers is included both in the question and in the linked GitHub repo. – Venantius May 08 '18 at 10:48
  • Should work when running with `goapp serve`. What do you mean by "running tests"? Are your refereing to `aetest`? If so I think there is known issue with supporting task queues there. But `goapp serve` should work after fixing pattern. – Alexander Trakhimenok May 08 '18 at 11:09
  • Yes, this works fine with `goapp serve`. I do mean using `aetest` // `goapp test`. – Venantius May 08 '18 at 11:11
  • I couldn't find any reference to known issues on supporting task queues on Google (other than supporting custom task queues, which is different from this issue). – Venantius May 08 '18 at 11:12
  • I’ve seen your issue https://github.com/golang/appengine/issues/134 and did not realize it was yours. Never tested queues with aetes but I recall for Python we had to implement custom hack and call in test something like custom process_queues. So I guess it can be similar with Go. – Alexander Trakhimenok May 08 '18 at 11:19