package servlet import ( "encoding/json" "errors" "fmt" "geniuscartel.xyz/vinegar/vinegarUtil" "log" "net/http" "regexp" "strconv" ) const ( defaultLruSize = int64(1024 * 1024 * 50) ContentTypeHeaderKey = "Content-Type" ContentEncodingHeaderKey = "Content-Encoding" AcceptEncodingHeaderKey = "Accept-Encoding" ) type ErrorResponse struct { Code int Message string } type ( // VinegarHttpServlet is the main server struct that handles HTTP requests and routing. // It contains the TCP port to listen on, the routes to match requests against, // and a map of status code to error handling routes. VinegarHttpServlet struct { Port string Routes []*VinegarWebRoute ErrorRoutes map[int]*TemplateRoute } // VinegarWebRoute defines a single route in the router. // It contains a regex Pattern to match against the URL path, // a Handler function to call when the route matches, // and an optional Cache to enable caching for the route. VinegarWebRoute struct { Pattern *regexp.Regexp Handler VinegarHandlerFunction Cache vinegarUtil.Cache } VinegarHandlerFunction func(w http.ResponseWriter, req *http.Request) ) func NewServlet(port string) *VinegarHttpServlet { errs := make(map[int]*TemplateRoute) srv := VinegarHttpServlet{Port: port, ErrorRoutes: errs} return &srv } func NewServletRoute(routePattern string, handleFunc VinegarHandlerFunction) *VinegarWebRoute { pattern := regexp.MustCompile(routePattern) route := VinegarWebRoute{Pattern: pattern, Handler: handleFunc, Cache: vinegarUtil.NewLRU(defaultLruSize)} return &route } func (s *VinegarHttpServlet) AddRoute(route *VinegarWebRoute) { route.Announce() s.Routes = append(s.Routes, route) } func (s *VinegarHttpServlet) AddErrorRoute(code int, route *TemplateRoute) { route.Announce() s.ErrorRoutes[code] = route } func (s *VinegarHttpServlet) ServeHTTP(w http.ResponseWriter, req *http.Request) { path := req.URL.Path for _, route := range s.Routes { if route.Pattern.MatchString(path) { //fmt.Printf("SERVING: [%s]=>{%s}\n", path, route.Pattern.String()) go route.Handler(w, req) return } } s.SendError(w, req, 404, "Couldn't find your content.", errors.New("failed to match route for ["+path+"]")) } func (s *VinegarHttpServlet) Start() error { if len(s.Routes) < 1 { log.Fatal("No routes found for server. Nothing to listen and serve.") } log.Printf("Listening on [%s]\n", s.Port) err := http.ListenAndServe(s.Port, s) if err != nil { return err } return nil } func (r *VinegarWebRoute) Announce() { log.Printf("Added route for [%s]\n", r.Pattern.String()) } func (s *VinegarHttpServlet) SendError(w http.ResponseWriter, req *http.Request, code int, msg string, aErr error) { fmt.Printf("[%d][%s]. Rendering template for code %d with message: %s\n", code, req.URL.Path, code, msg) out, _ := json.Marshal(aErr) fmt.Println(string(out)) tmpl, exists := s.ErrorRoutes[code] if exists { err := tmpl.TemplateManager.AddMixin("code", strconv.Itoa(code)) if err != nil { writeGeneric(w, code, msg) return } err = tmpl.TemplateManager.AddMixin("msg", msg) if err != nil { writeGeneric(w, code, msg) return } w.WriteHeader(code) bitties, err := tmpl.TemplateManager.RenderTemplate(fmt.Sprintf("%d.html", code)) _, err = w.Write([]byte(bitties)) if err != nil { writeGeneric(w, code, msg) return } return } writeGeneric(w, code, msg) return } func writeGeneric(w http.ResponseWriter, code int, msg string) { w.WriteHeader(code) errorPayload := ErrorResponse{Code: code, Message: msg} genericError, _ := json.Marshal(errorPayload) w.Write(genericError) }