2016-05-30 16:08:09 +02:00
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 ( '*' )
2016-06-04 15:20:32 +02:00
// 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"
2016-05-30 16:08:09 +02:00
// 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 (
2016-06-04 15:20:32 +02:00
// 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 )
2016-05-30 16:08:09 +02:00
// 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
2016-06-02 03:45:03 +02:00
// 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 )
2016-05-30 16:08:09 +02:00
// 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 ,
2016-06-02 03:45:03 +02:00
lookups : make ( [ ] IRoute , 0 ) ,
2016-05-30 16:08:09 +02:00
HTTPErrorContainer : defaultHTTPErrors ( ) ,
GardenParty : & GardenParty { relativePath : "/" , station : station , root : true } ,
errorPool : station . newContextPool ( ) }
r . ServeRequest = r . serveFunc
return r
}
2016-06-02 03:45:03 +02:00
// addRoute is a middleware between router and garden
// it just calls the garden's Plant method
// is 'thread-safe'
2016-05-30 16:08:09 +02:00
func ( r * router ) addRoute ( route IRoute ) {
r . mu . Lock ( )
defer r . mu . Unlock ( )
2016-06-02 03:45:03 +02:00
r . lookups = append ( r . lookups , route )
2016-05-30 16:08:09 +02:00
r . garden . Plant ( r . station , route )
}
2016-06-02 03:45:03 +02:00
// 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 { }
}
2016-05-30 16:08:09 +02:00
//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
}
2016-06-02 18:22:36 +02:00
// optimizeLookups runs AFTER server's listen
func ( r * router ) optimizeLookups ( ) {
2016-06-04 15:20:32 +02:00
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 )
}
2016-06-02 18:22:36 +02:00
2016-06-04 15:20:32 +02:00
}
2016-06-02 18:22:36 +02:00
}
2016-06-04 15:20:32 +02:00
2016-06-02 18:22:36 +02:00
}
2016-05-30 16:08:09 +02:00
// 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 ( ) )
2016-06-04 15:20:32 +02:00
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 ) )
2016-05-30 16:08:09 +02:00
tree := r . garden . first
for tree != nil {
2016-06-04 15:20:32 +02:00
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
}
2016-05-30 16:08:09 +02:00
}
2016-06-04 15:20:32 +02:00
2016-05-30 16:08:09 +02:00
if r . methodMatch ( tree . method , method ) {
2016-06-04 15:20:32 +02:00
if tree . serve ( reqCtx , path ) {
2016-05-30 16:08:09 +02:00
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 )
}