201 lines
4.9 KiB
Go
201 lines
4.9 KiB
Go
package servlet
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"geniuscartel.xyz/vinegar/vinegarUtil"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
defaultLruSize = int64(1024 * 1024 * 50)
|
|
ContentTypeHeaderKey = "Content-Type"
|
|
ContentEncodingHeaderKey = "Content-Encoding"
|
|
AcceptEncodingHeaderKey = "Accept-Encoding"
|
|
)
|
|
|
|
type ErrorResponse struct {
|
|
Code int
|
|
Message string
|
|
}
|
|
|
|
type (
|
|
|
|
// VinegarWebServlet is the main server struct that handles HTTP requests and routing.
|
|
// It contains the following key fields:
|
|
//
|
|
//Port - The TCP port to listen on for incoming requests. Should be a string like ":8080".
|
|
//
|
|
//Router - A VinegarWebRouter instance that handles routing requests to handlers.
|
|
//
|
|
//ErrorRoutes - A map of status code to TemplateRoute for custom error pages.
|
|
//
|
|
//interrupts - A channel used to signal shutdown/interrupts to the server.
|
|
//
|
|
//errors - A channel that the server writes errors to.
|
|
//
|
|
//The main methods are:
|
|
//
|
|
//ServeHTTP - Handles all incoming requests. Calls Router.RouteRequest to handle routing.
|
|
//
|
|
//AddErrorRoute - Adds a custom error route for the given status code.
|
|
//
|
|
//Start - Starts the HTTP server listening on Port. Returns any errors.
|
|
//
|
|
//Shutdown - Shuts down the server by sending a signal on interrupts.
|
|
//
|
|
//SendError - Helper to send an error response with custom status code and error template.
|
|
//
|
|
//Typical usage:
|
|
//
|
|
// srv := NewServlet(":8080")
|
|
// srv.AddErrorRoute(404, notFoundRoute)
|
|
// srv.Router.AddRoute(someRoute)
|
|
// srv.Start()
|
|
// // ...
|
|
// srv.Shutdown()
|
|
VinegarWebServlet struct {
|
|
Port string
|
|
Router VinegarWebRouter
|
|
|
|
// ErrorRoutes is a map that stores TemplateRoute pointers
|
|
// for different HTTP status code error pages.
|
|
//
|
|
// It gets populated when custom error routes are defined via AddErrorRoute.
|
|
// For example:
|
|
//
|
|
// err404 := NewTemplateRoute(/*...*/)
|
|
// srv.AddErrorRoute(404, err404)
|
|
//
|
|
// This will add err404 to ErrorRoutes mapped to 404.
|
|
//
|
|
// Routes can then be rendered like:
|
|
//
|
|
// tmpl, exists := s.ErrorRoutes[404]
|
|
// if exists {
|
|
// // render tmpl
|
|
// }
|
|
//
|
|
// So ErrorRoutes provides a lookup from status code
|
|
// to the associated custom error route.
|
|
//
|
|
// It allows easily rendering custom error pages
|
|
// for different status codes.
|
|
ErrorRoutes map[int]*TemplateRoute
|
|
interrupts chan struct{}
|
|
errors chan error
|
|
}
|
|
|
|
VinegarHandlerFunction func(w http.ResponseWriter, req *http.Request)
|
|
)
|
|
|
|
func NewServlet(port string) *VinegarWebServlet {
|
|
errs := make(map[int]*TemplateRoute)
|
|
srv := VinegarWebServlet{Port: port, ErrorRoutes: errs, interrupts: make(chan struct{}, 500)}
|
|
|
|
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 *VinegarWebServlet) AddErrorRoute(code int, route *TemplateRoute) {
|
|
route.Announce()
|
|
s.ErrorRoutes[code] = route
|
|
}
|
|
|
|
func (s *VinegarWebServlet) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
err := s.Router.RouteRequest(w, req)
|
|
if err != nil {
|
|
http.NotFound(w, req)
|
|
}
|
|
}
|
|
|
|
func (s *VinegarWebServlet) Start() error {
|
|
if len(s.Router.Routes) < 1 {
|
|
log.Fatal("No routes found for server. Nothing to listen and serve.")
|
|
}
|
|
|
|
l, listenErr := net.Listen("tcp", s.Port)
|
|
|
|
if listenErr != nil {
|
|
return listenErr
|
|
}
|
|
go func() {
|
|
err := http.Serve(l, s)
|
|
if err != nil {
|
|
s.errors <- err
|
|
}
|
|
}()
|
|
log.Printf("Listening on [%s]\n", s.Port)
|
|
running := true
|
|
for running {
|
|
select {
|
|
case err := <-s.errors:
|
|
log.Printf("server on port %s failed: %v", s.Port, err)
|
|
|
|
case <-s.interrupts:
|
|
err := l.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
running = false
|
|
default:
|
|
time.Sleep(5)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *VinegarWebServlet) Shutdown() {
|
|
s.interrupts <- struct{}{}
|
|
}
|
|
|
|
func (r *VinegarWebRoute) Announce() {
|
|
log.Printf("Added route for [%s]\n", r.Pattern.String())
|
|
}
|
|
|
|
func (s *VinegarWebServlet) SendError(w http.ResponseWriter, req *http.Request, code int, msg string, aErr error) {
|
|
log.Printf("[%d][%s]. Rendering template for code %d with message: %s\n", code, req.URL.Path, code, msg)
|
|
out, _ := json.Marshal(aErr)
|
|
|
|
log.Println(string(out))
|
|
tmpl, exists := s.ErrorRoutes[code]
|
|
if exists {
|
|
err := tmpl.TemplateManager.AddMixin("code", strconv.Itoa(code))
|
|
if err != nil {
|
|
http.Error(w, msg, code)
|
|
return
|
|
}
|
|
err = tmpl.TemplateManager.AddMixin("msg", msg)
|
|
if err != nil {
|
|
http.Error(w, msg, code)
|
|
return
|
|
}
|
|
w.WriteHeader(code)
|
|
bitties, err := tmpl.TemplateManager.RenderTemplate(fmt.Sprintf("%d.html", code))
|
|
if err != nil {
|
|
http.Error(w, msg, code)
|
|
return
|
|
}
|
|
|
|
_, err = w.Write([]byte(bitties))
|
|
if err != nil {
|
|
http.Error(w, msg, code)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
http.Error(w, msg, code)
|
|
return
|
|
}
|