Introduction to the

Gorilla/MUX is a routing management library in the Gorilla Web Development Kit. Gorilla Web Development Kit is a toolkit in the Go language that assists in developing Web servers. It includes all aspects of Web server development, such as Gorilla/Schema, Gorilla/WebSocket, Gorilla/Handlers, There is the session management package gorilla/sessions, and there is the securecookie package gorilla/ secureCookie. This article starts with Gorilla/MUX (hereafter referred to as MUX), and subsequent articles will look at each of the Gorilla packages listed above.

MUX has the following advantages:

  • The implementation of the standardhttp.HandlerInterface, so it can be connected tonet/httpStandard library combined use, very lightweight;
  • You can match handlers based on the request’s hostname, path, path prefix, protocol, HTTP header, query string, and HTTP method, and you can customize the matching logic.
  • You can use variables in the hostname, path, and request parameters, and you can specify a regular expression for them.
  • You can pass parameters to the specified processor to construct the full URL;
  • Support routing grouping for easy management and maintenance.

Quick to use

The code in this article uses Go Modules.

Create a directory and initialize it:

$ mkdir -p gorilla/mux && cd gorilla/mux
$ go mod init github.com/darjun/go-daily-lib/gorilla/mux

Install the Gorilla/MUX library:

$ go get -u github.com/gorilla/gorilla/mux

I have a few classic Go books with me right now:

Let’s write a Web service that manages book information. Books are uniquely identified by the ISBN, which stands for International Standard Book Number.

First define the structure of the book:

type Book struct {
  ISBN        string   `json:"isbn"`
  Name        string   `json:"name"`
  Authors     []string `json:"authors"`
  Press       string   `json:"press"`
  PublishedAt string   `json:"published_at"`
}

var (
  mapBooks map[string]*Book
  slcBooks []*Book
)

Define the init() function to load data from the file:

func init() { mapBooks = make(map[string]*Book) slcBooks = make([]*Book, 0, 1) data, err := ioutil.ReadFile(".. /data/books.json") if err ! = nil { log.Fatalf("failed to read book.json:%v", err) } err = json.Unmarshal(data, &slcBooks) if err ! = nil { log.Fatalf("failed to unmarshal books:%v", err) } for _, book := range slcBooks { mapBooks[book.ISBN] = book } }

Then there are two handlers, one for the entire list and one for a specific book:

func BooksHandler(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.Encode(slcBooks) } func BookHandler(w http.ResponseWriter, r *http.Request) { book, ok := mapBooks[mux.Vars(r)["isbn"]] if ! ok { http.NotFound(w, r) return } enc := json.NewEncoder(w) enc.Encode(book) }

Registered Processor:

func main() {
  r := mux.NewRouter()
  r.HandleFunc("/", BooksHandler)
  r.HandleFunc("/books/{isbn}", BookHandler)
  http.Handle("/", r)
  log.Fatal(http.ListenAndServe(":8080", nil))
}

The use of MUX is very similar to NET/HTTP. First, call mux.newrOuter () to create a routing object of type * mux.router, which registers the handler in exactly the same way as the library’s * http.servemux. This means calling the handleFunc () method to register a Handler of type Func(http.responseWriter, * http.request), and calling the Handle() method to register a Handler object that implements the http.handler interface. The above registered two handlers, one is to display a list of book information, a display of a specific book information.

Notice that the path /books/{isbn} uses a variable, named in the middle of the {}, that matches a particular part of the path. Returns Map [String] String, which can then be accessed by the variable name. The Muc.vars (R) is used to retrieve the routing variable of request R. The access to the variable isbn in BookHandler above.

Since the *mux.Router also implements the http.handler interface, it can be registered directly as a Handler object parameter to the http.handle (“/”, r). Here we register the root path /, which is essentially hosting the processing of all requests to * mux.router.

Finally, http.listenAndServe (“:8080”, nil) starts a Web server and waits to receive the request.

Run and type localhost:8080 in your browser to display a list of books:

Type localhost:8080/books/978-7-111-55842-2 to display the details of the Go Programming Language book:

As you can see from the above usage, the MUX library is very lightweight and works well with the standard library NET/HTTP.

