In Spring Boot app a base path can be set for all API resources with a property server.servlet.context-path. So the actual endpoint path will be server.servlet.context-path + endpoint path.
For example, if server.servlet.context-path is set to "/api/v1", and a resource is mapped to "articles", the full path to that resource is "/api/v1/articles".
Is there something like this in go-chi? Or do I have to define a route with "full" path like
r.Route("/api/v1/articles", func(r chi.Router) {...
Thanks
This is just a rough example that will hopefully point you in the direction. As you can see .Mount() accepts a pattern and then a .Router. Play around with the two and figure out how you'd like to structure it.
package main
import (
"github.com/go-chi/chi"
"net/http"
)
func main() {
r := chi.NewRouter()
r.Mount("/api", Versions())
http.ListenAndServe("localhost:8080", r)
}
func Versions() chi.Router {
r := chi.NewRouter()
r.Mount("/v1", V1())
// r.Mount("/v2", V2())
return r
}
func V1() chi.Router {
r := chi.NewRouter()
r.Mount("/user", User())
// r.Mount("/posts", Post())
return r
}
func User() chi.Router {
r := chi.NewRouter()
r.Route("/hello", func(r chi.Router){
r.Get("/", hello)
})
return r
}
func hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
}
Visiting localhost:8080/api/v1/user/hello should result in a "Hello" response.
Related
I want to use a rate limiting or throttler library to limit the number of client requests. I use a vendor library in my code base. I want to pass in a ResponseWriter, Request and a third variable retrieved from the URL. When I use the library for throttling, it gives me back a handler that only handles two arguments. How can I pass my third argument into the handler?
Here is my current code:
package main
import (
"fmt"
"github.com/didip/tollbooth"
"net/http"
)
func viewHandler(
w http.ResponseWriter,
r *http.Request,
uniqueId string,
) {
//data := getData(uniqueId)
fmt.Println("Id:", uniqueId)
p := &objects.ModelApp{LoggedUser: "Ryan Hardy", ViewData: "data"}
renderTemplate(w, "view", p)
}
//URL validation for basic web services
var validPath = regexp.MustCompile("^/$|/(home|about|view)/(|[a-zA-Z0-9]+)$")
func makeHandler(
fn func(
http.ResponseWriter,
*http.Request, string,
)) http.HandlerFunc {
return func(
w http.ResponseWriter,
r *http.Request,
) {
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil {
http.NotFound(w, r)
return
}
fn(w, r, m[2])
}
}
func main() {
http.Handle("/view/", makeHandler(tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), viewHandler)))
http.ListenAndServe(":8080", nil)
}
Could anyone help me with this?
I'm on my phone so this may be difficult to type but you could use the http.Handle function which takes an interface of Handler something like
type makeHandler struct {
YourVariable string
}
func (m *makeHandler) ServeHTTP (w http.ResponseWriter, r *http.Request) {
yourVariableYouNeed := m.YourVariable
// do whatever
w.Write()
}
// do whatever you need to get your variable
blah := &makeHandler{ yourThing }
http.Handle("/views", blah)
On my phone so can't test but it should work, let me know if it doesn't.
I'm reasonably new to golang and am trying to do work out the best way to do this idiomatically.
I have an array of routes I am statically defining and passing to gorilla/mux. I am wrapping each handler function with something to time the request and handle panics (mainly so I could understand how the wrapping worked).
I want them each to be able to have access to a 'context' - a struct that's going to be one-per-http-server, which might have things like database handles, config etc. What I don't want to do is use a static global variable.
The way I'm currently doing it I can give the wrappers access to the context structure, but I can't see how to get this into the actual handler, as it wants that to be an http.HandlerFunc. I thought what I could do is convert http.HandlerFunc into a type of my own that was a receiver for Context (and do similarly for the wrappers, but (after much playing about) I couldn't then get Handler() to accept this.
I can't help but think I'm missing something obvious here. Code below.
package main
import (
"fmt"
"github.com/gorilla/mux"
"html"
"log"
"net/http"
"time"
)
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
type Context struct {
route *Route
// imagine other stuff here, like database handles, config etc.
}
type Routes []Route
var routes = Routes{
Route{
"Index",
"GET",
"/",
index,
},
// imagine lots more routes here
}
func wrapLogger(inner http.Handler, context *Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
log.Printf(
"%s\t%s\t%s\t%s",
r.Method,
r.RequestURI,
context.route.Name,
time.Since(start),
)
})
}
func wrapPanic(inner http.Handler, context *Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic caught: %+v", err)
http.Error(w, http.StatusText(500), 500)
}
}()
inner.ServeHTTP(w, r)
})
}
func newRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
// the context object is created here
context := Context {
&route,
// imagine more stuff here
}
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(wrapLogger(wrapPanic(route.HandlerFunc, &context), &context))
}
return router
}
func index(w http.ResponseWriter, r *http.Request) {
// I want this function to be able to have access to 'context'
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}
func main() {
fmt.Print("Starting\n");
router := newRouter()
log.Fatal(http.ListenAndServe("127.0.0.1:8080", router))
}
Here's a way to do it, but it seems pretty horrible. I can't help but think there must be some better way to do it - perhaps to subclass (?) http.Handler.
package main
import (
"fmt"
"github.com/gorilla/mux"
"html"
"log"
"net/http"
"time"
)
type Route struct {
Name string
Method string
Pattern string
HandlerFunc ContextHandlerFunc
}
type Context struct {
route *Route
secret string
}
type ContextHandlerFunc func(c *Context, w http.ResponseWriter, r *http.Request)
type Routes []Route
var routes = Routes{
Route{
"Index",
"GET",
"/",
index,
},
}
func wrapLogger(inner ContextHandlerFunc) ContextHandlerFunc {
return func(c *Context, w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner(c, w, r)
log.Printf(
"%s\t%s\t%s\t%s",
r.Method,
r.RequestURI,
c.route.Name,
time.Since(start),
)
}
}
func wrapPanic(inner ContextHandlerFunc) ContextHandlerFunc {
return func(c *Context, w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic caught: %+v", err)
http.Error(w, http.StatusText(500), 500)
}
}()
inner(c, w, r)
}
}
func newRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
context := Context{
&route,
"test",
}
router.Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wrapLogger(wrapPanic(route.HandlerFunc))(&context, w, r)
})
}
return router
}
func index(c *Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q secret is %s\n", html.EscapeString(r.URL.Path), c.secret)
}
func main() {
fmt.Print("Starting\n")
router := newRouter()
log.Fatal(http.ListenAndServe("127.0.0.1:8080", router))
}
I am learning Go and currently in the middle of a nearly identical problem, and this is how I've dealt with it:
First, I think you missed an important detail: There are no global variables in Go. The widest scope you can have for a variable is package scope. The only true globals in Go are predeclared identifiers like true and false (and you can't change these or make your own).
So, it's perfectly fine to set a variable scoped to package main to hold context for your program. Coming from a C/C++ background this took me a little time to get used to. Since the variables are package scoped, they do not suffer from the problems of global variables. If something in another package needs such a variable, you will have to pass it explicitly.
Don't be afraid to use package variables when it makes sense. This can help you reduce complexity in your program, and in a lot of cases make your custom handlers much simpler (where calling http.HandlerFunc() and passing a closure will suffice).
Such a simple handler might look like this:
func simpleHandler(c Context, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// FIXME Do something with our context
next.ServeHTTP(w, r)
})
}
and be used by:
r = mux.NewRouter()
http.Handle("/", simpleHandler(c, r))
If your needs are more complex, you may need to implement your own http.Handler. Remember that an http.Handler is just an interface which implements ServeHTTP(w http.ResponseWriter, r *http.Request).
This is untested but should get you about 95% of the way there:
package main
import (
"net/http"
)
type complicatedHandler struct {
h http.Handler
opts ComplicatedOptions
}
type ComplicatedOptions struct {
// FIXME All of the variables you want to set for this handler
}
func (m complicatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// FIXME Do stuff before serving page
// Call the next handler
m.h.ServeHTTP(w, r)
// FIXME Do stuff after serving page
}
func ComplicatedHandler(o ComplicatedOptions) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return complicatedHandler{h, o}
}
}
To use it:
r := mux.NewRouter()
// FIXME: Add routes to the mux
opts := ComplicatedOptions{/* FIXME */}
myHandler := ComplicatedHandler(opts)
http.Handle("/", myHandler(r))
For a more developed handler example see basicAuth in goji/httpauth, from which this example was shamelessly ripped off.
Some further reading:
A Recap of Request Handling
Making and Using HTTP Middleware
justinas/alice (for chaining lots of handlers)
I am trying to use gorilla mux and httputil.ReverseProxy together, but when trying to get the mux.Vars it is empty. According to https://golang.org/src/net/http/httputil/reverseproxy.go?s=2744:2819#L93 it seems like the http.Request pointer is a shallow copy of the original request, which should still work.
Any ideas?
https://play.golang.org/p/JpjNvEMIFB
package main
import (
"github.com/gorilla/mux"
"log"
"net/http"
"net/http/httputil"
"net/url"
)
type route struct {
match string
base string
}
var routes = []route{
// proxy http://localhost:3000/api/foo/bar => https://api.bar.com/5/foo/bar
route{match: "/api/{path}", base: "https://api.bar.com/5"},
route{match: "/sales/{path}", base: "https://sales.bar.com/3"},
}
func NewProxy(r *route) http.Handler {
director := func(req *http.Request) {
out, _ := url.Parse(r.base)
req.URL.Scheme = out.Scheme
req.URL.Host = out.Host
req.URL.Path = out.Path + "/" + mux.Vars(req)["path"] // mux Vars are empty here
}
return &httputil.ReverseProxy{Director: director}
}
func main() {
for _, route := range routes {
http.Handle(route.match, NewProxy(&route))
}
log.Println("Listening on port 8080")
http.ListenAndServe(":8080", nil)
}
You have two different problems here.
The first one, you are not using a mux.Router, so gorilla/mux has not the opportunity to pre-process your request. In other words, the requests are going directly from http package to your reverse proxies. This issue has an easy fix:
r := mux.NewRouter()
for _, route := range routes {
r.Handle(route.match, NewProxy(&route))
}
http.Handle("/", r)
The second problem is more tricky than the first one. This issue is related to how is mux package implemented. If you look mux.Vars() implementation, you will see that it uses something called Context. A Context, as described in the official documentation, is something that stores values shared during a request lifetime. A simplified Context implementation will be:
type Context map[*http.Request]interface{}
func (c Context) Set(req *http.Request, v interface{}) {
c[req] = v
}
func (c Context) Get(req *http.Request) interface{} {
return c[req]
}
As you see, given a http.Request, we can store values in a context. Later we can retrieve these values using the same Context and the same http.Request. mux uses a global Context to store the vars parsed in routing process so that you can use the standard http.request. But, because httputil.ReverseProxy passes a copy of the actual request and Context links values by request, this new Request has no values in the Context.
To fix it, you can implement your own ReverseProxy based on httputil.ReverseProxy:
type MyReverseProxy struct {
httputil.ReverseProxy
Director func(inr, outr *http.Request)
}
func (p *MyReverseProxy) ServeHTTP(rw http.ResponseWriter, inr *http.Request) {
p.ReverseProxy.Director = func(outr *http.Request) {
p.Director(inr, outr)
}
p.ReverseProxy.ServeHTTP(rw, inr)
}
func NewProxy(r *route) http.Handler {
director := func(inr, outr *http.Request) {
out, _ := url.Parse(r.base)
outr.URL.Scheme = out.Scheme
outr.URL.Host = out.Host
outr.URL.Path = out.Path + "/" + mux.Vars(inr)["path"]
log.Printf("IN VARS: %#v\n", mux.Vars(inr)) // Now inr has proper vars
log.Printf("OUT VARS: %#v\n", mux.Vars(outr))
}
return &MyReverseProxy{Director: director}
You can even use context and keep Director declaration:
type MyReverseProxy struct {
httputil.ReverseProxy
Director func(req *http.Request)
}
func (p *MyReverseProxy) ServeHTTP(rw http.ResponseWriter, inr *http.Request) {
p.ReverseProxy.Director = func(outr *http.Request) {
context.Set(outr, "in_req", inr)
p.Director(outr)
}
p.ReverseProxy.ServeHTTP(rw, inr)
}
func NewProxy(r *route) http.Handler {
director := func(outr *http.Request) {
out, _ := url.Parse(r.base)
inr := context.Get(outr, "in_req").(*http.Request)
outr.URL.Scheme = out.Scheme
outr.URL.Host = out.Host
outr.URL.Path = out.Path + "/" + mux.Vars(inr)["path"]
log.Printf("IN VARS: %#v\n", mux.Vars(inr)) // Now inr has proper vars
log.Printf("OUT VARS: %#v\n", mux.Vars(outr))
}
return &MyReverseProxy{Director: director}
}
Both implementations seem tricky to me. They have to change httputil.ReverseProxy's Director in every call. So, I probably accept that mux is not a good choice here, and instead I will use some simpler solution:
var routes = []route{
route{match: "/api/", base: "https://api.bar.com/5"},
route{match: "/sales/", base: "https://sales.bar.com/3"},
}
func NewProxy(r *route) http.Handler {
director := func(req *http.Request) {
out, _ := url.Parse(r.base)
req.URL.Scheme = out.Scheme
req.URL.Host = out.Host
req.URL.Path = out.Path + "/" + strings.TrimPrefix(req.URL.Path, r.match)
}
return &httputil.ReverseProxy{Director: director}
}
You can read mux source code to implement a complex solution based on regular expressions.
I would like to map each route and it's request type (GET, POST, PUT, ...) to generate something like a sitemap.xml in JSON for my restful API.
Goji uses functions to create a new route. I could store the paths and handlers in a map.
My approach would be something like this, except that the compiler gives the following initialization loop error, because sitemap and routes refer to each other (the routemap contains the handler sitemap that should marhsall itself).
main.go:18: initialization loop:
main.go:18 routes refers to
main.go:41 sitemap refers to
main.go:18 routes
Can this be achieved in a more idiomatic way?
package main
import (
"encoding/json"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
var routes = []Route{
Route{"Get", "/index", hello},
Route{"Get", "/sitemap", sitemap},
}
type Route struct {
Method string `json:"method"`
Pattern string `json:"pattern"`
Handler web.HandlerType `json:"-"`
}
func NewRoute(method, pattern string, handler web.HandlerType) {
switch method {
case "Get", "get":
goji.DefaultMux.Get(pattern, handler)
case "Post", "post":
goji.DefaultMux.Post(pattern, handler)
// and so on...
}
}
func hello(c web.C, w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
}
func sitemap(c web.C, w http.ResponseWriter, r *http.Request) {
// BUG: sitemap tries to marshall itself recursively
resp, _ := json.MarshalIndent(routes, "", " ")
// some error handling...
w.Write(resp)
}
func main() {
for _, r := range routes {
NewRoute(r.Method, r.Pattern, r.Handler)
}
goji.Serve()
}
The easiest way to avoid the initialization loop is to break the loop by delaying one of the initializations.
E.g.:
var routes []Route
func init() {
routes = []Route{
Route{"Get", "/index", hello},
Route{"Get", "/sitemap", sitemap},
}
}
With this change your code compiles.
[Edit after chat:]
A fully edited and runnable example that also addresses your question about the switch follows:
package main
import (
"encoding/json"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
var routes []Route
func init() {
// Initialzed in init() to avoid an initialization loop
// since `routes` refers to `sitemap` refers to `routes`.
routes = []Route{
Route{"Get", "/index", hello},
Route{"Get", "/sitemap", sitemap},
//Route{"Post", "/somewhereElse", postHandlerExample},
}
}
type Route struct {
Method string `json:"method"`
Pattern string `json:"pattern"`
Handler web.HandlerType `json:"-"`
}
var methods = map[string]func(web.PatternType, web.HandlerType){
"Get": goji.Get,
"Post": goji.Post,
// … others?
}
func (r Route) Add() {
//log.Println("adding", r)
methods[r.Method](r.Pattern, r.Handler)
}
func hello(c web.C, w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
}
func sitemap(c web.C, w http.ResponseWriter, r *http.Request) {
resp, err := json.MarshalIndent(routes, "", " ")
if err != nil {
http.Error(w, "Can't generate response properly.", 500)
return
}
w.Write(resp)
}
func main() {
for _, r := range routes {
r.Add()
}
goji.Serve()
}
Available as a gist.
I'll note there is nothing wrong with a switch like you had it,
and in this case if there are only two methods a map may be overkill.
A previous version of the example
didn't use a map and explicitly specified both the function and method name (which were expected to match).
Also this version doesn't check for invalid method names (which if routes is always hard coded and never changed at runtime is reasonable). It would be straight forward to do fn, ok := methods[r.Method] and do something else if/when !ok if desired.
Let's take the following pattern:
package main
import (
"fmt"
"net/http"
)
func main() {
admin := http.NewServeMux()
admin.HandleFunc("/", root)
admin.HandleFunc("/foo", foo)
http.Handle("/admin", admin)
http.ListenAndServe(":4567", nil)
}
func root(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Admin: ROOT")
}
func foo(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Admin: FOO")
}
How is it that when I run /admin, it will fire the root handler, but when I run /admin/foo it won't? Just to be clear, I'm not looking for an alternative package, I actually have a custom router, I'm just generally curious to what's going on here as this pattern isn't making much sense to me.
Like #DewyBroto said, you have to use the full path in the child mux.
You could make a wrapper like this:
func NewChildMux(prefix string, vars ...interface{}) *http.ServeMux {
sm := http.NewServeMux()
for i := 0; i < len(vars); i += 2 {
path := prefix + vars[i].(string)
sm.HandleFunc(path, vars[i+1].(func(http.ResponseWriter, *http.Request)))
}
return sm
}
func main() {
admin := NewChildMux("/admin",
"/", root,
"/foo/", foo,
)
http.Handle("/admin/", admin)
http.ListenAndServe(":4567", nil)
}
Try the following:
func main() {
admin := http.NewServeMux()
// Prefix all paths with the mount point. A ServeMux matches
// the full path, even when invoked from another ServeMux.
mountPoint := "/admin"
admin.HandleFunc(mountPoint, root)
admin.HandleFunc(mountPoint + "/foo", foo)
// Add a trailing "/" to the mount point to indicate a subtree match.
http.Handle(mountPoint + "/", admin)
http.ListenAndServe(":4567", nil)
}