aboutsummaryrefslogtreecommitdiffstats
path: root/routers/routes/chi.go
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/routes/chi.go
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/routes/chi.go')
-rw-r--r--routers/routes/chi.go260
1 files changed, 260 insertions, 0 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)
+ })
+}