http.ServeMux route mounting? - go

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)
}

Related

How to set context-path for go-chi?

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.

How can I easily include a routes.go?

I would like to put all my routes in a routes.go file. Currently I have a class here:
cmd-> src -> main.go
Where I have this line of code:
func startServer(port string, services Services, stop chan os.Signal, psFact *ps.Factory, logger log.Logger) *http.Server {
router := mux.NewRouter()
apiHandler.RegisterRoutes(router)
router.HandleFunc
srv := &http.Server{Addr: fmt.Sprintf(":%v", port), Handler: router}
go func() {
if err := srv.ListenAndServe(); err != nil {
if strings.HasPrefix(err.Error(), "listen tcp :5002: bind") {
stop <- syscall.SIGTERM
}
log.Log.Error("error shutting down server", zap.Error(err))
}
}()
return srv
}
I would like to throw all my router.HandleFuncs in a separate file called routes.go in the cmd ->src -> routes.go, but am having trouble figuring out how I can do that. Currently, what I have is this:
package main
import "net/http"
func (services Services) routes() {
}
But how can I call these routes.go from my main.go?
I am not sure if I get it... but try something like the following:
$ tree
.
└── cmd
└── src
├── main.go
└── routes.go
2 directories, 2 files
// $ cat cmd/src/main.go
package main
func main() {
println("hello world")
muxRouter := "SuperRouter"
myService := &MyService{}
myService.routes(muxRouter)
}
// $ cat cmd/src/routes.go
package main
import "fmt"
// MyService ...
type MyService struct {
// deps
}
// routes ...
func (ms *MyService) routes(router string) { //
// register here your endpoints & handlers
fmt.Printf("routes - router: %s & GetServices: %s \n", router, ms.GetServices())
}
// GetServices ...
func (ms *MyService) GetServices() string {
return "baz"
}
$ go run cmd/src/*.go
hello world
routes - router: SuperRouter & GetServices: baz
Best,
The actual problem here is that your service knows best what it can provide and your router does not. Since your Services methods are handler functions anyway, you can delegate the setup of the router to the Services. In the example below this is done in BaseRoutes.
Then, all you have to do is to get a router and let the instance of your Service modify the behavior of said router.
This does not prevent you from setting up addition routes, say for providing an HTMl GUI, as shown by the usage of HelloRoute as a variadic arg in NewRouter.
At the end of the day, you hand your Services instance and the additional routes you might want (this is optional!) over to the NewRouter wrapper and can use the result as usual.
NOTE: I did not do any error handling in the Services, as this highly depends on what you actually wanna do. Please put proper though into error handling anyway.
// Services are a dummy for the actual implementation.
// However, using an interface might well make sense if you have multiple Services.
type Services interface {
GetServices(w http.ResponseWriter, r *http.Request)
GetAssetStatus(w http.ResponseWriter, r *http.Request)
UpdateEncoderConfigForLid(w http.ResponseWriter, r *http.Request)
BaseRoutes(router *mux.Router) error
}
// MyServices is dummy implementation of Services.
type MyServices struct {
}
// GetServices implements the Services interface for MyServices
func (s MyServices) GetServices(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("GetServices"))
}
// GetAssetStatus implements the Services interface for MyServices
func (s MyServices) GetAssetStatus(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("GetAssetStatus"))
}
// UpdateEncoderConfigForLid implements the Services interface for MyServices
func (s MyServices) UpdateEncoderConfigForLid(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("UpdateEncoderConfigForLid"))
}
// BaseRoutes will set up the routes
func (s *MyServices) BaseRoutes(router *mux.Router) error {
router.HandleFunc("/api/services", s.GetServices).Methods(http.MethodGet)
router.HandleFunc("/api/services/status", s.GetAssetStatus).Methods(http.MethodGet)
router.HandleFunc("/api/services/{lid}/encoders", s.UpdateEncoderConfigForLid).Methods(http.MethodPut)
return nil
}
// HelloRoute is an example for an additional route you might want to set up
func HelloRoute(router *mux.Router) error {
router.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
return nil
}
type stdRoute func(router *mux.Router) error
// NewRouter wraps the setup of your service routes and additional routes
// We use the Services interface here to make it more generic.
func NewRouter(s Services, additionalRoutes ...stdRoute) *mux.Router {
r := mux.NewRouter()
var err error
if err = s.BaseRoutes(r); err != nil {
log.Printf("setting up service routes: %s => HANDLE THIS!", err)
}
for _, route := range additionalRoutes {
if err = route(r); err != nil {
log.Printf("setting up additional routes: %s => HANDLE THIS!", err)
}
}
return r
}
// Of course, if you have additional services, you can use variadic args again.
func web(s Services) {
router := NewRouter(s, HelloRoute)
log.Fatal(http.ListenAndServe(":8080", router))
}

prevent access to files in folder with a golang server

I've a server in golang who handle folder path like that :
fs := http.FileServer(http.Dir("./assets"))
http.Handle("/Images/", fs)
http.ListenAndServe(":8000", nil)
But in this folder there are privates images, and it shouldn't be possible to access files. So how can i secure image access and prevent anybody to access content of folder.
like that for example :
If you want to block a directory using http package, maybe this will be useful to you :
https://groups.google.com/forum/#!topic/golang-nuts/bStLPdIVM6w
package main
import (
"net/http"
"os"
)
type justFilesFilesystem struct {
fs http.FileSystem
}
func (fs justFilesFilesystem) Open(name string) (http.File, error) {
f, err := fs.fs.Open(name)
if err != nil {
return nil, err
}
return neuteredReaddirFile{f}, nil
}
type neuteredReaddirFile struct {
http.File
}
func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil
}
func main() {
fs := justFilesFilesystem{http.Dir("/tmp/")}
http.ListenAndServe(":8080", http.FileServer(fs))
}
A little wrapper over FileServer() solves your problem, now you have to add some sort of logic to do Authorization, it looks like you have unique names, that's good, so I just filter the image name for you creating a map of names, now you can add something more dynamic like a key/store(memcached, redis. etc.) Hope you can follow the comments
package main
import (
"log"
"net/http"
"strings"
)
// put the allowed hashs or keys here
// you may consider put them in a key/value store
//
var allowedImages = map[string]bool{
"key-abc.jpg": true,
"key-123.jpg": true,
}
func main() {
http.Handle("/Images/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// here we can do any kind of checking, in this case we'll just split the url and
// check if the image name is in the allowedImages map, we can check in a DB or something
//
parts := strings.Split(r.URL.Path, "/")
imgName := parts[len(parts)-1]
if _, contains := allowedImages[imgName]; !contains { // if the map contains the image name
log.Printf("Not found image: %q path: %s\n", imgName, r.URL.Path)
// if the image is not found we write a 404
//
// Bonus: we don't list the directory, so nobody can know what's inside :)
//
http.NotFound(w, r)
return
}
log.Printf("Serving allowed image: %q\n", imgName)
fileServer := http.StripPrefix("/Images/", http.FileServer(http.Dir("./assets")))
fileServer.ServeHTTP(w, r) // StripPrefix() and FileServer() return a Handler that implements ServerHTTP()
}))
http.ListenAndServe(":8000", nil)
}
https://play.golang.org/p/ehrd_AWXim

Passing context to gorilla mux - go idioms

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)

mux.Vars is empty when using httputil.ReverseProxy

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.

Resources