iris/http.go
Makis Maropoulos f21faa7cfd MustUse and MustUseFunc - registers middleware for all parties and subdomains - Subdomains don't care about registering order now
1st:
MustUse and MustUseFunc - registers middleware for all parties and
subdomains
2nd:
Subdomains don't care about registering order now
3rd:
iris control plugin realtime logger
2016-06-17 07:18:09 +03:00

1354 lines
38 KiB
Go

package iris
import (
"bytes"
"net"
"net/http"
"net/http/pprof"
"os"
"strings"
"sync"
"github.com/kataras/iris/config"
"github.com/kataras/iris/errors"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpadaptor"
)
const (
// 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 (
// AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
AllMethods = [...]string{MethodGet, MethodPost, MethodPut, MethodDelete, MethodConnect, MethodHead, MethodPatch, MethodOptions, MethodTrace}
/* methods as []byte, these are really used by iris */
// methodGetBytes "GET"
methodGetBytes = []byte(MethodGet)
// methodPostBytes "POST"
methodPostBytes = []byte(MethodPost)
// methodPutBytes "PUT"
methodPutBytes = []byte(MethodPut)
// methodDeleteBytes "DELETE"
methodDeleteBytes = []byte(MethodDelete)
// methodConnectBytes "CONNECT"
methodConnectBytes = []byte(MethodConnect)
// methodHeadBytes "HEAD"
methodHeadBytes = []byte(MethodHead)
// methodPatchBytes "PATCH"
methodPatchBytes = []byte(MethodPatch)
// methodOptionsBytes "OPTIONS"
methodOptionsBytes = []byte(MethodOptions)
// methodTraceBytes "TRACE"
methodTraceBytes = []byte(MethodTrace)
/* */
)
const (
// StatusContinue http status '100'
StatusContinue = 100
// StatusSwitchingProtocols http status '101'
StatusSwitchingProtocols = 101
// StatusOK http status '200'
StatusOK = 200
// StatusCreated http status '201'
StatusCreated = 201
// StatusAccepted http status '202'
StatusAccepted = 202
// StatusNonAuthoritativeInfo http status '203'
StatusNonAuthoritativeInfo = 203
// StatusNoContent http status '204'
StatusNoContent = 204
// StatusResetContent http status '205'
StatusResetContent = 205
// StatusPartialContent http status '206'
StatusPartialContent = 206
// StatusMultipleChoices http status '300'
StatusMultipleChoices = 300
// StatusMovedPermanently http status '301'
StatusMovedPermanently = 301
// StatusFound http status '302'
StatusFound = 302
// StatusSeeOther http status '303'
StatusSeeOther = 303
// StatusNotModified http status '304'
StatusNotModified = 304
// StatusUseProxy http status '305'
StatusUseProxy = 305
// StatusTemporaryRedirect http status '307'
StatusTemporaryRedirect = 307
// StatusBadRequest http status '400'
StatusBadRequest = 400
// StatusUnauthorized http status '401'
StatusUnauthorized = 401
// StatusPaymentRequired http status '402'
StatusPaymentRequired = 402
// StatusForbidden http status '403'
StatusForbidden = 403
// StatusNotFound http status '404'
StatusNotFound = 404
// StatusMethodNotAllowed http status '405'
StatusMethodNotAllowed = 405
// StatusNotAcceptable http status '406'
StatusNotAcceptable = 406
// StatusProxyAuthRequired http status '407'
StatusProxyAuthRequired = 407
// StatusRequestTimeout http status '408'
StatusRequestTimeout = 408
// StatusConflict http status '409'
StatusConflict = 409
// StatusGone http status '410'
StatusGone = 410
// StatusLengthRequired http status '411'
StatusLengthRequired = 411
// StatusPreconditionFailed http status '412'
StatusPreconditionFailed = 412
// StatusRequestEntityTooLarge http status '413'
StatusRequestEntityTooLarge = 413
// StatusRequestURITooLong http status '414'
StatusRequestURITooLong = 414
// StatusUnsupportedMediaType http status '415'
StatusUnsupportedMediaType = 415
// StatusRequestedRangeNotSatisfiable http status '416'
StatusRequestedRangeNotSatisfiable = 416
// StatusExpectationFailed http status '417'
StatusExpectationFailed = 417
// StatusTeapot http status '418'
StatusTeapot = 418
// StatusPreconditionRequired http status '428'
StatusPreconditionRequired = 428
// StatusTooManyRequests http status '429'
StatusTooManyRequests = 429
// StatusRequestHeaderFieldsTooLarge http status '431'
StatusRequestHeaderFieldsTooLarge = 431
// StatusUnavailableForLegalReasons http status '451'
StatusUnavailableForLegalReasons = 451
// StatusInternalServerError http status '500'
StatusInternalServerError = 500
// StatusNotImplemented http status '501'
StatusNotImplemented = 501
// StatusBadGateway http status '502'
StatusBadGateway = 502
// StatusServiceUnavailable http status '503'
StatusServiceUnavailable = 503
// StatusGatewayTimeout http status '504'
StatusGatewayTimeout = 504
// StatusHTTPVersionNotSupported http status '505'
StatusHTTPVersionNotSupported = 505
// StatusNetworkAuthenticationRequired http status '511'
StatusNetworkAuthenticationRequired = 511
)
var statusText = map[int]string{
StatusContinue: "Continue",
StatusSwitchingProtocols: "Switching Protocols",
StatusOK: "OK",
StatusCreated: "Created",
StatusAccepted: "Accepted",
StatusNonAuthoritativeInfo: "Non-Authoritative Information",
StatusNoContent: "No Content",
StatusResetContent: "Reset Content",
StatusPartialContent: "Partial Content",
StatusMultipleChoices: "Multiple Choices",
StatusMovedPermanently: "Moved Permanently",
StatusFound: "Found",
StatusSeeOther: "See Other",
StatusNotModified: "Not Modified",
StatusUseProxy: "Use Proxy",
StatusTemporaryRedirect: "Temporary Redirect",
StatusBadRequest: "Bad Request",
StatusUnauthorized: "Unauthorized",
StatusPaymentRequired: "Payment Required",
StatusForbidden: "Forbidden",
StatusNotFound: "Not Found",
StatusMethodNotAllowed: "Method Not Allowed",
StatusNotAcceptable: "Not Acceptable",
StatusProxyAuthRequired: "Proxy Authentication Required",
StatusRequestTimeout: "Request Timeout",
StatusConflict: "Conflict",
StatusGone: "Gone",
StatusLengthRequired: "Length Required",
StatusPreconditionFailed: "Precondition Failed",
StatusRequestEntityTooLarge: "Request Entity Too Large",
StatusRequestURITooLong: "Request URI Too Long",
StatusUnsupportedMediaType: "Unsupported Media Type",
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
StatusExpectationFailed: "Expectation Failed",
StatusTeapot: "I'm a teapot",
StatusPreconditionRequired: "Precondition Required",
StatusTooManyRequests: "Too Many Requests",
StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
StatusInternalServerError: "Internal Server Error",
StatusNotImplemented: "Not Implemented",
StatusBadGateway: "Bad Gateway",
StatusServiceUnavailable: "Service Unavailable",
StatusGatewayTimeout: "Gateway Timeout",
StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
StatusNetworkAuthenticationRequired: "Network Authentication Required",
}
// StatusText returns a text for the HTTP status code. It returns the empty
// string if the code is unknown.
func StatusText(code int) string {
return statusText[code]
}
// Errors introduced by server.
var (
errServerPortAlreadyUsed = errors.New("Server can't run, port is already used")
errServerAlreadyStarted = errors.New("Server is already started and listening")
errServerConfigMissing = errors.New("Empty Config for server")
errServerHandlerMissing = errors.New("Handler is missing from server, can't start without handler")
errServerIsClosed = errors.New("Can't close the server, propably is already closed or never started")
errServerRemoveUnix = errors.New("Unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s")
errServerChmod = errors.New("Cannot chmod %#o for %q: %s")
)
// Server the http server
type Server struct {
*fasthttp.Server
listener net.Listener
Config *config.Server
started bool
tls bool
}
// newServer returns a pointer to a Server object, and set it's options if any, nothing more
func newServer(c *config.Server) *Server {
s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: c}
return s
}
// SetHandler sets the handler in order to listen on client requests
func (s *Server) SetHandler(mux *serveMux) {
if s.Server != nil {
s.Server.Handler = mux.ServeRequest()
}
}
// IsListening returns true if server is listening/started, otherwise false
func (s *Server) IsListening() bool {
return s.started && s.listener != nil && s.listener.Addr().String() != ""
}
// IsSecure returns true if server uses TLS, otherwise false
func (s *Server) IsSecure() bool {
return s.tls
}
// Listener returns the net.Listener which this server (is) listening to
func (s *Server) Listener() net.Listener {
return s.listener
}
// Host returns the Listener().Addr().String(), if server is not listening it returns the config.ListeningAddr
func (s *Server) Host() (host string) {
if s.IsListening() {
return s.Listener().Addr().String()
}
return s.Config.ListeningAddr
}
// VirtualHost returns the s.Config.ListeningAddr
//
func (s *Server) VirtualHost() (host string) {
return s.Config.ListeningAddr
}
// Hostname returns the hostname part only, if host == localhost:8080 it will return the localhost
// if server is not listening it returns the config.ListeningAddr's hostname part
func (s *Server) Hostname() (hostname string) {
if s.IsListening() {
fullhost := s.Listener().Addr().String()
hostname = fullhost[0:strings.IndexByte(fullhost, ':')] // no the port
} else {
hostname = s.VirtualHostname()
}
return
}
// VirtualHostname returns the hostname that user registers, host path maybe differs from the real which is HostString, which taken from a net.listener
func (s *Server) VirtualHostname() (hostname string) {
hostname = s.Config.ListeningAddr
if idx := strings.IndexByte(hostname, ':'); idx > 1 { // at least after second char
hostname = hostname[0:idx]
} else {
hostname = config.DefaultServerHostname
}
return
}
func (s *Server) listen() error {
if s.started {
return errServerAlreadyStarted.Return()
}
listener, err := net.Listen("tcp4", s.Config.ListeningAddr)
if err != nil {
return err
}
go s.serve(listener) // we don't catch underline errors, we catched all already
return nil
}
func (s *Server) listenUNIX() error {
mode := s.Config.Mode
addr := s.Config.ListeningAddr
if errOs := os.Remove(addr); errOs != nil && !os.IsNotExist(errOs) {
return errServerRemoveUnix.Format(s.Config.ListeningAddr, errOs.Error())
}
listener, err := net.Listen("unix", addr)
if err != nil {
return errServerPortAlreadyUsed.Return()
}
if err = os.Chmod(addr, mode); err != nil {
return errServerChmod.Format(mode, addr, err.Error())
}
go s.serve(listener) // we don't catch underline errors, we catched all already
return nil
}
//Serve just serves a listener, it is a blocking action, plugin.PostListen is not fired here.
func (s *Server) serve(l net.Listener) error {
s.listener = l
s.started = true
if s.Config.CertFile != "" && s.Config.KeyFile != "" {
s.tls = true
return s.Server.ServeTLS(s.listener, s.Config.CertFile, s.Config.KeyFile)
}
s.tls = false
return s.Server.Serve(s.listener)
}
// Open opens/starts/runs/listens (to) the server, listen tls if Cert && Key is registed, listenUNIX if Mode is registed, otherwise listen
func (s *Server) Open() error {
if s.IsListening() {
return errServerAlreadyStarted.Return()
}
if s.Config.ListeningAddr == "" {
return errServerConfigMissing.Return()
}
if s.Handler == nil {
return errServerHandlerMissing.Return()
}
// check the addr if :8080 do it 0.0.0.0:8080 ,we need the hostname for many cases
a := s.Config.ListeningAddr
//check if contains hostname, we need the full host, :8080 should be : 127.0.0.1:8080
if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
// then the : is the first letter, so we dont have setted a hostname, lets set it
s.Config.ListeningAddr = config.DefaultServerHostname + a
}
if s.Config.Mode > 0 {
return s.listenUNIX()
}
return s.listen()
}
// close closes the server
func (s *Server) close() (err error) {
if !s.started || s.listener == nil {
return errServerIsClosed.Return()
}
s.started = false
err = s.listener.Close()
return
}
// errHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)
// It seems to be a +type Points to: +pointer.'
var errHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a %T Points to: %v.")
type (
// Handler the main Iris Handler interface.
Handler interface {
Serve(ctx *Context)
}
// HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
HandlerFunc func(*Context)
// Middleware is just a slice of Handler []func(c *Context)
Middleware []Handler
// HandlerAPI empty interface used for .API
HandlerAPI interface{}
)
// Serve implements the Handler, is like ServeHTTP but for Iris
func (h HandlerFunc) Serve(ctx *Context) {
h(ctx)
}
// ToHandler converts an httapi.Handler or http.HandlerFunc to an iris.Handler
func ToHandler(handler interface{}) Handler {
//this is not the best way to do it, but I dont have any options right now.
switch handler.(type) {
case Handler:
//it's already an iris handler
return handler.(Handler)
case http.Handler:
//it's http.Handler
h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(http.Handler).ServeHTTP)
return ToHandlerFastHTTP(h)
case func(http.ResponseWriter, *http.Request):
//it's http.HandlerFunc
h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(func(http.ResponseWriter, *http.Request)))
return ToHandlerFastHTTP(h)
default:
panic(errHandler.Format(handler, handler))
}
}
// ToHandlerFunc converts an http.Handler or http.HandlerFunc to an iris.HandlerFunc
func ToHandlerFunc(handler interface{}) HandlerFunc {
return ToHandler(handler).Serve
}
// ToHandlerFastHTTP converts an fasthttp.RequestHandler to an iris.Handler
func ToHandlerFastHTTP(h fasthttp.RequestHandler) Handler {
return HandlerFunc((func(ctx *Context) {
h(ctx.RequestCtx)
}))
}
// convertToHandlers just make []HandlerFunc to []Handler, although HandlerFunc and Handler are the same
// we need this on some cases we explicit want a interface Handler, it is useless for users.
func convertToHandlers(handlersFn []HandlerFunc) []Handler {
hlen := len(handlersFn)
mlist := make([]Handler, hlen)
for i := 0; i < hlen; i++ {
mlist[i] = Handler(handlersFn[i])
}
return mlist
}
// joinMiddleware uses to create a copy of all middleware and return them in order to use inside the node
func joinMiddleware(middleware1 Middleware, middleware2 Middleware) Middleware {
nowLen := len(middleware1)
totalLen := nowLen + len(middleware2)
// create a new slice of middleware in order to store all handlers, the already handlers(middleware) and the new
newMiddleware := make(Middleware, totalLen)
//copy the already middleware to the just created
copy(newMiddleware, middleware1)
//start from there we finish, and store the new middleware too
copy(newMiddleware[nowLen:], middleware2)
return newMiddleware
}
func profileMiddleware(debugPath string) Middleware {
htmlMiddleware := HandlerFunc(func(ctx *Context) {
ctx.SetContentType(contentHTML + "; charset=" + config.Charset)
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"))
return Middleware{htmlMiddleware, HandlerFunc(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)
}
})}
}
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('*')
isStatic entryCase = iota
isRoot
hasParams
matchEverything
)
type (
// PathParameter is a struct which contains Key and Value, used for named path parameters
PathParameter struct {
Key string
Value string
}
// PathParameters type for a slice of PathParameter
// Tt's a slice of PathParameter type, because it's faster than map
PathParameters []PathParameter
// entryCase is the type which the type of muxEntryusing in order to determinate what type (parameterized, anything, static...) is the perticular node
entryCase uint8
// muxEntry is the node of a tree of the routes,
// in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
// this method is used by the BSD's kernel also
muxEntry struct {
part string
entryCase entryCase
hasWildNode bool
tokens string
nodes []*muxEntry
middleware Middleware
precedence uint64
paramsLen uint8
}
)
var (
errMuxEntryConflictsWildcard = errors.New("Router: Path's part: '%s' conflicts with wildcard '%s' in the route path: '%s' !")
errMuxEntryMiddlewareAlreadyExists = errors.New("Router: Middleware were already registered for the path: '%s' !")
errMuxEntryInvalidWildcard = errors.New("Router: More than one wildcard found in the path part: '%s' in route's path: '%s' !")
errMuxEntryConflictsExistingWildcard = errors.New("Router: Wildcard for route path: '%s' conflicts with existing children in route path: '%s' !")
errMuxEntryWildcardUnnamed = errors.New("Router: Unnamed wildcard found in path: '%s' !")
errMuxEntryWildcardInvalidPlace = errors.New("Router: Wildcard is only allowed at the end of the path, in the route path: '%s' !")
errMuxEntryWildcardConflictsMiddleware = errors.New("Router: Wildcard conflicts with existing middleware for the route path: '%s' !")
errMuxEntryWildcardMissingSlash = errors.New("Router: No slash(/) were found before wildcard in the route path: '%s' !")
)
// Get returns a value from a key inside this Parameters
// If no parameter with this key given then it returns an empty string
func (params PathParameters) Get(key string) string {
for _, p := range params {
if p.Key == key {
return p.Value
}
}
return ""
}
// String returns a string implementation of all parameters that this PathParameters object keeps
// hasthe form of key1=value1,key2=value2...
func (params PathParameters) String() string {
var buff bytes.Buffer
for i := range params {
buff.WriteString(params[i].Key)
buff.WriteString("=")
buff.WriteString(params[i].Value)
if i < len(params)-1 {
buff.WriteString(",")
}
}
return buff.String()
}
// ParseParams receives a string and returns PathParameters (slice of PathParameter)
// received string must have this form: key1=value1,key2=value2...
func ParseParams(str string) PathParameters {
_paramsstr := strings.Split(str, ",")
if len(_paramsstr) == 0 {
return nil
}
params := make(PathParameters, 0) // PathParameters{}
// for i := 0; i < len(_paramsstr); i++ {
for i := range _paramsstr {
idxOfEq := strings.IndexRune(_paramsstr[i], '=')
if idxOfEq == -1 {
//error
return nil
}
key := _paramsstr[i][:idxOfEq]
val := _paramsstr[i][idxOfEq+1:]
params = append(params, PathParameter{key, val})
}
return params
}
// getParamsLen returns the parameters length from a given path
func getParamsLen(path string) uint8 {
var n uint
for i := 0; i < len(path); i++ {
if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
continue
}
n++
}
if n >= 255 {
return 255
}
return uint8(n)
}
// add adds a muxEntry to the existing muxEntry or to the tree if no muxEntry has the prefix of
func (e *muxEntry) add(path string, middleware Middleware) error {
fullPath := path
e.precedence++
numParams := getParamsLen(path)
if len(e.part) > 0 || len(e.nodes) > 0 {
loop:
for {
if numParams > e.paramsLen {
e.paramsLen = numParams
}
i := 0
max := utils.FindLower(len(path), len(e.part))
for i < max && path[i] == e.part[i] {
i++
}
if i < len(e.part) {
node := muxEntry{
part: e.part[i:],
hasWildNode: e.hasWildNode,
tokens: e.tokens,
nodes: e.nodes,
middleware: e.middleware,
precedence: e.precedence - 1,
}
for i := range node.nodes {
if node.nodes[i].paramsLen > node.paramsLen {
node.paramsLen = node.nodes[i].paramsLen
}
}
e.nodes = []*muxEntry{&node}
e.tokens = string([]byte{e.part[i]})
e.part = path[:i]
e.middleware = nil
e.hasWildNode = false
}
if i < len(path) {
path = path[i:]
if e.hasWildNode {
e = e.nodes[0]
e.precedence++
if numParams > e.paramsLen {
e.paramsLen = numParams
}
numParams--
if len(path) >= len(e.part) && e.part == path[:len(e.part)] {
if len(e.part) >= len(path) || path[len(e.part)] == '/' {
continue loop
}
}
return errMuxEntryConflictsWildcard.Format(path, e.part, fullPath)
}
c := path[0]
if e.entryCase == hasParams && c == '/' && len(e.nodes) == 1 {
e = e.nodes[0]
e.precedence++
continue loop
}
//we need the i here to be re-setting, so use the same i variable as we declare it on line 176
for i := range e.tokens {
if c == e.tokens[i] {
i = e.precedenceTo(i)
e = e.nodes[i]
continue loop
}
}
if c != parameterStartByte && c != matchEverythingByte {
e.tokens += string([]byte{c})
node := &muxEntry{
paramsLen: numParams,
}
e.nodes = append(e.nodes, node)
e.precedenceTo(len(e.tokens) - 1)
e = node
}
e.addNode(numParams, path, fullPath, middleware)
return nil
} else if i == len(path) {
if e.middleware != nil {
return errMuxEntryMiddlewareAlreadyExists.Format(fullPath)
}
e.middleware = middleware
}
return nil
}
} else {
e.addNode(numParams, path, fullPath, middleware)
e.entryCase = isRoot
}
return nil
}
// addNode adds a muxEntry as children to other muxEntry
func (e *muxEntry) addNode(numParams uint8, path string, fullPath string, middleware Middleware) error {
var offset int
for i, max := 0, len(path); numParams > 0; i++ {
c := path[i]
if c != parameterStartByte && c != matchEverythingByte {
continue
}
end := i + 1
for end < max && path[end] != slashByte {
switch path[end] {
case parameterStartByte, matchEverythingByte:
/*
panic("only one wildcard per path segment is allowed, has: '" +
path[i:] + "' in path '" + fullPath + "'")
*/
return errMuxEntryInvalidWildcard.Format(path[i:], fullPath)
default:
end++
}
}
if len(e.nodes) > 0 {
return errMuxEntryConflictsExistingWildcard.Format(path[i:end], fullPath)
}
if end-i < 2 {
return errMuxEntryWildcardUnnamed.Format(fullPath)
}
if c == parameterStartByte {
if i > 0 {
e.part = path[offset:i]
offset = i
}
child := &muxEntry{
entryCase: hasParams,
paramsLen: numParams,
}
e.nodes = []*muxEntry{child}
e.hasWildNode = true
e = child
e.precedence++
numParams--
if end < max {
e.part = path[offset:end]
offset = end
child := &muxEntry{
paramsLen: numParams,
precedence: 1,
}
e.nodes = []*muxEntry{child}
e = child
}
} else {
if end != max || numParams > 1 {
return errMuxEntryWildcardInvalidPlace.Format(fullPath)
}
if len(e.part) > 0 && e.part[len(e.part)-1] == '/' {
return errMuxEntryWildcardConflictsMiddleware.Format(fullPath)
}
i--
if path[i] != slashByte {
return errMuxEntryWildcardMissingSlash.Format(fullPath)
}
e.part = path[offset:i]
child := &muxEntry{
hasWildNode: true,
entryCase: matchEverything,
paramsLen: 1,
}
e.nodes = []*muxEntry{child}
e.tokens = string(path[i])
e = child
e.precedence++
child = &muxEntry{
part: path[i:],
entryCase: matchEverything,
paramsLen: 1,
middleware: middleware,
precedence: 1,
}
e.nodes = []*muxEntry{child}
return nil
}
}
e.part = path[offset:]
e.middleware = middleware
return nil
}
// get is used by the Router, it finds and returns the correct muxEntry for a path
func (e *muxEntry) get(path string, _params PathParameters) (middleware Middleware, params PathParameters, mustRedirect bool) {
params = _params
loop:
for {
if len(path) > len(e.part) {
if path[:len(e.part)] == e.part {
path = path[len(e.part):]
if !e.hasWildNode {
c := path[0]
for i := range e.tokens {
if c == e.tokens[i] {
e = e.nodes[i]
continue loop
}
}
mustRedirect = (path == slash && e.middleware != nil)
return
}
e = e.nodes[0]
switch e.entryCase {
case hasParams:
end := 0
for end < len(path) && path[end] != '/' {
end++
}
if cap(params) < int(e.paramsLen) {
params = make(PathParameters, 0, e.paramsLen)
}
i := len(params)
params = params[:i+1]
params[i].Key = e.part[1:]
params[i].Value = path[:end]
if end < len(path) {
if len(e.nodes) > 0 {
path = path[end:]
e = e.nodes[0]
continue loop
}
mustRedirect = (len(path) == end+1)
return
}
if middleware = e.middleware; middleware != nil {
return
} else if len(e.nodes) == 1 {
e = e.nodes[0]
mustRedirect = (e.part == slash && e.middleware != nil)
}
return
case matchEverything:
if cap(params) < int(e.paramsLen) {
params = make(PathParameters, 0, e.paramsLen)
}
i := len(params)
params = params[:i+1]
params[i].Key = e.part[2:]
params[i].Value = path
middleware = e.middleware
return
default:
return
}
}
} else if path == e.part {
if middleware = e.middleware; middleware != nil {
return
}
if path == slash && e.hasWildNode && e.entryCase != isRoot {
mustRedirect = true
return
}
for i := range e.tokens {
if e.tokens[i] == '/' {
e = e.nodes[i]
mustRedirect = (len(e.part) == 1 && e.middleware != nil) ||
(e.entryCase == matchEverything && e.nodes[0].middleware != nil)
return
}
}
return
}
mustRedirect = (path == slash) ||
(len(e.part) == len(path)+1 && e.part[len(path)] == '/' &&
path == e.part[:len(e.part)-1] && e.middleware != nil)
return
}
}
// precedenceTo just adds the priority of this muxEntry by an index
func (e *muxEntry) precedenceTo(index int) int {
e.nodes[index].precedence++
_precedence := e.nodes[index].precedence
newindex := index
for newindex > 0 && e.nodes[newindex-1].precedence < _precedence {
tmpN := e.nodes[newindex-1]
e.nodes[newindex-1] = e.nodes[newindex]
e.nodes[newindex] = tmpN
newindex--
}
if newindex != index {
e.tokens = e.tokens[:newindex] +
e.tokens[index:index+1] +
e.tokens[newindex:index] + e.tokens[index+1:]
}
return newindex
}
type (
// Route contains some useful information about a route
Route interface {
// Name returns the name of the route
Name() string
// Subdomain returns the subdomain,if any
Subdomain() string
// Method returns the http method
Method() string
// Path returns the path
Path() string
// Middleware returns the slice of Handler([]Handler) registed to this route
Middleware() Middleware
}
route struct {
// if no name given then it's the subdomain+path
name string
subdomain string
method []byte
methodStr string
path string
middleware Middleware
formattedPath string
formattedParts int
}
bySubdomain []*route
)
// Sorting happens when the mux's request handler initialized
func (s bySubdomain) Len() int {
return len(s)
}
func (s bySubdomain) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s bySubdomain) Less(i, j int) bool {
return len(s[i].Subdomain()) > len(s[j].Subdomain())
}
var _ Route = &route{}
func newRoute(method []byte, subdomain string, path string, middleware Middleware) *route {
r := &route{name: path + subdomain, method: method, subdomain: subdomain, path: path, middleware: middleware}
r.formatPath()
return r
}
func (r *route) formatPath() {
// we don't care about performance here.
n1Len := strings.Count(r.path, ":")
isMatchEverything := len(r.path) > 0 && r.path[len(r.path)-1] == matchEverythingByte
if n1Len == 0 && !isMatchEverything {
// its a static
return
}
if n1Len == 0 && isMatchEverything {
//if we have something like: /mypath/anything/* -> /mypatch/anything/%v
r.formattedPath = r.path[0:len(r.path)-2] + "%v"
r.formattedParts++
return
}
tempPath := r.path
splittedN1 := strings.Split(r.path, "/")
for _, v := range splittedN1 {
if len(v) > 0 {
if v[0] == ':' || v[0] == matchEverythingByte {
r.formattedParts++
tempPath = strings.Replace(tempPath, v, "%v", -1) // n1Len, but let it we don't care about performance here.
}
}
}
r.formattedPath = tempPath
}
func (r *route) setName(newName string) {
r.name = newName
}
func (r route) Name() string {
return r.name
}
func (r route) Subdomain() string {
return r.subdomain
}
func (r route) Method() string {
if r.methodStr == "" {
r.methodStr = string(r.method)
}
return r.methodStr
}
func (r route) Path() string {
return r.path
}
func (r route) Middleware() Middleware {
return r.middleware
}
const (
// subdomainIndicator where './' exists in a registed path then it contains subdomain
subdomainIndicator = "./"
// dynamicSubdomainIndicator where a registed path starts with '*.' then it contains a dynamic subdomain, if subdomain == "*." then its dynamic
dynamicSubdomainIndicator = "*."
)
type (
muxTree struct {
method []byte
// subdomain is empty for default-hostname routes,
// ex: mysubdomain.
subdomain string
entry *muxEntry
next *muxTree
}
serveMux struct {
cPool *sync.Pool
tree *muxTree
lookups []*route
api *muxAPI
errorHandlers map[int]Handler
logger *logger.Logger
// the main server host's name, ex: localhost, 127.0.0.1, iris-go.com
hostname string
// if any of the trees contains not empty subdomain
hosts bool
// if false then searching by unescaped path
// defaults to true
escapePath bool
// if false then the /something it's not the same as /something/
// defaults to true
correctPath bool
mu sync.Mutex
}
)
func newServeMux(contextPool sync.Pool, logger *logger.Logger) *serveMux {
mux := &serveMux{
cPool: &contextPool,
lookups: make([]*route, 0),
errorHandlers: make(map[int]Handler, 0),
hostname: "127.0.0.1",
escapePath: !config.DefaultDisablePathEscape,
correctPath: !config.DefaultDisablePathCorrection,
logger: logger,
}
return mux
}
func (mux *serveMux) setHostname(h string) {
mux.hostname = h
}
func (mux *serveMux) setEscapePath(b bool) {
mux.escapePath = b
}
func (mux *serveMux) setCorrectPath(b bool) {
mux.correctPath = b
}
// registerError registers a handler to a http status
func (mux *serveMux) registerError(statusCode int, handler Handler) {
mux.mu.Lock()
func(statusCode int, handler Handler) {
mux.errorHandlers[statusCode] = HandlerFunc(func(ctx *Context) {
ctx.ResetBody()
ctx.SetStatusCode(statusCode)
handler.Serve(ctx)
})
}(statusCode, handler)
mux.mu.Unlock()
}
// fireError fires an error
func (mux *serveMux) fireError(statusCode int, ctx *Context) {
mux.mu.Lock()
errHandler := mux.errorHandlers[statusCode]
if errHandler == nil {
errHandler = HandlerFunc(func(ctx *Context) {
ctx.ResetBody()
ctx.SetStatusCode(statusCode)
ctx.SetBodyString(statusText[statusCode])
})
mux.errorHandlers[statusCode] = errHandler
}
mux.mu.Unlock()
errHandler.Serve(ctx)
}
func (mux *serveMux) getTree(method []byte, subdomain string) (tree *muxTree) {
tree = mux.tree
for tree != nil {
if bytes.Equal(tree.method, method) && tree.subdomain == subdomain {
return
}
tree = tree.next
}
// tree is nil here, return that.
return
}
func (mux *serveMux) register(method []byte, subdomain string, path string, middleware Middleware) *route {
mux.mu.Lock()
defer mux.mu.Unlock()
if subdomain != "" {
mux.hosts = true
}
// add to the lookups, it's just a collection of routes information
lookup := newRoute(method, subdomain, path, middleware)
mux.lookups = append(mux.lookups, lookup)
return lookup
}
// build collects all routes info and adds them to the registry in order to be served from the request handler
// this happens once when server is setting the mux's handler.
func (mux *serveMux) build() {
routes := bySubdomain(mux.lookups)
for _, r := range routes {
// add to the registry tree
tree := mux.getTree(r.method, r.subdomain)
if tree == nil {
//first time we register a route to this method with this domain
tree = &muxTree{method: r.method, subdomain: r.subdomain, entry: &muxEntry{}, next: nil}
if mux.tree == nil {
// it's the first entry
mux.tree = tree
} else {
// find the last tree and make the .next to the tree we created before
lastTree := mux.tree
for lastTree != nil {
if lastTree.next == nil {
lastTree.next = tree
break
}
lastTree = lastTree.next
}
}
}
// I decide that it's better to explicit give subdomain and a path to it than registedPath(mysubdomain./something) now its: subdomain: mysubdomain., path: /something
// we have different tree for each of subdomains, now you can use everyting you can use with the normal paths ( before you couldn't set /any/*path)
if err := tree.entry.add(r.path, r.middleware); err != nil {
mux.logger.Panic(err.Error())
}
}
}
func (mux *serveMux) lookup(routeName string) *route {
for i := range mux.lookups {
if r := mux.lookups[i]; r.name == routeName {
return r
}
}
return nil
}
func (mux *serveMux) ServeRequest() fasthttp.RequestHandler {
// initialize the router once
mux.build()
// optimize this once once, we could do that: context.RequestPath(mux.escapePath), but we lose some nanoseconds on if :)
getRequestPath := func(reqCtx *fasthttp.RequestCtx) string {
return utils.BytesToString(reqCtx.Path())
}
if !mux.escapePath {
getRequestPath = func(reqCtx *fasthttp.RequestCtx) string { return utils.BytesToString(reqCtx.RequestURI()) }
}
return func(reqCtx *fasthttp.RequestCtx) {
context := mux.cPool.Get().(*Context)
context.Reset(reqCtx)
routePath := getRequestPath(reqCtx)
tree := mux.tree
for tree != nil {
if !bytes.Equal(tree.method, reqCtx.Method()) {
// we break any CORS OPTIONS method
// but for performance reasons if user wants http method OPTIONS to be served
// then must register it with .Options(...)
tree = tree.next
continue
}
// we have at least one subdomain on the root
if mux.hosts && tree.subdomain != "" {
// context.VirtualHost() is a slow method because it makes string.Replaces but user can understand that if subdomain then server will have some nano/or/milleseconds performance cost
requestHost := context.VirtualHostname()
if requestHost != mux.hostname {
// we have a subdomain
if strings.Index(tree.subdomain, dynamicSubdomainIndicator) != -1 {
} else {
// mux.host = iris-go.com:8080, the subdomain for example is api.,
// so the host must be api.iris-go.com:8080
if tree.subdomain+mux.hostname != requestHost {
// go to the next tree, we have a subdomain but it is not the correct
tree = tree.next
continue
}
}
} else {
//("it's subdomain but the request is the same as the listening addr mux.host == requestHost =>" + mux.host + "=" + requestHost + " ____ and tree's subdomain was: " + tree.subdomain)
tree = tree.next
continue
}
}
middleware, params, mustRedirect := tree.entry.get(routePath, context.Params) // pass the parameters here for 0 allocation
if middleware != nil {
// ok we found the correct route, serve it and exit entirely from here
context.Params = params
context.middleware = middleware
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
context.Do()
mux.cPool.Put(context)
return
} else if mustRedirect && mux.correctPath && !bytes.Equal(reqCtx.Method(), methodConnectBytes) {
reqPath := routePath
pathLen := len(reqPath)
if pathLen > 1 {
if reqPath[pathLen-1] == '/' {
reqPath = reqPath[:pathLen-1] //remove the last /
} else {
//it has path prefix, it doesn't ends with / and it hasn't be found, then just add the slash
reqPath = reqPath + "/"
}
context.Request.URI().SetPath(reqPath)
urlToRedirect := utils.BytesToString(context.Request.RequestURI())
context.Redirect(urlToRedirect, StatusMovedPermanently) // StatusMovedPermanently
// RFC2616 recommends that a short note "SHOULD" be included in the
// response because older user agents may not understand 301/307.
// Shouldn't send the response for POST or HEAD; that leaves GET.
if bytes.Equal(tree.method, methodGetBytes) {
note := "<a href=\"" + utils.HTMLEscape(urlToRedirect) + "\">Moved Permanently</a>.\n"
context.Write(note)
}
mux.cPool.Put(context)
return
}
}
// not found
break
}
mux.fireError(StatusNotFound, context)
mux.cPool.Put(context)
}
}