4
\$\begingroup\$

I know Gorilla and others have packages that accomplish this, but I'm trying to learn Go by making stuff. In the router below, the first route / is for all http methods, the second route /hello is only for GET, and the third route hello/:name has one parameter for all HTTP methods.

func main() {
 http.HandleFunc("/", route)
 http.ListenAndServe(":8080", nil)
}
func match(re string, route string) bool {
 match, err := regexp.MatchString(re, route)
 if err != nil {
 return false
 }
 return match
}
func route(w http.ResponseWriter, r *http.Request) {
 route := r.URL.Path
 switch {
 case route == "/":
 home(w, r)
 case route == "/hello" && r.Method == "GET":
 hello(w, r)
 case match("^/hello/([^/]+)$", route) == true:
 var re = regexp.MustCompile("^/hello/([^/]+)$")
 match := re.FindStringSubmatch(route)
 helloName(w, r, match[1])
 default:
 notFound(w, r)
 }
}
// func home, hello, helloName, and notFound

How can I make this more efficient? Is it reliable?

asked Jun 1, 2017 at 18:34
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Instead of case x == true you can use boolean expressions directly, and write simply:

case x:

The same regex is written twice here. What's worse is that the pattern matching is also evaluated twice:

case match("^/hello/([^/]+)$", route) == true:
 var re = regexp.MustCompile("^/hello/([^/]+)$")

I'm not sure if there's an elegant solution for this situation in Go. But at the minimum you could move the regex pattern into a variable to avoid typing it twice.

Another alternative is to create a matcher structure, with a match function that will not only match a pattern and return a boolean, but at the same time also store the sub-string you want to extract.

type matcher struct {
 value string
}
func (m *matcher) match(pattern string, route string) bool {
 var re = regexp.MustCompile(pattern)
 matches := re.FindStringSubmatch(route)
 if len(matches) < 2 {
 return false
 }
 m.value = matches[1]
 return true
}

With the help of this, you could rewrite the route function as:

func route(w http.ResponseWriter, r *http.Request) {
 route := r.URL.Path
 m := &matcher{}
 switch {
 case route == "/":
 home(w, r)
 case route == "/hello" && r.Method == "GET":
 hello(w, r)
 case m.match("^/hello/([^/]+)$", route):
 helloName(w, r, m.value)
 default:
 notFound(w, r)
 }
}
answered Jun 1, 2017 at 19:33
\$\endgroup\$
2
\$\begingroup\$

I found that I can put all of the regexp.MustCompile's into the func init, change a few other things, and increase the speed by more than a factor of 50:

var routeRegex map[string]*regexp.Regexp
func init() {
 r := make(map[string]string)
 r["helloName"] = "^/hello/([a-zA-Z]+?)$"
 r["helloNameAge"] = "^/hello/([a-zA-Z]+?)/([0-9]+?)$"
 routeRegex = make(map[string]*regexp.Regexp)
 for k, v := range r {
 routeRegex[k] = regexp.MustCompile(v)
 }
}
func match(x string, r string) (bool, []string) {
 if m := routeRegex[x].FindStringSubmatch(r); m != nil {
 return true, m[1:]
 }
 return false, nil
}
func Route(w http.ResponseWriter, r *http.Request) {
 rt := r.URL.Path
 if rt == "/" {
 home(w, r)
 } else if rt == "/hello" && r.Method == "GET" {
 hello(w, r)
 } else if m, p := match("helloName", rt); m {
 helloName(w, r, p[0])
 } else if m, p := match("helloNameAge", rt); m {
 helloNameAge(w, r, p[0], p[1] )
 } else {
 notFound(w, r)
 }
}

I benchmarked func route with 1) no regex, 2) one regex match, 3) two regex matches, and 4) not matching route / not found.

new solution:

  1. 3.6 ns/op
  2. 279 ns/op
  3. 332 ns/op
  4. 212 ns/op

old solution:

  1. 3.33 ns/op
  2. 16237 ns/op
  3. 16479 ns/op
  4. 16543 ns/op
answered Jun 2, 2017 at 3:34
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.