We can also use regular expressions to qualify the patterns of variables. There is a fixed schema for ISBN. The schema used now looks something like this: 978-7-111-55842-2 (this is the ISBN of the book “Go Programming Language”), which is three numbers-one numbers-three numbers-five numbers-one number, represented by a regular expression as \d{3}-\d-\d{3}-\d{5}-\d. Add a separate variable and regular expression after the variable name:

r.HandleFunc("/books/{isbn:\\d{3}-\\d-\\d{3}-\\d{5}-\\d}", BookHandler)

Flexible matching methods

MUX provides a rich set of ways to match requests. In contrast, NET/HTTP can only specify a specific path, which is a bit clunky.

We can specify the domain or subdomain of the route:

r.Host("github.io")
r.Host("{subdomain:[a-zA-Z0-9]+}.github.io")

The above route only accepts requests for the domain github. IO or one of its subdomains, such as darjun. Github. IO, my blog. You can use a regular expression when specifying a domain name, and the second line of code above restricts the first part of the subdomain to be a number of letters or numbers.

Specify the path prefix:

// Handle only requests with the path prefix '/books/' r.athprefix ("/books/")

Specify the method of the request:

R.methods ("GET", "POST")

Protocol used (HTTP/HTTPS) :

// Handle only HTTPS requests R.Chemes (" HTTPS ")

First:

// Handle only XMLHttpRequest ("X-Requested-With", "XMLHttpRequest ")

Query parameters (that is, in the URL? The latter part) :

// Only handle requests where the query parameter contains key=value. R.queries ("key", "value")

Finally, we can combine these conditions:

r.HandleFunc("/", HomeHandler)
 .Host("bookstore.com")
 .Methods("GET")
 .Schemes("http")

In addition, MUX allows for custom matchers. A custom matcher is a function of type func(r * http.request, rm * routeMatch) bool that determines whether the match was successful or not based on the information in the Request r. The http.request structure contains a lot of information: HTTP methods, HTTP version numbers, URLs, headers, and so on. For example, if we want to process only HTTP/1.1 requests we could write:

r.MatchrFunc(func(r *http.Request, rm *RouteMatch) bool {
  return r.ProtoMajor == 1 && r.ProtoMinor == 1
})

Note that the MUX matches in the order in which the routes are registered. Therefore, it is common to put special routes first and general routes later. If it were the other way around, the particular route would not be matched:

r.HandleFunc("/specific", specificHandler)
r.PathPrefix("/").Handler(catchAllHandler)

Zi lu by

Sometimes grouping paths can make program modules cleaner and easier to maintain. Now the site has expanded to include information about movies. We can define two subpaths to manage separately:

r := mux.NewRouter()
bs := r.PathPrefix("/books").Subrouter()
bs.HandleFunc("/", BooksHandler)
bs.HandleFunc("/{isbn}", BookHandler)

ms := r.PathPrefix("/movies").Subrouter()
ms.HandleFunc("/", MoviesHandler)
ms.HandleFunc("/{imdb}", MovieHandler)

The child Route is usually qualified by the path prefix. R.PathPrefix () returns a *mux.Route object, creates a child *mux.Router by calling its subrOuter () method, and then registers the handler with the handleFunc /Handle method of the object.

There is no international ISBN standard for movies like books, only one civil “quasi-standard” : IMDB. We used the information from the Douban movie:

Define the structure of a film:

type Movie struct {
  IMDB        string `json:"imdb"`
  Name        string `json:"name"`
  PublishedAt string `json:"published_at"`
  Duration    uint32 `json:"duration"`
  Lang        string `json:"lang"`
}

Loading:

var ( mapMovies map[string]*Movie slcMovies []*Movie ) func init() { mapMovies = make(map[string]*Movie) slcMovies = make([]*Movie, 0, 1) data, := ioutil.ReadFile(".. /.. /data/movies.json") json.Unmarshal(data, &slcMovies) for _, movie := range slcMovies { mapMovies[movie.IMDB] = movie } }

With subrouting, you can also divide the routes for each part into their own module to load. Define an initBooksRouter () method in the file Book. go to register the books-related routes:

func InitBooksRouter(r *mux.Router) {
  bs := r.PathPrefix("/books").Subrouter()
  bs.HandleFunc("/", BooksHandler)
  bs.HandleFunc("/{isbn}", BookHandler)
}

