I'm using Go's built in http server and pat to respond to some URLs:
mux.Get("/products", http.HandlerFunc(index))
func index(w http.ResponseWriter, r *http.Request) {
// Do something.
}
I need to pass in an extra parameter to this handler function - an interface.
func (api Api) Attach(resourceManager interface{}, route string) {
// Apply typical REST actions to Mux.
// ie: Product - to /products
mux.Get(route, http.HandlerFunc(index(resourceManager)))
// ie: Product ID: 1 - to /products/1
mux.Get(route+"/:id", http.HandlerFunc(show(resourceManager)))
}
func index(w http.ResponseWriter, r *http.Request, resourceManager interface{}) {
managerType := string(reflect.TypeOf(resourceManager).String())
w.Write([]byte(fmt.Sprintf("%v", managerType)))
}
func show(w http.ResponseWriter, r *http.Request, resourceManager interface{}) {
managerType := string(reflect.TypeOf(resourceManager).String())
w.Write([]byte(fmt.Sprintf("%v", managerType)))
}
How can I send in an extra paramter to the handler function?
You should be able to do what you wish by using closures.
Change func index() to the following (untested):
func index(resourceManager interface{}) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
managerType := string(reflect.TypeOf(resourceManager).String())
w.Write([]byte(fmt.Sprintf("%v", managerType)))
}
}
And then do the same to func show()
Another option is to use types implementing http.Handler directly rather than only using functions. For example:
type IndexHandler struct {
resourceManager interface{}
}
func (ih IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
managerType := string(reflect.TypeOf(ih.resourceManager).String())
w.Write([]byte(fmt.Sprintf("%v", managerType)))
}
...
mux.Get(route, IndexHandler{resourceManager})
This kind of pattern can be useful if you want to refactor your ServeHTTP handler method into multiple methods.
Related
could anyone help me here please as I'm new to golang? I have a yaml file which looks like this:
port: 5000
handlers:
- name: test1
uri: /api/test1
response:
status: 200
body: test1
- name: test2
uri: /api/test2
response:
status: 500
body: test2
based on this file I want to create a server. Currently I'm trying to do it this way, but looks like it doesn't work as expected.
What am I doing wrong and what is the better way to achieve what I need?
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"gopkg.in/yaml.v2"
)
func main() {
config := parseYaml("conf.yaml")
configHandlers := config.Handlers
mux := http.NewServeMux()
for _, handler := range *configHandlers {
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(*handler.Response.Status)
fmt.Fprintf(w, *handler.Response.Body)
})
}
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", *config.Port), mux))
}
type YamlConfig struct {
Port *string `yaml:"port"`
Handlers *[]HandlerConfig `yaml:"handlers"`
}
type HandlerConfig struct {
Uri *string `yaml:"uri"`
Name *string `yaml:"name"`
Response *Response `yaml:"response"`
}
type Response struct {
Status *int `yaml:"status"`
Body *string `yaml:"body"`
}
func (c *YamlConfig) parseYaml(data []byte) error {
return yaml.Unmarshal(data, c)
}
func parseYaml(path string) YamlConfig {
data, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal(err)
}
var config YamlConfig
if err := config.parseYaml(data); err != nil {
log.Fatal(err)
}
return config
}
Update:
If I run this server then regardless of which endpoint I hit, it will always return me 500 and test2 in body
What you're seeing is seemingly a common pitfall for people:
configHandlers := config.Handlers
mux := http.NewServeMux()
for _, handler := range *configHandlers {
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(*handler.Response.Status)
fmt.Fprintf(w, *handler.Response.Body)
})
}
The for loop, on each iteration, reassigns the handler variable. In the loop body, you create a new function and pass it to mux.HandlerFun. These function bodies kind of inherit the outer scope, and access this handler variable. The variable is reassigned outside of the functions, and thus the values each handler function has access to changes with it. What you can do to address the issue is mask the handler variable the loop uses, and create a scope that is unique to each handler. The classic way in languages like JavaScript (where this is - or used to be back when I wrote some JS - a common issue) is to wrap the code in an IIFE (Immediately Invoked Function Expression):
for _, handler := range *configHandlers {
func (handler *HandlerConfig) { // handler is now the argument passed to this function
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(*handler.Response.Status)
fmt.Fprintf(w, *handler.Response.Body)
})
}(handler) // call the function with the _current_ value of handler
}
This is a tad messy, and because golang is properly block-scoped, you can just do this:
for _, handler := range *configHandlers {
h := handler // create a variable in the inner scope
mux.HandleFunc(*handler.Uri, func(w http.ResponseWriter, r *http.Request) {
// now h will reference a copy unique to each iteration
w.WriteHeader(*h.Response.Status)
fmt.Fprintf(w, *h.Response.Body)
})
}
That ought to fix it. I've noticed some weirdness with your use of pointers in the types you've added to your question, though... Fields like Port being of type *string? Why wouldn't you just use string? No Same for the Body and Status fields in the Response type. By changing them to plain string fields you don't have to dereference them in your handler functions. It will look a lot cleaner.
A bigger worry is this field:
Handlers *[]HandlerConfig `yaml:"handlers"`
I'm not sure if you really know what the type of this field is, but it makes next to no sense. Handlers is now a pointer to a slice of HandlerConfig values. I'm assuming you wanted this field to be:
// Handlers is a slice of HandlerConfig values:
Handlers []HandlerConfig `yaml:"handlers"`
// or Handlers is a slice of pointers to HandlerConfig values
Handlers []*HandlerConfig `yaml:"handlers"`
Generally speaking, a pointer to a slice, especially in a config type is bad code.
If you define a struct that will represent the configuration in your YAML file, you can unmarshall the yaml into an instantiated struct of that type using the yaml package. From there, you can reference the fields in the struct as any other struct.
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
type YamlExample struct {
FieldOne string `yaml:"fieldOne"`
NestedField struct {
Name string `yaml:"name"`
} `yaml:"nestedField"`
}
const YamlEx string = `
fieldOne: one
nestedField:
name: nestedFieldName
`
func main() {
var yamlE YamlExample
err := yaml.Unmarshal([]byte(YamlEx), &yamlE)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", yamlE)
}
Link to example.
In your case, you'd probably want to handle the routes in a struct and then reference the fields in the struct for things like route name, how to handle the body of the request, etc. If your YAML is stored in a file, you'll have to use something like the io package to read the file into a byte array that the YAML package can parse. See here for a reference.
I am learning Go and have a quick question about http handler implementation in Go.
I am asking it in a small sample code.
So assume there is a handler function called Test() as defined like below
func Test() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
name := params.Get("name")
if name == "axy" {
common.UpdateHttpResponse("Trying to updating the response", w, http.StatusBadRequest)
//return
}
w.WriteHeader(http.StatusOK)
}
}
lets say the UpdateHttpResponse method is also define in common package as below:
func LogExtractionErrorResponse(errMsg string, w http.ResponseWriter, responseHeader int) {
fmt.Printf("%s", errMsg)
jsonErrorOut := map[string]string{
"Error": errMsg,
}
w.WriteHeader(responseHeader)
encodedResponse, _ := json.Marshal(jsonErrorOut)
if w != nil {
w.Write(encodedResponse)
}
}
I call the Test() HTTP handler in the http server part as below.
// this is how Test() http handler is called as well
http.HandleFunc("/test", httpserver.Test())
So here is my question:
Based on my understanding all values are passed by value in go (as discussed in this thread as well)
In that case why if the http handler is called with a parameter (i.e localhost:PORT_NUM/test?name=axy), i observe "StatusBadRequest" in resonse. In other words, why the commented "return" keyword is not needed and why the header response is not overwritten by "w.WriteHeader(http.StatusOK)" at the end of Test() http handler?
I am writing a golang program using revel framework, in which i need to check the initial timestamp of a http request.
I know how to do it in C# :
HttpContextWrapper context = Request.Properties["MS_HttpContext"] as HttpContextWrapper;
DateTime t2 = context.Timestamp.ToUniversalTime();
Didn't get much how to do it in Go.
HttpContext class in .Net framework sets the timestamp when a request arrives at the server. You might as well store the timestamp in the first line of your request handler function.
The simplest thing to do is grab the current time within your handler.
type Handler struct {
}
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rs := time.Now().UTC()
//TODO: Use the time.
}
If you want to measure the time taken by all middleware preceding your handler, then you can update the Go context and place your middleware at the start of your middleware chain.
Here's an example of what that middleware might look like:
package timemiddleware
import (
"context"
"net/http"
"time"
)
// New returns new middleware which tracks the time that a request started.
func New(next http.Handler) http.Handler {
return handler{
next: next,
}
}
type key int
const id = key(1)
type handler struct {
next http.Handler
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), id, time.Now().UTC())
h.next.ServeHTTP(w, r.WithContext(ctx))
}
// GetTime returns time from the current request, where it has previously been added by the middleware.
func GetTime(r *http.Request) (t time.Time, ok bool) {
v := r.Context().Value(id)
t, ok = v.(time.Time)
return
}
You'd use this as per this example:
package main
import (
"fmt"
"net/http"
"time"
"github.com/xxxxx/timemiddleware"
)
func main() {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second * 5)
w.Write([]byte("Hello"))
if t, ok := timemiddleware.GetTime(r); ok {
fmt.Println(t)
fmt.Println(time.Now().UTC())
}
})
h := timemiddleware.New(next)
fmt.Println(http.ListenAndServe("0.0.0.0:8080", h))
}
I have a set of requests handlers like the one below:
func GetProductsHandler(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
products := db.GetProducts()
// ...
// return products as JSON array
}
How do I test them the right way? Should I send mock ResponseWriter and Request objects to the function and see the results?
Are there tools to mock request and response objects in Go to simplify the process without having to start server before testing?
Go provides a mock writer for use in testing handlers. The standard library documentation provides an example:
package main
import (
"fmt"
"net/http"
"net/http/httptest"
)
func main() {
handler := func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "something failed", http.StatusInternalServerError)
}
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)
fmt.Printf("%d - %s", w.Code, w.Body.String())
}
I think having a global dependency (db) throws a wrench into clean unit testing. Using go your test could reassign a value, masking, the global value of db.
Another strategy (my preferred) is to package your handler in a struct, which has a db attribute..
type Handlers struct {
db DB_INTERFACE
}
func (hs *Handlers) GetProductsHandler(w http.ResponseWriter, req *http.Request) {...}
This way your test can instantiate a Handlers with a stub db object which will allow you to create IO free unit tests.
I'm using httprouter to set up an API endpoint, but I'm having some trouble getting it to output a response correctly.
My main file as it stands:
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"infrastructure/routing"
"log"
"net/http"
)
func testPrint(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
fmt.Fprint(w, "Test output.")
}
func main() {
fmt.Printf("\n************************************\n")
fmt.Printf("MySite Application Server\n")
fmt.Printf("Version: %s", "Unversioned Development Build\n")
fmt.Printf("************************************\n\n")
router := httprouter.New()
//////Records//////
router.GET("/test", testPrint)
router.GET("/records", routing.RecordList)
router.GET("/records/id/:id", routing.RecordGetById)
router.GET("/records/name/:name", routing.RecordGetByName)
router.GET("/records/search/*search-string", routing.RecordSearch)
router.HEAD("/records/:id", routing.RecordGetExists)
router.POST("/records", routing.RecordCreate)
router.PATCH("/records", routing.RecordUpdate)
router.PUT("/records", routing.RecordUpsert)
router.DELETE("/records/:id", routing.RecordDelete)
router.DELETE("/records/:id/undelete", routing.RecordUndelete)
router.DELETE("/records/:id/purge", routing.RecordPurge)
fmt.Printf("Listening on port 8080...\n")
log.Fatal(http.ListenAndServe(":8080", router))
}
Most of the handler functions are located in infrastructure/routing, but this is where the problem starts. The code for routing is currently:
package routing
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
)
func RecordList(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
//Returns a list of records.
fmt.Fprint(w, "Returned a list of records.")
}
func RecordGetById(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Return a record with the specified ID.
w.Write([]byte("Returned a record by its ID."))
}
func RecordGetByName(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Returns a record with the specified string in the name field.
w.Write([]byte("Returned a record by its name."))
}
func RecordSearch(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Full text search for the search term provided.
w.Write([]byte("Search results."))
}
func RecordGetExists(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Returns whether a record exists.
w.Write([]byte("Returns whether a record exists. Should usually return 200 or 404."))
}
func RecordCreate(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Creates new records from JSON data in request body.
w.Write([]byte("Creates new records."))
}
func RecordUpdate(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Updates records using JSON data in request body.
w.Write([]byte("Updates existing records."))
}
func RecordUpsert(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Updates existing records and creates new ones using JSON data in request body.
w.Write([]byte("Upserts records."))
}
func RecordDelete(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Marks the record with the specified ID as deleted.
w.Write([]byte("Marks records as deleted."))
}
func RecordUndelete(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Marks the record with the specified ID as undeleted.
w.Write([]byte("Marks deleted records as undeleted."))
}
func RecordPurge(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
//Permanently deletes the record with the specified ID.
w.Write([]byte("Permanently deletes record with given ID."))
}
When I make a request to localhost:8080/test, it calls the handler function in the main package and prints "Test output.". But any of the other defined URLs return a 200 OK status code and an empty (0 bytes) response body instead of the expected text.
I'm fairly sure this is something to do with the ResponseWriter object being passed through but I can't figure out why it works for one function in the same package but not another function in a different package, even though both functions have a copy of the writer passed through. I've tried a few different ways of writing including fmt.FPrintf, io.WriteString, and w.Write, to no avail.
Well, as it turns out when I was building my code for whatever reason it wasn't keeping the object files updated. I've been using LiteIDE for a while and this is the first time clicking Build on all relevant packages individually hasn't worked.
I cleaned it and then used Build All, now it works.