aboutsummaryrefslogtreecommitdiffstats
path: root/routers
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2020-11-13 20:51:07 +0800
committerGitHub <noreply@github.com>2020-11-13 14:51:07 +0200
commitc296f4fed66288431fa7ec3a64f990beccd29eb1 (patch)
tree6b2f1971303967671bd5da1d1149407e410d62bd /routers
parent0ae35c66f2efe608e3176f796866c18461f0780f (diff)
downloadgitea-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.go260
-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)
})