go.sdls.io/sin@v0.0.9/pkg/sin/gin.go (about) 1 // Copyright 2014 Manu Martinez-Almeida. All rights reserved. 2 // Use of this source code is governed by a MIT style 3 // license that can be found in the LICENSE file. 4 5 package sin 6 7 import ( 8 "net" 9 "net/http" 10 "sync" 11 ) 12 13 const defaultMultipartMemory = 32 << 20 // 32 MB 14 15 var ( 16 default404Body = []byte("404 page not found") 17 default405Body = []byte("405 method not allowed") 18 ) 19 20 // HandlerFunc defines the handler used by gin middleware as return value. 21 type HandlerFunc func(*Context) 22 23 // HandlersChain defines a HandlerFunc array. 24 type HandlersChain []HandlerFunc 25 26 // Last returns the last handler in the chain. ie. the last handler is the main one. 27 func (c HandlersChain) Last() HandlerFunc { 28 if length := len(c); length > 0 { 29 return c[length-1] 30 } 31 return nil 32 } 33 34 // RouteInfo represents a request route's specification which contains method and path and its handler. 35 type RouteInfo struct { 36 HandlerFunc HandlerFunc 37 Method string 38 Path string 39 Handler string 40 } 41 42 // RoutesInfo defines a RouteInfo array. 43 type RoutesInfo []RouteInfo 44 45 // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. 46 // Create an instance of Engine, by using New() or Default() 47 type Engine struct { 48 pool sync.Pool 49 secureJSONPrefix string 50 allNoRoute HandlersChain 51 trees methodTrees 52 noMethod HandlersChain 53 noRoute HandlersChain 54 allNoMethod HandlersChain 55 RouterGroup 56 MaxMultipartMemory int64 57 maxParams uint16 58 UnescapePathValues bool 59 UseRawPath bool 60 HandleMethodNotAllowed bool 61 } 62 63 var _ IRouter = &Engine{} 64 65 // New returns a new blank Engine instance without any middleware attached. 66 // By default the configuration is: 67 // - RedirectTrailingSlash: true 68 // - RedirectFixedPath: false 69 // - HandleMethodNotAllowed: false 70 // - ForwardedByClientIP: true 71 // - UseRawPath: false 72 // - UnescapePathValues: true 73 func New() *Engine { 74 engine := &Engine{ 75 RouterGroup: RouterGroup{ 76 Handlers: nil, 77 basePath: "/", 78 root: true, 79 }, 80 HandleMethodNotAllowed: false, 81 UseRawPath: false, 82 UnescapePathValues: true, 83 MaxMultipartMemory: defaultMultipartMemory, 84 trees: make(methodTrees, 0, 9), 85 secureJSONPrefix: "while(1);", 86 } 87 engine.RouterGroup.engine = engine 88 engine.pool.New = func() interface{} { 89 return engine.allocateContext() 90 } 91 return engine 92 } 93 94 func (engine *Engine) allocateContext() *Context { 95 v := make(Params, 0, engine.maxParams) 96 return &Context{engine: engine, params: &v} 97 } 98 99 // NoRoute adds handlers for NoRoute. It return a 404 code by default. 100 func (engine *Engine) NoRoute(handlers ...HandlerFunc) { 101 engine.noRoute = handlers 102 engine.rebuild404Handlers() 103 } 104 105 // NoMethod sets the handlers called when... TODO. 106 func (engine *Engine) NoMethod(handlers ...HandlerFunc) { 107 engine.noMethod = handlers 108 engine.rebuild405Handlers() 109 } 110 111 // Use attaches a global middleware to the router. ie. the middleware attached though Use() will be 112 // included in the handlers chain for every single request. Even 404, 405, static files... 113 // For example, this is the right place for a logger or error management middleware. 114 func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { 115 engine.RouterGroup.Use(middleware...) 116 engine.rebuild404Handlers() 117 engine.rebuild405Handlers() 118 return engine 119 } 120 121 func (engine *Engine) rebuild404Handlers() { 122 engine.allNoRoute = engine.combineHandlers(engine.noRoute) 123 } 124 125 func (engine *Engine) rebuild405Handlers() { 126 engine.allNoMethod = engine.combineHandlers(engine.noMethod) 127 } 128 129 func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { 130 assert1(path[0] == '/', "path must begin with '/'") 131 assert1(method != "", "HTTP method can not be empty") 132 assert1(len(handlers) > 0, "there must be at least one handler") 133 134 root := engine.trees.get(method) 135 if root == nil { 136 root = new(node) 137 root.fullPath = "/" 138 engine.trees = append(engine.trees, methodTree{method: method, root: root}) 139 } 140 root.addRoute(path, handlers) 141 142 // Update maxParams 143 if paramsCount := countParams(path); paramsCount > engine.maxParams { 144 engine.maxParams = paramsCount 145 } 146 } 147 148 // Routes returns a slice of registered routes, including some useful information, such as: 149 // the http method, path and the handler name. 150 func (engine *Engine) Routes() (routes RoutesInfo) { 151 for _, tree := range engine.trees { 152 routes = iterate("", tree.method, routes, tree.root) 153 } 154 return routes 155 } 156 157 func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { 158 path += root.path 159 if len(root.handlers) > 0 { 160 handlerFunc := root.handlers.Last() 161 routes = append(routes, RouteInfo{ 162 Method: method, 163 Path: path, 164 Handler: nameOfFunction(handlerFunc), 165 HandlerFunc: handlerFunc, 166 }) 167 } 168 for _, child := range root.children { 169 routes = iterate(path, method, routes, child) 170 } 171 return routes 172 } 173 174 // Run attaches the router to a http.Server and starts listening and serving HTTP requests. 175 // It is a shortcut for http.ListenAndServe(addr, router) 176 // Note: this method will block the calling goroutine indefinitely unless an error happens. 177 func (engine *Engine) Run(address string) error { 178 return http.ListenAndServe(address, engine) 179 } 180 181 // RunListener attaches the router to a http.Server and starts listening and serving HTTP requests 182 // through the specified net.Listener 183 func (engine *Engine) RunListener(listener net.Listener) error { 184 return http.Serve(listener, engine) 185 } 186 187 // ServeHTTP conforms to the http.Handler interface. 188 func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { 189 c := engine.pool.Get().(*Context) 190 c.writermem.reset(w) 191 c.Request = req 192 c.reset() 193 194 engine.handleHTTPRequest(c) 195 196 engine.pool.Put(c) 197 } 198 199 // HandleContext re-enter a context that has been rewritten. 200 // This can be done by setting c.Request.URL.Path to your new target. 201 // Disclaimer: You can loop yourself to death with this, use wisely. 202 func (engine *Engine) HandleContext(c *Context) { 203 oldIndexValue := c.index 204 c.reset() 205 engine.handleHTTPRequest(c) 206 207 c.index = oldIndexValue 208 } 209 210 func (engine *Engine) handleHTTPRequest(c *Context) { 211 httpMethod := c.Request.Method 212 rPath := c.Request.URL.Path 213 unescape := false 214 if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { 215 rPath = c.Request.URL.RawPath 216 unescape = engine.UnescapePathValues 217 } 218 219 // Find root of the tree for the given HTTP method 220 t := engine.trees 221 for i, tl := 0, len(t); i < tl; i++ { 222 if t[i].method != httpMethod { 223 continue 224 } 225 root := t[i].root 226 // Find route in tree 227 value := root.getValue(rPath, c.params, unescape) 228 if value.params != nil { 229 c.Params = *value.params 230 } 231 if value.handlers != nil { 232 c.handlers = value.handlers 233 c.fullPath = value.fullPath 234 c.Next() 235 c.writermem.WriteHeaderNow() 236 return 237 } 238 break 239 } 240 241 if engine.HandleMethodNotAllowed { 242 for _, tree := range engine.trees { 243 if tree.method == httpMethod { 244 continue 245 } 246 if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { 247 c.handlers = engine.allNoMethod 248 serveError(c, http.StatusMethodNotAllowed, default405Body) 249 return 250 } 251 } 252 } 253 c.handlers = engine.allNoRoute 254 serveError(c, http.StatusNotFound, default404Body) 255 } 256 257 var mimePlain = []string{"text/plain"} 258 259 func serveError(c *Context, code int, defaultMessage []byte) { 260 c.writermem.status = code 261 c.Next() 262 if c.writermem.Written() { 263 return 264 } 265 if c.writermem.Status() == code { 266 c.writermem.Header()["Content-Type"] = mimePlain 267 _, _ = c.Writer.Write(defaultMessage) 268 return 269 } 270 c.writermem.WriteHeaderNow() 271 }