package iris import ( "bytes" "crypto/tls" "log" "net" "net/http" "os" "sort" "strconv" "strings" "sync" "time" "github.com/iris-contrib/letsencrypt" "github.com/kataras/go-errors" "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] } // 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 http.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 } 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)] == slashByte { continue loop } } return errMuxEntryConflictsWildcard.Format(path, e.part, fullPath) } c := path[0] if e.entryCase == hasParams && c == slashByte && len(e.nodes) == 1 { e = e.nodes[0] e.precedence++ continue loop } 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] == slashByte { 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)] == slashByte && 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 // SetPath changes/sets the path for this route SetPath(string) // Middleware returns the slice of Handler([]Handler) registed to this route Middleware() Middleware // SetMiddleware changes/sets the middleware(handler(s)) for this route SetMiddleware(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, methodStr: string(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) SetPath(s string) { r.path = s } func (r route) Middleware() Middleware { return r.middleware } func (r *route) SetMiddleware(m Middleware) { r.middleware = m } // RouteConflicts checks for route's middleware conflicts func RouteConflicts(r *route, with string) bool { for _, h := range r.middleware { if m, ok := h.(interface { Conflicts() string }); ok { if c := m.Conflicts(); c == with { return true } } } return false } func (r *route) hasCors() bool { return RouteConflicts(r, "httpmethod") } 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 } serveMux struct { garden []*muxTree lookups []*route maxParameters uint8 onLookup func(Route) api *muxAPI errorHandlers map[int]Handler logger *log.Logger // the main server host's name, ex: localhost, 127.0.0.1, 0.0.0.0, 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(logger *log.Logger) *serveMux { mux := &serveMux{ lookups: make([]*route, 0), errorHandlers: make(map[int]Handler, 0), hostname: DefaultServerHostname, // these are changing when the server is up escapePath: !DefaultDisablePathEscape, correctPath: !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) *muxTree { for i := range mux.garden { t := mux.garden[i] if bytes.Equal(t.method, method) && t.subdomain == subdomain { return t } } return nil } 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) if mux.onLookup != nil { mux.onLookup(lookup) } 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() func(reqCtx *fasthttp.RequestCtx) string { // check for cors conflicts FIRST in order to put them in OPTIONS tree also for i := range mux.lookups { r := mux.lookups[i] if r.hasCors() { if exists := mux.lookup(r.path + r.subdomain); exists == nil || exists.Method() != MethodOptions { // skip any already registed to OPTIONS, some users maybe do that manually, so we should be careful here, we do not catch custom names but that's fairly enough mux.register(MethodOptionsBytes, r.subdomain, r.path, r.middleware) } } } sort.Sort(bySubdomain(mux.lookups)) for i := range mux.lookups { r := mux.lookups[i] // 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{}} mux.garden = append(mux.garden, tree) } // 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 everything 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()) } if mp := tree.entry.paramsLen; mp > mux.maxParameters { mux.maxParameters = mp } } // 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()) //string(ctx.Path()[:]) // a little bit of memory allocation, old method used: BytesToString, If I see the benchmarks get low I will change it back to old, but this way is safer. } if !mux.escapePath { getRequestPath = func(reqCtx *fasthttp.RequestCtx) string { return utils.BytesToString(reqCtx.RequestURI()) } } return getRequestPath } func (mux *serveMux) lookup(routeName string) *route { for i := range mux.lookups { if r := mux.lookups[i]; r.name == routeName { return r } } return nil } // BuildHandler the default Iris router when iris.Handler is nil func (mux *serveMux) BuildHandler() HandlerFunc { // initialize the router once getRequestPath := mux.build() return func(context *Context) { routePath := getRequestPath(context.RequestCtx) for i := range mux.garden { tree := mux.garden[i] if !bytes.Equal(tree.method, context.Method()) { continue } 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 { //println(requestHost + " != " + mux.hostname) // we have a subdomain if strings.Index(tree.subdomain, dynamicSubdomainIndicator) != -1 { } else { //println(requestHost + " = " + mux.hostname) // 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 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) 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() return } else if mustRedirect && mux.correctPath && !bytes.Equal(context.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()) statisForRedirect := StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this, so no performance difference to the next call if bytes.Equal(tree.method, MethodPostBytes) || bytes.Equal(tree.method, MethodPutBytes) || bytes.Equal(tree.method, MethodDeleteBytes) { statisForRedirect = StatusTemporaryRedirect // To mantain POST data } context.Redirect(urlToRedirect, statisForRedirect) // 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 := "Moved Permanently.\n" context.Write(note) } return } } // not found break } mux.fireError(StatusNotFound, context) } } var ( errPortAlreadyUsed = errors.New("Port is already used") errRemoveUnix = errors.New("Unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s") errChmod = errors.New("Cannot chmod %#o for %q: %s") errCertKeyMissing = errors.New("You should provide certFile and keyFile for TLS/SSL") errParseTLS = errors.New("Couldn't load TLS, certFile=%q, keyFile=%q. Trace: %s") ) // TCP4 returns a new tcp4 Listener // *tcp6 has some bugs in some operating systems, as reported by Go Community* func TCP4(addr string) (net.Listener, error) { return net.Listen("tcp4", ParseHost(addr)) } // UNIX returns a new unix(file) Listener func UNIX(addr string, mode os.FileMode) (net.Listener, error) { if errOs := os.Remove(addr); errOs != nil && !os.IsNotExist(errOs) { return nil, errRemoveUnix.Format(addr, errOs.Error()) } listener, err := net.Listen("unix", addr) if err != nil { return nil, errPortAlreadyUsed.AppendErr(err) } if err = os.Chmod(addr, mode); err != nil { return nil, errChmod.Format(mode, addr, err.Error()) } return listener, nil } // TLS returns a new TLS Listener func TLS(addr, certFile, keyFile string) (net.Listener, error) { if certFile == "" || keyFile == "" { return nil, errCertKeyMissing } cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, errParseTLS.Format(certFile, keyFile, err) } return CERT(addr, cert) } // CERT returns a listener which contans tls.Config with the provided certificate, use for ssl func CERT(addr string, cert tls.Certificate) (net.Listener, error) { ln, err := TCP4(addr) if err != nil { return nil, err } tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, PreferServerCipherSuites: true, } return tls.NewListener(ln, tlsConfig), nil } // LETSENCRYPT returns a new Automatic TLS Listener using letsencrypt.org service func LETSENCRYPT(addr string) (net.Listener, error) { if portIdx := strings.IndexByte(addr, ':'); portIdx == -1 { addr += ":443" } ln, err := TCP4(addr) if err != nil { return nil, err } var m letsencrypt.Manager if err = m.CacheFile("letsencrypt.cache"); err != nil { return nil, err } tlsConfig := &tls.Config{GetCertificate: m.GetCertificate} tlsLn := tls.NewListener(ln, tlsConfig) return tlsLn, nil } // TCPKeepAliveListener sets TCP keep-alive timeouts on accepted // connections. // Dead TCP connections (e.g. closing laptop mid-download) eventually // go away // It is not used by default if you want to pass a keep alive listener // then just pass the child listener, example: // listener := iris.TCPKeepAliveListener{iris.TCP4(":8080").(*net.TCPListener)} type TCPKeepAliveListener struct { *net.TCPListener } // Accept implements the listener and sets the keep alive period which is 2minutes func (ln TCPKeepAliveListener) Accept() (c net.Conn, err error) { tc, err := ln.AcceptTCP() if err != nil { return } tc.SetKeepAlive(true) tc.SetKeepAlivePeriod(2 * time.Minute) return tc, nil } // ParseHost tries to convert a given string to an address which is compatible with net.Listener and server func ParseHost(addr string) string { // check if addr has :port, if not do it +:80 ,we need the hostname for many cases a := addr if a == "" { // check for os environments if oshost := os.Getenv("ADDR"); oshost != "" { a = oshost } else if oshost := os.Getenv("HOST"); oshost != "" { a = oshost } else if oshost := os.Getenv("HOSTNAME"); oshost != "" { a = oshost // check for port also here if osport := os.Getenv("PORT"); osport != "" { a += ":" + osport } } else if osport := os.Getenv("PORT"); osport != "" { a = ":" + osport } else { a = DefaultServerAddr } } if portIdx := strings.IndexByte(a, ':'); portIdx == 0 { if a[portIdx:] == ":https" { a = DefaultServerHostname + ":443" } else { // if contains only :port ,then the : is the first letter, so we dont have setted a hostname, lets set it a = DefaultServerHostname + a } } /* changed my mind, don't add 80, this will cause problems on unix listeners, and it's not really necessary because we take the port using parsePort if portIdx := strings.IndexByte(a, ':'); portIdx < 0 { // missing port part, add it a = a + ":80" }*/ return a } // ParseHostname receives an addr of form host[:port] and returns the hostname part of it // ex: localhost:8080 will return the `localhost`, mydomain.com:8080 will return the 'mydomain' func ParseHostname(addr string) string { idx := strings.IndexByte(addr, ':') if idx == 0 { // only port, then return 0.0.0.0 return "0.0.0.0" } else if idx > 0 { return addr[0:idx] } // it's already hostname return addr } // ParsePort receives an addr of form host[:port] and returns the port part of it // ex: localhost:8080 will return the `8080`, mydomain.com will return the '80' func ParsePort(addr string) int { if portIdx := strings.IndexByte(addr, ':'); portIdx != -1 { afP := addr[portIdx+1:] p, err := strconv.Atoi(afP) if err == nil { return p } else if afP == "https" { // it's not number, check if it's :https return 443 } } return 80 } const ( // SchemeHTTPS returns "https://" (full) SchemeHTTPS = "https://" // SchemeHTTP returns "http://" (full) SchemeHTTP = "http://" ) // ParseScheme returns the scheme based on the host,addr,domain // Note: the full scheme not just http*,https* *http:// *https:// func ParseScheme(domain string) string { // pure check if strings.HasPrefix(domain, SchemeHTTPS) || ParsePort(domain) == 443 { return SchemeHTTPS } return SchemeHTTP } var proxyHandler = func(proxyAddr string, redirectSchemeAndHost string) fasthttp.RequestHandler { return func(reqCtx *fasthttp.RequestCtx) { // override the handler and redirect all requests to this addr redirectTo := redirectSchemeAndHost fakehost := string(reqCtx.Request.Host()) path := string(reqCtx.Path()) if strings.Count(fakehost, ".") >= 3 { // propably a subdomain, pure check but doesn't matters don't worry if sufIdx := strings.LastIndexByte(fakehost, '.'); sufIdx > 0 { // check if the last part is a number instead of .com/.gr... // if it's number then it's propably is 0.0.0.0 or 127.0.0.1... so it shouldn' use subdomain if _, err := strconv.Atoi(fakehost[sufIdx+1:]); err != nil { // it's not number then process the try to parse the subdomain redirectScheme := ParseScheme(redirectSchemeAndHost) realHost := strings.Replace(redirectSchemeAndHost, redirectScheme, "", 1) redirectHost := strings.Replace(fakehost, fakehost, realHost, 1) redirectTo = redirectScheme + redirectHost + path reqCtx.Redirect(redirectTo, StatusMovedPermanently) return } } } if path != "/" { redirectTo += path } reqCtx.Redirect(redirectTo, StatusMovedPermanently) } } // Proxy not really a proxy, it's just // starts a server listening on proxyAddr but redirects all requests to the redirectToSchemeAndHost+$path // nothing special, use it only when you want to start a secondary server which its only work is to redirect from one requested path to another // // returns a close function func Proxy(proxyAddr string, redirectSchemeAndHost string) func() error { proxyAddr = ParseHost(proxyAddr) // override the handler and redirect all requests to this addr h := proxyHandler(proxyAddr, redirectSchemeAndHost) prx := New(OptionDisableBanner(true)) prx.Router = h go prx.Listen(proxyAddr) return prx.Close }