Fix recovery middleware to render gitea style page. (#13857)

* Some changes to fix recovery

* Move Recovery to middlewares

* Remove trace code

* Fix lint

* add session middleware and remove dependent on macaron for sso

* Fix panic 500 page rendering

* Fix bugs

* Fix fmt

* Fix vendor

* recover unnecessary change

* Fix lint and addd some comments about the copied codes.

* Use util.StatDir instead of com.StatDir

Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
Lunny Xiao 2021-01-05 21:05:40 +08:00 committed by GitHub
parent 126c9331d6
commit 15a475b7db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 5233 additions and 307 deletions

View file

@ -26,19 +26,18 @@ import (
"code.gitea.io/gitea/modules/markup/external"
repo_migrations "code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/ssh"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/svg"
"code.gitea.io/gitea/modules/task"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/services/mailer"
mirror_service "code.gitea.io/gitea/services/mirror"
pull_service "code.gitea.io/gitea/services/pull"
"code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/services/webhook"
"gitea.com/macaron/i18n"
"gitea.com/macaron/macaron"
)
@ -93,33 +92,6 @@ func initDBEngine(ctx context.Context) (err error) {
return nil
}
// InitLocales loads the locales
func InitLocales() {
localeNames, err := options.Dir("locale")
if err != nil {
log.Fatal("Failed to list locale files: %v", err)
}
localFiles := make(map[string][]byte)
for _, name := range localeNames {
localFiles[name], err = options.Locale(name)
if err != nil {
log.Fatal("Failed to load %s locale file. %v", name, err)
}
}
i18n.I18n(i18n.Options{
SubURL: setting.AppSubURL,
Files: localFiles,
Langs: setting.Langs,
Names: setting.Names,
DefaultLang: "en-US",
Redirect: false,
CookieDomain: setting.SessionConfig.Domain,
})
}
// PreInstallInit preloads the configuration to check if we need to run install
func PreInstallInit(ctx context.Context) bool {
setting.NewContext()
@ -129,7 +101,7 @@ func PreInstallInit(ctx context.Context) bool {
log.Trace("Custom path: %s", setting.CustomPath)
log.Trace("Log path: %s", setting.LogRootPath)
log.Trace("Preparing to run install page")
InitLocales()
translation.InitLocales()
if setting.EnableSQLite3 {
log.Info("SQLite3 Supported")
}
@ -170,7 +142,7 @@ func GlobalInit(ctx context.Context) {
log.Trace("Log path: %s", setting.LogRootPath)
// Setup i18n
InitLocales()
translation.InitLocales()
NewServices()

View file

@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/routers"
"gitea.com/go-chi/session"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/prometheus/client_golang/prometheus"
@ -37,7 +38,7 @@ type routerLoggerOptions struct {
}
// SignedUserName returns signed user's name via context
// FIXME currently no any data stored on gin.Context but macaron.Context, so this will
// FIXME currently no any data stored on chi.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 {
@ -97,24 +98,6 @@ func LoggerHandler(level log.Level) func(next http.Handler) http.Handler {
}
}
// 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 {
@ -202,6 +185,17 @@ func NewChi() chi.Router {
c.Use(LoggerHandler(setting.RouterLogLevel))
}
}
c.Use(session.Sessioner(session.Options{
Provider: setting.SessionConfig.Provider,
ProviderConfig: setting.SessionConfig.ProviderConfig,
CookieName: setting.SessionConfig.CookieName,
CookiePath: setting.SessionConfig.CookiePath,
Gclifetime: setting.SessionConfig.Gclifetime,
Maxlifetime: setting.SessionConfig.Maxlifetime,
Secure: setting.SessionConfig.Secure,
Domain: setting.SessionConfig.Domain,
}))
c.Use(Recovery())
if setting.EnableAccessLog {
setupAccessLogger(c)

105
routers/routes/recovery.go Normal file
View file

@ -0,0 +1,105 @@
// 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 (
"fmt"
"net/http"
"code.gitea.io/gitea/modules/auth/sso"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"gitea.com/go-chi/session"
"github.com/unrolled/render"
)
type dataStore struct {
Data map[string]interface{}
}
func (d *dataStore) GetData() map[string]interface{} {
return d.Data
}
// 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 {
var isDevelopment = setting.RunMode != "prod"
return func(next http.Handler) http.Handler {
rnd := render.New(render.Options{
Extensions: []string{".tmpl"},
Directory: "templates",
Funcs: templates.NewFuncMap(),
Asset: templates.GetAsset,
AssetNames: templates.GetAssetNames,
IsDevelopment: isDevelopment,
})
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
defer func() {
// Why we need this? The first recover will try to render a beautiful
// error page for user, but the process can still panic again, then
// we have to just 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(combinedErr)
if isDevelopment {
http.Error(w, combinedErr, 500)
} else {
http.Error(w, http.StatusText(500), 500)
}
}
}()
if err := recover(); err != nil {
combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
log.Error("%v", combinedErr)
lc := middlewares.Locale(w, req)
sess := session.GetSession(req)
var store = dataStore{
Data: templates.Vars{
"Language": lc.Language(),
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
"i18n": lc,
},
}
// Get user from session if logged in.
user, _ := sso.SignedInUser(req, &store, sess)
if user != nil {
store.Data["IsSigned"] = true
store.Data["SignedUser"] = user
store.Data["SignedUserID"] = user.ID
store.Data["SignedUserName"] = user.Name
store.Data["IsAdmin"] = user.IsAdmin
} else {
store.Data["SignedUserID"] = int64(0)
store.Data["SignedUserName"] = ""
}
w.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
if setting.RunMode != "prod" {
store.Data["ErrMsg"] = combinedErr
}
err := rnd.HTML(w, 500, "status/500", templates.BaseVars().Merge(store.Data))
if err != nil {
log.Error("%v", err)
}
}
}()
next.ServeHTTP(w, req)
})
}
}