I have the following controller function that is supposed to delete an item within a collection. I am using the gorilla/mux package. The problem arises when I try to handle the case where the user forgot to send an id on a DELETE request.
localhost:8000/movies/ or localhost:8000/movies
While an example of the proper request would be
localhost:8000/movies/3
Although params["id"] is empty, the execution never goes into the else block. I am not sure why.
func (c Controller) Delete(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var error Error
params := mux.Vars(r)
movieRepo := movieRepo.MovieRepository{}
if _, ok := params["id"]; ok {
id, err := strconv.Atoi(params["id"])
if err != nil {
error.Message = "Server error"
utils.SendError(w, http.StatusInternalServerError, error)
return
}
rowsDeleted, err := movieRepo.delete(db, id)
w.Header().Set("Content-Type", "text/plain")
successMessage(w, rowsDeleted)
} else {
fmt.Println("errrr..")
error.Message = "Id is not present."
errorMessage(w, http.StatusBadRequest, error)
return
}
}
}
EDIT
This is how the endpoint is registered on the router:
router.HandleFunc("/movies/{id}",controller.Delete(db)).Methods("DELETE")
Related
i am building an promethues exporter in golang, the url to the exporter will be http://exporter-ip:9000/unique-id/metrics.
By parsing the url in ProcessParameters() function i am getting unique-id and with unique-id i am getting ip,username,password.
how can i pass IP, Username, Password from ProcessParameters() middleware function to Collect() function.
There variables are request scoped
func (collector *Collector) Collect(ch chan<- prometheus.Metric) {
//need IP,Username & Password here.
}
func ProcessParameters(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Print("Executing middlewareOne")
DeviceID = strings.Split(r.URL.Path, "/")[1] //getting unique ID from the URL and verifying if that id is valid
_, ok := util.Devices[DeviceID] //if device id is not present in map, return StatusForbidden error in if block.
if !ok{
errMsg := "Device not found"
http.Error(w, errMsg, http.StatusForbidden)
log.Println(errMsg)
w.WriteHeader(http.StatusForbidden)
w.Header().Set("Content-Type", "application/json")
resp := make(map[string]string)
resp["message"] = "Forbidden"
jsonResp, err := json.Marshal(resp)
if err != nil {
log.Fatalf("Error happened in JSON marshal. Err: %s", err)
}
w.Write(jsonResp)
} else { //if id is present pass controller final handler(deviceHandler)
// tried setting it to request context also how to access it from collect() func
ctx := context.WithValue(r.Context(), "IP", util.Devices["10.0.0.1"])
context.WithValue(r.Context(), "UserName", util.Devices["user1"])
context.WithValue(r.Context(), "Password", util.Devices["pass1"])
next.ServeHTTP(w, r.WithContext(ctx))
}
})
}
func main() {
collector := metricsCollector()
registry := prometheus.NewRegistry()
registry.Register(collector)
deviceHandler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
mux := http.NewServeMux()
mux.Handle("/", ProcessParameters(deviceHandler)) // how can pass variables from ProcessParameters() middleware handler to deviceHandler
err := http.ListenAndServe(":9090", mux)
log.Fatal(err)
}
I am trying to use an authentication middleware that checks if the user is currently connected and have a session before executing a route, but it seems like my middleware is not stopping the execution of the route and executing the next one even I am not calling next().
This is my code :
func checkUserAuth(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if err := next(c); err != nil {
c.Error(err)
}
currSess, _ := session.Get("session", c)
if userId, ok := currSess.Values["user_id"].(string); ok {
fmt.Println("User is currently connected with id", userId);
return next(c)
}
// Even if middleware reaches here, it still execute the next route, why?
return echo.ErrUnauthorized
}
}
func main() {
e := echo.New()
e.Use(checkUserAuth)
e.Use(session.Middleware(store))
e.GET("/", func(c echo.Context) error {
sess, _ := session.Get("session", c)
fmt.Println("got session" , sess.Values["user_id"], "id", sess.ID)
return c.String(http.StatusOK, "Hello")
})
e.GET("/session", func(c echo.Context) error {
sess, _ := session.Get("session", c)
//test
sess.Values["user_id"] = rand.Intn(50000)
sess.Save(c.Request(), c.Response())
return c.String(http.StatusOK, "session saved")
})
When I send a GET request to the / route, the middleware is executed correctly and reaches the return echo.ErrUnauthorized statement, but then the / still gets executed regardless and I don't get any 401 status code.
Thanks
on your checkUserAuth remove the
if err := next(c); err != nil {
c.Error(err)
}
next() is triggered first in your middleware.
I am new to go and I am following a tutorial online. I get this error from VS Code
"cannot use c.ReadConfig (type func(http.ResponseWriter, *http.Request)) as type http.Handler in argument to router.Get:
func(http.ResponseWriter, *http.Request) does not implement http.Handler (missing ServeHTTP method)".
I checked the Get and Redconfig functions and they look alright. The teacher on his end does not get the error and he is able to run the Go code fine. this is the snippet in the main
This the main function
func main() {
config := domain.Config{}
configService := service.ConfigService{
Config: &config,
Location: "config.yaml",
}
go configService.Watch(time.Second * 30)
c := controller.Controller{
Config: &config,
}
router := muxinator.NewRouter()
router.Get("/read/{serviceName}", c.ReadConfig)
log.Fatal(router.ListenAndServe(":8080"))
}
This is the Get function
// Get returns the config for a particular service
func (c *Config) Get(serviceName string) (map[string]interface{}, error) {
c.lock.RLock()
defer c.lock.RUnlock()
a, ok := c.config["base"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("base config is not a map")
}
// If no config is defined for the service
if _, ok = c.config[serviceName]; !ok {
// Return the base config
return a, nil
}
b, ok := c.config[serviceName].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("service %q config is not a map", serviceName)
}
// Merge the maps with the service config taking precedence
config := make(map[string]interface{})
for k, v := range a {
config[k] = v
}
for k, v := range b {
config[k] = v
}
return config, nil
}
This is ReadConfig
// ReadConfig writes the config for the given service to the ResponseWriter
func (c *Controller) ReadConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
vars := mux.Vars(r)
serviceName, ok := vars["serviceName"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "error")
}
config, err := c.Config.Get(serviceName)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "error")
}
rsp, err := json.Marshal(&config)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "error")
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, string(rsp))
}
What should happen is that I should be able to run and I can go to http://localhost:8080/read/base
Use http.HandlerFunc:
router := muxinator.NewRouter()
router.Get("/read/{serviceName}", http.HandlerFunc(c.ReadConfig))
It's expecting a ServeHTTP method, but you gave it a direct function. http.HandlerFunc acts as a wrapper so you can use a plain function as your handler.
I am trying to write simple RESTful app in Golang using gorilla mux.
I wrote few handlers that look as follows:
func getUser(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-type") == "application/json" {
w.Header().Set("Content-Type", "application/json")
u, err := _getUser(r)
if err != nil {
http.NotFound(w, r)
return
}
json.NewEncoder(w).Encode(u) //asked for json, return json
} else {
w.Header().Set("Content-Type", "text/html")
u, err := _getUser(r)
if err != nil {
http.NotFound(w, r)
return
}
renderTemplate(w, "view", u) // asked for html, return html
}
}
func _getUser(r *http.Request) (*User, error) {
params := mux.Vars(r)
for _, u := range users {
if u.ID == params["id"] {
return &u, nil
}
}
return nil, errors.New("")
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/v1/users/{id}", getUser).Methods("GET")
}
The problem I got here is that I have got a lot of duplication. Every CRUD method would have to check for content type and return either json or html.
I thought about writing a closure
func htmlOrJSON(fn func(http.ResponseWriter, *http.Request) (interface {}, error), templateName string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-type") == "application/json" {
w.Header().Set("Content-Type", "application/json")
result, err := fn(w, r)
if err != nil {
http.NotFound(w, r)
return
}
json.NewEncoder(w).Encode(result)
} else {
w.Header().Set("Content-Type", "text/html")
result, err := fn(w, r)
if err != nil {
http.NotFound(w, r)
return
}
renderTemplate(w, templateName, result)
}
}
}
// and use as:
router.HandleFunc("/v1/users/{id}", htmlOrJSON(getUser, "view")).Methods("GET")
To remove duplication but it also does not look well. Could anyone help me make this code cleaner?
Although this is a code review question and should be in the CodeReview community, I’ll try to answer it.
Write a generic function that handles HTML and JSON rendering. The error handling IMO should happen on each handler even if you duplicate some code. It makes more sense there and makes the code more readable and explicit. You will soon see that there will be other errors that require special handling.
On the logic, most APIs accept query parameters http://api.com/user/1?fomtat=json. This makes more sense because when a client accept more than content types you will be stuck.
const JSON = "application/json"
func getUser(w http.ResponseWriter, r *http.Request) {
u, err := _getUser(r)
if err != nil {
http.NotFound(w, r)
return
}
responseBody(u, r.Header.Get("Content-type"), &w)
}
func responseBody(u User, contentType string, w io.writer) {
switch contentType {
case JSON:
w.Header().Set("Content-Type", JSON)
json.NewEncoder(w).Encode(u) //asked for json, return json
default:
w.Header().Set("Content-Type", "text/html")
renderTemplate(w, "view", u) // asked for html, return html
}
}
I am using the gorilla/sessions package to implement sessions. the relevant code (or at least what I think is the only relevant part) is as follows:
// Function handler for executing HTML code
func lobbyHandler(w http.ResponseWriter, req *http.Request) {
if isLoggedIn := validateSession(w, req); isLoggedIn {
lobbyTempl.Execute(w, req.Host)
} else {
homeTempl.Execute(w, map[string]string{
"loginErrors": "Must log in first",
})
}
}
// Serves the files as needed, whenever they are requested
// used for all images, js, css, and other static files
func sourceHandler(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, r.URL.Path[1:])
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
un, pw := r.FormValue("lUn"), r.FormValue("lPw")
if usr := findUser(un, pw); usr != nil {
if createSession(w, r) {
http.Redirect(w, req, "/lobby.html", http.StatusFound)
}
} else {
homeTempl.Execute(w, map[string]string{
"loginErrors": "User not found",
})
}
}
func createSession(w http.ResponseWriter, r *http.Request) bool {
session, _ := store.Get(r, sessionName)
session.Values["isAuthorized"] = true
if err := session.Save(r, w); err != nil {
fmt.Println("saving error: ", err.Error())
return false
}
return true
}
func validateSession(w http.ResponseWriter, r *http.Request) bool {
if session, err := store.Get(r, sessionName); err == nil {
if v, ok := session.Values["isAuthorized"]; ok && v == true {
fmt.Println("Authorized user identified!")
return true
} else {
fmt.Println("Unauthorized user detected!")
return false
}
}
return false
}
func main() {
//...
// serving files for the game
http.HandleFunc("/", homeHandler)
http.Handle("/ws", websocket.Handler(wsLobbyHandler))
http.HandleFunc("/lobby.html", lobbyHandler)
http.HandleFunc("/formlogin", loginHandler)
//...
//http.HandleFunc("/*.html", SourceHandler)
if err := http.ListenAndServeTLS(*addr, "cert.pem", "key.pem", nil); err != nil {
log.Fatal("ListenAndServe:", err)
}
}`
in my html i have:
<form id="login_form" action="/formlogin" method="post">
When logging in, the request is handled within loginHandler
The user is identified correctly from the database and a session is created (via createSession()) and placed into the cookie store.
But after the redirect to lobby.html, back in loginHandler
http.Redirect(w, req, "/lobby.html", http.StatusFound)
the validation within lobbyHandler does not work. Does this have to do with the store.Save(...) altering the headers?
I'm very new to go, as well as web apps in general, so I would really appreciate feedback.
Thanks to the comments, i was able to stumble across a similar search that works for me.
session.Options = &sessions.Options{
Path: "/lobby.html",
}
I needed to make sure the cookies know where they are going to be redirected properly.