Gorilla mux - modify request before passing it to routers - go

Is there a way to catch a *http.Request object before It will be parsed and forwarded to Gorilla mux router handler?
For example, we have some routing map with their handlers:
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
I plan to use a dynamic language prefix (2 symbols). Example:
without language code (for default language option):
https://example.com/products/1
https://example.com/articels/2
with language code:
https://example.com/ru/products/1
https://example.com/ru/articels/2
Is there a way to catch full URL in the middleware, extract language (if exists) and then after some modifications pass It to Gorilla mux routers? It will help to build beautiful URLs:
https://example.com/products/1 <- default language
https://example.com/ru/products/1 <- russian language (same resource but in different language)
That looks more attractive than this variant:
https://example.com/en/products/1 <- mandatory default language
https://example.com/ru/products/1 <- russian language

Something like this will probably work:
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
m := http.NewServeMux()
m.HandeFunc("/", func(w http.ResponseWriter, req *http.Request) {
// do something with req
r.ServeHTTP(w, req)
})
http.ListenAndServe(":8080", m)

Related

Set gorilla mux subrouter

If I have a mux.Router, how do I set it to be a "subrouter"? All examples I can find creates a new router by calling Route.Subrouter() and then setting Handlers on it, but I already have a router!
// does not know about "/api/v1/"
v1_router := mux.NewRouter()
subrouter.HandleFuc("/route1/", ...)
subrouter.HandleFuc("/route2/", ...)
// does not now about route1, route2
r := mux.NewRouter()
r.PathPrefix("/api/v1/").???(v1_router)
I hope I'm making sense...
I feel the same way, and have to live with the same "workaround". I would like to set the subrouter to an existing router. Like:
r.PathPrefix("/api").SetSubrouter(api.GetRouter()) //won't work
That would let my api feel more autonomous / loosely coupled. But getting a subrouter is all we have from gorilla.
s := r.PathPrefix("/api").Subrouter()
api.SetRoutes(s)
You can do it like this:
v1 package file:
func Handlers(subrouter *mux.Router) {
//base handler, i.e. /v1
r.StrictSlash(true)
subrouter.HandleFuc("/route1/", ...)
subrouter.HandleFuc("/route2/", ...)
}
main file:
r := mux.NewRouter()
package.Handlers(r.PathPrefix("/api/v1").Subrouter())

golang with fastcgi how to read REMOTE_USER

