You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

chi.go 8.6 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package routes
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "net/http"
  11. "os"
  12. "path"
  13. "strings"
  14. "text/template"
  15. "time"
  16. "code.gitea.io/gitea/modules/context"
  17. "code.gitea.io/gitea/modules/httpcache"
  18. "code.gitea.io/gitea/modules/log"
  19. "code.gitea.io/gitea/modules/metrics"
  20. "code.gitea.io/gitea/modules/public"
  21. "code.gitea.io/gitea/modules/setting"
  22. "code.gitea.io/gitea/modules/storage"
  23. "code.gitea.io/gitea/routers"
  24. "gitea.com/go-chi/session"
  25. "github.com/go-chi/chi"
  26. "github.com/go-chi/chi/middleware"
  27. "github.com/prometheus/client_golang/prometheus"
  28. )
  29. type routerLoggerOptions struct {
  30. req *http.Request
  31. Identity *string
  32. Start *time.Time
  33. ResponseWriter http.ResponseWriter
  34. }
  35. // SignedUserName returns signed user's name via context
  36. // FIXME currently no any data stored on chi.Context but macaron.Context, so this will
  37. // return "" before we remove macaron totally
  38. func SignedUserName(req *http.Request) string {
  39. if v, ok := req.Context().Value("SignedUserName").(string); ok {
  40. return v
  41. }
  42. return ""
  43. }
  44. func setupAccessLogger(c chi.Router) {
  45. logger := log.GetLogger("access")
  46. logTemplate, _ := template.New("log").Parse(setting.AccessLogTemplate)
  47. c.Use(func(next http.Handler) http.Handler {
  48. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  49. start := time.Now()
  50. next.ServeHTTP(w, req)
  51. identity := "-"
  52. if val := SignedUserName(req); val != "" {
  53. identity = val
  54. }
  55. rw := w
  56. buf := bytes.NewBuffer([]byte{})
  57. err := logTemplate.Execute(buf, routerLoggerOptions{
  58. req: req,
  59. Identity: &identity,
  60. Start: &start,
  61. ResponseWriter: rw,
  62. })
  63. if err != nil {
  64. log.Error("Could not set up macaron access logger: %v", err.Error())
  65. }
  66. err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "")
  67. if err != nil {
  68. log.Error("Could not set up macaron access logger: %v", err.Error())
  69. }
  70. })
  71. })
  72. }
  73. // LoggerHandler is a handler that will log the routing to the default gitea log
  74. func LoggerHandler(level log.Level) func(next http.Handler) http.Handler {
  75. return func(next http.Handler) http.Handler {
  76. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  77. start := time.Now()
  78. _ = log.GetLogger("router").Log(0, level, "Started %s %s for %s", log.ColoredMethod(req.Method), req.URL.RequestURI(), req.RemoteAddr)
  79. next.ServeHTTP(w, req)
  80. var status int
  81. if v, ok := w.(context.ResponseWriter); ok {
  82. status = v.Status()
  83. }
  84. _ = 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)))
  85. })
  86. }
  87. }
  88. func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler {
  89. return func(next http.Handler) http.Handler {
  90. if storageSetting.ServeDirect {
  91. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  92. if req.Method != "GET" && req.Method != "HEAD" {
  93. next.ServeHTTP(w, req)
  94. return
  95. }
  96. if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
  97. next.ServeHTTP(w, req)
  98. return
  99. }
  100. rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
  101. u, err := objStore.URL(rPath, path.Base(rPath))
  102. if err != nil {
  103. if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
  104. log.Warn("Unable to find %s %s", prefix, rPath)
  105. http.Error(w, "file not found", 404)
  106. return
  107. }
  108. log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err)
  109. http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), 500)
  110. return
  111. }
  112. http.Redirect(
  113. w,
  114. req,
  115. u.String(),
  116. 301,
  117. )
  118. })
  119. }
  120. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  121. if req.Method != "GET" && req.Method != "HEAD" {
  122. next.ServeHTTP(w, req)
  123. return
  124. }
  125. if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
  126. next.ServeHTTP(w, req)
  127. return
  128. }
  129. rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
  130. rPath = strings.TrimPrefix(rPath, "/")
  131. fi, err := objStore.Stat(rPath)
  132. if err == nil && httpcache.HandleTimeCache(req, w, fi) {
  133. return
  134. }
  135. //If we have matched and access to release or issue
  136. fr, err := objStore.Open(rPath)
  137. if err != nil {
  138. if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
  139. log.Warn("Unable to find %s %s", prefix, rPath)
  140. http.Error(w, "file not found", 404)
  141. return
  142. }
  143. log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
  144. http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), 500)
  145. return
  146. }
  147. defer fr.Close()
  148. _, err = io.Copy(w, fr)
  149. if err != nil {
  150. log.Error("Error whilst rendering %s %s. Error: %v", prefix, rPath, err)
  151. http.Error(w, fmt.Sprintf("Error whilst rendering %s %s", prefix, rPath), 500)
  152. return
  153. }
  154. })
  155. }
  156. }
  157. var (
  158. sessionManager *session.Manager
  159. )
  160. // NewChi creates a chi Router
  161. func NewChi() chi.Router {
  162. c := chi.NewRouter()
  163. c.Use(func(next http.Handler) http.Handler {
  164. return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  165. next.ServeHTTP(context.NewResponse(resp), req)
  166. })
  167. })
  168. c.Use(middleware.RealIP)
  169. if !setting.DisableRouterLog && setting.RouterLogLevel != log.NONE {
  170. if log.GetLogger("router").GetLevel() <= setting.RouterLogLevel {
  171. c.Use(LoggerHandler(setting.RouterLogLevel))
  172. }
  173. }
  174. var opt = session.Options{
  175. Provider: setting.SessionConfig.Provider,
  176. ProviderConfig: setting.SessionConfig.ProviderConfig,
  177. CookieName: setting.SessionConfig.CookieName,
  178. CookiePath: setting.SessionConfig.CookiePath,
  179. Gclifetime: setting.SessionConfig.Gclifetime,
  180. Maxlifetime: setting.SessionConfig.Maxlifetime,
  181. Secure: setting.SessionConfig.Secure,
  182. Domain: setting.SessionConfig.Domain,
  183. }
  184. opt = session.PrepareOptions([]session.Options{opt})
  185. var err error
  186. sessionManager, err = session.NewManager(opt.Provider, opt)
  187. if err != nil {
  188. panic(err)
  189. }
  190. c.Use(Recovery())
  191. if setting.EnableAccessLog {
  192. setupAccessLogger(c)
  193. }
  194. c.Use(public.Custom(
  195. &public.Options{
  196. SkipLogging: setting.DisableRouterLog,
  197. },
  198. ))
  199. c.Use(public.Static(
  200. &public.Options{
  201. Directory: path.Join(setting.StaticRootPath, "public"),
  202. SkipLogging: setting.DisableRouterLog,
  203. },
  204. ))
  205. c.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
  206. c.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
  207. return c
  208. }
  209. // RegisterInstallRoute registers the install routes
  210. func RegisterInstallRoute(c chi.Router) {
  211. m := NewMacaron()
  212. RegisterMacaronInstallRoute(m)
  213. // We need at least one handler in chi so that it does not drop
  214. // our middleware: https://github.com/go-gitea/gitea/issues/13725#issuecomment-735244395
  215. c.Get("/", func(w http.ResponseWriter, req *http.Request) {
  216. m.ServeHTTP(w, req)
  217. })
  218. c.NotFound(func(w http.ResponseWriter, req *http.Request) {
  219. m.ServeHTTP(w, req)
  220. })
  221. c.MethodNotAllowed(func(w http.ResponseWriter, req *http.Request) {
  222. m.ServeHTTP(w, req)
  223. })
  224. }
  225. // NormalRoutes represents non install routes
  226. func NormalRoutes() http.Handler {
  227. r := chi.NewRouter()
  228. // for health check
  229. r.Head("/", func(w http.ResponseWriter, req *http.Request) {
  230. w.WriteHeader(http.StatusOK)
  231. })
  232. if setting.HasRobotsTxt {
  233. r.Get("/robots.txt", func(w http.ResponseWriter, req *http.Request) {
  234. filePath := path.Join(setting.CustomPath, "robots.txt")
  235. fi, err := os.Stat(filePath)
  236. if err == nil && httpcache.HandleTimeCache(req, w, fi) {
  237. return
  238. }
  239. http.ServeFile(w, req, filePath)
  240. })
  241. }
  242. r.Get("/apple-touch-icon.png", func(w http.ResponseWriter, req *http.Request) {
  243. http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "img/apple-touch-icon.png"), 301)
  244. })
  245. // prometheus metrics endpoint
  246. if setting.Metrics.Enabled {
  247. c := metrics.NewCollector()
  248. prometheus.MustRegister(c)
  249. r.Get("/metrics", routers.Metrics)
  250. }
  251. return r
  252. }
  253. // DelegateToMacaron delegates other routes to macaron
  254. func DelegateToMacaron(r chi.Router) {
  255. m := NewMacaron()
  256. RegisterMacaronRoutes(m)
  257. r.NotFound(func(w http.ResponseWriter, req *http.Request) {
  258. m.ServeHTTP(w, req)
  259. })
  260. r.MethodNotAllowed(func(w http.ResponseWriter, req *http.Request) {
  261. m.ServeHTTP(w, req)
  262. })
  263. }