How do I split URLS in multiple Files using gorilla/mux?
Asked Answered
E

2

5

My directory structure looks like this:

myapp/
|
+-- moduleX
|      |
|      +-- views.go
|
+-- start.go

The app gets started with start.go and from there I configure all the routes and import the handlers from moduleX/views.go like this:

package main

import (
    "net/http"
    "github.com/gorilla/mux"
    "myapp/moduleX"
)

func main() {
    r := mux.NewRouter()
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./templates/static/"))))
    r.HandleFunc("/", moduleX.SomePostHandler).Methods("POST")
    r.HandleFunc("/", moduleX.SomeHandler)
    http.Handle("/", r)
    http.ListenAndServe(":8080", nil)
}

Now I want to add more modules and ask myself if (and how) it is possible to define the urls in the module in a urls.go file and somehow "import" them in start.go. Specifically I want start.go to know all the URLs in all the somemodule/urls.go files with just one import or some kind of a module.GetURLs function.

Emalia answered 14/10, 2013 at 8:53 Comment(0)
R
3

EDIT:

To create a group of mux.Route's in one go, you could define a custom type (handler in the example below) and do do something like:

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "net/http"
)

type handler struct {
    path    string
    f       http.HandlerFunc
    methods []string
}

func makeHandlers(hs []handler, r *mux.Router) {
    for _, h := range hs {
        if len(h.methods) == 0 {
            r.HandleFunc(h.path, h.f)
        } else {
            r.HandleFunc(h.path, h.f).Methods(h.methods...)
        }
    }
}

// create some example handler functions

func somePostHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "POST Handler")
}

func someHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Normal Handler")
}

func main() {
    //define some handlers
    handlers := []handler{{path: "/", f: somePostHandler, methods: []string{"POST"}}, {path: "/", f: someHandler}}
    r := mux.NewRouter()
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./templates/static/"))))
    // Initialise the handlers
    makeHandlers(handlers, r)
    http.Handle("/", r)
    http.ListenAndServe(":8080", nil)
}

Playground

ORIGINAL ANSWER:

You don't need to import them if they're in the same package.

You can define the URL variables in urls.go, and then the logic in views.go (or another file in package moduleX) as long as they have the same package declaration.

For instance:

// moduleX/urls.go

package moduleX

var (
    urls = []string{"http://google.com/", "http://stackoverflow.com/"}
)

Then:

// moduleX/views.go (or some other file in package moduleX)

package moduleX

func GetUrls() []string {
    return urls
}

Then:

// start.go

package main

import (
    "fmt"
    "myapp/moduleX"
)

func main() {
    for _, url := range moduleX.GetUrls() {
        fmt.Println(url)
    }
}

Or, even easier, just export the variable from the moduleX package by giving it a capitalised name.

For instance:

// moduleX/urls.go

package moduleX

var URLs = []string{"http://google.com/", "http://stackoverflow.com/"}

and then:

// start.go

package main    

import (
    "fmt"
    "myapp/moduleX"
)

func main() {
    for _, url := range moduleX.URLs {
        fmt.Println(url)
    }
}

Have a look at any of the Go source to see how they handle the same problem. A good example is in the SHA512 source where the lengthy variable is stored in sha512block.go and the logic is in sha512.go.

