// BasicAuth returns an HTTP basic authentication middleware. // // For valid credentials it calls the next handler. // For invalid credentials, it sends "401 - Unauthorized" response. func BasicAuth(fn BasicValidateFunc) echo.MiddlewareFunc { return func(h echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { auth := c.Request().Header().Get(echo.HeaderAuthorization) l := len(basic) if len(auth) > l+1 && auth[:l] == basic { b, err := base64.StdEncoding.DecodeString(auth[l+1:]) if err == nil { cred := string(b) for i := 0; i < len(cred); i++ { if cred[i] == ':' { // Verify credentials if fn(cred[:i], cred[i+1:]) { return nil } } } } } c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm=Restricted") return echo.NewHTTPError(http.StatusUnauthorized) }) } }
// BodyLimitWithConfig returns a body limit middleware from config. // See: `BodyLimit()`. func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc { limit, err := bytes.Parse(config.Limit) if err != nil { panic(fmt.Errorf("invalid body-limit=%s", config.Limit)) } config.limit = limit pool := limitedReaderPool(config) return func(next echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { req := c.Request() // Based on content length if req.Size() > config.limit { return echo.ErrStatusRequestEntityTooLarge } // Based on content read r := pool.Get().(*limitedReader) r.Reset(req.Body(), c) defer pool.Put(r) req.SetBody(r) return next.Handle(c) }) } }
func (j *JWT) Validate() echo.MiddlewareFunc { return echo.MiddlewareFunc(func(h echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { if j.CondFn != nil && j.CondFn(c) == false { return h.Handle(c) } /*//Test tokenString, err := j.Response(map[string]interface{}{"uid": "1", "username": "******"}) if err == nil { println("jwt token:", tokenString) } //*/ token, err := ParseFromRequest(c.Request(), func(token *jwt.Token) (interface{}, error) { b := ([]byte(j.Secret)) return b, nil }) if err != nil { return err } if !token.Valid { return errors.New(`Incorrect signature.`) } c.Set(`webx:jwtClaims`, token.Claims) return h.Handle(c) }) }) }
// Gzip returns a middleware which compresses HTTP response using gzip compression // scheme. func Gzip() echo.MiddlewareFunc { return func(h echo.Handler) echo.Handler { scheme := `gzip` return echo.HandlerFunc(func(c echo.Context) error { c.Response().Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding) if strings.Contains(c.Request().Header().Get(echo.HeaderAcceptEncoding), scheme) { rw := c.Response().Writer() w := writerPool.Get().(*gzip.Writer) w.Reset(rw) defer func() { if c.Response().Size() == 0 { // We have to reset response to it's pristine state when // nothing is written to body or error is returned. // See issue #424, #407. c.Response().SetWriter(rw) c.Response().Header().Del(echo.HeaderContentEncoding) w.Reset(ioutil.Discard) } w.Close() writerPool.Put(w) }() gw := gzipWriter{Writer: w, Response: c.Response()} c.Response().Header().Set(echo.HeaderContentEncoding, scheme) c.Response().SetWriter(gw) } return h.Handle(c) }) } }
//路由注册方案1:注册函数(可匿名)或静态实例的成员函数 //例如:Controller.R(`/index`,Index.Index,"GET","POST") func (a *Wrapper) R(path string, h HandlerFunc, methods ...string) *Wrapper { if len(methods) < 1 { methods = append(methods, "GET") } _, ctl, act := a.App.Server.URL.Set(path, h) a.Webx.Match(methods, path, echo.HandlerFunc(a.wrapHandler(h, ctl, act))) return a }
func webxHeader() echo.MiddlewareFunc { return echo.MiddlewareFunc(func(h echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { c.Response().Header().Set(`Server`, `webx v`+VERSION) return h.Handle(c) }) }) }
func LimitMiddleware(limiter *config.Limiter) echo.MiddlewareFunc { return func(h echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { httpError := LimitByRequest(limiter, c.Request()) if httpError != nil { return c.String(httpError.StatusCode, httpError.Message) } return h.Handle(c) }) } }
func main() { e := echo.New() // Create a limiter struct. limiter := tollbooth.NewLimiter(1, time.Second) e.Get("/", echo.HandlerFunc(func(c echo.Context) error { return c.String(200, "Hello, World!") }), tollbooth_echo.LimitHandler(limiter)) e.Run(standard.New(":4444")) }
// 注册路由:app.R(`/index`,Index.Index,"GET","POST") func (a *App) R(path string, h HandlerFunc, methods ...string) *App { if len(methods) < 1 { methods = append(methods, "GET") } _, ctl, act := a.Server.URL.Set(path, h) a.Webx().Match(methods, path, echo.HandlerFunc(func(ctx echo.Context) error { c := X(ctx) if err := c.Init(a, nil, ctl, act); err != nil { return err } return h(c) })) return a }
func Sessions(name string, store ss.Store) echo.MiddlewareFunc { return echo.MiddlewareFunc(func(h echo.Handler) echo.Handler { return echo.HandlerFunc(func(ctx echo.Context) error { c := X.X(ctx) s := ss.NewMySession(store, name, ctx) if se, ok := interface{}(c).(Sessionser); ok { se.InitSession(s) } err := h.Handle(c) s.Save() return err }) }) }
func (a *Language) Middleware() echo.MiddlewareFunc { return echo.MiddlewareFunc(func(h echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { lang := a.DetectURI(c.Response(), c.Request()) c.SetFunc("Lang", func() string { return lang }) c.SetFunc("T", func(key string, args ...interface{}) string { return i18n.T(lang, key, args...) }) X.X(c).Language = lang return h.Handle(c) }) }) }
// Override checks for the X-HTTP-Method-Override header // or the HTML for parameter, `_method` // and uses (if valid) the http method instead of // Request.Method. // This is especially useful for http clients // that don't support many http verbs. // It isn't secure to override e.g a GET to a POST, // so only Request.Method which are POSTs are considered. func Override() echo.HandlerFunc { return echo.HandlerFunc(func(c echo.Context) error { if c.Request().Method() == "POST" { m := c.Form(ParamHTTPMethodOverride) if isValidOverrideMethod(m) { OverrideRequestMethod(c, m) } m = c.Request().Header().Get(HeaderHTTPMethodOverride) if isValidOverrideMethod(m) { c.Request().SetMethod(m) } } return nil }) }
// Recover returns a middleware which recovers from panics anywhere in the chain // and handles the control to the centralized HTTPErrorHandler. func Recover() echo.MiddlewareFunc { return func(h echo.Handler) echo.Handler { // TODO: Provide better stack trace `https://github.com/go-errors/errors` `https://github.com/docker/libcontainer/tree/master/stacktrace` return echo.HandlerFunc(func(c echo.Context) error { defer func() { if err := recover(); err != nil { trace := make([]byte, 1<<16) n := runtime.Stack(trace, true) c.Error(echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("panic recover\n %v\n stack trace %d bytes\n %s", err, n, trace[:n]))) } }() return h.Handle(c) }) } }
func (c *Config) Middleware() echo.MiddlewareFunc { return echo.MiddlewareFunc(func(h echo.Handler) echo.Handler { return echo.HandlerFunc(func(ctx echo.Context) error { if c.Read(ctx) { return nil } if err := h.Handle(ctx); err != nil { return err } if X.X(ctx).Exit { return nil } c.Write(ctx.Response().Body(), ctx) return nil }) }) }
func main() { e := echo.New() e.Use(mw.Log(), mw.Recover()) e.Use(markdown.Markdown(&markdown.Options{ Path: "/book/", Root: filepath.Join(os.Getenv(`GOPATH`), `src`, `github.com/admpub/gopl-zh`), Browse: true, })) e.Get("/", echo.HandlerFunc(func(c echo.Context) error { return c.String(200, "Hello, World!") })) // FastHTTP // e.Run(fasthttp.New(":4444")) // Standard e.Run(standard.New(":4444")) }
func (c *Xsrf) Middleware() echo.MiddlewareFunc { return echo.MiddlewareFunc(func(h echo.Handler) echo.Handler { return echo.HandlerFunc(func(ctx echo.Context) error { if !c.On { return h.Handle(ctx) } if ignore, _ := ctx.Get(`webx:ignoreXsrf`).(bool); ignore { return h.Handle(ctx) } c.Register(ctx) val := c.Value(ctx) if ctx.Request().Method() == `POST` { formVal := ctx.Form(c.FieldName) if formVal == "" || val != formVal { return errors.New("xsrf token error.") } } return h.Handle(ctx) }) }) }
func Log() echo.MiddlewareFunc { return func(h echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { req := c.Request() res := c.Response() logger := c.Logger() start := time.Now() if err := h.Handle(c); err != nil { c.Error(err) } remoteAddr := req.RealIP() stop := time.Now() method := req.Method() uri := req.URI() size := res.Size() code := res.Status() logger.Info(remoteAddr + " " + method + " " + uri + " " + fmt.Sprint(code) + " " + stop.Sub(start).String() + " " + fmt.Sprint(size)) return nil }) } }
//路由注册方案2:从动态实例内Mapper类型字段标签中获取路由信息 func (a *Wrapper) RouteTags() { if _, valid := a.Controller.(Initer); !valid { a.Server.Core.Logger().Infof("%T is no method Init(*Context),skip.", a.Controller) return } t := reflect.TypeOf(a.Controller) e := t.Elem() v := reflect.ValueOf(a.Controller) ctlPath := e.PkgPath() + ".(*" + e.Name() + ")." //github.com/webx-top/{Project}/app/{App}/controller.(*Index). ctl := strings.ToLower(e.Name()) for i := 0; i < e.NumField(); i++ { f := e.Field(i) if f.Type != mapperType { continue } fn := strings.Title(f.Name) name := fn m := v.MethodByName(fn) if !m.IsValid() { continue } //支持的tag: // 1. webx - 路由规则 // 2. memo - 注释说明 //webx标签内容支持以下格式: // 1、只指定http请求方式,如`webx:"POST|GET"` // 2、只指定路由规则,如`webx:"index"` // 3、只指定扩展名规则,如`webx:".JSON|XML"` // 4、指定以上全部规则,如`webx:"GET|POST.JSON|XML index"` tag := e.Field(i).Tag tagv := tag.Get("webx") methods := []string{} extends := []string{} var p, w string if tagv != "" { tags := strings.Split(tagv, " ") length := len(tags) if length >= 2 { //`webx:"GET|POST /index"` w = tags[0] p = tags[1] } else if length == 1 { if matched, _ := regexp.MatchString(`^[A-Z.]+(\|[A-Z]+)*$`, tags[0]); !matched { //非全大写字母时,判断为网址规则 p = tags[0] } else { //`webx:"GET|POST"` w = tags[0] } } } if p == "" { p = "/" + f.Name } else if p[0] != '/' { p = "/" + p } path := "/" + ctl + p met := "" ext := "" if w != "" { me := strings.Split(w, ".") met = me[0] if len(me) > 1 { ext = me[1] } } if met != "" { methods = strings.Split(met, "|") } if ext != "" { ext = strings.ToLower(ext) extends = strings.Split(ext, "|") } k := ctlPath + name + "-fm" u := a.App.Server.URL.SetByKey(path, k, tag.Get("memo")) u.SetExts(extends) h := echo.HandlerFunc(func(ctx echo.Context) error { c := X(ctx) if !u.ValidExt(c.Format) { return c.HTML(404, `The contents can not be displayed in this format: `+c.Format) } v := reflect.New(e) ac := v.Interface() if err := c.Init(a.App, ac, e.Name(), name); err != nil { return err } if err := ac.(Initer).Init(c); err != nil { return err } if a.HasBefore { if err := ac.(Before).Before(); err != nil { return err } if c.Exit { return nil } } format := strings.ToUpper(c.Format) m := v.MethodByName(fn + `_` + c.Method() + format) if !m.IsValid() { m = v.MethodByName(fn + `_` + c.Method()) if !m.IsValid() { m = v.MethodByName(fn + `_` + format) if !m.IsValid() { m = v.MethodByName(fn) } } } if r, err := a.SafelyCall(m, []reflect.Value{}); err != nil { return err } else if len(r) > 0 { if err, ok := r[0].Interface().(error); ok && err != nil { return c.DisplayError(err.Error()) } } if a.HasAfter { if c.Exit { return nil } return ac.(After).After() } return nil }) if len(methods) < 1 { a.Webx.Any(path, h) for strings.HasSuffix(path, `/index`) { path = strings.TrimSuffix(path, `/index`) a.Webx.Any(path+`/`, h) } continue } a.Webx.Match(methods, path, h) for strings.HasSuffix(path, `/index`) { path = strings.TrimSuffix(path, `/index`) a.Webx.Match(methods, path+`/`, h) } } }
//路由注册方案3:自动注册动态实例内带HTTP方法名后缀的成员函数作为路由 func (a *Wrapper) RouteMethods() { if _, valid := a.Controller.(Initer); !valid { a.Server.Core.Logger().Infof("%T is no method Init(*Context),skip.", a.Controller) return } t := reflect.TypeOf(a.Controller) e := t.Elem() ctlPath := e.PkgPath() + ".(*" + e.Name() + ")." //github.com/webx-top/{Project}/app/{App}/controller.(*Index). ctl := strings.ToLower(e.Name()) for i := t.NumMethod() - 1; i >= 0; i-- { m := t.Method(i) name := m.Name fn := name h := func(u *Url) func(ctx echo.Context) error { return func(ctx echo.Context) error { c := X(ctx) if !u.ValidExt(c.Format) { return c.HTML(404, `The contents can not be displayed in this format: `+c.Format) } v := reflect.New(e) ac := v.Interface() if err := c.Init(a.App, ac, e.Name(), name); err != nil { return err } if err := ac.(Initer).Init(c); err != nil { return err } if a.HasBefore { if err := ac.(Before).Before(); err != nil { return err } if c.Exit { return nil } } format := strings.ToUpper(c.Format) m := v.MethodByName(fn + `_` + c.Method() + format) if !m.IsValid() { m = v.MethodByName(fn + `_` + c.Method()) if !m.IsValid() { m = v.MethodByName(fn + `_` + format) if !m.IsValid() { m = v.MethodByName(fn) } } } if r, err := a.SafelyCall(m, []reflect.Value{}); err != nil { return err } else if len(r) > 0 { if err, ok := r[0].Interface().(error); ok && err != nil { return c.DisplayError(err.Error()) } } if a.HasAfter { if c.Exit { return nil } return ac.(After).After() } return nil } } if strings.HasSuffix(name, `_ANY`) { name = strings.TrimSuffix(name, `_ANY`) path := "/" + ctl + "/" + strings.ToLower(name) u := a.App.Server.URL.SetByKey(path, ctlPath+name+"-fm") handler := echo.HandlerFunc(h(u)) a.Webx.Any(path, handler) for strings.HasSuffix(path, `/index`) { path = strings.TrimSuffix(path, `/index`) a.Webx.Any(path+`/`, handler) } continue } matches := methodSuffixRegex.FindAllString(name, 1) if len(matches) < 1 { continue } methods := strings.Split(strings.TrimPrefix(matches[0], `_`), `_`) name = strings.TrimSuffix(name, matches[0]) path := "/" + ctl + "/" + strings.ToLower(name) u := a.App.Server.URL.SetByKey(path, ctlPath+name+"-fm") handler := echo.HandlerFunc(h(u)) a.Webx.Match(methods, path, handler) for strings.HasSuffix(path, `/index`) { path = strings.TrimSuffix(path, `/index`) a.Webx.Match(methods, path+`/`, handler) } } }
func Markdown(options ...*Options) echo.MiddlewareFunc { // Default options opts := new(Options) if len(options) > 0 { opts = options[0] } if opts.Index == "" { opts.Index = "SUMMARY.md" } if opts.MarkdownExtensions == nil { opts.MarkdownExtensions = []string{`.md`, `.mdown`, `.markdown`} } opts.Root, _ = filepath.Abs(opts.Root) if opts.Preprocessor == nil { opts.Preprocessor = func(c echo.Context, b []byte) []byte { return b } } if opts.Filter == nil { opts.Filter = func(string) bool { return true } } length := len(opts.Path) return func(next echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { file := c.Request().URL().Path() if len(file) < length || file[0:length] != opts.Path { return next.Handle(c) } if !opts.Filter(file) { return next.Handle(c) } file = filepath.Clean(file[length:]) absFile := filepath.Join(opts.Root, file) if !strings.HasPrefix(absFile, opts.Root) { return next.Handle(c) } fi, err := os.Stat(absFile) if err != nil { return echo.ErrNotFound } w := c.Response() if fi.IsDir() { // Index file indexFile := filepath.Join(absFile, opts.Index) fi, err = os.Stat(indexFile) if err != nil || fi.IsDir() { if opts.Browse { fs := http.Dir(filepath.Dir(absFile)) d, err := fs.Open(filepath.Base(absFile)) if err != nil { return echo.ErrNotFound } defer d.Close() dirs, err := d.Readdir(-1) if err != nil { return echo.ErrNotFound } // Create a directory index w.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) if _, err = fmt.Fprintf(w, `<!doctype html> <html> <head> <meta charset="UTF-8"> <title>`+file+`</title> <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" /> <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport" /> <link href="/favicon.ico" rel="shortcut icon"> </head> <body>`); err != nil { return err } if _, err = fmt.Fprintf(w, "<ul id=\"fileList\">\n"); err != nil { return err } for _, d := range dirs { name := d.Name() color := "#212121" if d.IsDir() { color = "#e91e63" name += "/" } if !opts.Filter(name) { continue } if _, err = fmt.Fprintf(w, "<li><a href=\"%s\" style=\"color: %s;\">%s</a></li>\n", name, color, name); err != nil { return err } } if _, err = fmt.Fprintf(w, "</ul>\n"); err != nil { return err } _, err = fmt.Fprintf(w, "</body>\n</html>") return err } return echo.ErrNotFound } absFile = indexFile } ext := strings.ToLower(filepath.Ext(fi.Name())) isMarkdownDocument := false for _, vext := range opts.MarkdownExtensions { if ext == vext { isMarkdownDocument = true break } } if isMarkdownDocument { modtime := fi.ModTime() if t, err := time.Parse(http.TimeFormat, c.Request().Header().Get(echo.HeaderIfModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) { w.Header().Del(echo.HeaderContentType) w.Header().Del(echo.HeaderContentLength) return c.NoContent(http.StatusNotModified) } b, err := ioutil.ReadFile(absFile) if err != nil { return echo.ErrNotFound } b = opts.Preprocessor(c, b) b = md2html.MarkdownCommon(b) w.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) w.Header().Set(echo.HeaderLastModified, modtime.UTC().Format(http.TimeFormat)) w.WriteHeader(http.StatusOK) _, err = w.Write(b) } else { w.Header().Set(echo.HeaderContentType, echo.ContentTypeByExtension(ext)) w.ServeFile(absFile) } return err }) } }
// CORSFromConfig returns a CORS middleware from config. // See `CORS()`. func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc { // Defaults if len(config.AllowOrigins) == 0 { config.AllowOrigins = DefaultCORSConfig.AllowOrigins } if len(config.AllowMethods) == 0 { config.AllowMethods = DefaultCORSConfig.AllowMethods } allowMethods := strings.Join(config.AllowMethods, ",") allowHeaders := strings.Join(config.AllowHeaders, ",") exposeHeaders := strings.Join(config.ExposeHeaders, ",") maxAge := strconv.Itoa(config.MaxAge) return func(next echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { rq := c.Request() origin := c.Request().Header().Get(echo.HeaderOrigin) header := c.Response().Header() // Check allowed origins allowedOrigin := "" for _, o := range config.AllowOrigins { if o == "*" || o == origin { allowedOrigin = o break } } // Simple request if rq.Method() != echo.OPTIONS { header.Add(echo.HeaderVary, echo.HeaderOrigin) if origin == "" || allowedOrigin == "" { return next.Handle(c) } header.Set(echo.HeaderAccessControlAllowOrigin, allowedOrigin) if config.AllowCredentials { header.Set(echo.HeaderAccessControlAllowCredentials, "true") } if exposeHeaders != "" { header.Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders) } return next.Handle(c) } // Preflight request header.Add(echo.HeaderVary, echo.HeaderOrigin) header.Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod) header.Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders) if origin == "" || allowedOrigin == "" { return next.Handle(c) } header.Set(echo.HeaderAccessControlAllowOrigin, allowedOrigin) header.Set(echo.HeaderAccessControlAllowMethods, allowMethods) if config.AllowCredentials { header.Set(echo.HeaderAccessControlAllowCredentials, "true") } if allowHeaders != "" { header.Set(echo.HeaderAccessControlAllowHeaders, allowHeaders) } else { h := rq.Header().Get(echo.HeaderAccessControlRequestHeaders) if h != "" { header.Set(echo.HeaderAccessControlAllowHeaders, h) } } if config.MaxAge > 0 { header.Set(echo.HeaderAccessControlMaxAge, maxAge) } return c.NoContent(http.StatusNoContent) }) } }
func Static(options ...*StaticOptions) echo.MiddlewareFunc { // Default options opts := new(StaticOptions) if len(options) > 0 { opts = options[0] } if opts.Index == "" { opts.Index = "index.html" } opts.Root, _ = filepath.Abs(opts.Root) length := len(opts.Path) return func(next echo.Handler) echo.Handler { return echo.HandlerFunc(func(c echo.Context) error { file := c.Request().URL().Path() if len(file) < length || file[0:length] != opts.Path { return next.Handle(c) } file = filepath.Clean(file[length:]) absFile := filepath.Join(opts.Root, file) if !strings.HasPrefix(absFile, opts.Root) { return next.Handle(c) } fi, err := os.Stat(absFile) if err != nil { return next.Handle(c) } w := c.Response() if fi.IsDir() { // Index file indexFile := filepath.Join(absFile, opts.Index) fi, err = os.Stat(indexFile) if err != nil || fi.IsDir() { if opts.Browse { fs := http.Dir(filepath.Dir(absFile)) d, err := fs.Open(filepath.Base(absFile)) if err != nil { return echo.ErrNotFound } defer d.Close() dirs, err := d.Readdir(-1) if err != nil { return echo.ErrNotFound } // Create a directory index w.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) if _, err = fmt.Fprintf(w, `<!doctype html> <html> <head> <meta charset="UTF-8"> <title>`+file+`</title> <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" /> <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport" /> <link href="/favicon.ico" rel="shortcut icon"> </head> <body>`); err != nil { return err } if _, err = fmt.Fprintf(w, "<ul id=\"fileList\">\n"); err != nil { return err } for _, d := range dirs { name := d.Name() color := "#212121" if d.IsDir() { color = "#e91e63" name += "/" } if _, err = fmt.Fprintf(w, "<li><a href=\"%s\" style=\"color: %s;\">%s</a></li>\n", name, color, name); err != nil { return err } } if _, err = fmt.Fprintf(w, "</ul>\n"); err != nil { return err } _, err = fmt.Fprintf(w, "</body>\n</html>") return err } return next.Handle(c) } absFile = indexFile } w.ServeFile(absFile) return nil }) } }