Core functionality for new aiterp.net servers
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.

123 lines
3.5 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)
if user != nil {
user.Session = sess
http.SetCookie(w, &http.Cookie{Name: auth.SessionCookieName, Value: sess.ID, Expires: sess.Time.Add(auth.SessionMaxTime), Path: "/", HttpOnly: true})
}
}
}
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()
}