Gorilla Mux to handle curl request - go

I want to use Gorilla mux to handle api requests.
Commands will be something like:
curl http://0.0.0.0:8000/api/myapiname/v1?number=10&target=google.com&message=hello
And I am serving with following handlers:
router.Methods("GET").Path("/api/myapiname/{version}").HandlerFunc(apihandler)
func apihandler(rw http.ResponseWriter, q *http.Request) {
vars := mux.Vars(q)
log.Println(vars["version"])
log.Println(q.FormValue("number"))
log.Println(q.FormValue("target"))
log.Println(q.FormValue("message"))
}
But for the curl requests I only get the form value of number not target and message's
What is the correct way of handle api requests using Gorilla mux? Do I need sub routing?
I just want to receive any http request of form http://0.0.0.0:8000/api/myapiname/v1?number=10&target=google.com&message=hello and be able to map its key to value
THanks!

You may try to add Queries to your router, in that case you will have all vars in one map.
router.Methods("GET").Path("/api/myapiname/{version}").Queries("number", "{number:[0-9]+}", "target", "{target:[^&]+}", "message", "{message:[^&]+}").HandlerFunc(apihandler)
func apihandler(rw http.ResponseWriter, q *http.Request) {
vars := mux.Vars(q)
log.Println(vars["version"])
log.Println(vars("number"))
log.Println(vars("target"))
log.Println(vars("message"))
}

Related

How to get full server URL from any endpoint handler in Gin

I'm creating an endpoint using Go's Gin web framework. I need full server URL in my handler function. For example, if server is running on http://localhost:8080 and my endpoint is /foo then I need http://localhost:8080/foo when my handler is called.
If anyone is familiar with Python's fast API, the Request object has a method url_for(<endpoint_name>) which has the exact same functionality: https://stackoverflow.com/a/63682957/5353128
In Go, I've tried accessing context.FullPath() but that only returns my endpoint /foo and not the full URL. Other than this, I can't find appropriate method in docs: https://pkg.go.dev/github.com/gin-gonic/gin#Context
So is this possible via gin.Context object itself or are there other ways as well? I'm completely new to Go.
c.Request.Host+c.Request.URL.Path should work but the scheme has to be determined.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/foo", func(c *gin.Context) {
fmt.Println("The URL: ", c.Request.Host+c.Request.URL.Path)
})
r.Run(":8080")
}
You can determine scheme which also you may know already. But you can check as follows:
scheme := "http"
if c.Request.TLS != nil {
scheme = "https"
}
If your server is behind the proxy, you can get the scheme by c.Request.Header.Get("X-Forwarded-Proto")
You can get host part localhost:8080 from context.Request.Host and path part /foo from context.Request.URL.String().
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/foo", func(c *gin.Context) {
c.String(http.StatusOK, "bar")
fmt.Println(c.Request.Host+c.Request.URL.String())
})
// Listen and Server in 0.0.0.0:8080
r.Run(":8080")
}
And you can get http protocol version by context.Request.Proto, But it will not determine http or https. you need to get it from your service specifications.

How to end to end/integration test a Go app that use a reverse proxy to manage subdomain?

I have a Go app that use Gin gonic and a Nginx reverse proxy that send trafic to another app on domain.com and send all the *.domain.com subdomains traffic directly to my go app.
My Go app then has a middleware that will read the hostname that nginx passes to it from Context and allow my handlers to know what subdomain is being request and return the proper data and cookies for said subdomain.
It's a pretty simple setup and it seems to work fine from my test in postman as all my routes are the same across all my subdomains so this way i can only use one router for all of them instead of one router per subodmain.
Now my big problem come when i'm trying to do end to end testing.
I'm setting up my test like this :
router := initRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/login", bytes.NewBuffer(jsonLogin))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
with initRouter() returning a gin engine with all my routes and middlewares loaded and the rest as a basic test setup.
Obviously the test will fail as the gin Context won't ever receive a subdomain from context and act as if everything is coming from localhost:8000.
Is there a way to either :
"Mock" a subdomain so that the router think the call is coming from foo.localhost.com instead of localhost
Setup my test suit so that the test request are routed thought nginx.. i'd prefer solution 1 as this would be a mess to setup / maintain.
Edit :
As per the httptest doc i've tried to hard code foo.localhost as the param of the NewRequest but it doesn't really behave as i need it to behave :
NewRequest returns a new incoming server Request, suitable for passing to an http.Handler for testing.
The target is the RFC 7230 "request-target": it may be either a path or an absolute URL. If target is an absolute URL, the host name from the URL is used. Otherwise, "example.com" is used.
When hardcoding http://foo.localhost.com/api/login or foo.localhost.com/api/login as the request target it directly passes it to my router under "foo.localhost.com/api/login" while nginx would just hit the /api/login directly and parse from c.Request.Host
Edit 2:
I'm currently exploring setting the host manually using :
req.Header.Set("Host", "foo.localhost")
The request returned by http.NewRequest isn't suitable for passing directly to ServeHTTP. Use one returned by httptest.NewRequest instead.
Simply set the Host field directly:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHelloWorld(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Host != "foobar" {
t.Errorf("Host is %q, want foobar", r.Host)
}
})
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/login", nil)
r.Host = "foobar"
mux.ServeHTTP(w, r)
}

