1
0
mirror of https://github.com/astaxie/beego.git synced 2024-11-16 17:50:54 +00:00
Beego/router.go
2012-12-15 23:53:19 +08:00

284 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package beego
import (
"net/http"
"net/url"
"reflect"
"regexp"
"runtime"
"strings"
)
type controllerInfo struct {
pattern string
regex *regexp.Regexp
params map[int]string
controllerType reflect.Type
}
type ControllerRegistor struct {
routers []*controllerInfo
fixrouters []*controllerInfo
filters []http.HandlerFunc
}
func NewControllerRegistor() *ControllerRegistor {
return &ControllerRegistor{routers: make([]*controllerInfo, 0)}
}
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
parts := strings.Split(pattern, "/")
j := 0
params := make(map[int]string)
for i, part := range parts {
if strings.HasPrefix(part, ":") {
expr := "([^/]+)"
//a user may choose to override the defult expression
// similar to expressjs: /user/:id([0-9]+)
if index := strings.Index(part, "("); index != -1 {
expr = part[index:]
part = part[:index]
}
params[j] = part
parts[i] = expr
j++
}
}
if j == 0 {
//now create the Route
t := reflect.Indirect(reflect.ValueOf(c)).Type()
route := &controllerInfo{}
route.pattern = pattern
route.controllerType = t
p.fixrouters = append(p.fixrouters, route)
} else { // add regexp routers
//recreate the url pattern, with parameters replaced
//by regular expressions. then compile the regex
pattern = strings.Join(parts, "/")
regex, regexErr := regexp.Compile(pattern)
if regexErr != nil {
//TODO add error handling here to avoid panic
panic(regexErr)
return
}
//now create the Route
t := reflect.Indirect(reflect.ValueOf(c)).Type()
route := &controllerInfo{}
route.regex = regex
route.params = params
route.controllerType = t
p.routers = append(p.routers, route)
}
}
// Filter adds the middleware filter.
func (p *ControllerRegistor) Filter(filter http.HandlerFunc) {
p.filters = append(p.filters, filter)
}
// FilterParam adds the middleware filter if the REST URL parameter exists.
func (p *ControllerRegistor) FilterParam(param string, filter http.HandlerFunc) {
if !strings.HasPrefix(param, ":") {
param = ":" + param
}
p.Filter(func(w http.ResponseWriter, r *http.Request) {
p := r.URL.Query().Get(param)
if len(p) > 0 {
filter(w, r)
}
})
}
// FilterPrefixPath adds the middleware filter if the prefix path exists.
func (p *ControllerRegistor) FilterPrefixPath(path string, filter http.HandlerFunc) {
p.Filter(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, path) {
filter(w, r)
}
})
}
// AutoRoute
func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
if !RecoverPanic {
// go back to panic
panic(err)
} else {
Critical("Handler crashed with error", err)
for i := 1; ; i += 1 {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
Critical(file, line)
}
}
}
}()
w := &responseWriter{writer: rw}
var runrouter *controllerInfo
var findrouter bool
params := make(map[string]string)
//static file server
for prefix, staticDir := range StaticDir {
if strings.HasPrefix(r.URL.Path, prefix) {
file := staticDir + r.URL.Path[len(prefix):]
http.ServeFile(w, r, file)
w.started = true
return
}
}
requestPath := r.URL.Path
//first find path from the fixrouters to Improve Performance
for _, route := range p.fixrouters {
n := len(requestPath)
if (requestPath[n-1] != '/' && route.pattern == requestPath) ||
(len(route.pattern) >= n && requestPath[0:n] == route.pattern) {
runrouter = route
findrouter = true
break
}
}
if !findrouter {
//find a matching Route
for _, route := range p.routers {
//check if Route pattern matches url
if !route.regex.MatchString(requestPath) {
continue
}
//get submatches (params)
matches := route.regex.FindStringSubmatch(requestPath)
//double check that the Route matches the URL pattern.
if len(matches[0]) != len(requestPath) {
continue
}
if len(route.params) > 0 {
//add url parameters to the query param map
values := r.URL.Query()
for i, match := range matches[1:] {
values.Add(route.params[i], match)
params[route.params[i]] = match
}
//reassemble query params and add to RawQuery
r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery
//r.URL.RawQuery = url.Values(values).Encode()
}
runrouter = route
findrouter = true
break
}
}
if runrouter != nil {
//execute middleware filters
for _, filter := range p.filters {
filter(w, r)
if w.started {
return
}
}
//Invoke the request handler
vc := reflect.New(runrouter.controllerType)
//call the controller init function
init := vc.MethodByName("Init")
in := make([]reflect.Value, 2)
ct := &Context{ResponseWriter: w, Request: r, Params: params}
in[0] = reflect.ValueOf(ct)
in[1] = reflect.ValueOf(runrouter.controllerType.Name())
init.Call(in)
//call prepare function
in = make([]reflect.Value, 0)
method := vc.MethodByName("Prepare")
method.Call(in)
//if response has written,yes don't run next
if !w.started {
if r.Method == "GET" {
method = vc.MethodByName("Get")
method.Call(in)
} else if r.Method == "POST" {
method = vc.MethodByName("Post")
method.Call(in)
} else if r.Method == "HEAD" {
method = vc.MethodByName("Head")
method.Call(in)
} else if r.Method == "DELETE" {
method = vc.MethodByName("Delete")
method.Call(in)
} else if r.Method == "PUT" {
method = vc.MethodByName("Put")
method.Call(in)
} else if r.Method == "PATCH" {
method = vc.MethodByName("Patch")
method.Call(in)
} else if r.Method == "OPTIONS" {
method = vc.MethodByName("Options")
method.Call(in)
}
if !w.started {
if AutoRender {
method = vc.MethodByName("Render")
method.Call(in)
}
if !w.started {
method = vc.MethodByName("Finish")
method.Call(in)
}
}
}
}
//if no matches to url, throw a not found exception
if w.started == false {
http.NotFound(w, r)
}
}
//responseWriter is a wrapper for the http.ResponseWriter
//started set to true if response was written to then don't execute other handler
type responseWriter struct {
writer http.ResponseWriter
started bool
status int
}
// Header returns the header map that will be sent by WriteHeader.
func (w *responseWriter) Header() http.Header {
return w.writer.Header()
}
// Write writes the data to the connection as part of an HTTP reply,
// and sets `started` to true
func (w *responseWriter) Write(p []byte) (int, error) {
w.started = true
return w.writer.Write(p)
}
// WriteHeader sends an HTTP response header with status code,
// and sets `started` to true
func (w *responseWriter) WriteHeader(code int) {
w.status = code
w.started = true
w.writer.WriteHeader(code)
}