cleanup of codesmells and architecture
This commit is contained in:
parent
7f4b41247e
commit
ca65acbea1
39
servlet/Router.go
Normal file
39
servlet/Router.go
Normal file
@ -0,0 +1,39 @@
|
||||
package servlet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"geniuscartel.xyz/vinegar/vinegarUtil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type (
|
||||
VinegarWebRouter struct {
|
||||
Routes []*VinegarWebRoute
|
||||
}
|
||||
|
||||
VinegarWebRoute struct {
|
||||
Pattern *regexp.Regexp
|
||||
Handler VinegarHandlerFunction
|
||||
Cache vinegarUtil.Cache
|
||||
}
|
||||
)
|
||||
|
||||
func (s *VinegarWebRouter) AddRoute(route *VinegarWebRoute) {
|
||||
route.Announce()
|
||||
s.Routes = append(s.Routes, route)
|
||||
}
|
||||
|
||||
func (r *VinegarWebRouter) RouteRequest(w http.ResponseWriter, req *http.Request) error {
|
||||
path := req.URL.Path
|
||||
for _, route := range r.Routes {
|
||||
if route.Pattern.MatchString(path) {
|
||||
//fmt.Printf("SERVING: [%s]=>{%s}\n", path, route.Pattern.String())
|
||||
go route.Handler(w, req)
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("failed to match route for [" + path + "]")
|
||||
}
|
||||
@ -96,7 +96,7 @@ func CreateBlankConfig() *Config {
|
||||
return &conf
|
||||
}
|
||||
|
||||
func LoadConfig(pathlike string) (*VinegarHttpServlet, error) {
|
||||
func LoadConfig(pathlike string) (*VinegarWebServlet, error) {
|
||||
contents, err := vinegarUtil.GetDiskContent(pathlike)
|
||||
if err != nil {
|
||||
CreateBlankConfig()
|
||||
@ -123,7 +123,7 @@ func LoadConfig(pathlike string) (*VinegarHttpServlet, error) {
|
||||
|
||||
}
|
||||
|
||||
func (e ConfigEntry) toRoute(serv *VinegarHttpServlet) error {
|
||||
func (e ConfigEntry) toRoute(serv *VinegarWebServlet) error {
|
||||
constructor, err := getConstructorFunction(e.ConfigType)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -9,39 +9,30 @@ import (
|
||||
type (
|
||||
ApiRoute struct {
|
||||
VinegarRoute *VinegarWebRoute
|
||||
HttpMethodRoutes *map[HttpMethod]VinegarHandlerFunction
|
||||
HttpMethodRoutes *map[string]VinegarHandlerFunction
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
HttpMethod int
|
||||
)
|
||||
|
||||
const (
|
||||
GET HttpMethod = iota
|
||||
POST
|
||||
PUT
|
||||
PATCH
|
||||
DELETE
|
||||
UNDEFINED
|
||||
)
|
||||
|
||||
func NewApiRoute(serv *VinegarHttpServlet, pattern string) *ApiRoute {
|
||||
functionMap := make(map[HttpMethod]VinegarHandlerFunction)
|
||||
//NewApiRoute this will cause a panic if serv is nil
|
||||
func NewApiRoute(serv *VinegarWebServlet, pattern string) *ApiRoute {
|
||||
functionMap := make(map[string]VinegarHandlerFunction)
|
||||
ancestorRoute := NewServletRoute(pattern, createMethodHandler(&functionMap))
|
||||
route := ApiRoute{
|
||||
ancestorRoute,
|
||||
&functionMap,
|
||||
}
|
||||
|
||||
serv.AddRoute(route.VinegarRoute)
|
||||
if serv != nil { //this will happen during testing
|
||||
serv.Router.AddRoute(route.VinegarRoute)
|
||||
}
|
||||
|
||||
return &route
|
||||
}
|
||||
|
||||
func createMethodHandler(m *map[HttpMethod]VinegarHandlerFunction) VinegarHandlerFunction {
|
||||
func createMethodHandler(m *map[string]VinegarHandlerFunction) VinegarHandlerFunction {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
method := getHttpMethod(req)
|
||||
fn, exists := (*m)[method]
|
||||
|
||||
fn, exists := (*m)[req.Method]
|
||||
if exists {
|
||||
fn(w, req)
|
||||
} else {
|
||||
@ -55,27 +46,12 @@ func createMethodHandler(m *map[HttpMethod]VinegarHandlerFunction) VinegarHandle
|
||||
}
|
||||
}
|
||||
|
||||
func (api *ApiRoute) RegisterHttpMethodHandler(method HttpMethod, handler VinegarHandlerFunction) {
|
||||
func (api *ApiRoute) RegisterHttpMethodHandler(method string, handler VinegarHandlerFunction) {
|
||||
(*api.HttpMethodRoutes)[method] = handler
|
||||
}
|
||||
|
||||
func getHttpMethod(req *http.Request) HttpMethod {
|
||||
switch req.Method {
|
||||
case http.MethodGet:
|
||||
return GET
|
||||
case "POST":
|
||||
return POST
|
||||
case "PUT":
|
||||
return PUT
|
||||
case "PATCH":
|
||||
return PATCH
|
||||
case "DELETE":
|
||||
return DELETE
|
||||
case "UNDEFINED":
|
||||
default:
|
||||
return UNDEFINED
|
||||
}
|
||||
return UNDEFINED
|
||||
func (api *ApiRoute) AddGetHandler(handler VinegarHandlerFunction) {
|
||||
(*api.HttpMethodRoutes)[http.MethodGet] = handler
|
||||
}
|
||||
|
||||
func SendApiError(w http.ResponseWriter, httpCode int, messageCode int, message string) {
|
||||
|
||||
@ -2,13 +2,14 @@ package servlet
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"geniuscartel.xyz/vinegar/vinegarUtil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -24,31 +25,20 @@ type ErrorResponse struct {
|
||||
}
|
||||
|
||||
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 {
|
||||
VinegarWebServlet struct {
|
||||
Port string
|
||||
Routes []*VinegarWebRoute
|
||||
Router VinegarWebRouter
|
||||
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
|
||||
interrupts chan struct{}
|
||||
errors chan error
|
||||
}
|
||||
|
||||
VinegarHandlerFunction func(w http.ResponseWriter, req *http.Request)
|
||||
)
|
||||
|
||||
func NewServlet(port string) *VinegarHttpServlet {
|
||||
func NewServlet(port string) *VinegarWebServlet {
|
||||
errs := make(map[int]*TemplateRoute)
|
||||
srv := VinegarHttpServlet{Port: port, ErrorRoutes: errs}
|
||||
srv := VinegarWebServlet{Port: port, ErrorRoutes: errs, interrupts: make(chan struct{})}
|
||||
|
||||
return &srv
|
||||
}
|
||||
@ -60,48 +50,62 @@ func NewServletRoute(routePattern string, handleFunc VinegarHandlerFunction) *Vi
|
||||
return &route
|
||||
}
|
||||
|
||||
func (s *VinegarHttpServlet) AddRoute(route *VinegarWebRoute) {
|
||||
route.Announce()
|
||||
s.Routes = append(s.Routes, route)
|
||||
}
|
||||
|
||||
func (s *VinegarHttpServlet) AddErrorRoute(code int, route *TemplateRoute) {
|
||||
func (s *VinegarWebServlet) 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
|
||||
func (s *VinegarWebServlet) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
err := s.Router.RouteRequest(w, req)
|
||||
if err != nil {
|
||||
s.SendError(w, req, 404, "Resource not found", err)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
for {
|
||||
|
||||
err := http.ListenAndServe(s.Port, s)
|
||||
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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
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 *VinegarHttpServlet) SendError(w http.ResponseWriter, req *http.Request, code int, msg string, aErr error) {
|
||||
func (s *VinegarWebServlet) 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))
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
package servlet
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewServlet(t *testing.T) {
|
||||
|
||||
port := ":8080"
|
||||
|
||||
srv := NewServlet(port)
|
||||
@ -14,7 +19,7 @@ func TestNewServlet(t *testing.T) {
|
||||
t.Errorf("Expected port %s, got %s", port, srv.Port)
|
||||
}
|
||||
|
||||
if srv.Routes != nil {
|
||||
if srv.Router.Routes != nil {
|
||||
t.Error("Expected Routes to be nil")
|
||||
}
|
||||
|
||||
@ -22,3 +27,113 @@ func TestNewServlet(t *testing.T) {
|
||||
t.Error("Expected ErrorRoutes to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServletServeTraffic(t *testing.T) {
|
||||
|
||||
// Create a new servlet
|
||||
srv := NewServlet(":8080")
|
||||
|
||||
// Add a test route
|
||||
srv.Router.AddRoute(&VinegarWebRoute{
|
||||
Pattern: regexp.MustCompile("/test"),
|
||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hello"))
|
||||
},
|
||||
})
|
||||
|
||||
// Start the server in a goroutine
|
||||
go srv.Start()
|
||||
defer srv.Shutdown()
|
||||
time.Sleep(10 * time.Second)
|
||||
// Make a request to the test route
|
||||
res, err := http.Get("http://localhost:8080/test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify successful response
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status OK, got %d", res.StatusCode)
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if string(body) != "hello" {
|
||||
t.Errorf("Unexpected response body %s", string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiRoute(t *testing.T) {
|
||||
|
||||
// Create a new API route
|
||||
route := NewApiRoute(nil, "/hello")
|
||||
|
||||
// Add a handler for GET requests
|
||||
route.AddGetHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("hello"))
|
||||
})
|
||||
|
||||
// Create a request
|
||||
req, _ := http.NewRequest(http.MethodGet, "localhost:8080/hello", nil)
|
||||
|
||||
// Create a ResponseRecorder to record the response
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Call the handler
|
||||
route.VinegarRoute.Handler(rr, req)
|
||||
|
||||
// Check the status code
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
// Check the response body
|
||||
expected := "hello"
|
||||
if rr.Body.String() != expected {
|
||||
t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiRouteStress(t *testing.T) {
|
||||
route := NewApiRoute(nil, "/hello")
|
||||
|
||||
route.AddGetHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("hello"))
|
||||
})
|
||||
|
||||
n := 1000 // number of requests
|
||||
c := make(chan struct{}, n) // concurrency control
|
||||
|
||||
start := time.Now()
|
||||
for i := 0; i < n; i++ {
|
||||
go func() {
|
||||
|
||||
// Create a request
|
||||
req, _ := http.NewRequest(http.MethodGet, "localhost:8080/hello", nil)
|
||||
|
||||
// Create a ResponseRecorder to record the response
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Call the handler
|
||||
route.VinegarRoute.Handler(rr, req)
|
||||
c <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
// wait for requests to complete
|
||||
for i := 0; i < n; i++ {
|
||||
<-c
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
// Print performance stats
|
||||
log.Printf("Completed %d requests in %s", n, elapsed)
|
||||
avg := elapsed / time.Duration(n)
|
||||
log.Printf("Average request time: %s", avg)
|
||||
|
||||
requestsPerSec := float64(n) / elapsed.Seconds()
|
||||
log.Printf("Requests per second: %f", requestsPerSec)
|
||||
}
|
||||
|
||||
@ -18,8 +18,8 @@ type (
|
||||
// VinegarRoute is the base route containing the URL pattern and handler.
|
||||
VinegarRoute *VinegarWebRoute
|
||||
|
||||
// srv is the VinegarHttpServlet instance that this route is attached to.
|
||||
srv *VinegarHttpServlet
|
||||
// srv is the VinegarWebServlet instance that this route is attached to.
|
||||
srv *VinegarWebServlet
|
||||
|
||||
// fileRoot is the base file path to serve files from.
|
||||
fileRoot string
|
||||
@ -32,7 +32,7 @@ type (
|
||||
//
|
||||
//Params:
|
||||
//
|
||||
//servlet - The VinegarHttpServlet instance to add the route to
|
||||
//servlet - The VinegarWebServlet instance to add the route to
|
||||
//
|
||||
//urlPattern - The URL regex pattern for route to match
|
||||
//
|
||||
@ -40,13 +40,13 @@ type (
|
||||
//
|
||||
//useCache - Whether to use caching for this route
|
||||
//
|
||||
// A RouteConstructor is a function that accepts a VinegarHttpServlet, urlPattern, file path, and cache option. It uses
|
||||
// A RouteConstructor is a function that accepts a VinegarWebServlet, urlPattern, file path, and cache option. It uses
|
||||
// these to construct and return a FileRoute.
|
||||
// The return value is a FileRoute that will serve the files from the given path.
|
||||
//
|
||||
// This function signature allows encapsulating the creation of different types of FileRoutes. It is used to define
|
||||
// constructor functions for each file type, like NewTextRoute or NewImageRoute.
|
||||
RouteConstructor func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error)
|
||||
RouteConstructor func(servlet *VinegarWebServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error)
|
||||
)
|
||||
|
||||
// NewTextRoute creates a new FileRoute for serving text files.
|
||||
@ -55,7 +55,7 @@ type (
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// servlet - The VinegarHttpServlet instance to attach the route to.
|
||||
// servlet - The VinegarWebServlet instance to attach the route to.
|
||||
//
|
||||
// urlPattern - The URL regex pattern that triggers this route.
|
||||
//
|
||||
@ -66,8 +66,8 @@ type (
|
||||
// Returns:
|
||||
//
|
||||
// A FileRoute instance configured for serving text files, added to
|
||||
// the provided VinegarHttpServlet.
|
||||
var NewTextRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error) {
|
||||
// the provided VinegarWebServlet.
|
||||
var NewTextRoute RouteConstructor = func(servlet *VinegarWebServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error) {
|
||||
fileRoot := filepath.Clean(pathlike)
|
||||
if strings.Contains(fileRoot, "../") {
|
||||
return nil, errors.New("Traversing the directory is not allowed, use an absolute filepath instead")
|
||||
@ -78,12 +78,12 @@ var NewTextRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern
|
||||
rootRoute := NewServletRoute(urlPattern, textRouteHandler) //i *still* kinda don't like this pattern
|
||||
route.VinegarRoute = rootRoute
|
||||
|
||||
servlet.AddRoute(route.VinegarRoute)
|
||||
servlet.Router.AddRoute(route.VinegarRoute)
|
||||
|
||||
return &route, nil
|
||||
}
|
||||
|
||||
var NewImageRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error) {
|
||||
var NewImageRoute RouteConstructor = func(servlet *VinegarWebServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error) {
|
||||
fileRoot := filepath.Clean(pathlike)
|
||||
if strings.Contains(fileRoot, "../") {
|
||||
return nil, errors.New("Traversing the directory is not allowed, use an absolute filepath instead")
|
||||
@ -93,11 +93,11 @@ var NewImageRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPatter
|
||||
rootRoute := NewServletRoute(urlPattern, createUncompressedFileServletFunction(&route, defaultPrune, pathlike))
|
||||
route.VinegarRoute = rootRoute //i *kinda* don't like this pattern
|
||||
|
||||
servlet.AddRoute(route.VinegarRoute)
|
||||
servlet.Router.AddRoute(route.VinegarRoute)
|
||||
return &route, nil
|
||||
}
|
||||
|
||||
var NewSingleFileRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error) {
|
||||
var NewSingleFileRoute RouteConstructor = func(servlet *VinegarWebServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error) {
|
||||
route := FileRoute{
|
||||
srv: servlet,
|
||||
fileRoot: pathlike,
|
||||
@ -115,7 +115,7 @@ var NewSingleFileRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlP
|
||||
|
||||
route.VinegarRoute = parentRoute
|
||||
|
||||
servlet.AddRoute(route.VinegarRoute)
|
||||
servlet.Router.AddRoute(route.VinegarRoute)
|
||||
|
||||
return &route, nil
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
type (
|
||||
TemplateRoute struct {
|
||||
*VinegarWebRoute
|
||||
srv *VinegarHttpServlet
|
||||
srv *VinegarWebServlet
|
||||
fileRoot string
|
||||
TemplateManager *TemplateManager
|
||||
UseCache bool
|
||||
@ -16,7 +16,7 @@ type (
|
||||
TemplateRouteHandlerFunc func(w http.ResponseWriter, r *http.Request, tm *TemplateManager)
|
||||
)
|
||||
|
||||
func NewTemplateRoute(servlet *VinegarHttpServlet, urlPattern string, templatePath string, componentPath string, handler TemplateRouteHandlerFunc) *TemplateRoute {
|
||||
func NewTemplateRoute(servlet *VinegarWebServlet, urlPattern string, templatePath string, componentPath string, handler TemplateRouteHandlerFunc) *TemplateRoute {
|
||||
defaultPrune := strings.Replace(urlPattern, ".*", "", -1)
|
||||
tm := NewTemplateManager(templatePath, componentPath)
|
||||
rootRoute := NewServletRoute(defaultPrune, createTemplateRouteFunction(tm, handler))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user