Reachmedown answered 14/10, 2013 at 8:59 Comment(7)
that works and I'm aware that this is possible - but it does not solve my Problem directly. Even if I get all URLs exported to start.go, how can I create a Route for them with the appropriate Handler? I want to do all the HandleFunc()-stuff in moduleX/urls.go and then "use" these Routers (or Routes or HandleFuncs?) in start.go. With one Module I could just define the Router in urls.go and use them in start.go, but with multiple modules I need a way to "append" to the Router.Emalia
Create a map of urls to http.HandlerFuncs, then create a function to initialise all of those as mux.Routes ?Reachmedown
OK, now I did something like this: func GetRoutes() []core.Route { routes := []core.Route{ core.Route{URL: "/new/", Handler: NewObjHandler}, core.Route{URL: "/", Handler: login.LoginFirst(ListObjHandler)}, } return routes } in my urls.go and obj_routes := obj.GetRoutes() s := r.PathPrefix("/obj/").Subrouter() for _, route := range obj_routes { s.HandleFunc(route.URL, route.Handler) } - which works, but looks ugly. And I dont know how I can apply the .Methods("POST") from gorillas mux now.Emalia
great, your makeHandlers was what was missing for Methods() to work. Works good for me. Only problem left: I have to write module.GetRoutes() and makeHandlers(moduleRoutes, subrouter) for each module. I guess there is no other way?Emalia
Sorry, not sure what you mean by "for each module". You should be able to export a generic Handler type from moduleX (just capitalise the current handler type), and a MakeHandlers() function (that returns a *mux.Router) if your using the code above in a package. Then, whatever code imports the package can just define h := moduleX.Handler{path: "/", f: someHandler} etc. and then just call r := moduleX.MakeHandlers. This should separate the code, as long as you create each moduleX.Handler out of a string, http.HandleFunc and a variadic list of methods ([]string).Reachmedown
"for each module" means I have more than one Module. In each module I define a GetRoutes(). In the start.go I have to call makeHandlers(module.GetRoutes(), router) for each module - if I add moduleZ I have to add makeHandlers(moduleZ.GetRoutes(), router) and import moduleZ in start.goEmalia
It sounds like you want to define a module interface. If each module presents an identical set of exported methods, you can define an interface for those methods and then treat them all the same. have a look at golang.org/doc/effective_go.html#interfacesReachmedown
L
4

Why not get the handlers to insert themselves into the routes table?

If you're defining each handler in its own go file, use the `init()` func for each file to add the handler to a global routing table

So something like:


main.go:

type route{
    method string
    path string
    handler func(w http.ResponseWriter, r *http.Request)
}
var routes = make([]route,0)

func registerRoute(r route){
    routes = append(routes,r)
}

func server(){
    r := mux.NewRouter()
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./templates/static/"))))
    // set up all the registered routes
    for _, rt = range(routes){
        r.HandleFunc(rt.path,rt.handler).Methods(rt.Method)
    }
    // and there's usually some other stuff that needs to go in here

    //before we finally serve the content here!
    http.ListenAndServe(":8080", nil)
}

yourmodule.go:

func init(){
    r = route{
        method="GET",
        path="/yourmodule/path/whatever",
        handler=yourHandlerFunc,
    }
    registerRoute(r)
}

func yourHandlerFunc(w http.ResponseWriter, r *http.Request){
    //awesome web stuff goes here
}

init() gets called for each file in a package before execution of the package main() so you can be sure that all your handlers will be registered before you kick the server off.

This pattern can be extended to allow for more tricksy registration gubbins to happen as needed, since the modules themselves are now in charge of their own registration, instead of trying to cram all the special cases into one registration func

Later answered 15/10, 2013 at 5:28 Comment(1)
perfect! I will try that. If every Module is completely in charge of their routes, this is the better solution.Emalia
R
3

EDIT:

To create a group of mux.Route's in one go, you could define a custom type (handler in the example below) and do do something like:

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "net/http"
)

type handler struct {
    path    string
    f       http.HandlerFunc
    methods []string
}

func makeHandlers(hs []handler, r *mux.Router) {
    for _, h := range hs {
        if len(h.methods) == 0 {
            r.HandleFunc(h.path, h.f)
        } else {
            r.HandleFunc(h.path, h.f).Methods(h.methods...)
        }
    }
}

// create some example handler functions

func somePostHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "POST Handler")
}

func someHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Normal Handler")
}

