222 lines
4.5 KiB
Go
222 lines
4.5 KiB
Go
/********************************
|
|
*** Multiplexer for Go ***
|
|
*** Bone is under MIT license ***
|
|
*** Code by CodingFerret ***
|
|
*** github.com/go-zoo ***
|
|
*********************************/
|
|
|
|
package bone
|
|
|
|
import (
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
PARAM = 2
|
|
SUB = 4
|
|
WC = 8
|
|
REGEX = 16
|
|
)
|
|
|
|
// Route content the required information for a valid route
|
|
// Path: is the Route URL
|
|
// Size: is the length of the path
|
|
// Token: is the value of each part of the path, split by /
|
|
// Pattern: is content information about the route, if it's have a route variable
|
|
// handler: is the handler who handle this route
|
|
// Method: define HTTP method on the route
|
|
type Route struct {
|
|
Path string
|
|
Method string
|
|
Size int
|
|
Atts int
|
|
wildPos int
|
|
Token Token
|
|
Pattern map[int]string
|
|
Compile map[int]*regexp.Regexp
|
|
Tag map[int]string
|
|
Handler http.Handler
|
|
}
|
|
|
|
// Token content all value of a spliting route path
|
|
// Tokens: string value of each token
|
|
// size: number of token
|
|
type Token struct {
|
|
raw []int
|
|
Tokens []string
|
|
Size int
|
|
}
|
|
|
|
// NewRoute return a pointer to a Route instance and call save() on it
|
|
func NewRoute(url string, h http.Handler) *Route {
|
|
r := &Route{Path: url, Handler: h}
|
|
r.save()
|
|
return r
|
|
}
|
|
|
|
// Save, set automatically the the Route.Size and Route.Pattern value
|
|
func (r *Route) save() {
|
|
r.Size = len(r.Path)
|
|
r.Token.Tokens = strings.Split(r.Path, "/")
|
|
for i, s := range r.Token.Tokens {
|
|
if len(s) >= 1 {
|
|
switch s[:1] {
|
|
case ":":
|
|
if r.Pattern == nil {
|
|
r.Pattern = make(map[int]string)
|
|
}
|
|
r.Pattern[i] = s[1:]
|
|
r.Atts |= PARAM
|
|
case "#":
|
|
if r.Compile == nil {
|
|
r.Compile = make(map[int]*regexp.Regexp)
|
|
r.Tag = make(map[int]string)
|
|
}
|
|
tmp := strings.Split(s, "^")
|
|
r.Tag[i] = tmp[0][1:]
|
|
r.Compile[i] = regexp.MustCompile("^" + tmp[1][:len(tmp[1])-1])
|
|
r.Atts |= REGEX
|
|
case "*":
|
|
r.wildPos = i
|
|
r.Atts |= WC
|
|
default:
|
|
r.Token.raw = append(r.Token.raw, i)
|
|
}
|
|
}
|
|
r.Token.Size++
|
|
}
|
|
}
|
|
|
|
// Match check if the request match the route Pattern
|
|
func (r *Route) Match(req *http.Request) bool {
|
|
ok, _ := r.matchAndParse(req)
|
|
return ok
|
|
}
|
|
|
|
// matchAndParse check if the request matches the route Pattern and returns a map of the parsed
|
|
// variables if it matches
|
|
func (r *Route) matchAndParse(req *http.Request) (bool, map[string]string) {
|
|
ss := strings.Split(req.URL.EscapedPath(), "/")
|
|
if r.matchRawTokens(&ss) {
|
|
if len(ss) == r.Token.Size || r.Atts&WC != 0 {
|
|
totalSize := len(r.Pattern)
|
|
if r.Atts®EX != 0 {
|
|
totalSize += len(r.Compile)
|
|
}
|
|
|
|
vars := make(map[string]string, totalSize)
|
|
for k, v := range r.Pattern {
|
|
vars[v] = ss[k]
|
|
}
|
|
|
|
if r.Atts®EX != 0 {
|
|
for k, v := range r.Compile {
|
|
if !v.MatchString(ss[k]) {
|
|
return false, nil
|
|
}
|
|
vars[r.Tag[k]] = ss[k]
|
|
}
|
|
}
|
|
|
|
return true, vars
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (r *Route) parse(rw http.ResponseWriter, req *http.Request) bool {
|
|
if r.Atts != 0 {
|
|
if r.Atts&SUB != 0 {
|
|
if len(req.URL.Path) >= r.Size {
|
|
if req.URL.Path[:r.Size] == r.Path {
|
|
req.URL.Path = req.URL.Path[r.Size:]
|
|
r.Handler.ServeHTTP(rw, req)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
if ok, vars := r.matchAndParse(req); ok {
|
|
r.serveMatchedRequest(rw, req, vars)
|
|
return true
|
|
}
|
|
}
|
|
if req.URL.Path == r.Path {
|
|
r.Handler.ServeHTTP(rw, req)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (r *Route) matchRawTokens(ss *[]string) bool {
|
|
if len(*ss) >= r.Token.Size {
|
|
for i, v := range r.Token.raw {
|
|
if (*ss)[v] != r.Token.Tokens[v] {
|
|
if r.Atts&WC != 0 && r.wildPos == i {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Get set the route method to Get
|
|
func (r *Route) Get() *Route {
|
|
r.Method = "GET"
|
|
return r
|
|
}
|
|
|
|
// Post set the route method to Post
|
|
func (r *Route) Post() *Route {
|
|
r.Method = "POST"
|
|
return r
|
|
}
|
|
|
|
// Put set the route method to Put
|
|
func (r *Route) Put() *Route {
|
|
r.Method = "PUT"
|
|
return r
|
|
}
|
|
|
|
// Delete set the route method to Delete
|
|
func (r *Route) Delete() *Route {
|
|
r.Method = "DELETE"
|
|
return r
|
|
}
|
|
|
|
// Head set the route method to Head
|
|
func (r *Route) Head() *Route {
|
|
r.Method = "HEAD"
|
|
return r
|
|
}
|
|
|
|
// Patch set the route method to Patch
|
|
func (r *Route) Patch() *Route {
|
|
r.Method = "PATCH"
|
|
return r
|
|
}
|
|
|
|
// Options set the route method to Options
|
|
func (r *Route) Options() *Route {
|
|
r.Method = "OPTIONS"
|
|
return r
|
|
}
|
|
|
|
func (r *Route) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|
if r.Method != "" {
|
|
if req.Method == r.Method {
|
|
r.Handler.ServeHTTP(rw, req)
|
|
return
|
|
}
|
|
http.NotFound(rw, req)
|
|
return
|
|
}
|
|
r.Handler.ServeHTTP(rw, req)
|
|
}
|