diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2020-11-13 20:51:07 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-13 14:51:07 +0200 |
commit | c296f4fed66288431fa7ec3a64f990beccd29eb1 (patch) | |
tree | 6b2f1971303967671bd5da1d1149407e410d62bd /routers | |
parent | 0ae35c66f2efe608e3176f796866c18461f0780f (diff) | |
download | gitea-c296f4fed66288431fa7ec3a64f990beccd29eb1.tar.gz gitea-c296f4fed66288431fa7ec3a64f990beccd29eb1.zip |
Introduce go chi web framework as frontend of macaron, so that we can move routes from macaron to chi step by step (#7420)
* When route cannot be found on chi, go to macaron
* Stick chi version to 1.5.0
* Follow router log setting
Diffstat (limited to 'routers')
-rw-r--r-- | routers/routes/chi.go | 260 | ||||
-rw-r--r-- | routers/routes/macaron.go (renamed from routers/routes/routes.go) | 187 |
2 files changed, 265 insertions, 182 deletions
diff --git a/routers/routes/chi.go b/routers/routes/chi.go new file mode 100644 index 0000000000..c78fe96ae4 --- /dev/null +++ b/routers/routes/chi.go @@ -0,0 +1,260 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package routes + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "strings" + "text/template" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/public" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" + + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" +) + +type routerLoggerOptions struct { + req *http.Request + Identity *string + Start *time.Time + ResponseWriter http.ResponseWriter +} + +// SignedUserName returns signed user's name via context +// FIXME currently no any data stored on gin.Context but macaron.Context, so this will +// return "" before we remove macaron totally +func SignedUserName(req *http.Request) string { + if v, ok := req.Context().Value("SignedUserName").(string); ok { + return v + } + return "" +} + +func setupAccessLogger(c chi.Router) { + logger := log.GetLogger("access") + + logTemplate, _ := template.New("log").Parse(setting.AccessLogTemplate) + c.Use(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + start := time.Now() + next.ServeHTTP(w, req) + identity := "-" + if val := SignedUserName(req); val != "" { + identity = val + } + rw := w + + buf := bytes.NewBuffer([]byte{}) + err := logTemplate.Execute(buf, routerLoggerOptions{ + req: req, + Identity: &identity, + Start: &start, + ResponseWriter: rw, + }) + if err != nil { + log.Error("Could not set up macaron access logger: %v", err.Error()) + } + + err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "") + if err != nil { + log.Error("Could not set up macaron access logger: %v", err.Error()) + } + }) + }) +} + +// LoggerHandler is a handler that will log the routing to the default gitea log +func LoggerHandler(level log.Level) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + start := time.Now() + + _ = log.GetLogger("router").Log(0, level, "Started %s %s for %s", log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr) + + next.ServeHTTP(w, req) + + ww := middleware.NewWrapResponseWriter(w, req.ProtoMajor) + + status := ww.Status() + _ = log.GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", log.ColoredMethod(req.Method), req.RequestURI, log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(start))) + }) + } +} + +// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so. +// Although similar to macaron.Recovery() the main difference is that this error will be created +// with the gitea 500 page. +func Recovery() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + defer func() { + if err := recover(); err != nil { + combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2))) + http.Error(w, combinedErr, 500) + } + }() + + next.ServeHTTP(w, req) + }) + } +} + +func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + if storageSetting.ServeDirect { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.Method != "GET" && req.Method != "HEAD" { + next.ServeHTTP(w, req) + return + } + + if !strings.HasPrefix(req.RequestURI, "/"+prefix) { + next.ServeHTTP(w, req) + return + } + + rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix) + u, err := objStore.URL(rPath, path.Base(rPath)) + if err != nil { + if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { + log.Warn("Unable to find %s %s", prefix, rPath) + http.Error(w, "file not found", 404) + return + } + log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err) + http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), 500) + return + } + http.Redirect( + w, + req, + u.String(), + 301, + ) + }) + } + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.Method != "GET" && req.Method != "HEAD" { + next.ServeHTTP(w, req) + return + } + + if !strings.HasPrefix(req.RequestURI, "/"+prefix) { + next.ServeHTTP(w, req) + return + } + + rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix) + rPath = strings.TrimPrefix(rPath, "/") + //If we have matched and access to release or issue + fr, err := objStore.Open(rPath) + if err != nil { + if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { + log.Warn("Unable to find %s %s", prefix, rPath) + http.Error(w, "file not found", 404) + return + } + log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err) + http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), 500) + return + } + defer fr.Close() + + _, err = io.Copy(w, fr) + if err != nil { + log.Error("Error whilst rendering %s %s. Error: %v", prefix, rPath, err) + http.Error(w, fmt.Sprintf("Error whilst rendering %s %s", prefix, rPath), 500) + return + } + }) + } +} + +// NewChi creates a chi Router +func NewChi() chi.Router { + c := chi.NewRouter() + if !setting.DisableRouterLog && setting.RouterLogLevel != log.NONE { + if log.GetLogger("router").GetLevel() <= setting.RouterLogLevel { + c.Use(LoggerHandler(setting.RouterLogLevel)) + } + } + c.Use(Recovery()) + if setting.EnableAccessLog { + setupAccessLogger(c) + } + if setting.ProdMode { + log.Warn("ProdMode ignored") + } + + c.Use(public.Custom( + &public.Options{ + SkipLogging: setting.DisableRouterLog, + ExpiresAfter: time.Hour * 6, + }, + )) + c.Use(public.Static( + &public.Options{ + Directory: path.Join(setting.StaticRootPath, "public"), + SkipLogging: setting.DisableRouterLog, + ExpiresAfter: time.Hour * 6, + }, + )) + + c.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) + c.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) + + return c +} + +// RegisterInstallRoute registers the install routes +func RegisterInstallRoute(c chi.Router) { + m := NewMacaron() + RegisterMacaronInstallRoute(m) + + c.NotFound(func(w http.ResponseWriter, req *http.Request) { + m.ServeHTTP(w, req) + }) + + c.MethodNotAllowed(func(w http.ResponseWriter, req *http.Request) { + m.ServeHTTP(w, req) + }) +} + +// RegisterRoutes registers gin routes +func RegisterRoutes(c chi.Router) { + // for health check + c.Head("/", func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + // robots.txt + if setting.HasRobotsTxt { + c.Get("/robots.txt", func(w http.ResponseWriter, req *http.Request) { + http.ServeFile(w, req, path.Join(setting.CustomPath, "robots.txt")) + }) + } + + m := NewMacaron() + RegisterMacaronRoutes(m) + + c.NotFound(func(w http.ResponseWriter, req *http.Request) { + m.ServeHTTP(w, req) + }) + + c.MethodNotAllowed(func(w http.ResponseWriter, req *http.Request) { + m.ServeHTTP(w, req) + }) +} diff --git a/routers/routes/routes.go b/routers/routes/macaron.go index 285510dbe3..7178ead679 100644 --- a/routers/routes/routes.go +++ b/routers/routes/macaron.go @@ -5,17 +5,8 @@ package routes import ( - "bytes" "encoding/gob" - "errors" - "fmt" - "io" - "net/http" - "os" "path" - "strings" - "text/template" - "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" @@ -24,9 +15,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/metrics" "code.gitea.io/gitea/modules/options" - "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/routers" @@ -58,129 +47,6 @@ import ( "github.com/tstranex/u2f" ) -type routerLoggerOptions struct { - Ctx *macaron.Context - Identity *string - Start *time.Time - ResponseWriter *macaron.ResponseWriter -} - -func setupAccessLogger(m *macaron.Macaron) { - logger := log.GetLogger("access") - - logTemplate, _ := template.New("log").Parse(setting.AccessLogTemplate) - m.Use(func(ctx *macaron.Context) { - start := time.Now() - ctx.Next() - identity := "-" - if val, ok := ctx.Data["SignedUserName"]; ok { - if stringVal, ok := val.(string); ok && stringVal != "" { - identity = stringVal - } - } - rw := ctx.Resp.(macaron.ResponseWriter) - - buf := bytes.NewBuffer([]byte{}) - err := logTemplate.Execute(buf, routerLoggerOptions{ - Ctx: ctx, - Identity: &identity, - Start: &start, - ResponseWriter: &rw, - }) - if err != nil { - log.Error("Could not set up macaron access logger: %v", err.Error()) - } - - err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "") - if err != nil { - log.Error("Could not set up macaron access logger: %v", err.Error()) - } - }) -} - -// RouterHandler is a macaron handler that will log the routing to the default gitea log -func RouterHandler(level log.Level) func(ctx *macaron.Context) { - return func(ctx *macaron.Context) { - start := time.Now() - - _ = log.GetLogger("router").Log(0, level, "Started %s %s for %s", log.ColoredMethod(ctx.Req.Method), ctx.Req.URL.RequestURI(), ctx.RemoteAddr()) - - rw := ctx.Resp.(macaron.ResponseWriter) - ctx.Next() - - status := rw.Status() - _ = log.GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", log.ColoredMethod(ctx.Req.Method), ctx.Req.URL.RequestURI(), log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(rw.Status())), log.ColoredTime(time.Since(start))) - } -} - -func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) macaron.Handler { - if storageSetting.ServeDirect { - return func(ctx *macaron.Context) { - req := ctx.Req.Request - if req.Method != "GET" && req.Method != "HEAD" { - return - } - - if !strings.HasPrefix(req.RequestURI, "/"+prefix) { - return - } - - rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix) - u, err := objStore.URL(rPath, path.Base(rPath)) - if err != nil { - if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { - log.Warn("Unable to find %s %s", prefix, rPath) - ctx.Error(404, "file not found") - return - } - log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err) - ctx.Error(500, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath)) - return - } - http.Redirect( - ctx.Resp, - req, - u.String(), - 301, - ) - } - } - - return func(ctx *macaron.Context) { - req := ctx.Req.Request - if req.Method != "GET" && req.Method != "HEAD" { - return - } - - if !strings.HasPrefix(req.RequestURI, "/"+prefix) { - return - } - - rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix) - rPath = strings.TrimPrefix(rPath, "/") - //If we have matched and access to release or issue - fr, err := objStore.Open(rPath) - if err != nil { - if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { - log.Warn("Unable to find %s %s", prefix, rPath) - ctx.Error(404, "file not found") - return - } - log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err) - ctx.Error(500, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath)) - return - } - defer fr.Close() - - _, err = io.Copy(ctx.Resp, fr) - if err != nil { - log.Error("Error whilst rendering %s %s. Error: %v", prefix, rPath, err) - ctx.Error(500, fmt.Sprintf("Error whilst rendering %s %s", prefix, rPath)) - return - } - } -} - // NewMacaron initializes Macaron instance. func NewMacaron() *macaron.Macaron { gob.Register(&u2f.Challenge{}) @@ -188,47 +54,19 @@ func NewMacaron() *macaron.Macaron { if setting.RedirectMacaronLog { loggerAsWriter := log.NewLoggerAsWriter("INFO", log.GetLogger("macaron")) m = macaron.NewWithLogger(loggerAsWriter) - if !setting.DisableRouterLog && setting.RouterLogLevel != log.NONE { - if log.GetLogger("router").GetLevel() <= setting.RouterLogLevel { - m.Use(RouterHandler(setting.RouterLogLevel)) - } - } } else { m = macaron.New() - if !setting.DisableRouterLog { - m.Use(macaron.Logger()) - } } - // Access Logger is similar to Router Log but more configurable and by default is more like the NCSA Common Log format - if setting.EnableAccessLog { - setupAccessLogger(m) - } - m.Use(macaron.Recovery()) + if setting.EnableGzip { m.Use(gzip.Middleware()) } if setting.Protocol == setting.FCGI || setting.Protocol == setting.FCGIUnix { m.SetURLPrefix(setting.AppSubURL) } - m.Use(public.Custom( - &public.Options{ - SkipLogging: setting.DisableRouterLog, - ExpiresAfter: setting.StaticCacheTime, - }, - )) - m.Use(public.Static( - &public.Options{ - Directory: path.Join(setting.StaticRootPath, "public"), - SkipLogging: setting.DisableRouterLog, - ExpiresAfter: setting.StaticCacheTime, - }, - )) m.Use(templates.HTMLRenderer()) - m.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) - m.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) - mailer.InitMailRender(templates.Mailer()) localeNames, err := options.Dir("locale") @@ -294,15 +132,12 @@ func NewMacaron() *macaron.Macaron { DisableDebug: !setting.EnablePprof, })) m.Use(context.Contexter()) - // OK we are now set-up enough to allow us to create a nicer recovery than - // the default macaron recovery - m.Use(context.Recovery()) m.SetAutoHead(true) return m } -// RegisterInstallRoute registers the install routes -func RegisterInstallRoute(m *macaron.Macaron) { +// RegisterMacaronInstallRoute registers the install routes +func RegisterMacaronInstallRoute(m *macaron.Macaron) { m.Combo("/", routers.InstallInit).Get(routers.Install). Post(binding.BindIgnErr(auth.InstallForm{}), routers.InstallPost) m.NotFound(func(ctx *context.Context) { @@ -310,8 +145,8 @@ func RegisterInstallRoute(m *macaron.Macaron) { }) } -// RegisterRoutes routes routes to Macaron -func RegisterRoutes(m *macaron.Macaron) { +// RegisterMacaronRoutes routes routes to Macaron +func RegisterMacaronRoutes(m *macaron.Macaron) { reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true}) ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView}) ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true}) @@ -353,9 +188,6 @@ func RegisterRoutes(m *macaron.Macaron) { // Especially some AJAX requests, we can reduce middleware number to improve performance. // Routers. // for health check - m.Head("/", func() string { - return "" - }) m.Get("/", routers.Home) m.Group("/explore", func() { m.Get("", func(ctx *context.Context) { @@ -1146,15 +978,6 @@ func RegisterRoutes(m *macaron.Macaron) { private.RegisterRoutes(m) }) - // robots.txt - m.Get("/robots.txt", func(ctx *context.Context) { - if setting.HasRobotsTxt { - ctx.ServeFileContent(path.Join(setting.CustomPath, "robots.txt")) - } else { - ctx.NotFound("", nil) - } - }) - m.Get("/apple-touch-icon.png", func(ctx *context.Context) { ctx.Redirect(path.Join(setting.StaticURLPrefix, "img/apple-touch-icon.png"), 301) }) |