iris/router.go
Makis Maropoulos 1a433e34d5 Wildcard subdomain full support | v3.0.0-beta.2
Update to v3.0.0-beta.2.
Wildcard subdomain e-book section:
https://kataras.gitbooks.io/iris/content/subdomains.html
2016-06-04 16:20:32 +03:00

313 lines
9.3 KiB
Go

package iris
import (
"net/http/pprof"
"strings"
"sync"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
)
const (
// ParameterStartByte is very used on the node, it's just contains the byte for the ':' rune/char
ParameterStartByte = byte(':')
// SlashByte is just a byte of '/' rune/char
SlashByte = byte('/')
// Slash is just a string of "/"
Slash = "/"
// MatchEverythingByte is just a byte of '*" rune/char
MatchEverythingByte = byte('*')
// PrefixDynamicSubdomain is the prefix which dynamic subdomains are registed to, as virtual. Used internaly by Iris but good to know.
PrefixDynamicSubdomain = "www.iris_subd0mAin.iris"
// HTTP Methods(1)
// MethodGet "GET"
MethodGet = "GET"
// MethodPost "POST"
MethodPost = "POST"
// MethodPut "PUT"
MethodPut = "PUT"
// MethodDelete "DELETE"
MethodDelete = "DELETE"
// MethodConnect "CONNECT"
MethodConnect = "CONNECT"
// MethodHead "HEAD"
MethodHead = "HEAD"
// MethodPatch "PATCH"
MethodPatch = "PATCH"
// MethodOptions "OPTIONS"
MethodOptions = "OPTIONS"
// MethodTrace "TRACE"
MethodTrace = "TRACE"
)
var (
// PrefixDynamicSubdomainBytes is the prefix (as []byte) which dynamic subdomains are registed to, as virtual. Used internaly by Iris but good to know.
PrefixDynamicSubdomainBytes = []byte(PrefixDynamicSubdomain)
// HTTP Methods(2)
// MethodConnectBytes []byte("CONNECT")
MethodConnectBytes = []byte(MethodConnect)
// AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
AllMethods = [...]string{"GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"}
)
// router internal is the route serving service, one router per server
type router struct {
*GardenParty
*HTTPErrorContainer
station *Iris
garden *Garden
methodMatch func(m1, m2 string) bool
getRequestPath func(*fasthttp.RequestCtx) []byte
// routes useful information, this info can be used to make custom links inside templates
// the route's information (can be) changed after its registration
lookups []IRoute
ServeRequest func(reqCtx *fasthttp.RequestCtx)
// errorPool is responsible to get the Context to handle not found errors
errorPool sync.Pool
//it's true when optimize already ran
optimized bool
mu sync.Mutex
}
// methodMatchCorsFunc is sets the methodMatch when cors enabled (look router.optimize), it's allowing OPTIONS method to all other methods except GET
func methodMatchCorsFunc(m1, reqMethod string) bool {
return m1 == reqMethod || reqMethod == MethodOptions //(m1 != MethodGet && reqMethod == MethodOptions)
}
// methodMatchFunc for normal method match
func methodMatchFunc(m1, m2 string) bool {
return m1 == m2
}
func getRequestPathDefault(reqCtx *fasthttp.RequestCtx) []byte {
// default to escape then
return reqCtx.Path()
}
// newRouter creates and returns an empty router
func newRouter(station *Iris) *router {
r := &router{
station: station,
garden: &Garden{},
methodMatch: methodMatchFunc,
getRequestPath: getRequestPathDefault,
lookups: make([]IRoute, 0),
HTTPErrorContainer: defaultHTTPErrors(),
GardenParty: &GardenParty{relativePath: "/", station: station, root: true},
errorPool: station.newContextPool()}
r.ServeRequest = r.serveFunc
return r
}
// addRoute is a middleware between router and garden
// it just calls the garden's Plant method
// is 'thread-safe'
func (r *router) addRoute(route IRoute) {
r.mu.Lock()
defer r.mu.Unlock()
r.lookups = append(r.lookups, route)
r.garden.Plant(r.station, route)
}
// RouteByName returns a route by its name,if not found then returns a route with empty path
// Note that the searching is case-sensitive
func (r *router) RouteByName(lookUpName string) IRoute {
for _, route := range r.lookups {
if route.GetName() == lookUpName {
return route
}
}
return &Route{}
}
//check if any tree has cors setted to true, means that cors middleware is added
func (r *router) cors() (has bool) {
r.garden.visitAll(func(i int, tree *tree) {
if tree.cors {
has = true
}
})
return
}
// check if any tree has subdomains
func (r *router) hosts() (has bool) {
r.garden.visitAll(func(i int, tree *tree) {
if tree.hosts {
has = true
}
})
return
}
// optimize runs once before listen, it checks if cors or hosts enabled and make the necessary changes to the Router itself
func (r *router) optimize() {
if r.optimized {
return
}
if r.cors() {
r.methodMatch = methodMatchCorsFunc
}
// For performance only,in order to not check at runtime for hosts and subdomains, I think it's better to do this:
if r.hosts() {
r.ServeRequest = r.serveDomainFunc
}
//if PathEscape disabled, then take the raw URI
if r.station.config.DisablePathEscape {
r.getRequestPath = func(reqCtx *fasthttp.RequestCtx) []byte {
// RequestURI fixes the https://github.com/kataras/iris/issues/135
return reqCtx.RequestURI()
}
}
// set the debug profiling handlers if Profile enabled, before the server startup, not earlier
if r.station.config.Profile && r.station.config.ProfilePath != "" {
debugPath := r.station.config.ProfilePath
htmlMiddleware := func(ctx *Context) {
ctx.SetContentType(ContentHTML + r.station.rest.CompiledCharset)
ctx.Next()
}
indexHandler := ToHandlerFunc(pprof.Index)
cmdlineHandler := ToHandlerFunc(pprof.Cmdline)
profileHandler := ToHandlerFunc(pprof.Profile)
symbolHandler := ToHandlerFunc(pprof.Symbol)
goroutineHandler := ToHandlerFunc(pprof.Handler("goroutine"))
heapHandler := ToHandlerFunc(pprof.Handler("heap"))
threadcreateHandler := ToHandlerFunc(pprof.Handler("threadcreate"))
debugBlockHandler := ToHandlerFunc(pprof.Handler("block"))
r.Get(debugPath+"/*action", htmlMiddleware, func(ctx *Context) {
action := ctx.Param("action")
if len(action) > 1 {
if strings.Contains(action, "cmdline") {
cmdlineHandler.Serve((ctx))
} else if strings.Contains(action, "profile") {
profileHandler.Serve(ctx)
} else if strings.Contains(action, "symbol") {
symbolHandler.Serve(ctx)
} else if strings.Contains(action, "goroutine") {
goroutineHandler.Serve(ctx)
} else if strings.Contains(action, "heap") {
heapHandler.Serve(ctx)
} else if strings.Contains(action, "threadcreate") {
threadcreateHandler.Serve(ctx)
} else if strings.Contains(action, "debug/block") {
debugBlockHandler.Serve(ctx)
}
} else {
indexHandler.Serve(ctx)
}
})
}
r.optimized = true
}
// optimizeLookups runs AFTER server's listen
func (r *router) optimizeLookups() {
if r.station.server != nil && r.station.server.IsListening() {
// set the isTLS on all routes and the listening full host
listeningHost := r.station.server.Listener().Addr().String()
for idx := range r.lookups {
theR := r.lookups[idx]
theR.setTLS(r.station.server.IsSecure())
if theR.GetDomain() == "" { // means local, no subdomain
theR.setHost(listeningHost)
} else {
// it's a subdomain route
theR.setHost(theR.GetDomain() + "." + listeningHost)
}
}
}
}
// notFound internal method, it justs takes the context from pool ( in order to have the custom errors available) and procedure a Not Found 404 error
// this is being called when no route was found used on the ServeRequest.
func (r *router) notFound(reqCtx *fasthttp.RequestCtx) {
ctx := r.errorPool.Get().(*Context)
ctx.Reset(reqCtx)
ctx.NotFound()
r.errorPool.Put(ctx)
}
//************************************************************************************
// serveFunc & serveDomainFunc selected on router.optimize, which runs before station's listen
// they are not used directly.
//************************************************************************************
// serve finds and serves a route by it's request context
// If no route found, it sends an http status 404
func (r *router) serveFunc(reqCtx *fasthttp.RequestCtx) {
method := utils.BytesToString(reqCtx.Method())
tree := r.garden.first
path := utils.BytesToString(r.getRequestPath(reqCtx))
for tree != nil {
if r.methodMatch(tree.method, method) {
if !tree.serve(reqCtx, path) {
r.notFound(reqCtx)
}
return
}
tree = tree.next
}
//not found, get the first's pool and use that to send a custom http error(if setted)
r.notFound(reqCtx)
}
// serveDomainFunc finds and serves a domain tree's route by it's request context
// If no route found, it sends an http status 404
func (r *router) serveDomainFunc(reqCtx *fasthttp.RequestCtx) {
method := utils.BytesToString(reqCtx.Method())
host := utils.BytesToString(reqCtx.Host())
fulldomain := ""
if strings.Count(host, ".") >= 2 {
if portIdx := strings.Index(host, ":"); portIdx != -1 {
fulldomain = host[0:portIdx]
} else {
fulldomain = host
}
}
path := utils.BytesToString(r.getRequestPath(reqCtx))
tree := r.garden.first
for tree != nil {
if tree.hosts && tree.domain != "" && fulldomain != "" {
if tree.domain == fulldomain { // it's a static subdomain
path = fulldomain + path
} else if strings.Index(tree.domain, PrefixDynamicSubdomain) != -1 { // it's a dynamic virtual subdomain
path = PrefixDynamicSubdomain + path
}
}
if r.methodMatch(tree.method, method) {
if tree.serve(reqCtx, path) {
return
}
}
tree = tree.next
}
//not found, get the first's pool and use that to send a custom http error(if setted)
r.notFound(reqCtx)
}