| golang |
| package gin |
| import ( |
| "fmt" |
| "htmltemplate" |
| "net" |
| "nethttp" |
| "os" |
| "path" |
| "sync" |
| "github.comgin-gonicgininternalbytesconv" |
| "github.comgin-gonicginrender" |
| ) |
| const defaultMultipartMemory = 32 << 20 32 MB |
| var ( |
| default404Body = []byte("404 page not found") |
| default405Body = []byte("405 method not allowed") |
| ) |
| var defaultAppEngine bool |
| HandlerFunc defines the handler used by gin middleware as return value. |
| type HandlerFunc func(*Context) |
| HandlersChain defines a HandlerFunc array. |
| type HandlersChain []HandlerFunc |
| Last returns the last handler in the chain. ie. the last handler is the main one. |
| func (c HandlersChain) Last() HandlerFunc { |
| if length := len(c); length > 0 { |
| return c[length-1] |
| } |
| return nil |
| } |
| RouteInfo represents a request route's specification which contains method and path and its handler. |
| type RouteInfo struct { |
| Method string |
| Path string |
| Handler string |
| HandlerFunc HandlerFunc |
| } |
| RoutesInfo defines a RouteInfo array. |
| type RoutesInfo []RouteInfo |
| Engine is the framework's instance, it contains the muxer, middleware and configuration settings. |
| Create an instance of Engine, by using New() or Default() |
| type Engine struct { |
| RouterGroup |
| Enables automatic redirection if the current route can't be matched but a |
| handler for the path with (without) the trailing slash exists. |
| For example if foo is requested but a route only exists for foo, the |
| client is redirected to foo with http status code 301 for GET requests |
| and 307 for all other request methods. |
| RedirectTrailingSlash bool |
| If enabled, the router tries to fix the current request path, if no |
| handle is registered for it. |
| First superfluous path elements like .. or are removed. |
| Afterwards the router does a case-insensitive lookup of the cleaned path. |
| If a handle can be found for this route, the router makes a redirection |
| to the corrected path with status code 301 for GET requests and 307 for |
| all other request methods. |
| For example FOO and ..Foo could be redirected to foo. |
| RedirectTrailingSlash is independent of this option. |
| RedirectFixedPath bool |
| If enabled, the router checks if another method is allowed for the |
| current route, if the current request can not be routed. |
| If this is the case, the request is answered with 'Method Not Allowed' |
| and HTTP status code 405. |
| If no other Method is allowed, the request is delegated to the NotFound |
| handler. |
| HandleMethodNotAllowed bool |
| ForwardedByClientIP bool |
| #726 #755 If enabled, it will thrust some headers starting with |
| 'X-AppEngine...' for better integration with that PaaS. |
| AppEngine bool |
| If enabled, the url.RawPath will be used to find parameters. |
| UseRawPath bool |
| If true, the path value will be unescaped. |
| If UseRawPath is false (by default), the UnescapePathValues effectively is true, |
| as url.Path gonna be used, which is already unescaped. |
| UnescapePathValues bool |
| Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm |
| method call. |
| MaxMultipartMemory int64 |
| RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. |
| See the PR #1817 and issue #1644 |
| RemoveExtraSlash bool |
| delims render.Delims |
| secureJSONPrefix string |
| HTMLRender render.HTMLRender |
| FuncMap template.FuncMap |
| allNoRoute HandlersChain |
| allNoMethod HandlersChain |
| noRoute HandlersChain |
| noMethod HandlersChain |
| pool sync.Pool |
| trees methodTrees |
| maxParams uint16 |
| } |
| var _ IRouter = &Engine{} |
| New returns a new blank Engine instance without any middleware attached. |
| By default the configuration is: |
| - RedirectTrailingSlash: true |
| - RedirectFixedPath: false |
| - HandleMethodNotAllowed: false |
| - ForwardedByClientIP: true |
| - UseRawPath: false |
| - UnescapePathValues: true |
| func New() *Engine { |
| debugPrintWARNINGNew() |
| engine := &Engine{ |
| RouterGroup: RouterGroup{ |
| Handlers: nil, |
| basePath: "", |
| root: true, |
| }, |
| FuncMap: template.FuncMap{}, |
| RedirectTrailingSlash: true, |
| RedirectFixedPath: false, |
| HandleMethodNotAllowed: false, |
| ForwardedByClientIP: true, |
| AppEngine: defaultAppEngine, |
| UseRawPath: false, |
| RemoveExtraSlash: false, |
| UnescapePathValues: true, |
| MaxMultipartMemory: defaultMultipartMemory, |
| trees: make(methodTrees, 0, 9), |
| delims: render.Delims{Left: ", Right: "}, |
| secureJSONPrefix: "while(1);", |
| } |
| engine.RouterGroup.engine = engine |
| engine.pool.New = func() interface{} { |
| return engine.allocateContext() |
| } |
| return engine |
| } |
| Default returns an Engine instance with the Logger and Recovery middleware already attached. |
| func Default() *Engine { |
| debugPrintWARNINGDefault() |
| engine := New() |
| engine.Use(Logger(), Recovery()) |
| return engine |
| } |
| func (engine *Engine) allocateContext() *Context { |
| v := make(Params, 0, engine.maxParams) |
| return &Context{engine: engine, params: &v} |
| } |
| Delims sets template left and right delims and returns a Engine instance. |
| func (engine *Engine) Delims(left, right string) *Engine { |
| engine.delims = render.Delims{Left: left, Right: right} |
| return engine |
| } |
| SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON. |
| func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { |
| engine.secureJSONPrefix = prefix |
| return engine |
| } |
| LoadHTMLGlob loads HTML files identified by glob pattern |
| and associates the result with HTML renderer. |
| func (engine *Engine) LoadHTMLGlob(pattern string) { |
| left := engine.delims.Left |
| right := engine.delims.Right |
| templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) |
| if IsDebugging() { |
| debugPrintLoadTemplate(templ) |
| engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} |
| return |
| } |
| engine.SetHTMLTemplate(templ) |
| } |
| LoadHTMLFiles loads a slice of HTML files |
| and associates the result with HTML renderer. |
| func (engine *Engine) LoadHTMLFiles(files ...string) { |
| if IsDebugging() { |
| engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} |
| return |
| } |
| templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) |
| engine.SetHTMLTemplate(templ) |
| } |
| SetHTMLTemplate associate a template with HTML renderer. |
| func (engine *Engine) SetHTMLTemplate(templ *template.Template) { |
| if len(engine.trees) > 0 { |
| debugPrintWARNINGSetHTMLTemplate() |
| } |
| engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)} |
| } |
| SetFuncMap sets the FuncMap used for template.FuncMap. |
| func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { |
| engine.FuncMap = funcMap |
| } |
| NoRoute adds handlers for NoRoute. It return a 404 code by default. |
| func (engine *Engine) NoRoute(handlers ...HandlerFunc) { |
| engine.noRoute = handlers |
| engine.rebuild404Handlers() |
| } |
| NoMethod sets the handlers called when... TODO. |
| func (engine *Engine) NoMethod(handlers ...HandlerFunc) { |
| engine.noMethod = handlers |
| engine.rebuild405Handlers() |
| } |
| Use attaches a global middleware to the router. ie. the middleware attached though Use() will be |
| included in the handlers chain for every single request. Even 404, 405, static files... |
| For example, this is the right place for a logger or error management middleware. |
| func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { |
| engine.RouterGroup.Use(middleware...) |
| engine.rebuild404Handlers() |
| engine.rebuild405Handlers() |
| return engine |
| } |
| func (engine *Engine) rebuild404Handlers() { |
| engine.allNoRoute = engine.combineHandlers(engine.noRoute) |
| } |
| func (engine *Engine) rebuild405Handlers() { |
| engine.allNoMethod = engine.combineHandlers(engine.noMethod) |
| } |
| func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { |
| assert1(path[0] == '', "path must begin with ''") |
| assert1(method != "", "HTTP method can not be empty") |
| assert1(len(handlers) > 0, "there must be at least one handler") |
| debugPrintRoute(method, path, handlers) |
| root := engine.trees.get(method) |
| if root == nil { |
| root = new(node) |
| root.fullPath = "" |
| engine.trees = append(engine.trees, methodTree{method: method, root: root}) |
| } |
| root.addRoute(path, handlers) |
| Update maxParams |
| if paramsCount := countParams(path); paramsCount > engine.maxParams { |
| engine.maxParams = paramsCount |
| } |
| } |
| Routes returns a slice of registered routes, including some useful information, such as: |
| the http method, path and the handler name. |
| func (engine *Engine) Routes() (routes RoutesInfo) { |
| for _, tree := range engine.trees { |
| routes = iterate("", tree.method, routes, tree.root) |
| } |
| return routes |
| } |
| func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { |
| path += root.path |
| if len(root.handlers) > 0 { |
| handlerFunc := root.handlers.Last() |
| routes = append(routes, RouteInfo{ |
| Method: method, |
| Path: path, |
| Handler: nameOfFunction(handlerFunc), |
| HandlerFunc: handlerFunc, |
| }) |
| } |
| for _, child := range root.children { |
| routes = iterate(path, method, routes, child) |
| } |
| return routes |
| } |
| Run attaches the router to a http.Server and starts listening and serving HTTP requests. |
| It is a shortcut for http.ListenAndServe(addr, router) |
| Note: this method will block the calling goroutine indefinitely unless an error happens. |
| func (engine *Engine) Run(addr ...string) (err error) { |
| defer func() { debugPrintError(err) }() |
| address := resolveAddress(addr) |
| debugPrint("Listening and serving HTTP on %s ", address) |
| err = http.ListenAndServe(address, engine) |
| return |
| } |
| RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. |
| It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) |
| Note: this method will block the calling goroutine indefinitely unless an error happens. |
| func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { |
| debugPrint("Listening and serving HTTPS on %s ", addr) |
| defer func() { debugPrintError(err) }() |
| err = http.ListenAndServeTLS(addr, certFile, keyFile, engine) |
| return |
| } |
| RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests |
| through the specified unix socket (ie. a file). |
| Note: this method will block the calling goroutine indefinitely unless an error happens. |
| func (engine *Engine) RunUnix(file string) (err error) { |
| debugPrint("Listening and serving HTTP on unix:%s", file) |
| defer func() { debugPrintError(err) }() |
| listener, err := net.Listen("unix", file) |
| if err != nil { |
| return |
| } |
| defer listener.Close() |
| defer os.Remove(file) |
| err = http.Serve(listener, engine) |
| return |
| } |
| RunFd attaches the router to a http.Server and starts listening and serving HTTP requests |
| through the specified file descriptor. |
| Note: this method will block the calling goroutine indefinitely unless an error happens. |
| func (engine *Engine) RunFd(fd int) (err error) { |
| debugPrint("Listening and serving HTTP on fd@%d", fd) |
| defer func() { debugPrintError(err) }() |
| f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) |
| listener, err := net.FileListener(f) |
| if err != nil { |
| return |
| } |
| defer listener.Close() |
| err = engine.RunListener(listener) |
| return |
| } |
| RunListener attaches the router to a http.Server and starts listening and serving HTTP requests |
| through the specified net.Listener |
| func (engine *Engine) RunListener(listener net.Listener) (err error) { |
| debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr()) |
| defer func() { debugPrintError(err) }() |
| err = http.Serve(listener, engine) |
| return |
| } |
| ServeHTTP conforms to the http.Handler interface. |
| func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
| c := engine.pool.Get().(*Context) |
| c.writermem.reset(w) |
| c.Request = req |
| c.reset() |
| engine.handleHTTPRequest(c) |
| engine.pool.Put(c) |
| } |
| HandleContext re-enter a context that has been rewritten. |
| This can be done by setting c.Request.URL.Path to your new target. |
| Disclaimer: You can loop yourself to death with this, use wisely. |
| func (engine *Engine) HandleContext(c *Context) { |
| oldIndexValue := c.index |
| c.reset() |
| engine.handleHTTPRequest(c) |
| c.index = oldIndexValue |
| } |
| func (engine *Engine) handleHTTPRequest(c *Context) { |
| httpMethod := c.Request.Method |
| rPath := c.Request.URL.Path |
| unescape := false |
| if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { |
| rPath = c.Request.URL.RawPath |
| unescape = engine.UnescapePathValues |
| } |
| if engine.RemoveExtraSlash { |
| rPath = cleanPath(rPath) |
| } |
| Find root of the tree for the given HTTP method |
| t := engine.trees |
| for i, tl := 0, len(t); i < tl; i++ { |
| if t[i].method != httpMethod { |
| continue |
| } |
| root := t[i].root |
| Find route in tree |
| value := root.getValue(rPath, c.params, unescape) |
| if value.params != nil { |
| c.Params = *value.params |
| } |
| if value.handlers != nil { |
| c.handlers = value.handlers |
| c.fullPath = value.fullPath |
| c.Next() |
| c.writermem.WriteHeaderNow() |
| return |
| } |
| if httpMethod != "CONNECT" && rPath != "" { |
| if value.tsr && engine.RedirectTrailingSlash { |
| redirectTrailingSlash(c) |
| return |
| } |
| if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { |
| return |
| } |
| } |
| break |
| } |
| if engine.HandleMethodNotAllowed { |
| for _, tree := range engine.trees { |
| if tree.method == httpMethod { |
| continue |
| } |
| if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { |
| c.handlers = engine.allNoMethod |
| serveError(c, http.StatusMethodNotAllowed, default405Body) |
| return |
| } |
| } |
| } |
| c.handlers = engine.allNoRoute |
| serveError(c, http.StatusNotFound, default404Body) |
| } |
| var mimePlain = []string{MIMEPlain} |
| func serveError(c *Context, code int, defaultMessage []byte) { |
| c.writermem.status = code |
| c.Next() |
| if c.writermem.Written() { |
| return |
| } |
| if c.writermem.Status() == code { |
| c.writermem.Header()["Content-Type"] = mimePlain |
| _, err := c.Writer.Write(defaultMessage) |
| if err != nil { |
| debugPrint("cannot write message to writer during serve error: %v", err) |
| } |
| return |
| } |
| c.writermem.WriteHeaderNow() |
| } |
| func redirectTrailingSlash(c *Context) { |
| req := c.Request |
| p := req.URL.Path |
| if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { |
| p = prefix + "" + req.URL.Path |
| } |
| req.URL.Path = p + "" |
| if length := len(p); length > 1 && p[length-1] == '' { |
| req.URL.Path = p[:length-1] |
| } |
| redirectRequest(c) |
| } |
| func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { |
| req := c.Request |
| rPath := req.URL.Path |
| if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok { |
| req.URL.Path = bytesconv.BytesToString(fixedPath) |
| redirectRequest(c) |
| return true |
| } |
| return false |
| } |
| func redirectRequest(c *Context) { |
| req := c.Request |
| rPath := req.URL.Path |
| rURL := req.URL.String() |
| code := http.StatusMovedPermanently Permanent redirect, request with GET method |
| if req.Method != http.MethodGet { |
| code = http.StatusTemporaryRedirect |
| } |
| debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL) |
| http.Redirect(c.Writer, req, rURL, code) |
| c.writermem.WriteHeaderNow() |
| } |
Комментарии