How would one go about adding a method to a struct that is a different file?
This is what I've tried so far but it doesn't seem to be working.
// ./src
package routes
type App struct{
}
func (a *App) initializeRoutes() {
a.Subrouter.HandleFunc("/products", a.getSomething).Methods("GET")
}
// ./src/controller
package routes
func (a *App) getSomething(w http.ResponseWriter, r *http.Request){
...
}
They are in the same package.
They are not in the same package. A Go package has both a name and a path. They're both named routes but they have different paths. The actual packages are routes and controller/routes. The result is subdirectories are different packages.
See Package Names on the Go Blog for more information.
Since they're in different packages, they can only access each other's public members and exported methods. You can't monkey patch someone else's package or interface in Go. This is by design to keep all the functionality of a package in one place, no action-at-a-distance.
You have options. You could put all methods of routes into a single package. If they all belong together, there's no need to split it up into multiple files.
If they don't really belong together, you can write a new struct with routes embedded into it and define new methods on that. Then you can access either the wrapper struct to get your added methods, or its embedded struct to get routes' methods. See this answer for an example.
But really I think you need to think about how your code is arranged. The App probably shouldn't be defined by the routes package, they should be separate. Instead, Go prefers a has-a relationship. App would contain an instance of routes.Route.
You'd rearrange your code tree like so:
app/
app.go
app_test.go
routes/
routes.go
routes_test.go
Note that rather than having it all in src/ it's now contained in its own project directory. app.go would look something like this.
// src/app/app.go
package app
import(
"app/routes"
"fmt"
"net/http"
)
type App struct {
routes routes.Route
}
func (a *App) initializeRoutes() {
a.routes.AddRoute("/products", a.getSomething)
}
func (a *App) getSomething(w http.ResponseWriter, r *http.Request) {
fmt.Println("Something!")
}
Notice how we're delegating responsibility to adding a route to a.routes rather than having App do it itself. This avoids the desire to smash all functionality into one ginormous package. routes.Route would be defined in app/routes/routes.go.
// src/app/routes/routes.go
package routes
import "net/http"
// A type specifying the interface for route handlers.
type RouteHandler func(w http.ResponseWriter, r *http.Request)
type Route struct {
handlers map[string]RouteHandler
}
func (r *Route) AddRoute(path string, handler RouteHandler) {
r.handlers[path] = handler
}
Now all routes has to worry about is handling routes. It doesn't know anything about your specific application logic.
I was trying to get my http.res & http.req functions in a controllers file.
Now that we've rearranged the file structure, you can do that. You can, if you like, define app/controllers.go to organize your code.
// src/app/controllers.go
package app
import(
"fmt"
"net/http"
)
func (a *App) getSomething(w http.ResponseWriter, r *http.Request) {
fmt.Println("Something!")
}
app/app.go and app/controllers.go are in the same package. They have the same path and the same name. So app/controllers.go can add methods to App.
You have an error because your files belong to different packages. Everything related to one struct must be in the same package.
It is possible to declare struct and its methods in different files, but they must belong same package (be in same folder).
Related
I want to expose the following URLs from my service:
GET /api/foo
GET /api/bar
I also want to structure it as a router nested inside another. The toplevel router will match all requests to /api and serve them with the nested router which will match requests to /foo and /bar. Basically, namespacing.
I could just have a single router and give the /api prefix to both the routes:
router.GET("/api/foo", apiFoo)
router.GET("/api/bar", apiBar)
But I would like the convenience of having a single router for all routes inside the /api prefix so that I can add appropriate middlewares to them all with a single function call.
Here's what I tried:
package main
import (
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
func apiFoo(w http.ResponseWriter, r *http.Request) {}
func apiBar(w http.ResponseWriter, r *http.Request) {}
func main() {
api := httprouter.New()
api.HandlerFunc(http.MethodGet, "/foo", apiFoo)
api.HandlerFunc(http.MethodGet, "/bar", apiBar)
router := httprouter.New()
router.Handler(http.MethodGet, "/api", api)
log.Fatal(http.ListenAndServe(":8080", router))
}
However, getting 404 not found on going to http://localhost:8080/api/foo or http://localhost:8080/api/bar
I had thought that nested routers would work because routers implement the http.Handler interface. What am I missing?
httprouter does not concatenate the path, so you can't do it that way. Both routers will just inspect the request path and act accordingly.
There is an open pull request for 7 years, that would implement it. You could have a look there and implement similar logic yourself, the PR is based on concatenating the path. Maybe you can write a small helper function for that.
If you are willing to switch the router package, you could look into alternatives such as chi, which support router groups.
I am using Revel framework for golang. I have a subdirectory in my controllers folder like below
controllers
new
app2.go
app1.go
Contents of app1.go
package controllers
import (
"github.com/revel/revel"
)
type APP1 struct {
*revel.Controller
}
func (c APP1) Show() revel.Result {
}
Contents of app2.go
import (
"github.com/revel/revel"
)
type APP2 struct {
*revel.Controller
}
func (c APP2) Show() revel.Result {
}
My routes file is like this
GET /v1/show APP1.show
GET /v2/show APP2.show
When I call /v2/show it gives me error failed to find controller APP2 while v1/show is working absolutely working fine. Can anybody tell me how to fix it.
Configure the route for APP2.show like this GET /new/v2/show
Add the below at the top of app2.go
package controllers
I tried the same using a similar example and it worked for me. I would suggest not to use full capitalized controller names; use App2 instead of APP2.
I was a little confused to find out that the methods referenced from the router need to be Pascal Cased to be recognized (e.g. like above func (c APP2) DescribeService() revel.Result). This might be obvious to seasoned Revel or Go developers but really wasn't obvious to me.
I am using gin gonic to build a web application. I use https://github.com/gin-gonic/contrib/tree/master/sessions to handle session. Forexample, i set a integer value to session:
function Test(c *gin.Context){
session:= sessions.Default(c)
session.Set("mysession",123)
session.Save()
}
And in another controllers, i can get this session by session.Get("mysession").
But if i set map or struct. I only can get the session in the same controller. something wrong here??
You probably forgot to register it, when your app starts you need to have something like:
package main
import (
"encoding/gob"
"path/to/yourpackage"
func init() {
gob.Register(&yourpackage.YourStruct{})
}
You can look here http://www.gorillatoolkit.org/pkg/sessions for more information (gin-gonic uses gorilla sessions under the hood)
I'm using gorilla/context in a web app. The example in the docs looks like:
func MyHandler(w http.ResponseWriter, r *http.Request) {
//...
val := context.Get(r, foo.MyKey)
//...
}
How can I unit test a handler that works like this? The only way I've managed so far is to use the context package inside my tests. I'm thinking at the moment that I could inject a context parameter into the handler but then I'm not conforming to the HandlerFunc interface.
This is a classic cross-cutting concerns example.
You are using a 3rd party to magically handle input params for your unit under test. By that very definition, you are going to have to do some extra setup to prep the context for the state you want.
When it comes to Go http handlers (which the convention is to KISS it), you shouldn't need to "reach out of context" of your func to get extra data: keep all the data you need within your single func.
Personally, I try to avoid corrupting my handlers like this. I think I've only used gorilla's context once out of the dozens of big sites I've built. And that was basically to get around a cached response, just to keep the data refesh to the end user. Of which I simply ignored in my unit tests, as it was out-of-scrope of what I was testing.
Instead, I use middle-ware wrappers to setup the basics I want in my handles and modify the handler's signature accordingly.
caching
logging
authentication and authorization
json marshaling
context (e.g. expected User{} object loaded from DB)
...etc. I would create a middle-ware that wraps your handler when you register it with mux that uses gorilla/context to lookup your cookie or userid or something, hidrates that user object from cache, DB, redis, etc, and then calls your handler that would have a signature like:
func MyHandler(u User, p Page, w http.ResponseWriter, r *http.Request) {
// u is the User object loaded from middle-ware
// p is your global Page object... etc
}
That way, your unit tests inject only the items you need under test.
And you can integration test your middle-ware on a QA server with expected User and Page objects in a datastore.
The way my team does it is to add a name to the route handler and then in the tests we call that route by name.
This is how to add a route:
r.HandleFunc("/<route>", MyHandler).Methods("GET").Name("MyHandlerByName")
Then this is how to test it
r.Get("MyHandlerByName")
One way to test handles is modify the way in which they are created. for example, Creating a function that return a http.HandlerFunc, this function can have parameters. You can mock the values that you send to the function
Without parameters
func State() http.HandlerFunc {
return http.HandlerFunc(func(pResponse http.ResponseWriter, r *http.Request) {
// your code
})
}
With Parameters
func State(pParam1,pParam2,pParam3 ...) http.HandlerFunc {
return http.HandlerFunc(func(pResponse http.ResponseWriter, r *http.Request) {
// your code using pParam1,pParam2,pParam3
})
}
The mapping will be
http.HandleFunc("/State", State())
or
http.HandleFunc("/State", State(value1,value2,value3 ....))
I want to have different middleware for different path. My current implementation is from this link
UserRouter := mux.NewRouter().StrictSlash(true)
AdminRouter := mux.NewRouter().StrictSlash(true)
Router.HandleFunc("/apps/{app_name}/xyz", Handler).Methods("GET")
I created three different routers, so that I can assosiate them with different path and middleware
nUserPath := negroni.New(middleware.NewAuthMiddleWare())
nUserPath.UseHandler(UserRouter)
nAdminPath := negroni.New()
nAdminPath.UseHandler(AdminRouter)
I created two different negroni instances and passed them the respective routers. As I wanted all this to run part of the same application on the same port so I created a Wrapper Router and negroni instance and associated them with the existing like below
BaseRouter := mux.NewRouter().StrictSlash(true)
BaseRouter.Handle(UserBasePath,nUserPath) // UserBasePath is `/apps`
BaseRouter.Handle(HealthCheck,nUserPath) // HealthCheck is `/health`
BaseRouter.Handle(AdminBasePath,nAdminPath) // AdminBasePath is `/Admin`
n := negroni.New(middleware.NewLogger()) // attached other common middleware here
n.UseHandler(router.BaseRouter)
n.Run(":8080")
Issues faced in this approach:
When I run /health it runs properly but when I run /apps/{app_name}/something I get a 404: Not Found
Note : I went through other approaches mentioned in below link but they don't satisfy my need.
- Route-specific Middlewares with Negroni
So, the issue with the above implementation is that BaseRouter.Handle() method take a path and not a path_matcher/template so all the url's which has path_length more than one were not working.
I figured out two ways to achieve what I needed:
First approach
// Create a rootRouter
var rootRouter *mux.Router = mux.NewRouter()
// Create as many subRouter you want with some prefix
var appsBasePath string = "/apps"
var adminBasePath string = "/admin"
upRouter := rootRouter.PathPrefix(appsBasePath).Subrouter()
apRouter := rootRouter.PathPrefix(adminBasePath).Subrouter()
// Register all the paths and mention middleware specifically for all of them
// Here middleware is a method with signature as
// func middleware( http.Handler) http.HandlerFunc {}
upRouter.Path("/test").Methods("POST").Handler(middleware(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request){
fmt.Fprintf(w, "Welcome to the home page!")
})))
n := negroni.New(middleware.NewLogger()) // attached other common middleware here
n.UseHandler(rootRouter)
n.Run(":8080")
Second approach
This is extension/solution of the original issue in the question
// Replace BaseRouter.handle() as below
// as PathPrefix takes a template so it won't have issue that we were facing
BaseRouter.PathPrefix(UserBasePath).Handler(nUserPath)
Thing to remember here is that within negroni nUserPath RequestContext of the middleware attached will be different from that of the actual router's HandlerMethod
Note:
By path length I mean something like this -- /abc or /abc/ has path_length=1 and /abc/xyz has path_length=2