URL shortener and file dump for hashru.link https://hashru.link
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

142 lines
4.3 KiB

  1. package rushlink
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "net/url"
  7. "runtime/debug"
  8. "strconv"
  9. "time"
  10. "gitea.hashru.nl/dsprenkels/rushlink/internal/db"
  11. "github.com/gorilla/mux"
  12. "github.com/pkg/errors"
  13. )
  14. const staticFilenameExpr = "[A-Za-z0-9-_.]+"
  15. const urlKeyExpr = "{key:[A-Za-z0-9-_]{4,}}"
  16. const urlKeyWithExtExpr = urlKeyExpr + "{ext:\\.[A-Za-z0-9-_]+}"
  17. type rushlink struct {
  18. db *db.Database
  19. fs *db.FileStore
  20. rootURL *url.URL
  21. }
  22. func (rl *rushlink) RootURL() *url.URL {
  23. return rl.rootURL
  24. }
  25. func (rl *rushlink) recoveryMiddleware(next http.Handler) http.Handler {
  26. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  27. logRequestInfo := func() {
  28. log.Printf("in request: %v - %v %q %v", r.RemoteAddr, r.Method, r.RequestURI, r.Proto)
  29. }
  30. defer func() {
  31. defer func() {
  32. if err := recover(); err != nil {
  33. w.WriteHeader(500)
  34. log.Printf("error: panic while recovering from another panic: %v\n", err)
  35. logRequestInfo()
  36. debug.PrintStack()
  37. fmt.Fprintf(w, "internal server error: %v\n", err)
  38. }
  39. }()
  40. if err := recover(); err != nil {
  41. w.WriteHeader(500)
  42. log.Printf("error: %v\n", err)
  43. logRequestInfo()
  44. debug.PrintStack()
  45. rl.renderInternalServerError(w, r, err)
  46. }
  47. }()
  48. next.ServeHTTP(w, r)
  49. })
  50. }
  51. func (rl *rushlink) metricsMiddleware(next http.Handler) http.Handler {
  52. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  53. srw := statusResponseWriter{Inner: w}
  54. next.ServeHTTP(&srw, r)
  55. status := strconv.Itoa(srw.StatusCode)
  56. metricRequestsTotalCounter.WithLabelValues(status, r.Method).Inc()
  57. })
  58. }
  59. type statusResponseWriter struct {
  60. Inner http.ResponseWriter
  61. StatusCode int
  62. }
  63. func (w *statusResponseWriter) Header() http.Header {
  64. return w.Inner.Header()
  65. }
  66. func (w *statusResponseWriter) Write(buf []byte) (int, error) {
  67. if w.StatusCode == 0 {
  68. w.WriteHeader(http.StatusOK)
  69. }
  70. return w.Inner.Write(buf)
  71. }
  72. func (w *statusResponseWriter) WriteHeader(statusCode int) {
  73. w.StatusCode = statusCode
  74. w.Inner.WriteHeader(statusCode)
  75. }
  76. // InitMainRouter creates the main Gorilla router for the application.
  77. //
  78. // This function will not populate the router with an error-recovery and
  79. // metrics-reporting middleware. If these middleware are required, then the
  80. // caller should encapsulate this router inside of another router and register
  81. // the middlewares on the encapsulating router.
  82. func InitMainRouter(r *mux.Router, rl *rushlink) {
  83. r.HandleFunc("/{path:img/"+staticFilenameExpr+"}", rl.staticGetHandler).Methods("GET", "HEAD")
  84. r.HandleFunc("/{path:css/"+staticFilenameExpr+"}", rl.staticGetHandler).Methods("GET", "HEAD")
  85. r.HandleFunc("/{path:js/"+staticFilenameExpr+"}", rl.staticGetHandler).Methods("GET", "HEAD")
  86. r.HandleFunc("/", rl.indexGetHandler).Methods("GET", "HEAD")
  87. r.HandleFunc("/", rl.newPasteHandler).Methods("POST")
  88. r.HandleFunc("/"+urlKeyExpr, rl.viewPasteHandler).Methods("GET", "HEAD")
  89. r.HandleFunc("/"+urlKeyWithExtExpr, rl.viewPasteHandler).Methods("GET", "HEAD")
  90. r.HandleFunc("/"+urlKeyExpr+"/nr", rl.viewPasteHandlerNoRedirect).Methods("GET", "HEAD")
  91. r.HandleFunc("/"+urlKeyWithExtExpr+"/nr", rl.viewPasteHandlerNoRedirect).Methods("GET", "HEAD")
  92. r.HandleFunc("/"+urlKeyExpr+"/meta", rl.viewPasteHandlerMeta).Methods("GET", "HEAD")
  93. r.HandleFunc("/"+urlKeyWithExtExpr+"/meta", rl.viewPasteHandlerMeta).Methods("GET", "HEAD")
  94. r.HandleFunc("/"+urlKeyExpr, rl.deletePasteHandler).Methods("DELETE")
  95. r.HandleFunc("/"+urlKeyWithExtExpr, rl.deletePasteHandler).Methods("DELETE")
  96. r.HandleFunc("/"+urlKeyExpr+"/delete", rl.deletePasteHandler).Methods("POST")
  97. r.HandleFunc("/"+urlKeyWithExtExpr+"/delete", rl.deletePasteHandler).Methods("POST")
  98. }
  99. // StartMainServer starts the main http server listening on addr.
  100. func StartMainServer(addr string, db *db.Database, fs *db.FileStore, rawRootURL string) {
  101. var rootURL *url.URL
  102. if rawRootURL != "" {
  103. var err error
  104. rootURL, err = url.Parse(rawRootURL)
  105. if err != nil {
  106. log.Fatalln(errors.Wrap(err, "could not parse rootURL flag"))
  107. }
  108. }
  109. rl := rushlink{
  110. db: db,
  111. fs: fs,
  112. rootURL: rootURL,
  113. }
  114. router := mux.NewRouter()
  115. router.Use(rl.metricsMiddleware)
  116. router.Use(rl.recoveryMiddleware)
  117. InitMainRouter(router, &rl)
  118. srv := &http.Server{
  119. Handler: router,
  120. Addr: addr,
  121. WriteTimeout: 15 * time.Second,
  122. ReadTimeout: 15 * time.Second,
  123. }
  124. log.Fatal(srv.ListenAndServe())
  125. }