I'm having an issue with Golang using gorilla/mux.
I've declared an endpoint and certain subpath, but when I try to call the subpath BEFORE call it's main path, it returns me a 404 error.
So, there's my schema:
on main.go:
services.StartRestService()
on services package:
func StartRestService() {
log.Println("Loading Rest Service")
router := mux.NewRouter()
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { mainHandleRequests(w, r, router) }).Methods("GET")
http.Handle("/", router)
log.Fatal(http.ListenAndServe(":8083", router))
}
on another go file in the same services package:
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the HomePage!")
fmt.Println("Endpoint Hit: homePage")
}
func pong(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Pong!")
fmt.Println("Endpoint Hit: pong")
}
func mainHandleRequests(w http.ResponseWriter, r *http.Request, router *mux.Router) {
homePage(w, r)
router.HandleFunc("/ping", pong)
}
Running my application, if I try to call http://localhost:8083/ping it returns me 404 not found. Only if I call http://localhost:8083, and after that I call the ping endpoint, it works.
What is wrong?
Yes, the router is unaware of the /ping endpoint until mainHandleRequests is called once.
So, the router should be set up and then not touched or changed in any way by incoming calls.
I have restructured your code a little:
func homePageHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the HomePage!")
fmt.Println("Endpoint Hit: homePage")
}
func pongHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Pong!")
fmt.Println("Endpoint Hit: pong")
}
func main() {
log.Println("Loading Rest Service")
router := mux.NewRouter()
router.Path("/").Methods(http.MethodGet).HandlerFunc(homePageHandler)
router.Path("/ping").Methods(http.MethodGet).HandlerFunc(pongHandler)
log.Fatal(http.ListenAndServe(":8083", router))
}
Related
What’s the equivalent to middleware handlers in Google Cloud Functions?
In standard approach, normally I do:
router.Handle("/receive", middlewares.ErrorHandler(MyReceiveHandler))
And then, in the middleware:
type ErrorHandler func(http.ResponseWriter, *http.Request) error
func (fn ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := fn(w, r)
if err == nil {
return
}
log.Printf("An error accured: %v", err)
clientError, ok := err.(errors.BaseError)
if !ok {
w.WriteHeader(500)
return
}
w.WriteHeader(clientError.GetStatusCode())
w.Write([]byte(clientError.Error()))
}
In AWS Lambda, I can achieve the same thing using, for example:
func main() {
lambda.Start(
middlewares.Authentication(Handler),
)
}
But I could not find a way to do this in GCP Functions.
How would it work?
Can you help me?
Let's say you start with the following server code in your development environment:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.Handle("/", MiddlewareFinalMsg(" Goodbye!", http.HandlerFunc(HelloWorld)))
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
func MiddlewareFinalMsg(msg string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
fmt.Fprint(w, msg)
})
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "Hello, World!")
}
As far as I can tell, GCF requires its entry point to be an exported identifier of type func(http.ResponseWriter, *http.Request) (not http.HandlerFunc, not http.Handler); therefore, if you have a http.Handler, you'll need to select its ServeHTTP method explicitly to obtain a function of the expected type. However, that identifier can be a package-level function, a method, or a variable.
Here is how you can adapt the code above for GCF:
package p
import (
"fmt"
"net/http"
)
// use F as your GCF's entry point
var F = MiddlewareFinalMsg(" Goodbye!", http.HandlerFunc(HelloWorld)).ServeHTTP
func MiddlewareFinalMsg(msg string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
fmt.Fprint(w, msg)
})
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "Hello, World!")
}
I'm trying to use Golang middleware to run after handling Gorilla Mux routing, in order to change the response of each request.
Code sample see below. Currently it returns 'run before, run test', goal is to return 'run before, run test, run after' using the runsafter middleware.
Is something like this possible with Gorilla Mux?
package main
import (
"fmt"
"github.com/gorilla/mux"
"net/http"
)
func runsbefore(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("run before, "))
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func runsafter(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("run after, "))
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func runtest(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("run test, "))
}
func main() {
fmt.Println("Server starting on port 8000")
r := mux.NewRouter()
r.HandleFunc("/", runtest).Methods("GET")
http.ListenAndServe(":8000", runsbefore(r))
// something like: http.ListenAndServe(":8000", runsbefore(r(runsafter)))
}
Use
http.ListenAndServe(":8000", runsAfter(runsbefore(r)))
and fix the error in runsAfter:
func runsafter(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
w.Write([]byte("run after, "))
}
return http.HandlerFunc(fn)
}
Here is my code about a small demonstration webserver written with the Go language and the gorilla mux package :
package main
import (
"fmt"
"github.com/gorilla/mux"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
fmt.Fprintf(w, "Hi there, I love %s!", vars["username"])
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
errorHandler(w, r, http.StatusNotFound)
return
}
vars := mux.Vars(r)
fmt.Fprintf(w, "Hi there, I love %s!", vars["username"])
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/help/{username}/", handler)
http.Handle("/", r)
http.ListenAndServe(":8080", nil)
}
But I don't find a way on how to implement a custom 404 page.
But I can't make a r.HandleFunc("/",...) but it will be too greedy.
The Router exports a NotFoundHandler field which you can set to your custom handler.
r := mux.NewRouter()
r.NotFoundHandler = MyCustom404Handler
Sometimes, you spend a lot of time building a stack of middleware that does many things like logging, sending metrics and so... And the default 404 handler just skips all the middlewares.
I was able to solve this issue by re-setting the default 404 handler like this:
router := mux.NewRouter()
router.Use(someMiddleware())
// Re-define the default NotFound handler
router.NotFoundHandler = router.NewRoute().HandlerFunc(http.NotFound).GetHandler()
Now, the 404 default handler is also going thru all the middlewares.
Set the NotFoundHandler to a handler method that returns your custom 404 page.
r := mux.NewRouter()
h := http.HandlerFunc(NotFound)
r.NotFoundHandler = h
func NotFound(w http.ResponseWriter, r *http.Request) {
}
I can access GET parameters using mux:
import (
"github.com/gorilla/mux"
)
func main(){
rtr := mux.NewRouter()
rtr.HandleFunc("/logon", logonGet).Methods("GET")
}
func logonGet(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
login := params["login"]
}
But cannot figure out how to access POST params
func main(){
rtr := mux.NewRouter()
rtr.HandleFunc("/logon", logonPost).Methods("POST")
}
func logonPost(w http.ResponseWriter, r *http.Request) {
// how to get POST parameters from request
}
By using (*http.Request).FormValue method.
func logonPost(w http.ResponseWriter, r *http.Request) {
login := r.FormValue("login")
// ...
}
I'd like to use httprouter with muxchain while keeping route parameters like /:user/.
Take the following example:
func log(res http.ResponseWriter, req *http.Request) {
fmt.Println("some logger")
}
func index(res http.ResponseWriter, req *http.Request) {
fmt.Fprintf(res, "Hi there, I love %s!", req.URL.Path[1:])
}
func main() {
logHandler := http.HandlerFunc(log)
indexHandler := http.HandlerFunc(index)
chain := muxchain.ChainHandlers(logHandler, indexHandler)
router := httprouter.New()
router.Handler("GET", "/:user", chain)
http.ListenAndServe(":8080", router)
}
When I visit http://localhost:8080/john I obviously don't have access to ps httprouter.Params
That's because httprouter needs to see type httprouter.Handle but the function is called with type http.Handler.
Is there any way to use both packages together? The HttpRouter GitHub repo says
The only disadvantage is, that no parameter values can be retrieved when a http.Handler or http.HandlerFunc is used, since there is no efficient way to pass the values with the existing function parameters.
If you strongly want to use that packages, you can try to do something like that:
package main
import (
"fmt"
"github.com/gorilla/context"
"github.com/julienschmidt/httprouter"
"github.com/stephens2424/muxchain"
"net/http"
)
func log(res http.ResponseWriter, req *http.Request) {
fmt.Printf("some logger")
}
func index(res http.ResponseWriter, req *http.Request) {
p := context.Get(req, "params").(httprouter.Params)
fmt.Fprintf(res, "Hi there, I love %s!", p.ByName("user"))
}
func MyContextHandler(h http.Handler) httprouter.Handle {
return func(res http.ResponseWriter, req *http.Request, p httprouter.Params) {
context.Set(req, "params", p)
h.ServeHTTP(res, req)
}
}
func main() {
logHandler := http.HandlerFunc(log)
indexHandler := http.HandlerFunc(index)
chain := muxchain.ChainHandlers(logHandler, indexHandler)
router := httprouter.New()
router.GET("/:user", MyContextHandler(chain))
http.ListenAndServe(":8080", router)
}
You would have to patch muxchain to accept httprouter.Handle, but it's rather simple to create your own chain handler, for example:
func chain(funcs ...interface{}) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
for _, h := range funcs {
switch h := h.(type) {
case httprouter.Handle:
h(w, r, p)
case http.Handler:
h.ServeHTTP(w, r)
case func(http.ResponseWriter, *http.Request):
h(w, r)
default:
panic("wth")
}
}
}
}
playground