193 lines
4.3 KiB
Go
193 lines
4.3 KiB
Go
package router
|
|
|
|
import (
|
|
"addrss/pkg/auth"
|
|
"addrss/pkg/config"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
var routeMap = map[string][]Route{}
|
|
|
|
type router struct{}
|
|
|
|
func Serve(addr string) error {
|
|
if len(routeMap) == 0 {
|
|
return errors.New("no registered routes")
|
|
}
|
|
|
|
return http.ListenAndServe(addr, router{})
|
|
}
|
|
|
|
func (r router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
route, ok := getRoute(req)
|
|
if !ok {
|
|
// See if the request is for a file that exists in the content directory
|
|
file := filepath.Join("content", req.URL.Path)
|
|
if info, err := os.Stat(file); err == nil && !info.IsDir() {
|
|
http.ServeFile(w, req, file)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
ctx := Context{
|
|
Request: Request{req, map[string]any{}},
|
|
Response: Response{w},
|
|
Claims: auth.AccessClaims{},
|
|
route: route,
|
|
}
|
|
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
err := fmt.Errorf("recovered from panic: %v", rec)
|
|
ctx.Response.InternalServerError(err)
|
|
}
|
|
}()
|
|
|
|
originHeader := req.Header.Get("Origin")
|
|
if originHeader != "" {
|
|
o, err := config.Get("cors.origins")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
origins := o.([]any)
|
|
for _, origin := range origins {
|
|
if origin.(string) == originHeader {
|
|
w.Header().Add("Access-Control-Allow-Origin", origin.(string))
|
|
}
|
|
}
|
|
}
|
|
|
|
if req.Method == http.MethodOptions {
|
|
acrh := req.Header.Get("Access-Control-Request-Headers")
|
|
acrm := req.Header.Get("Access-Control-Request-Method")
|
|
if route.method != acrm {
|
|
w.WriteHeader(http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
// Congrats, you get whatever headers you want
|
|
w.Header().Add("Access-Control-Allow-Headers", acrh)
|
|
w.Header().Add("Access-Control-Allow-Methods", acrm)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
return
|
|
}
|
|
|
|
if !route.anonymous {
|
|
authSegments := strings.Split(req.Header.Get("Authorization"), " ")
|
|
|
|
if len(authSegments) != 2 || authSegments[0] != "Bearer" {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
if err := auth.ValidateJwtToken(authSegments[1], &ctx.Claims); err != nil {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
}
|
|
|
|
if route.scope != "" {
|
|
if !strings.Contains(ctx.Claims.Scopes, route.scope) {
|
|
w.WriteHeader(http.StatusForbidden)
|
|
return
|
|
}
|
|
}
|
|
|
|
route.handler(&ctx)
|
|
}
|
|
|
|
func AddGet(pattern string, handler func(*Context)) Route {
|
|
return addRoute(http.MethodGet, pattern, handler)
|
|
}
|
|
|
|
func AddPost(pattern string, handler func(*Context)) Route {
|
|
return addRoute(http.MethodPost, pattern, handler)
|
|
}
|
|
|
|
func AddPut(pattern string, handler func(*Context)) Route {
|
|
return addRoute(http.MethodPut, pattern, handler)
|
|
}
|
|
|
|
func AddPatch(pattern string, handler func(*Context)) Route {
|
|
return addRoute(http.MethodPatch, pattern, handler)
|
|
}
|
|
|
|
func AddDelete(pattern string, handler func(*Context)) Route {
|
|
return addRoute(http.MethodDelete, pattern, handler)
|
|
}
|
|
|
|
func addRoute(method string, path string, handler func(*Context)) Route {
|
|
route := Route{
|
|
handler: handler,
|
|
method: method,
|
|
path: path,
|
|
}
|
|
|
|
if routeMap[method] == nil {
|
|
routeMap[method] = []Route{}
|
|
}
|
|
|
|
routeMap[method] = append(routeMap[method], route)
|
|
|
|
return route
|
|
}
|
|
|
|
func getRoute(req *http.Request) (Route, bool) {
|
|
method := req.Method
|
|
if method == http.MethodOptions {
|
|
method = req.Header.Get("Access-Control-Request-Method")
|
|
}
|
|
|
|
req.URL.Path = strings.TrimSuffix(req.URL.Path, "/")
|
|
|
|
for _, route := range routeMap[method] {
|
|
//if strings.Contains(route.path, ":") {
|
|
// rte, ok := regexMatchRoute(route, req.URL.Path)
|
|
// if ok {
|
|
// return rte, true
|
|
// }
|
|
//} else {
|
|
if route.path == req.URL.Path {
|
|
return route, true
|
|
}
|
|
//}
|
|
}
|
|
|
|
return Route{}, false
|
|
}
|
|
|
|
//func regexMatchRoute(route Route, path string) (Route, bool) {
|
|
// regex := regexp.MustCompile(`:[a-zA-Z]+`)
|
|
// matchPath := regex.ReplaceAllString(route.path, `([A-Za-z0-9\-_.]+)`)
|
|
// matchPath += "$"
|
|
// regex = regexp.MustCompile(matchPath)
|
|
//
|
|
// // matchPath := route.path
|
|
// if matches := regex.FindStringSubmatch(path); len(matches) > 0 {
|
|
// if len(matches) > 1 {
|
|
// route.params = map[string]any{}
|
|
//
|
|
// values := strings.Split(path, "/")
|
|
// for i, param := range strings.Split(route.path, "/") {
|
|
// if strings.Contains(param, ":") {
|
|
// route.params[param[1:]] = values[i]
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// return route, true
|
|
// }
|
|
//
|
|
// return route, false
|
|
//}
|