summaryrefslogtreecommitdiffstats
path: root/routers/common
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2021-06-09 07:33:54 +0800
committerGitHub <noreply@github.com>2021-06-09 01:33:54 +0200
commit1bfb0a24d843e10d6d95c4319a84980485e584ed (patch)
treee4a736f9abee3eaad1270bf3b60ee3bb9401a9dc /routers/common
parente03a91a48ef7fb716cc7c8bfb411ca8f332dcfe5 (diff)
downloadgitea-1bfb0a24d843e10d6d95c4319a84980485e584ed.tar.gz
gitea-1bfb0a24d843e10d6d95c4319a84980485e584ed.zip
Refactor routers directory (#15800)
* refactor routers directory * move func used for web and api to common * make corsHandler a function to prohibit side efects * rm unused func Co-authored-by: 6543 <6543@obermui.de>
Diffstat (limited to 'routers/common')
-rw-r--r--routers/common/db.go39
-rw-r--r--routers/common/logger.go33
-rw-r--r--routers/common/middleware.go76
-rw-r--r--routers/common/repo.go127
4 files changed, 275 insertions, 0 deletions
diff --git a/routers/common/db.go b/routers/common/db.go
new file mode 100644
index 0000000000..069a46f64f
--- /dev/null
+++ b/routers/common/db.go
@@ -0,0 +1,39 @@
+// Copyright 2021 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 common
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/migrations"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// InitDBEngine In case of problems connecting to DB, retry connection. Eg, PGSQL in Docker Container on Synology
+func InitDBEngine(ctx context.Context) (err error) {
+ log.Info("Beginning ORM engine initialization.")
+ for i := 0; i < setting.Database.DBConnectRetries; i++ {
+ select {
+ case <-ctx.Done():
+ return fmt.Errorf("Aborted due to shutdown:\nin retry ORM engine initialization")
+ default:
+ }
+ log.Info("ORM engine initialization attempt #%d/%d...", i+1, setting.Database.DBConnectRetries)
+ if err = models.NewEngine(ctx, migrations.Migrate); err == nil {
+ break
+ } else if i == setting.Database.DBConnectRetries-1 {
+ return err
+ }
+ log.Error("ORM engine initialization attempt #%d/%d failed. Error: %v", i+1, setting.Database.DBConnectRetries, err)
+ log.Info("Backing off for %d seconds", int64(setting.Database.DBConnectBackoff/time.Second))
+ time.Sleep(setting.Database.DBConnectBackoff)
+ }
+ models.HasEngine = true
+ return nil
+}
diff --git a/routers/common/logger.go b/routers/common/logger.go
new file mode 100644
index 0000000000..bc1149543c
--- /dev/null
+++ b/routers/common/logger.go
@@ -0,0 +1,33 @@
+// Copyright 2021 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 common
+
+import (
+ "net/http"
+ "time"
+
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/log"
+)
+
+// 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.URL.RequestURI(), req.RemoteAddr)
+
+ next.ServeHTTP(w, req)
+
+ var status int
+ if v, ok := w.(context.ResponseWriter); ok {
+ status = v.Status()
+ }
+
+ _ = log.GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", log.ColoredMethod(req.Method), req.URL.RequestURI(), log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(start)))
+ })
+ }
+}
diff --git a/routers/common/middleware.go b/routers/common/middleware.go
new file mode 100644
index 0000000000..1d96522dd9
--- /dev/null
+++ b/routers/common/middleware.go
@@ -0,0 +1,76 @@
+// Copyright 2021 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 common
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/chi-middleware/proxy"
+ "github.com/go-chi/chi/middleware"
+)
+
+// Middlewares returns common middlewares
+func Middlewares() []func(http.Handler) http.Handler {
+ var handlers = []func(http.Handler) http.Handler{
+ func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ next.ServeHTTP(context.NewResponse(resp), req)
+ })
+ },
+ }
+
+ if setting.ReverseProxyLimit > 0 {
+ opt := proxy.NewForwardedHeadersOptions().
+ WithForwardLimit(setting.ReverseProxyLimit).
+ ClearTrustedProxies()
+ for _, n := range setting.ReverseProxyTrustedProxies {
+ if !strings.Contains(n, "/") {
+ opt.AddTrustedProxy(n)
+ } else {
+ opt.AddTrustedNetwork(n)
+ }
+ }
+ handlers = append(handlers, proxy.ForwardedHeaders(opt))
+ }
+
+ handlers = append(handlers, middleware.StripSlashes)
+
+ if !setting.DisableRouterLog && setting.RouterLogLevel != log.NONE {
+ if log.GetLogger("router").GetLevel() <= setting.RouterLogLevel {
+ handlers = append(handlers, LoggerHandler(setting.RouterLogLevel))
+ }
+ }
+ if setting.EnableAccessLog {
+ handlers = append(handlers, context.AccessLogger())
+ }
+
+ handlers = append(handlers, func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ // Why we need this? The Recovery() will try to render a beautiful
+ // error page for user, but the process can still panic again, and other
+ // middleware like session also may panic then we have to recover twice
+ // and send a simple error page that should not panic any more.
+ defer func() {
+ if err := recover(); err != nil {
+ combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
+ log.Error("%v", combinedErr)
+ if setting.IsProd() {
+ http.Error(resp, http.StatusText(500), 500)
+ } else {
+ http.Error(resp, combinedErr, 500)
+ }
+ }
+ }()
+ next.ServeHTTP(resp, req)
+ })
+ })
+ return handlers
+}
diff --git a/routers/common/repo.go b/routers/common/repo.go
new file mode 100644
index 0000000000..c61b5ec57f
--- /dev/null
+++ b/routers/common/repo.go
@@ -0,0 +1,127 @@
+// Copyright 2021 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 common
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "code.gitea.io/gitea/modules/charset"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/httpcache"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/typesniffer"
+ "code.gitea.io/gitea/services/archiver"
+)
+
+// ServeBlob download a git.Blob
+func ServeBlob(ctx *context.Context, blob *git.Blob) error {
+ if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) {
+ return nil
+ }
+
+ dataRc, err := blob.DataAsync()
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err = dataRc.Close(); err != nil {
+ log.Error("ServeBlob: Close: %v", err)
+ }
+ }()
+
+ return ServeData(ctx, ctx.Repo.TreePath, blob.Size(), dataRc)
+}
+
+// Download an archive of a repository
+func Download(ctx *context.Context) {
+ uri := ctx.Params("*")
+ aReq := archiver.DeriveRequestFrom(ctx, uri)
+
+ if aReq == nil {
+ ctx.Error(http.StatusNotFound)
+ return
+ }
+
+ downloadName := ctx.Repo.Repository.Name + "-" + aReq.GetArchiveName()
+ complete := aReq.IsComplete()
+ if !complete {
+ aReq = archiver.ArchiveRepository(aReq)
+ complete = aReq.WaitForCompletion(ctx)
+ }
+
+ if complete {
+ ctx.ServeFile(aReq.GetArchivePath(), downloadName)
+ } else {
+ ctx.Error(http.StatusNotFound)
+ }
+}
+
+// ServeData download file from io.Reader
+func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) error {
+ buf := make([]byte, 1024)
+ n, err := reader.Read(buf)
+ if err != nil && err != io.EOF {
+ return err
+ }
+ if n >= 0 {
+ buf = buf[:n]
+ }
+
+ ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400")
+
+ if size >= 0 {
+ ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
+ } else {
+ log.Error("ServeData called to serve data: %s with size < 0: %d", name, size)
+ }
+ name = path.Base(name)
+
+ // Google Chrome dislike commas in filenames, so let's change it to a space
+ name = strings.ReplaceAll(name, ",", " ")
+
+ st := typesniffer.DetectContentType(buf)
+
+ if st.IsText() || ctx.QueryBool("render") {
+ cs, err := charset.DetectEncoding(buf)
+ if err != nil {
+ log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err)
+ cs = "utf-8"
+ }
+ ctx.Resp.Header().Set("Content-Type", "text/plain; charset="+strings.ToLower(cs))
+ } else {
+ ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
+
+ if (st.IsImage() || st.IsPDF()) && (setting.UI.SVG.Enabled || !st.IsSvgImage()) {
+ ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name))
+ if st.IsSvgImage() {
+ ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
+ ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
+ ctx.Resp.Header().Set("Content-Type", typesniffer.SvgMimeType)
+ }
+ } else {
+ ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
+ if setting.MimeTypeMap.Enabled {
+ fileExtension := strings.ToLower(filepath.Ext(name))
+ if mimetype, ok := setting.MimeTypeMap.Map[fileExtension]; ok {
+ ctx.Resp.Header().Set("Content-Type", mimetype)
+ }
+ }
+ }
+ }
+
+ _, err = ctx.Resp.Write(buf)
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(ctx.Resp, reader)
+ return err
+}