In the file.go file, define an initMoviesRouter () method that registers movie-related routes:

func InitMoviesRouter(r *mux.Router) {
  ms := r.PathPrefix("/movies").Subrouter()
  ms.HandleFunc("/", MoviesHandler)
  ms.HandleFunc("/{imdb}", MovieHandler)
}

In main.go’s main function:

func main() {
  r := mux.NewRouter()
  InitBooksRouter(r)
  InitMoviesRouter(r)

  http.Handle("/", r)
  log.Fatal(http.ListenAndServe(":8080", nil))
}

Note that subpath matches need to include a path prefix, which means that /books/ matches BookHandler.

Construct routing URLs

We can give a route a name, for example:

r.HandleFunc("/books/{isbn}", BookHandler).Name("book")

The above route has parameters, and we can construct a complete path by passing in the parameter values:

fmt.Println(r.Get("book").URL("isbn", "978-7-111-55842-2"))
// /books/978-7-111-55842-2 <nil>

A * url.url object with a path portion of /books/978-7-111-55842-2 is returned. The same applies to hostname and query parameters:

r := mux.Router()
r.Host("{name}.github.io").
 Path("/books/{isbn}").
 HandlerFunc(BookHandler).
 Name("book")

url, err := r.Get("book").URL("name", "darjun", "isbn", "978-7-111-55842-2")

All parameters in the path need to be specified, and the values need to match the specified regular expression (if any). Run output:

$ go run main.go
http://darjun.github.io/books/978-7-111-55842-2

You can call URLHost() to generate only the hostname part, and URLPath() to generate only the path part.

The middleware

MUX defines middleware type MIDDLEWAREFUNC:

type MiddlewareFunc func(http.Handler) http.Handler

All functions that satisfy this type can be used as the middleware of the MUX, applying the middleware by calling the Use() method of the routing object * MUX.router. This kind of middleware should be familiar to you if you read my last article, “NET/HTTP (Basic and Middleware) for Go Library of the Day.” Writing middleware typically involves passing in the original handler, manually calling the original handler function, and adding common processing logic before and after:

func loggingMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    log.Println(r.RequestURI)
    next.ServeHTTP(w, r)
  })
}

The three middleware I wrote about in the previous article can be used directly, which is the benefit of NET/HTTP compatibility:

func main() { logger = log.New(os.Stdout, "[goweb]", The Lshortfile | log. LstdFlags) r: = mux. NewRouter () / / used directly in an article on the definition of middleware r.U se (PanicRecover WithLogger, Metric) InitBooksRouter(r) InitMoviesRouter(r) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }

If the original handler is not called manually, the original handler will not be executed, which can be used to return an error directly if the validation fails. For example, Web sites require a login to access, and HTTP is a stateless protocol. So the Cookie mechanism was invented to record some information between the client and the server.

After successful login, we will generate a Cookie with token key to indicate successful login. We can write a middleware to work out this logic. If the Cookie does not exist or is illegal, we will redirect to the login interface:

func login(w http.ResponseWriter, r *http.Request) { ptTemplate.ExecuteTemplate(w, "login.tpl", nil) } func doLogin(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.Form.Get("username") password := r.Form.Get("password") if username ! = "darjun" || password ! = "handsome" { http.Redirect(w, r, "/login", http.StatusFound) return } token := fmt.Sprintf("username=%s&password=%s", username, password) data := base64.StdEncoding.EncodeToString([]byte(token)) http.SetCookie(w, &http.Cookie{ Name: "token", Value: data, Path: "/", HttpOnly: true, Expires: time.Now().Add(24 * time.Hour), }) http.Redirect(w, r, "/", http.StatusFound) }

To record the login status above, I combined the login username and password into a string in the form of username=xxx&password= XXX, base64 encoded this string, and then set it in the Cookie. Cookies are valid for 24 hours. At the same time, for security, only HTTP access to the Cookie is allowed (JavaScript script is not accessible). Of course, this is a very insecure approach, but it’s just for demonstration purposes. Redirect to/after successful login.

To demonstrate the login screen, I created several template files, using HTML /template parsing:

Login display page:

