Initial commit
This commit is contained in:
192
pkg/router/router.go
Normal file
192
pkg/router/router.go
Normal file
@@ -0,0 +1,192 @@
|
||||
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
|
||||
//}
|
||||
Reference in New Issue
Block a user