2019-06-03 17:16:49 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
nanoid "github.com/matoous/go-nanoid"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
xRequestIDHeader = "X-Request-ID"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
requestIDRe = regexp.MustCompile(`^[A-Za-z0-9_\-]+$`)
|
|
|
|
)
|
|
|
|
|
|
|
|
type routeHandler func(string, http.ResponseWriter, *http.Request)
|
|
|
|
type panicHandler func(string, http.ResponseWriter, *http.Request, error)
|
|
|
|
|
|
|
|
type route struct {
|
|
|
|
Method string
|
|
|
|
Prefix string
|
|
|
|
Handler routeHandler
|
2019-08-20 15:26:17 +02:00
|
|
|
Exact bool
|
2019-06-03 17:16:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type router struct {
|
|
|
|
Routes []*route
|
|
|
|
PanicHandler panicHandler
|
|
|
|
}
|
|
|
|
|
2019-08-20 15:26:17 +02:00
|
|
|
func (r *route) IsMatch(req *http.Request) bool {
|
|
|
|
if r.Method != req.Method {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Exact {
|
|
|
|
return req.URL.Path == r.Prefix
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.HasPrefix(req.URL.Path, r.Prefix)
|
|
|
|
}
|
|
|
|
|
2019-06-03 17:16:49 +02:00
|
|
|
func newRouter() *router {
|
|
|
|
return &router{
|
|
|
|
Routes: make([]*route, 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-20 15:26:17 +02:00
|
|
|
func (r *router) Add(method, prefix string, handler routeHandler, exact bool) {
|
2019-06-03 17:16:49 +02:00
|
|
|
r.Routes = append(
|
|
|
|
r.Routes,
|
2019-08-20 15:26:17 +02:00
|
|
|
&route{Method: method, Prefix: prefix, Handler: handler, Exact: exact},
|
2019-06-03 17:16:49 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-08-20 15:26:17 +02:00
|
|
|
func (r *router) GET(prefix string, handler routeHandler, exact bool) {
|
|
|
|
r.Add(http.MethodGet, prefix, handler, exact)
|
2019-06-03 17:16:49 +02:00
|
|
|
}
|
|
|
|
|
2019-08-20 15:26:17 +02:00
|
|
|
func (r *router) OPTIONS(prefix string, handler routeHandler, exact bool) {
|
|
|
|
r.Add(http.MethodOptions, prefix, handler, exact)
|
2019-06-03 17:16:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
2019-09-16 11:53:45 +02:00
|
|
|
req = req.WithContext(setTimerSince(req.Context()))
|
|
|
|
|
2019-06-03 17:16:49 +02:00
|
|
|
reqID := req.Header.Get(xRequestIDHeader)
|
|
|
|
|
|
|
|
if len(reqID) == 0 || !requestIDRe.MatchString(reqID) {
|
|
|
|
reqID, _ = nanoid.Nanoid()
|
|
|
|
}
|
|
|
|
|
|
|
|
rw.Header().Set("Server", "imgproxy")
|
|
|
|
rw.Header().Set(xRequestIDHeader, reqID)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if rerr := recover(); rerr != nil {
|
|
|
|
if err, ok := rerr.(error); ok && r.PanicHandler != nil {
|
|
|
|
r.PanicHandler(reqID, rw, req, err)
|
|
|
|
} else {
|
|
|
|
panic(rerr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
logRequest(reqID, req)
|
|
|
|
|
|
|
|
for _, rr := range r.Routes {
|
2019-08-20 15:26:17 +02:00
|
|
|
if rr.IsMatch(req) {
|
2019-06-03 17:16:49 +02:00
|
|
|
rr.Handler(reqID, rw, req)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logWarning("Route for %s is not defined", req.URL.Path)
|
|
|
|
|
|
|
|
rw.WriteHeader(404)
|
|
|
|
}
|