// login.tpl <form action="/login" method="post"> <label>Username:</label> <input name="username"><br> < label > Password: < / label > < input name = "Password" type = "Password" > < br > < button type = "submit" > login < / button > < / form >

The main page

< ul > < li > < a href = "/ books/" > library < / a > < / li > < li > < a href ="/movies/" > movies < / a > < / li > < / ul >

I also created pages for books and movies:

/ / movies. TPL < ol > {{range.}} < li > < p > title: < a href = "/ movies / {{. IMDB}}" > {{. Name}} < / a > < / p > < p > release date: {{. PublishedAt}} < / p > < p > length: {{. Duration}} points < / p > < p > language: {{. Lang}} < / p > < / li > < / ol > {{end}}
/ / movie. The TPL < p > IMDB: {{the IMDB}} < / p > < p > the movie Name: {{. Name}} < / p > < p > release date: {{. PublishedAt}} < / p > < p > length: {{.duration}} sub </p> <p> language: {{.lang}}</p>

Book pages are similar. Next, parse the template:

var ( ptTemplate *template.Template ) func init() { var err error ptTemplate, err = template.New("").ParseGlob("./tpls/*.tpl") if err ! = nil { log.Fatalf("load templates failed:%v", err) } }

Access the corresponding page logic:

func MoviesHandler(w http.ResponseWriter, r *http.Request) { ptTemplate.ExecuteTemplate(w, "movies.tpl", slcMovies) } func MovieHandler(w http.ResponseWriter, r *http.Request) { movie, ok := mapMovies[mux.Vars(r)["imdb"]] if ! ok { http.NotFound(w, r) return } ptTemplate.ExecuteTemplate(w, "movie.tpl", movie) }

Execute the corresponding template, passing in a list of movies or a specific movie information. Now that the page does not restrict access, let’s write a middleware that restricts access to only logged in users. When unlogged users visit the page, they jump to the login screen:

func authenticateMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("token") if err ! = nil { // no cookie http.Redirect(w, r, "/login", http.StatusFound) return } data, _ := base64.StdEncoding.DecodeString(cookie.Value) values, _ := url.ParseQuery(string(data)) if values.Get("username") ! = "dj" && values.Get("password") ! = "handsome" { // failed http.Redirect(w, r, "/login", http.StatusFound) return } next.ServeHTTP(w, r) }) }

Again, just for demonstration purposes, this type of validation is very insecure.

Then we let the books and movies subpaths authenticateMiddleware (which requires login authentication), while the login subpaths do not:

Func initBooksRouter (r *mux.Router) {bs := R.PathPrefix ("/books").subrouter () // Here bs.use (authenticateMiddleware) bs.HandleFunc("/", BooksHandler) bs.HandleFunc("/{isbn}", BookHandler)} func initMoviesRouter (r *mux.Router) {ms := R.PathPrefix ("/movies").subrOuter () // Here ms.Use(authenticateMiddleware) ms.HandleFunc("/", MoviesHandler) ms.HandleFunc("/{id}", MovieHandler) } func InitLoginRouter(r *mux.Router) { ls := r.PathPrefix("/login").Subrouter() ls.Methods("GET").HandlerFunc(login) ls.Methods("POST").HandlerFunc(doLogin) }

Run the program (note how the multi-file program runs) :

$ go run .

Visit localhost:8080/movies/ and you’ll be redirected to localhost:8080/login. Enter the user name darjun, the password handsome, login successful display the main page. The following requests do not need to verify, please feel free to click on 😀

conclusion

This article introduces the lightweight, powerful routing library Gorilla/MUX. It supports a wealth of request matching methods, sub-routing can greatly facilitate us to manage routing. Because it is compatible with the standard library NET/HTTP, it can be seamlessly integrated into programs that use NET/HTTP, taking advantage of middleware resources written for NET/HTTP. The next article introduces gorilla/handlers — some commonly used middleware.

If you find a fun and useful Go library, please submit an issue😄 on Go Daily’s GitHub

reference

  1. Gorilla/mux GitHub:github.com/gorilla/gorilla/mux
  2. Go a library GitHub:https://github.com/darjun/go-daily-lib daily

I

My blog: https://darjun.github.io

Welcome to pay attention to my WeChat public number [GOUPUP], learn together, make progress together ~