0

I am learning GO and wanted to make a simple rest API.

What I want to do is fire off a goroutine after handling the api request and do the work asynchronously in the background.

Here's my implementation so far:

package main

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

    "github.com/julienschmidt/httprouter"
)

// APIResponse represents common structure of every api call response
type APIResponse struct {
    Status string `json:"status"`
    Error  string `json:"error,omitempty"`
    Data   string `json:"data,omitempty"`
}

// Action represents what could be passed to the goroutine
type Action = func()

// ActionQueue represents a buffered channel
type ActionQueue = chan Action

func main() {
    r := httprouter.New()
    r.GET("/test", test)

    var apiServerPort = ":80"
    err := http.ListenAndServe(apiServerPort, r)
    if err != nil {
        log.Fatal("ListenAndServe:", err)
    } else {
        log.Printf("Started server on port %s", apiServerPort)
    }

    var queueSize = 10
    queue := make(ActionQueue, queueSize)
    for i := 0; i < queueSize; i++ {
        go worker(queue)
    }
    log.Printf("Started %d queue workers", queueSize)
}

func test(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
    successResponse(w, http.StatusOK, "Hello World")
    queue <- func() {
        log.Println("Hello from queue worker, initiated by api call")
    }
}

func successResponse(w http.ResponseWriter, statusCode int, successData string) {
    sendResponse(w, statusCode, "", successData)
}

func errorResponse(w http.ResponseWriter, statusCode int, errorData string) {
    sendResponse(w, statusCode, errorData, "")
}

func sendResponse(w http.ResponseWriter, statusCode int, errorData string, responseData string) {
    w.WriteHeader(statusCode)
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(&APIResponse{Status: http.StatusText(statusCode), Error: errorData, Data: responseData})
}

func worker(queue ActionQueue) {
    for action := range queue {
        action()
    }
}

When I try to run this code, I am getting the following error (on this line queue <- func() { ... }):

./main.go:46:2: undefined: queue

How can I make the queue channel available to my request handler (i.e. httprouter GET request handler func)?

Secondly, I cannot see the output from my log.Printf() calls in the console output (stdout), e.g. server status message when the app runs. Any ideas why?

Latheesan
  • 23,247
  • 32
  • 107
  • 201
  • 3
    *"How can I make the queue channel available to my request handler"* by making it a global (not recommended), or by defining the handler as a struct and have the queue be a field of the struct, or using a closure. – mkopriva Jul 25 '18 at 21:50
  • 4
    `http.ListenAndServe` blocks until a server error occurs, and so all the code after it is idle, and will be executed only after http.ListenAndServe returns, therefore log.Printf is not executed. – mkopriva Jul 25 '18 at 21:52
  • @mkopriva that makes sense. So, if i want to do simple debug output, what's the best method? – Latheesan Jul 25 '18 at 21:53
  • Move your queue code above http.ListenAndServe. – mkopriva Jul 25 '18 at 21:58
  • @mkopriva I have tried that and still not getting anything in the stdout from `log.Printf()` call – Latheesan Jul 25 '18 at 22:03
  • Try moving the printf statement above listenandserve, if that doesn't output anything then try moving it all the way to the first line of the `main` func. If that doesn't work then that would suggest that some code you're not showing, maybe something you're importing is reseting the log package's default output. In which case remove that or overwrite it with https://golang.org/pkg/log/#SetOutput passing os.Stdout as the argument. – mkopriva Jul 25 '18 at 22:11

1 Answers1

1

Couple of things are wrong, first your main function is not ordered correctly, you want to initialize you channel and start workers before you run err := http.ListenAndServe(apiServerPort, r) so something like this

func main() {
    var queueSize = 10
    queue := make(ActionQueue, queueSize)
    for i := 0; i < queueSize; i++ {
        go worker(queue)
    }
    log.Printf("Started %d queue workers", queueSize)

    // routers and stuff...
}

Then queue variable is not defined in the test() function, which is why you are getting ./main.go:46:2: undefined: queue. You could fix it with a higher order function e.g.

func test(queue ActionQueue) httprouter.Handle {
    return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
        successResponse(w, http.StatusOK, "Hello World")
        queue <- func() {
            log.Println("Hello from queue worker, initiated by api call")
        }
    }
}

and then just bind it to the route with r.GET("/test", test(queue))

DarkPhoton
  • 154
  • 2
  • 8