How to use swaggo (swagger doc) in GoLang lang with http.ServeMux?

In documentation https://github.com/swaggo/swag is using gin to initialize server, but in my application i'm using http.ServeMux and how initialize swaggo without use gin server
in Docs use
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
how can I use something like ...
mu.Handle("/swagger/*any", swaggerFiles.Handler)
......
follows as my initial idea, but don't work... rsrs
func Server() *http.ServeMux {
docs.SwaggerInfo.Title = "Swagger Example API"
docs.SwaggerInfo.Description = "This is a sample server Petstore server."
docs.SwaggerInfo.Version = "1.0"
docs.SwaggerInfo.Host = "petstore.swagger.io"
mu := http.NewServeMux()
mu.Handle("/metrics", promhttp.Handler())
mu.Handle("/swagger/*any", swaggerFiles.Handler)
mu.HandleFunc("/helloWorld", handlers.NewHelloWorldHandler().HelloWorldHandler)
mu.HandleFunc("/production/", handlers.NewProductionHandler().ProductionHandler)
return mu
}
If you have your swagger files built for distribution (i.e. static files) and are in say the directory: /some/dir/path/static/swagger
This should work with go's http router:
staticFilesPath := "/some/dir/path/static"
staticRoute := "/static/"
h := http.NewServeMux()
// static file handler for route '/static/*'
h.Handle(
staticRoute,
http.StripPrefix(
staticRoute,
http.FileServer(http.Dir(staticFilesPath)),
),
)
I find it helpful to add this also:
// (redirect a route if not recognized - remove in production)
//
// unrecognized routes will redirect to Swagger API docs
h.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, staticRoute + "swagger/", http.StatusSeeOther)
})

Understanding of Prometheus

I would like to try simple example of using Prometheus.
I have downloaded server binaries
I have started simple code, but with few modifications
var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
func main() {
flag.Parse()
http.Handle("/metrics", promhttp.Handler())
http.Handle("/test/{id}", myHandler(promhttp.Handler()))
log.Fatal(http.ListenAndServe(*addr, nil))
}
func myHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello, you've hit %s\n", r.URL.Path)
next.ServeHTTP(w, r)
})
}
Questions:
I assume Prometheus is monitoring tool and I would like to monitor endpoints metrics and /test/{id} separately. Did I get the idea correctly by creating several handlers and using promhttp.Handler() as middleware?
What else apart of quantity and latency of requests can be monitored in e.g. simple web app with database?
To follow up on #David Maze's answer, the default handler promhttp.Handler is for reporting metrics. (gathers from all registered handlers and reports them on request).
Unfortunately, it is not a generic http middleware that gives you any metrics out of the box.
I have seen many of go's web frameworks have some sort of community prometheus middleware (gin's) that give metrics out of the box (latency, response code, request counts, etc).
The go prometheus client library has examples of how to add metrics to your application.
var (
httpRequests = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Number of http requests.",
},
)
)
func init() {
// Metrics have to be registered to be exposed:
prometheus.MustRegister(httpRequests)
}
func myHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpRequests.Inc()
fmt.Fprintf(w, "hello, you've hit %s\n", r.URL.Path)
next.ServeHTTP(w, r)
})
}
As for your second question lots :) Google advocates for monitoring 4 golden signals:
https://landing.google.com/sre/book/chapters/monitoring-distributed-systems.html#xref_monitoring_golden-signals
These are
Traffic - Throughput - Counts/Time
Latency - distribution / histogram
Errors - HTTP Response codes/explicit error counts
Saturation - resource queues ie if there is a goroutine pool how many goroutines are active at a given time
In my experie8inces it has also been helpful to have visibility of all the interactions between your appl8ication and your database (ie the 4 golden signals applied to your database):
number of calls made to db from app
latencies of calls made
results (err/success) of your calls made to determine availability (success / total)
saturation available from your db driver (https://golang.org/pkg/database/sql/#DBStats)

Is there no way to redirect from https://www.. to https://.. in Go?

I saw this post by someone here but there are no answers: Redirecting https://www.domain.com to https://domain.com in Go
I tried to see if I could find a way to check if the request was made with a www url by checking the variables in *http.Request variable but all I got was relative paths and empty strings "".
I tried to fmt.Println() these variables:
func handleFunc(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.string())
fmt.Println(r.Host)
fmt.Println(r.RequestURI)
}
But none of these variables contained the absolute path with the www part. How can I check if a request was made from a www url? I want to figure this out so that I can redirect from www to non-www.
Is this really not even possible in Go? Some people suggested putting nginx in front of Go, but there has to be a way without nginx right? Do I really need to install and use nginx in front of Go just to do a simple redirect from www to non-www? This does not seem like a good solution to a seemingly small problem.
Is there no way to achieve this?
Wrap your handlers with a redirector:
func wwwRedirect(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if host := strings.TrimPrefix(r.Host, "www."); host != r.Host {
// Request host has www. prefix. Redirect to host with www. trimmed.
u := *r.URL
u.Host = host
u.Scheme = "https"
http.Redirect(w, r, u.String(), http.StatusFound)
return
}
h.ServeHTTP(w, r)
})
}
Use the redirector like this:
log.Fatal(http.ListenAndServeTLS(addr, certFile, keyFile, wwwRedirect(handler))

Resources