github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/http/fast/router.go (about) 1 // 🚀 Fast is an Express inspired web framework written in Go. 2 3 package fast 4 5 import ( 6 "log" 7 "regexp" 8 "strings" 9 "time" 10 11 "github.com/valyala/fasthttp" 12 ) 13 14 // Route struct 15 type Route struct { 16 isGet bool // allows HEAD requests if GET 17 18 isMiddleware bool // is middleware route 19 20 isStar bool // path == '*' 21 isSlash bool // path == '/' 22 isRegex bool // needs regex parsing 23 24 Method string // http method 25 Path string // original path 26 Params []string // path params 27 Regexp *regexp.Regexp // regexp matcher 28 29 Handler func(*Ctx) // ctx handler 30 } 31 32 func (app *Fast) nextRoute(ctx *Ctx) { 33 // Keep track of head matches 34 l := len(app.routes) - 1 35 for ctx.index < l { 36 ctx.index++ 37 route := app.routes[ctx.index] 38 match, values := route.matchRoute(ctx.method, ctx.path) 39 if match { 40 ctx.route = route 41 ctx.values = values 42 route.Handler(ctx) 43 return 44 } 45 } 46 if len(ctx.C.Response.Body()) == 0 { 47 ctx.SendStatus(404) 48 } 49 } 50 51 func (r *Route) matchRoute(method, path string) (match bool, values []string) { 52 // is route middleware? matches all http methods 53 if r.isMiddleware { 54 // '*' or '/' means its a valid match 55 if r.isStar || r.isSlash { 56 return true, values 57 } 58 // if midware path starts with req.path 59 if strings.HasPrefix(path, r.Path) { 60 return true, values 61 } 62 // middlewares dont support regex so bye! 63 return false, values 64 } 65 // non-middleware route, http method must match! 66 // the wildcard method is for .All() & .Use() methods 67 // If route is GET, also match HEAD requests 68 if r.Method == method || r.Method[0] == '*' || (r.isGet && len(method) == 4 && method == "HEAD") { 69 // '*' means we match anything 70 if r.isStar { 71 return true, values 72 } 73 // simple '/' bool, so you avoid unnecessary comparison for long paths 74 if r.isSlash && path == "/" { 75 return true, values 76 } 77 // does this route need regex matching? 78 // does req.path match regex pattern? 79 if r.isRegex && r.Regexp.MatchString(path) { 80 // do we have parameters 81 if len(r.Params) > 0 { 82 // get values for parameters 83 matches := r.Regexp.FindAllStringSubmatch(path, -1) 84 // did we get the values? 85 if len(matches) > 0 && len(matches[0]) > 1 { 86 values = matches[0][1:len(matches[0])] 87 return true, values 88 } 89 return false, values 90 } 91 return true, values 92 } 93 // last thing to do is to check for a simple path match 94 if len(r.Path) == len(path) && r.Path == path { 95 return true, values 96 } 97 } 98 // Nothing match 99 return false, values 100 } 101 102 func (app *Fast) handler(fctx *fasthttp.RequestCtx) { 103 // get fiber context from sync pool 104 ctx := acquireCtx(fctx) 105 defer releaseCtx(ctx) 106 // attach app pointer and compress settings 107 ctx.app = app 108 109 // Case sensitive routing 110 if !app.Settings.CaseSensitive { 111 ctx.path = strings.ToLower(ctx.path) 112 } 113 // Strict routing 114 if !app.Settings.StrictRouting && len(ctx.path) > 1 { 115 ctx.path = strings.TrimRight(ctx.path, "/") 116 } 117 // Find route 118 app.nextRoute(ctx) 119 } 120 121 func (app *Fast) registerMethod(method, path string, handlers ...func(*Ctx)) { 122 // Route requires atleast one handler 123 if len(handlers) == 0 { 124 log.Fatalf("Missing handler in route") 125 } 126 // Cannot have an empty path 127 if path == "" { 128 path = "/" 129 } 130 // Path always start with a '/' or '*' 131 if path[0] != '/' && path[0] != '*' { 132 path = "/" + path 133 } 134 // Store original path to strip case sensitive params 135 original := path 136 // Case sensitive routing, all to lowercase 137 if !app.Settings.CaseSensitive { 138 path = strings.ToLower(path) 139 } 140 // Strict routing, remove last `/` 141 if !app.Settings.StrictRouting && len(path) > 1 { 142 path = strings.TrimRight(path, "/") 143 } 144 // SetHeader route booleans 145 var isGet = method == "GET" 146 var isMiddleware = method == "USE" 147 // Middleware / All allows all HTTP methods 148 if isMiddleware || method == "ALL" { 149 method = "*" 150 } 151 var isStar = path == "*" || path == "/*" 152 // Middleware containing only a `/` equals wildcard 153 if isMiddleware && path == "/" { 154 isStar = true 155 } 156 var isSlash = path == "/" 157 var isRegex = false 158 // Route properties 159 var Params = getParams(original) 160 var Regexp *regexp.Regexp 161 // Params requires regex pattern 162 if len(Params) > 0 { 163 regex, err := getRegex(path) 164 if err != nil { 165 log.Fatal("Router: Invalid path pattern: " + path) 166 } 167 isRegex = true 168 Regexp = regex 169 } 170 for i := range handlers { 171 app.routes = append(app.routes, &Route{ 172 isGet: isGet, 173 isMiddleware: isMiddleware, 174 isStar: isStar, 175 isSlash: isSlash, 176 isRegex: isRegex, 177 Method: method, 178 Path: path, 179 Params: Params, 180 Regexp: Regexp, 181 Handler: handlers[i], 182 }) 183 } 184 } 185 186 func (app *Fast) registerStatic(prefix, root string, config ...Static) { 187 // Cannot have an empty prefix 188 if prefix == "" { 189 prefix = "/" 190 } 191 // Prefix always start with a '/' or '*' 192 if prefix[0] != '/' && prefix[0] != '*' { 193 prefix = "/" + prefix 194 } 195 // Match anything 196 var wildcard = false 197 if prefix == "*" || prefix == "/*" { 198 wildcard = true 199 prefix = "/" 200 } 201 // Case sensitive routing, all to lowercase 202 if !app.Settings.CaseSensitive { 203 prefix = strings.ToLower(prefix) 204 } 205 // For security we want to restrict to the current work directory. 206 if len(root) == 0 { 207 root = "." 208 } 209 // Strip trailing slashes from the root path 210 if len(root) > 0 && root[len(root)-1] == '/' { 211 root = root[:len(root)-1] 212 } 213 // isSlash ? 214 var isSlash = prefix == "/" 215 if strings.Contains(prefix, "*") { 216 wildcard = true 217 prefix = strings.Split(prefix, "*")[0] 218 } 219 var stripper = len(prefix) 220 if isSlash { 221 stripper = 0 222 } 223 // File server settings 224 fs := &fasthttp.FS{ 225 Root: root, 226 GenerateIndexPages: false, 227 AcceptByteRange: false, 228 Compress: false, 229 CompressedFileSuffix: ".gz", 230 CacheDuration: 10 * time.Second, 231 IndexNames: []string{"index.html"}, 232 PathRewrite: fasthttp.NewPathPrefixStripper(stripper), 233 PathNotFound: func(ctx *fasthttp.RequestCtx) { 234 ctx.Response.SetStatusCode(404) 235 ctx.Response.SetBodyString("Not Found") 236 }, 237 } 238 // SetHeader config if provided 239 if len(config) > 0 { 240 fs.Compress = config[0].Compress 241 fs.AcceptByteRange = config[0].ByteRange 242 fs.GenerateIndexPages = config[0].Browse 243 if config[0].Index != "" { 244 fs.IndexNames = []string{config[0].Index} 245 } 246 } 247 fileHandler := fs.NewRequestHandler() 248 app.routes = append(app.routes, &Route{ 249 isMiddleware: true, 250 isSlash: isSlash, 251 Method: "*", 252 Path: prefix, 253 Handler: func(c *Ctx) { 254 // Only handle GET & HEAD methods 255 if c.method == "GET" || c.method == "HEAD" { 256 // Do stuff 257 if wildcard { 258 c.C.Request.SetRequestURI(prefix) 259 } 260 // Serve file 261 fileHandler(c.C) 262 // End response when file is found 263 if c.C.Response.StatusCode() != 404 { 264 return 265 } 266 } 267 c.Next() 268 }, 269 }) 270 }