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.
118 lines
3.3 KiB
118 lines
3.3 KiB
package wrouter
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.aiterp.net/gisle/wrouter/auth"
|
|
)
|
|
|
|
// Route is an interface for a request handler.
|
|
type Route interface {
|
|
Handle(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool
|
|
}
|
|
|
|
// Router is the main structure of the wrouter package. It routes requests to the appropriate
|
|
// Route
|
|
type Router struct {
|
|
paths map[Route]string
|
|
routes []Route
|
|
}
|
|
|
|
// Mount a router to the router, prefixing all the paths of it
|
|
func (router *Router) Mount(path string, subRouter *Router) {
|
|
for _, route := range subRouter.routes {
|
|
router.Route(strings.Replace(path+subRouter.paths[route], "//", "/", 1), route)
|
|
}
|
|
}
|
|
|
|
// Route mounts a route interface to a path
|
|
func (router *Router) Route(path string, route Route) {
|
|
if router.paths == nil {
|
|
router.paths = make(map[Route]string, 16)
|
|
}
|
|
|
|
router.paths[route] = path
|
|
router.routes = append(router.routes, route)
|
|
}
|
|
|
|
// ServeHTTP serves a HTTP request using the router's routes
|
|
func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
req.ParseForm()
|
|
defer req.Body.Close()
|
|
|
|
// Allow REST for clients of yore
|
|
if req.Header.Get("X-Method") != "" {
|
|
req.Method = strings.ToUpper(req.Header.Get("X-Method"))
|
|
}
|
|
|
|
// Resolve session cookies
|
|
var user *auth.User
|
|
var sess *auth.Session
|
|
cookie, err := req.Cookie(auth.SessionCookieName)
|
|
|
|
if cookie != nil && err == nil {
|
|
sess = auth.FindSession(cookie.Value)
|
|
if sess != nil {
|
|
user, _ = auth.FindUser(sess.UserID)
|
|
}
|
|
}
|
|
|
|
for index, route := range router.routes {
|
|
path := router.paths[route]
|
|
|
|
if strings.HasPrefix(strings.ToLower(req.URL.Path), path) {
|
|
// Just so the handler can replace the path properly in case of case
|
|
// insensitive clients getting fancy on it.
|
|
path = req.URL.Path[:len(path)]
|
|
|
|
// Attach a little something for testing
|
|
w.Header().Set("X-Route-Path", path)
|
|
w.Header().Set("X-Route-Index", fmt.Sprint(index))
|
|
|
|
if route.Handle(path, w, req, user) {
|
|
if user != nil && user.LoggedOut() {
|
|
auth.CloseSession(sess.ID)
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
w.WriteHeader(404)
|
|
w.Write([]byte("Not Found: " + req.URL.Path))
|
|
}
|
|
|
|
// Resource is a shorthand for creating and adding a new REST resource to the router
|
|
func (router *Router) Resource(mount string, list, create ResourceFunc, get, update, delete ResourceIDFunc) {
|
|
router.Route(mount, NewResource(list, create, get, update, delete))
|
|
}
|
|
|
|
// Static is a shorthand for creating a static file server on the following path
|
|
func (router *Router) Static(mount string, filePath string) {
|
|
router.Route(mount, NewStatic(filePath))
|
|
}
|
|
|
|
// Function creates a simple bare-bones handler that just runs a function, prevents
|
|
// the need for dummy interfaces
|
|
func (router *Router) Function(mount string, function FunctionHandlerFunc) {
|
|
router.Route(mount, &functionHandler{function})
|
|
}
|
|
|
|
// Listen creates a http.Server with some sane defaults and pointing to this structure
|
|
// for request handling.
|
|
func (router *Router) Listen(host string, port int) (*http.Server, error) {
|
|
srv := &http.Server{
|
|
Addr: fmt.Sprintf("%s:%d", host, port),
|
|
ReadTimeout: 5 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
Handler: router,
|
|
}
|
|
|
|
return srv, srv.ListenAndServe()
|
|
}
|