Skip to content

Instantly share code, notes, and snippets.

@plvhx
Created March 7, 2022 14:29
Show Gist options
  • Save plvhx/52ffd772119e0e5ef79296eedb902127 to your computer and use it in GitHub Desktop.
Save plvhx/52ffd772119e0e5ef79296eedb902127 to your computer and use it in GitHub Desktop.
Routing method approach #1 (Regex Table)
// Routing approach using Regex Table method.
// 2022 (c) Paulus Gandung Prakosa <[email protected]>
package main
import (
"fmt"
"log"
"regexp"
"sync"
"net/http"
)
var (
methodNotAllowed = "Method Not Allowed."
)
type Route struct {
method string
pattern *regexp.Regexp
handler func([]string) (func(w http.ResponseWriter, r *http.Request))
}
type Aggregator struct {
count int
lock sync.Mutex
route *Route
routes []*Route
methods []string
matches []string
}
func NewRoute(method string, pattern string, handler func([]string) (func(w http.ResponseWriter, r *http.Request))) *Route {
return &Route{method, regexp.MustCompile("^" + pattern + "$"), handler}
}
func NewAggregator() *Aggregator {
return &Aggregator{}
}
func (r *Route) GetMethod() string {
return r.method
}
func (r *Route) SetMethod(method string) {
r.method = method
}
func (r *Route) GetPattern() *regexp.Regexp {
return r.pattern
}
func (r *Route) SetPattern(pattern *regexp.Regexp) {
r.pattern = pattern
}
func (r *Route) GetHandler() (func([]string) (func(w http.ResponseWriter, r *http.Request))) {
return r.handler
}
func (r *Route) SetHandler(handler func([]string) (func(w http.ResponseWriter, r *http.Request))) {
r.handler = handler
}
func (a *Aggregator) AddRoute(route *Route) {
a.routes = append(a.routes, route)
}
func (a *Aggregator) GetRoutes() []*Route {
return a.routes
}
func (a *Aggregator) SetRoutes(routes []*Route) {
a.routes = routes
}
func (a *Aggregator) AddMethod(method string) {
a.methods = append(a.methods, method)
}
func (a *Aggregator) GetMethods() []string {
return a.methods
}
func (a *Aggregator) SetMethods(methods []string) {
a.methods = methods
}
func (a *Aggregator) Dispatch(host string, port int) {
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), http.HandlerFunc(a.coreDispatcher)))
}
func (a *Aggregator) coreDispatcher(w http.ResponseWriter, r *http.Request) {
for _, route := range a.GetRoutes() {
matches := route.GetPattern().FindStringSubmatch(r.URL.Path)
if len(matches) > 0 && r.Method != route.GetMethod() {
a.AddMethod(route.GetMethod())
continue
}
if len(matches) > 0 {
a.AddMethod(route.GetMethod())
a.setRoute(route)
a.setMatches(matches)
break
}
a.incRefCount()
}
defer a.resetRefCount()
if a.getRefCount() == len(a.GetRoutes()) {
http.NotFound(w, r)
return
}
methodMatch := false
for _, method := range a.GetMethods() {
if r.Method == method {
methodMatch = true
break
}
}
if !methodMatch {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte(methodNotAllowed))
return
}
handler := a.getRoute().GetHandler()(a.getMatches())
handler(w, r)
}
func (a *Aggregator) incRefCount() {
a.lock.Lock()
defer a.lock.Unlock()
a.count++
}
func (a *Aggregator) decRefCount() {
a.lock.Lock()
defer a.lock.Unlock()
a.count--
}
func (a *Aggregator) getRefCount() int {
a.lock.Lock()
defer a.lock.Unlock()
return a.count
}
func (a *Aggregator) resetRefCount() {
for {
if a.getRefCount() == 0 {
break
}
a.decRefCount()
}
}
func (a *Aggregator) getRoute() *Route {
return a.route
}
func (a *Aggregator) setRoute(route *Route) {
a.route = route
}
func (a *Aggregator) getMatches() []string {
return a.matches
}
func (a *Aggregator) setMatches(matches []string) {
a.matches = matches
}
func callback0(fields []string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is %s (method: %s)\n", r.URL, r.Method)
}
}
func callback1(fields []string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is %s (method: %s)\n", r.URL, r.Method)
}
}
func callback2(fields []string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(
w,
"This is %s (method: %s, segment 1: %s)\n",
r.URL,
r.Method,
fields[0],
)
}
}
func callback3(fields []string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(
w,
"This is %s (method: %s, segment 1: %s)\n",
r.URL,
r.Method,
fields[0],
)
}
}
func main() {
a := NewAggregator()
a.AddRoute(NewRoute("GET", "/foo", callback0))
a.AddRoute(NewRoute("POST", "/foo", callback1))
a.AddRoute(NewRoute("GET", "/foo/([^/]+)", callback2))
a.AddRoute(NewRoute("POST", "/foo/([^/]+)", callback3))
a.Dispatch("0.0.0.0", 9000)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment