2020-11-13 15:51:07 +03:00
// 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"
2020-11-18 01:44:52 +03:00
"code.gitea.io/gitea/modules/httpcache"
2020-11-13 15:51:07 +03:00
"code.gitea.io/gitea/modules/log"
2020-11-17 23:50:06 +03:00
"code.gitea.io/gitea/modules/metrics"
2020-11-13 15:51:07 +03:00
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
2020-11-17 23:50:06 +03:00
"code.gitea.io/gitea/routers"
2020-11-13 15:51:07 +03:00
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
2020-11-17 23:50:06 +03:00
"github.com/prometheus/client_golang/prometheus"
2020-11-13 15:51:07 +03:00
)
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 , "/" )
2020-11-18 01:44:52 +03:00
fi , err := objStore . Stat ( rPath )
if err == nil && httpcache . HandleTimeCache ( req , w , fi ) {
return
}
2020-11-13 15:51:07 +03:00
//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 ( )
2020-11-16 10:33:41 +03:00
c . Use ( middleware . RealIP )
2020-11-13 15:51:07 +03:00
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 )
}
2020-11-16 10:33:41 +03:00
2020-11-13 15:51:07 +03:00
c . Use ( public . Custom (
& public . Options {
2020-11-18 01:44:52 +03:00
SkipLogging : setting . DisableRouterLog ,
2020-11-13 15:51:07 +03:00
} ,
) )
c . Use ( public . Static (
& public . Options {
2020-11-18 01:44:52 +03:00
Directory : path . Join ( setting . StaticRootPath , "public" ) ,
SkipLogging : setting . DisableRouterLog ,
2020-11-13 15:51:07 +03:00
} ,
) )
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 )
} )
}
2020-11-16 10:33:41 +03:00
// NormalRoutes represents non install routes
func NormalRoutes ( ) http . Handler {
r := chi . NewRouter ( )
2020-11-13 15:51:07 +03:00
// for health check
2020-11-16 10:33:41 +03:00
r . Head ( "/" , func ( w http . ResponseWriter , req * http . Request ) {
2020-11-13 15:51:07 +03:00
w . WriteHeader ( http . StatusOK )
} )
if setting . HasRobotsTxt {
2020-11-16 10:33:41 +03:00
r . Get ( "/robots.txt" , func ( w http . ResponseWriter , req * http . Request ) {
2020-11-18 01:44:52 +03:00
filePath := path . Join ( setting . CustomPath , "robots.txt" )
fi , err := os . Stat ( filePath )
if err == nil && httpcache . HandleTimeCache ( req , w , fi ) {
return
}
http . ServeFile ( w , req , filePath )
2020-11-13 15:51:07 +03:00
} )
}
2020-11-17 23:50:06 +03:00
r . Get ( "/apple-touch-icon.png" , func ( w http . ResponseWriter , req * http . Request ) {
http . Redirect ( w , req , path . Join ( setting . StaticURLPrefix , "img/apple-touch-icon.png" ) , 301 )
} )
// prometheus metrics endpoint
if setting . Metrics . Enabled {
c := metrics . NewCollector ( )
prometheus . MustRegister ( c )
r . Get ( "/metrics" , routers . Metrics )
}
2020-11-16 10:33:41 +03:00
return r
}
// DelegateToMacaron delegates other routes to macaron
func DelegateToMacaron ( r chi . Router ) {
2020-11-13 15:51:07 +03:00
m := NewMacaron ( )
RegisterMacaronRoutes ( m )
2020-11-16 10:33:41 +03:00
r . NotFound ( func ( w http . ResponseWriter , req * http . Request ) {
2020-11-13 15:51:07 +03:00
m . ServeHTTP ( w , req )
} )
2020-11-16 10:33:41 +03:00
r . MethodNotAllowed ( func ( w http . ResponseWriter , req * http . Request ) {
2020-11-13 15:51:07 +03:00
m . ServeHTTP ( w , req )
} )
}