Short: How can I read the CGI var REMOTE_USER on golang using fastcgi?
Long:
I'm trying to write a program in go to work behind a httpd using fcgi over a socket. The httpd does the ssl termination and provides basic auth. I need to read $REMOTE_USER, but I cannot in golang, while I can in perl.
My code is based on this fcgi example. I try
func homeView(w http.ResponseWriter, r *http.Request) {
user, pass, authok := r.BasicAuth()
But authok is always false, user and pass remain empty, although I know for sure that the authorization (done by httpd) was OK. To eliminate other errors, I have done it in perl:
my $socket = FCGI::OpenSocket("/run/fcgi-check.sock", 5);
my $q = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);
while ($q->Accept() >= 0) {
my $c = CGI::Simple->new;
my $user_id = $c->remote_user();
and it works fine in perl.
To debug, I printed the output of r.Header and I got:
map[Authorization:[]
Am I right that the header that go sees does no hold any information about any authorization? But it does in perl.
Here is a full but minimal golang code example that demonstrates the problem (on OpenBSD 5.8 with go version go1.4.2 openbsd/amd64 and OpenBSDs httpd with 'authenticate "/" with restricted_users' in httpd.conf.
package main
import (
"github.com/gorilla/mux"
"io"
"log"
"fmt"
"net"
"net/http"
"net/http/fcgi"
)
func homeView(w http.ResponseWriter, r *http.Request) {
headers := w.Header()
headers.Add("Content-Type", "text/html")
headers.Add("Cache-Control", "no-cache, no-store, must-revalidate")
headers.Add("Pragma", "no-cache")
headers.Add("Expires", "0")
r.ParseForm()
user, pass, authok := r.BasicAuth()
if authok {
io.WriteString(w, fmt.Sprintln("Auth OK"))
io.WriteString(w, fmt.Sprintln("user is: "+user+", pass is: "+pass))
} else {
io.WriteString(w, fmt.Sprintln("Auth NOT OK"))
}
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/check/", homeView)
var err error
listener, err := net.Listen("unix", "/run/fcgi-check.sock")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
err = fcgi.Serve(listener, r)
if err != nil { log.Fatal(err)}
}
Help will be appreciated!
Thanks in advance!
T.
Go 1.9 will expose cgi environment variables. As seen in this closed ticket:
https://go-review.googlesource.com/c/40012
The simple answer (as of go version 1.4.2) is that go currently does not support the transfer of CGI variable REMOTE_USER.
While #JimB is correct on that you're wrong in your approach, I'll answer the question as stated.
The net/http/fcgi package uses the machinery of net/http/cgi to populate an instance of http.Request—which is passed to your handler—with "parameters" (key/value pairs) submitted by the webserver during the FastCGI session (call).
This is done here.
Now if you'll inspect the relevant bit of the net/http/cgi code, you'll see that the variables which are not mapped to specific dedicated fields of http.Request get converted to HTTP "headers".
This means, your code should be able to access the variable you need using something like
ruser := r.Header.Get("Remote-User")
Update 2015-12-02: the reseach performed by #JimB and the OP showed that there's apparently no way to read the REMOTE_USER variable under FastCGI. Sorry for the noise.
This core change to the fcgi package is in review and is close to being merged. If it's no longer relevant to you, hopefully it will be useful to others.

mux.Vars not working

I'm running on HTTPS (port 10443) and use subroutes:
mainRoute := mux.NewRouter()
mainRoute.StrictSlash(true)
mainRoute.Handle("/", http.RedirectHandler("/static/", 302))
mainRoute.PathPrefix("/static/").Handler(http.StripPrefix("/static", *fh))
// Bind API Routes
apiRoute := mainRoute.PathPrefix("/api").Subrouter()
apiProductRoute := apiRoute.PathPrefix("/products").Subrouter()
apiProductRoute.Handle("/", handler(listProducts)).Methods("GET")
And the functions:
func listProducts(w http.ResponseWriter, r *http.Request) (interface{}, *handleHTTPError) {
vars := mux.Vars(r)
productType, ok := vars["id"]
log.Println(productType)
log.Println(ok)
}
ok is false and I have no idea why. I'm doing a simple ?type=model after my URL..
When you enter a URL like somedomain.com/products?type=model you're specifying a query string, not a variable.
Query strings in Go are accessed via r.URL.Query - e.g.
vals := r.URL.Query() // Returns a url.Values, which is a map[string][]string
productTypes, ok := vals["type"] // Note type, not ID. ID wasn't specified anywhere.
var pt string
if ok {
if len(productTypes) >= 1 {
pt = productTypes[0] // The first `?type=model`
}
}
As you can see, this can be a little clunky as it has to account for the map value being empty and for the possibility of a URL like somedomain.com/products?type=model&this=that&here=there&type=cat where a key can be specified more than once.
As per the gorilla/mux docs you can use route variables:
// List all products, or the latest
apiProductRoute.Handle("/", handler(listProducts)).Methods("GET")
// List a specific product
apiProductRoute.Handle("/{id}/", handler(showProduct)).Methods("GET")
This is where you would use mux.Vars:
vars := mux.Vars(request)
id := vars["id"]
Hope that helps clarify. I'd recommend the variables approach unless you specifically need to use query strings.
An easier way to solve this is to add query parameters in your route through Queries, like:
apiProductRoute.Handle("/", handler(listProducts)).
Queries("type","{type}").Methods("GET")
You can get it using:
v := mux.Vars(r)
type := v["type"]
NOTE: This might not have been possible when the question was originally posted but I stumbled across this when I encountered a similar problem and the gorilla docs helped.

Nesting subrouters in Gorilla Mux

I've been using gorilla/mux for my routing needs. But I noticed one problem, when I nest multiple Subrouters it doesn't work.
Here is the example:
func main() {
r := mux.NewRouter().StrictSlash(true)
api := r.Path("/api").Subrouter()
u := api.Path("/user").Subrouter()
u.Methods("GET").HandleFunc(UserHandler)
http.ListenAndServe(":8080", r)
}
I wanted to use this approach so I can delegate populating the router to some other package, for example user.Populate(api)
However this doesn't seem to work. It works only if I use single Subrouter in the chain.
Any ideas?
I figured it out, so I'll just post it here in case someone is as stupid as I was. :D
When creating path-based subrouter, you have to obtain it with PathPrefix instead of Path.
r.PathPrefix("/api").Subrouter()
Use r.Path("/api") only when attaching handlers to that endpoint.
For those who are struggling to split between auth and noauth routes, the following works fine for me:
r := mux.NewRouter()
noAuthRouter := r.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
return r.Header.Get("Authorization") == ""
}).Subrouter()
authRouter := r.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
return true
}).Subrouter()
Then you can apply middleware for authRouter only
If you need to Separate out the UI and API routers, you can simply do what the OP suggested:
appRouter := r.PathPrefix("/").Subrouter()
appRouter.Use(myAppRouter)
apiRouter := r.PathPrefix("/api").Subrouter()
apiRouter.Use(myAPIRouter)
Many thanks for the OP for providing the answer. Hopefully having it all in one place for my use case will help someone.

Key into map with undefined integer

I'm using Gorilla mux for my handlers and using mux.Vars. I'm trying to write a test for one of the handlers that uses mux.Vars so what I do is
var vars = map[string]string{
"id": user.ID,
}
context.Set(req, 0, vars)
In mux the key (an integer) is undefined so by default 0. I've logged the key when mux.Vars gets called and it prints 0. I should be able to key into this map
map[0:map[id:522d14f5b1b92235d6000002]]
by doing map[key] but that returns nil. However, I get the correct value back if I hardcode map[0]. Any thoughts?
I'm not entirely sure I understand the question, but it looks like you might be confusing mux.Vars with mux.context. The two are separate entities. The former returns route variables that are parsed from the URL path. For instance, you could do:
r := mux.NewRouter()
r.HandleFunc("/blah/{foo}/", MyHandler)
...
func MyHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
...
}
The latter contains context variables you set yourself. For instance:
func MyHandler(w http.ResponseWriter, r *http.Request) {
context.Set(r, 0, map[string]string{"id": "myid"})
myMap := context.Get(r, 0)
...
}
You might check out some usage examples of how others use both to see what is most appropriate for your use case:
mux.Vars: https://sourcegraph.com/github.com/gorilla/mux/symbols/go/github.com/gorilla/mux/Vars
mux.context: https://sourcegraph.com/github.com/gorilla/context/symbols/go/github.com/gorilla/context

Resources