cleanup of codesmells and architecture

This commit is contained in:
dtookey 2023-08-01 10:02:31 -04:00
parent 7f4b41247e
commit ca65acbea1
7 changed files with 230 additions and 96 deletions

39
servlet/Router.go Normal file
View 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 + "]")
}

View File

@ -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

View File

@ -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) {

View File

@ -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))

View File

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

View File

@ -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
}

View File

@ -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))