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() }