func main() {
    //define some handlers
    handlers := []handler{{path: "/", f: somePostHandler, methods: []string{"POST"}}, {path: "/", f: someHandler}}
    r := mux.NewRouter()
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./templates/static/"))))
    // Initialise the handlers
    makeHandlers(handlers, r)
    http.Handle("/", r)
    http.ListenAndServe(":8080", nil)
}

Playground

ORIGINAL ANSWER:

You don't need to import them if they're in the same package.

You can define the URL variables in urls.go, and then the logic in views.go (or another file in package moduleX) as long as they have the same package declaration.

For instance:

// moduleX/urls.go

package moduleX

var (
    urls = []string{"http://google.com/", "http://stackoverflow.com/"}
)

Then:

// moduleX/views.go (or some other file in package moduleX)

package moduleX

func GetUrls() []string {
    return urls
}

Then:

// start.go

package main

import (
    "fmt"
    "myapp/moduleX"
)

func main() {
    for _, url := range moduleX.GetUrls() {
        fmt.Println(url)
    }
}

Or, even easier, just export the variable from the moduleX package by giving it a capitalised name.

For instance:

// moduleX/urls.go

package moduleX

var URLs = []string{"http://google.com/", "http://stackoverflow.com/"}

and then:

// start.go

package main    

import (
    "fmt"
    "myapp/moduleX"
)

func main() {
    for _, url := range moduleX.URLs {
        fmt.Println(url)
    }
}

Have a look at any of the Go source to see how they handle the same problem. A good example is in the SHA512 source where the lengthy variable is stored in sha512block.go and the logic is in sha512.go.

Reachmedown answered 14/10, 2013 at 8:59 Comment(7)
that works and I'm aware that this is possible - but it does not solve my Problem directly. Even if I get all URLs exported to start.go, how can I create a Route for them with the appropriate Handler? I want to do all the HandleFunc()-stuff in moduleX/urls.go and then "use" these Routers (or Routes or HandleFuncs?) in start.go. With one Module I could just define the Router in urls.go and use them in start.go, but with multiple modules I need a way to "append" to the Router.Emalia
Create a map of urls to http.HandlerFuncs, then create a function to initialise all of those as mux.Routes ?Reachmedown
OK, now I did something like this: func GetRoutes() []core.Route { routes := []core.Route{ core.Route{URL: "/new/", Handler: NewObjHandler}, core.Route{URL: "/", Handler: login.LoginFirst(ListObjHandler)}, } return routes } in my urls.go and obj_routes := obj.GetRoutes() s := r.PathPrefix("/obj/").Subrouter() for _, route := range obj_routes { s.HandleFunc(route.URL, route.Handler) } - which works, but looks ugly. And I dont know how I can apply the .Methods("POST") from gorillas mux now.Emalia
great, your makeHandlers was what was missing for Methods() to work. Works good for me. Only problem left: I have to write module.GetRoutes() and makeHandlers(moduleRoutes, subrouter) for each module. I guess there is no other way?Emalia
Sorry, not sure what you mean by "for each module". You should be able to export a generic Handler type from moduleX (just capitalise the current handler type), and a MakeHandlers() function (that returns a *mux.Router) if your using the code above in a package. Then, whatever code imports the package can just define h := moduleX.Handler{path: "/", f: someHandler} etc. and then just call r := moduleX.MakeHandlers. This should separate the code, as long as you create each moduleX.Handler out of a string, http.HandleFunc and a variadic list of methods ([]string).Reachmedown
"for each module" means I have more than one Module. In each module I define a GetRoutes(). In the start.go I have to call makeHandlers(module.GetRoutes(), router) for each module - if I add moduleZ I have to add makeHandlers(moduleZ.GetRoutes(), router) and import moduleZ in start.goEmalia
It sounds like you want to define a module interface. If each module presents an identical set of exported methods, you can define an interface for those methods and then treat them all the same. have a look at golang.org/doc/effective_go.html#interfacesReachmedown

© 2022 - 2024 — McMap. All rights reserved.