Iris Lightshard
756a0739fd
router with static files and dynamic handlers, renderers for templates, json, and xml, and beginnings of auth and cookie management
173 lines
4.3 KiB
Go
173 lines
4.3 KiB
Go
package router
|
|
|
|
import (
|
|
"net/http"
|
|
"html/template"
|
|
"regexp"
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
"path"
|
|
"os"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
type Router struct {
|
|
/* This is the template for error pages */
|
|
Fallback template.Template
|
|
/* Routes are only filled by using the appropriate methods. */
|
|
routes []Route
|
|
/* StaticPaths can be filled from outside when constructing the Router.
|
|
* key = uri
|
|
* value = file path
|
|
*/
|
|
StaticPaths map[string]string
|
|
}
|
|
|
|
|
|
type Route struct {
|
|
path *regexp.Regexp
|
|
handlerMap map[string]http.Handler
|
|
}
|
|
|
|
/* This represents what the server should do with a given request. */
|
|
type ServerTask struct {
|
|
/* template and apiFmt are mutually exclusive. */
|
|
template *template.Template
|
|
apiFmt string
|
|
|
|
/* doWork represents serverside work to fulfill the request.
|
|
* This function can be composed any way you see fit when creating
|
|
* a route.
|
|
*/
|
|
doWork func(http.ResponseWriter, *http.Request)
|
|
}
|
|
|
|
func (self *Router) Get(path string, h http.Handler) {
|
|
self.AddRoute("GET", path, h)
|
|
}
|
|
|
|
func (self *Router) Post(path string, h http.Handler) {
|
|
self.AddRoute("POST", path, h)
|
|
}
|
|
|
|
func (self *Router) Put(path string, h http.Handler) {
|
|
self.AddRoute("PUT", path, h)
|
|
}
|
|
|
|
func (self *Router) Delete(path string, h http.Handler) {
|
|
self.AddRoute("DELETE", path, h)
|
|
}
|
|
|
|
func (self *Router) AddRoute(method string, path string, h http.Handler) {
|
|
|
|
exactPath := regexp.MustCompile("^" + path + "$")
|
|
|
|
/* If the route already exists, try to add this method to the ServerTask map. */
|
|
for _, r := range self.routes {
|
|
if r.path == exactPath {
|
|
r.handlerMap[method] = h
|
|
return
|
|
}
|
|
}
|
|
|
|
/* Otherwise add a new route */
|
|
self.routes = append(self.routes, Route{
|
|
path: exactPath,
|
|
handlerMap: map[string]http.Handler{method: h},
|
|
})
|
|
|
|
}
|
|
|
|
func (self *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
/* Show the 500 error page if we panic */
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Println("ERROR:", r)
|
|
self.ErrorPage(w, req, 500, "There was an error on the server.")
|
|
}
|
|
}()
|
|
|
|
/* If the request matches any our StaticPaths, try to serve a file. */
|
|
for uri, dir := range self.StaticPaths {
|
|
if req.Method == "GET" && strings.HasPrefix(req.URL.Path, uri) {
|
|
restOfUri := strings.TrimPrefix(req.URL.Path, uri)
|
|
p := path.Join(dir, restOfUri)
|
|
p = path.Clean(p)
|
|
|
|
/* If the file exists, try to serve it. */
|
|
info, err := os.Stat(p);
|
|
if err == nil && !info.IsDir() {
|
|
http.ServeFile(w, req, p)
|
|
/* Handle the common errors */
|
|
} else if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrExist) {
|
|
self.ErrorPage(w, req, 404, "The requested file does not exist")
|
|
} else if errors.Is(err, os.ErrPermission) || info.IsDir() {
|
|
self.ErrorPage(w, req, 403, "Access forbidden")
|
|
/* If it's some weird error, serve a 500. */
|
|
} else {
|
|
self.ErrorPage(w, req, 500, "Internal server error")
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
/* Otherwise, this is a normal route */
|
|
for _, r := range self.routes {
|
|
|
|
/* Pull the params out of the regex;
|
|
* If the path doesn't match the regex, params will be nil.
|
|
*/
|
|
params := r.Match(req)
|
|
if params == nil {
|
|
continue
|
|
}
|
|
for method, handler := range r.handlerMap {
|
|
if method == req.Method {
|
|
/* Parse the form and add the params to it */
|
|
req.ParseForm()
|
|
ProcessParams(req, params)
|
|
/* handle the request! */
|
|
handler.ServeHTTP(w, req);
|
|
return
|
|
}
|
|
}
|
|
}
|
|
self.ErrorPage(w, req, 404, "The page you requested does not exist!")
|
|
}
|
|
|
|
/*******************
|
|
* Utility Methods *
|
|
*******************/
|
|
|
|
func ProcessParams(req *http.Request, params map[string]string) {
|
|
for key, value := range params {
|
|
req.Form.Add(key, value)
|
|
}
|
|
}
|
|
|
|
func (self *Route) Match(r *http.Request) map[string]string {
|
|
match := self.path.FindStringSubmatch(r.URL.Path)
|
|
if match == nil {
|
|
return nil
|
|
}
|
|
|
|
params := map[string]string{}
|
|
groupNames := self.path.SubexpNames()
|
|
|
|
for i, group := range match {
|
|
params[groupNames[i]] = group
|
|
}
|
|
|
|
return params
|
|
}
|
|
|
|
func (self *Router) ErrorPage(w http.ResponseWriter, req *http.Request, code int, errMsg string) {
|
|
w.WriteHeader(code)
|
|
req.ParseForm()
|
|
req.Form.Add("ErrorCode", strconv.Itoa(code))
|
|
req.Form.Add("ErrorMessage", errMsg)
|
|
self.Fallback.Execute(w, req)
|
|
}
|