router.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package server
  2. import (
  3. "encoding/json"
  4. "net"
  5. "net/http"
  6. "regexp"
  7. "strings"
  8. nanoid "github.com/matoous/go-nanoid/v2"
  9. "github.com/imgproxy/imgproxy/v3/httpheaders"
  10. )
  11. const (
  12. // defaultServerName is the default name of the server
  13. defaultServerName = "imgproxy"
  14. )
  15. var (
  16. // requestIDRe is a regular expression for validating request IDs
  17. requestIDRe = regexp.MustCompile(`^[A-Za-z0-9_\-]+$`)
  18. )
  19. // RouteHandler is a function that handles HTTP requests.
  20. type RouteHandler func(string, http.ResponseWriter, *http.Request) error
  21. // Middleware is a function that wraps a RouteHandler with additional functionality.
  22. type Middleware func(next RouteHandler) RouteHandler
  23. // route represents a single route in the router.
  24. type route struct {
  25. method string // method is the HTTP method for a route
  26. path string // path represents a route path
  27. exact bool // exact means that path must match exactly, otherwise any prefixed matches
  28. handler RouteHandler // handler is the function that handles the route
  29. silent bool // Silent route (no logs)
  30. }
  31. // Router is responsible for routing HTTP requests
  32. type Router struct {
  33. // config represents the server configuration
  34. config *Config
  35. // routes is the collection of all routes
  36. routes []*route
  37. }
  38. // NewRouter creates a new Router instance
  39. func NewRouter(config *Config) *Router {
  40. return &Router{config: config}
  41. }
  42. // add adds an abitary route to the router
  43. func (r *Router) add(method, prefix string, exact bool, handler RouteHandler, middlewares ...Middleware) *route {
  44. for _, m := range middlewares {
  45. handler = m(handler)
  46. }
  47. route := &route{method: method, path: r.config.PathPrefix + prefix, handler: handler, exact: exact}
  48. r.routes = append(
  49. r.routes,
  50. route,
  51. )
  52. return route
  53. }
  54. // GET adds GET route
  55. func (r *Router) GET(prefix string, exact bool, handler RouteHandler, middlewares ...Middleware) *route {
  56. return r.add(http.MethodGet, prefix, exact, handler, middlewares...)
  57. }
  58. // OPTIONS adds OPTIONS route
  59. func (r *Router) OPTIONS(prefix string, exact bool, handler RouteHandler, middlewares ...Middleware) *route {
  60. return r.add(http.MethodOptions, prefix, exact, handler, middlewares...)
  61. }
  62. // HEAD adds HEAD route
  63. func (r *Router) HEAD(prefix string, exact bool, handler RouteHandler, middlewares ...Middleware) *route {
  64. return r.add(http.MethodHead, prefix, exact, handler, middlewares...)
  65. }
  66. // ServeHTTP serves routes
  67. func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
  68. // Attach timer to the context
  69. req, timeoutCancel := startRequestTimer(req)
  70. defer timeoutCancel()
  71. // Create the response writer which times out on write
  72. rw = newTimeoutResponse(rw, r.config.WriteResponseTimeout)
  73. // Get/create request ID
  74. reqID := r.getRequestID(req)
  75. // Replace request IP from headers
  76. r.replaceRemoteAddr(req)
  77. rw.Header().Set(httpheaders.Server, defaultServerName)
  78. rw.Header().Set(httpheaders.XRequestID, reqID)
  79. for _, rr := range r.routes {
  80. if rr.isMatch(req) {
  81. if !rr.silent {
  82. LogRequest(reqID, req)
  83. }
  84. rr.handler(reqID, rw, req)
  85. return
  86. }
  87. }
  88. // Means that we have not found matching route
  89. LogRequest(reqID, req)
  90. LogResponse(reqID, req, http.StatusNotFound, newRouteNotDefinedError(req.URL.Path))
  91. r.NotFoundHandler(reqID, rw, req)
  92. }
  93. // NotFoundHandler is default 404 handler
  94. func (r *Router) NotFoundHandler(reqID string, rw http.ResponseWriter, req *http.Request) error {
  95. rw.Header().Set(httpheaders.ContentType, "text/plain")
  96. rw.WriteHeader(http.StatusNotFound)
  97. rw.Write([]byte{' '}) // Write a single byte to make AWS Lambda happy
  98. return nil
  99. }
  100. // OkHandler is a default 200 OK handler
  101. func (r *Router) OkHandler(reqID string, rw http.ResponseWriter, req *http.Request) error {
  102. rw.Header().Set(httpheaders.ContentType, "text/plain")
  103. rw.WriteHeader(http.StatusOK)
  104. rw.Write([]byte{' '}) // Write a single byte to make AWS Lambda happy
  105. return nil
  106. }
  107. // getRequestID tries to read request id from headers or from lambda
  108. // context or generates a new one if nothing found.
  109. func (r *Router) getRequestID(req *http.Request) string {
  110. // Get request ID from headers (if any)
  111. reqID := req.Header.Get(httpheaders.XRequestID)
  112. if len(reqID) == 0 || !requestIDRe.MatchString(reqID) {
  113. lambdaContextVal := req.Header.Get(httpheaders.XAmznRequestContextHeader)
  114. if len(lambdaContextVal) > 0 {
  115. var lambdaContext struct {
  116. RequestID string `json:"requestId"`
  117. }
  118. err := json.Unmarshal([]byte(lambdaContextVal), &lambdaContext)
  119. if err == nil && len(lambdaContext.RequestID) > 0 {
  120. reqID = lambdaContext.RequestID
  121. }
  122. }
  123. }
  124. if len(reqID) == 0 || !requestIDRe.MatchString(reqID) {
  125. reqID, _ = nanoid.New()
  126. }
  127. return reqID
  128. }
  129. // replaceRemoteAddr rewrites the req.RemoteAddr property from request headers
  130. func (r *Router) replaceRemoteAddr(req *http.Request) {
  131. cfConnectingIP := req.Header.Get(httpheaders.CFConnectingIP)
  132. xForwardedFor := req.Header.Get(httpheaders.XForwardedFor)
  133. xRealIP := req.Header.Get(httpheaders.XRealIP)
  134. switch {
  135. case len(cfConnectingIP) > 0:
  136. replaceRemoteAddr(req, cfConnectingIP)
  137. case len(xForwardedFor) > 0:
  138. if index := strings.Index(xForwardedFor, ","); index > 0 {
  139. xForwardedFor = xForwardedFor[:index]
  140. }
  141. replaceRemoteAddr(req, xForwardedFor)
  142. case len(xRealIP) > 0:
  143. replaceRemoteAddr(req, xRealIP)
  144. }
  145. }
  146. // replaceRemoteAddr sets the req.RemoteAddr for request
  147. func replaceRemoteAddr(req *http.Request, ip string) {
  148. _, port, err := net.SplitHostPort(req.RemoteAddr)
  149. if err != nil {
  150. port = "80"
  151. }
  152. req.RemoteAddr = net.JoinHostPort(strings.TrimSpace(ip), port)
  153. }
  154. // isMatch checks that a request matches route
  155. func (r *route) isMatch(req *http.Request) bool {
  156. methodMatches := r.method == req.Method
  157. notExactPathMathes := !r.exact && strings.HasPrefix(req.URL.Path, r.path)
  158. exactPathMatches := r.exact && req.URL.Path == r.path
  159. return methodMatches && (notExactPathMathes || exactPathMatches)
  160. }
  161. // Silent sets Silent flag which supresses logs to true. We do not need to log
  162. // requests like /health of /favicon.ico
  163. func (r *route) Silent() *route {
  164. r.silent = true
  165. return r
  166. }