removed basically all the panics

This commit is contained in:
dtookey 2023-07-31 16:57:21 -04:00
parent e4d6348fc3
commit 465266c36d
8 changed files with 217 additions and 145 deletions

View File

@ -58,40 +58,40 @@ func CreateBlankConfig() *Config {
return &conf
}
func LoadConfig(pathlike string) *VinegarHttpServlet {
contents, exists := vinegarUtil.GetDiskContent(pathlike)
if exists {
conf := Config{}
err := json.Unmarshal(*contents, &conf)
if err != nil {
panic(err)
return nil
}
servlet := NewServlet(conf.ListeningAddress)
for _, v := range conf.Routes {
constructor, err := getConstructorFunction(v.ConfigType)
if err != nil {
panic(err)
return nil
}
//we don't have to invoke servlet.AddRoute because the static routes already do that for us
constructor(servlet, v.UrlPattern, v.FileLocation, v.UseBuiltinCache)
}
return servlet
} else {
func LoadConfig(pathlike string) (*VinegarHttpServlet, error) {
contents, err := vinegarUtil.GetDiskContent(pathlike)
if err != nil {
CreateBlankConfig()
panic("Could not find config file at" + pathlike)
return nil
return nil, err
}
conf := Config{}
err = json.Unmarshal(*contents, &conf)
if err != nil {
return nil, err
}
servlet := NewServlet(conf.ListeningAddress)
for _, v := range conf.Routes {
constructor, err := getConstructorFunction(v.ConfigType)
if err != nil {
return nil, err
}
//we don't have to invoke servlet.AddRoute because the static routes already do that for us
constructor(servlet, v.UrlPattern, v.FileLocation, v.UseBuiltinCache)
}
return servlet, nil
}
func (e ConfigEntry) toRoute(serv *VinegarHttpServlet) {
func (e ConfigEntry) toRoute(serv *VinegarHttpServlet) error {
constructor, err := getConstructorFunction(e.ConfigType)
if err != nil {
panic(err)
return err
}
constructor(serv, e.UrlPattern, e.FileLocation, e.UseBuiltinCache)
return nil
}
func getConstructorFunction(t ConfigType) (RouteConstructor, error) {
@ -106,13 +106,14 @@ func getConstructorFunction(t ConfigType) (RouteConstructor, error) {
return nil, errors.New(fmt.Sprintf("could not determine constructor for invalid ConfigType: %s", t))
}
func GenerateBlankConfig() {
func GenerateBlankConfig() error {
fileName := "servlet.json.template.tmpl"
conf := CreateBlankConfig()
content, err := json.Marshal(&conf)
if err != nil {
panic(err)
return err
}
fmt.Println("Generating a blank configuration file at ")
ioutil.WriteFile(fileName, content, 0755)
return nil
}

View File

@ -2,6 +2,7 @@ package servlet
import (
"fmt"
"log"
"net/http"
)
@ -60,7 +61,7 @@ func (api *ApiRoute) RegisterHttpMethodHandler(method HttpMethod, handler Vinega
func getHttpMethod(req *http.Request) HttpMethod {
switch req.Method {
case "GET":
case http.MethodGet:
return GET
case "POST":
return POST
@ -79,10 +80,10 @@ func getHttpMethod(req *http.Request) HttpMethod {
func SendApiError(w http.ResponseWriter, httpCode int, messageCode int, message string) {
respMessage := fmt.Sprintf("{\"code\":%d, \"message\":\"%s\"}", messageCode, message)
w.WriteHeader(httpCode)
log.Printf("[ERROR]\t%s\n", respMessage)
_, err := w.Write([]byte(respMessage))
if err != nil {
panic(err)
log.Println(err)
}
w.WriteHeader(httpCode)
}

View File

@ -7,7 +7,6 @@ import (
"geniuscartel.xyz/vinegar/vinegarUtil"
"log"
"net/http"
"os"
"regexp"
"strconv"
)
@ -83,10 +82,9 @@ func (s *VinegarHttpServlet) ServeHTTP(w http.ResponseWriter, req *http.Request)
s.SendError(w, req, 404, "Couldn't find your content.", errors.New("failed to match route for ["+path+"]"))
}
func (s *VinegarHttpServlet) Start() {
func (s *VinegarHttpServlet) Start() error {
if len(s.Routes) < 1 {
log.Fatal("No routes found for server. Nothing to listen and serve.")
os.Exit(1)
}
log.Printf("Listening on [%s]\n", s.Port)
@ -94,9 +92,9 @@ func (s *VinegarHttpServlet) Start() {
err := http.ListenAndServe(s.Port, s)
if err != nil {
panic(err)
return err
}
return nil
}
func (r *VinegarWebRoute) Announce() {
@ -105,28 +103,36 @@ func (r *VinegarWebRoute) Announce() {
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)
fmt.Println(aErr)
out, _ := json.Marshal(aErr)
fmt.Println(string(out))
tmpl, exists := s.ErrorRoutes[code]
if exists {
tmpl.TemplateManager.AddMixin("code", strconv.Itoa(code))
tmpl.TemplateManager.AddMixin("msg", msg)
w.WriteHeader(code)
_, err := w.Write([]byte(tmpl.TemplateManager.RenderTemplate(fmt.Sprintf("%d.html", code))))
err := tmpl.TemplateManager.AddMixin("code", strconv.Itoa(code))
if err != nil {
panic(err)
writeGeneric(w, code, msg)
return
}
return
} else {
w.WriteHeader(code)
errorPayload := ErrorResponse{Code: code, Message: msg}
genericError, sErr := json.Marshal(errorPayload)
if sErr != nil {
panic(sErr)
}
_, err := w.Write(genericError)
err = tmpl.TemplateManager.AddMixin("msg", msg)
if err != nil {
panic(err)
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)
}

24
servlet/server_test.go Normal file
View File

@ -0,0 +1,24 @@
package servlet
import (
"testing"
)
func TestNewServlet(t *testing.T) {
port := ":8080"
srv := NewServlet(port)
if srv.Port != port {
t.Errorf("Expected port %s, got %s", port, srv.Port)
}
if srv.Routes != nil {
t.Error("Expected Routes to be nil")
}
if srv.ErrorRoutes == nil {
t.Error("Expected ErrorRoutes to be initialized")
}
}

View File

@ -3,6 +3,7 @@ package servlet
import (
"errors"
util "geniuscartel.xyz/vinegar/vinegarUtil"
"log"
"net/http"
"path"
"path/filepath"
@ -45,7 +46,7 @@ type (
//
// 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
RouteConstructor func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error)
)
// NewTextRoute creates a new FileRoute for serving text files.
@ -66,10 +67,10 @@ type (
//
// 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 {
var NewTextRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error) {
fileRoot := filepath.Clean(pathlike)
if strings.Contains(fileRoot, "../") {
panic("Traversing the directory is not allowed, use an absolute filepath instead")
return nil, errors.New("Traversing the directory is not allowed, use an absolute filepath instead")
}
defaultPrune := strings.Replace(urlPattern, ".*", "", -1)
route := FileRoute{srv: servlet, fileRoot: fileRoot, UseCache: useCache}
@ -79,13 +80,13 @@ var NewTextRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern
servlet.AddRoute(route.VinegarRoute)
return &route
return &route, nil
}
var NewImageRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) *FileRoute {
var NewImageRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error) {
fileRoot := filepath.Clean(pathlike)
if strings.Contains(fileRoot, "../") {
panic("Traversing the directory is not allowed, use an absolute filepath instead")
return nil, errors.New("Traversing the directory is not allowed, use an absolute filepath instead")
}
defaultPrune := strings.Replace(urlPattern, ".*", "", -1)
route := FileRoute{srv: servlet, fileRoot: fileRoot, UseCache: useCache}
@ -93,17 +94,20 @@ var NewImageRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPatter
route.VinegarRoute = rootRoute //i *kinda* don't like this pattern
servlet.AddRoute(route.VinegarRoute)
return &route
return &route, nil
}
var NewSingleFileRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) *FileRoute {
var NewSingleFileRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlPattern string, pathlike string, useCache bool) (*FileRoute, error) {
route := FileRoute{
srv: servlet,
fileRoot: pathlike,
UseCache: useCache,
}
singleFileServletHandler := createSingleFileServletFunction(&route)
sfCache := util.NewSingleFileCache(pathlike)
sfCache, err := util.NewSingleFileCache(pathlike)
if err != nil {
return nil, err
}
parentRoute := NewServletRoute(urlPattern, singleFileServletHandler)
parentRoute.Handler = singleFileServletHandler
@ -113,7 +117,7 @@ var NewSingleFileRoute RouteConstructor = func(servlet *VinegarHttpServlet, urlP
servlet.AddRoute(route.VinegarRoute)
return &route
return &route, nil
}
// createSingleFileServletFunction creates a handler function for serving a single file.
@ -167,7 +171,7 @@ func createSingleFileServletFunction(route *FileRoute) VinegarHandlerFunction {
w.Header().Add(ContentTypeHeaderKey, cache.MimeType)
_, err := w.Write(content)
if err != nil {
panic(err)
log.Println(err)
}
}
@ -189,20 +193,23 @@ func createCompressibleFileServletFunction(route *FileRoute, basePattern string,
resourcePath := path.Join(pathRoot, filePath)
if !exists {
content, fileExists := util.GetDiskContent(resourcePath)
if fileExists {
if route.UseCache {
route.VinegarRoute.Cache.Put(stub, resourcePath)
cachedContent, _ = route.VinegarRoute.Cache.Get(stub)
} else {
w.Header().Add(ContentTypeHeaderKey, util.GuessMimetype(stub))
w.Write(*content)
return
}
} else {
content, err := util.GetDiskContent(resourcePath)
if err != nil {
route.srv.SendError(w, req, 404, "Couldn't find your content.", errors.New("could not find valid file at ["+resourcePath+"]"))
return
}
if route.UseCache {
err := route.VinegarRoute.Cache.Put(stub, resourcePath)
if err != nil {
route.srv.SendError(w, req, 500, "Internal Server Error", err)
}
cachedContent, _ = route.VinegarRoute.Cache.Get(stub)
} else {
w.Header().Add(ContentTypeHeaderKey, util.GuessMimetype(stub))
w.Write(*content)
return
}
}
w.Header().Add(ContentTypeHeaderKey, cachedContent.MimeType)
var err error = nil
@ -214,7 +221,7 @@ func createCompressibleFileServletFunction(route *FileRoute, basePattern string,
_, err = w.Write(cachedContent.Content)
}
if err != nil {
panic(err)
log.Println(err)
}
}
@ -241,7 +248,7 @@ func createUncompressedFileServletFunction(route *FileRoute, basePattern string,
w.Header().Add(ContentTypeHeaderKey, util.GuessMimetype(stub))
_, err := w.Write(entry.Content)
if err != nil {
panic(err)
log.Println(err)
}
return

View File

@ -31,17 +31,17 @@ func NewTemplateManager(templatePath string, componentPath string) *TemplateMana
return &tm
}
func (tm *TemplateManager) loadAssets() {
func (tm *TemplateManager) loadAssets() error {
templateFiles, err := os.ReadDir(tm.templatePath)
if err != nil {
panic(err)
return err
}
for _, templateFile := range templateFiles {
if !templateFile.IsDir() {
filePath := path.Join(tm.templatePath, templateFile.Name())
dataContent, exists := util.GetDiskContent(filePath)
if !exists {
panic("Could not read file contents for " + filePath)
dataContent, err := util.GetDiskContent(filePath)
if err != nil {
return err
}
tmpl := template.New(templateFile.Name())
tmpl.Parse(string(*dataContent))
@ -52,44 +52,43 @@ func (tm *TemplateManager) loadAssets() {
componentFiles, err := os.ReadDir(tm.componentPath)
if err != nil {
panic(err)
return err
}
for _, componentFile := range componentFiles {
if !componentFile.IsDir() {
filePath := path.Join(tm.componentPath, componentFile.Name())
contentBytes, exists := util.GetDiskContent(filePath)
if !exists {
panic("could not find file for " + filePath)
return
contentBytes, err := util.GetDiskContent(filePath)
if err != nil {
return err
}
content := string(*contentBytes)
tm.components[componentFile.Name()] = template.HTML(content)
}
}
return nil
}
func (tm *TemplateManager) RenderTemplate(key string) string {
func (tm *TemplateManager) RenderTemplate(key string) (string, error) {
tmpl, exists := tm.templates[key]
if !exists {
panic("Could not load template for key: " + key)
return "", errors.New("Could not load template for key: " + key)
}
buff := strings.Builder{}
err := tmpl.Execute(&buff, tm)
if err != nil {
panic(err)
return "", err
}
return buff.String()
return buff.String(), nil
}
func (tm *TemplateManager) GetComponent(key string) template.HTML {
func (tm *TemplateManager) GetComponent(key string) (template.HTML, error) {
content, exists := tm.components[key]
if !exists {
panic("Could not load data for key: " + key)
return ""
return "", errors.New("Could not load data for key: " + key)
}
return content
return content, nil
}
func (tm *TemplateManager) GetMixin(key string) template.HTML {
@ -107,50 +106,54 @@ func (tm *TemplateManager) AddMixin(key string, value string) error {
}
func (tm *TemplateManager) AddMixinFromFile(key string, pathlike string) error {
content, exists := util.GetDiskContent(pathlike)
if exists {
tm.AddMixin(key, string(*content))
return nil
content, err := util.GetDiskContent(pathlike)
if err != nil {
return err
} else {
return errors.New("mixin source file not found at " + pathlike)
err2 := tm.AddMixin(key, string(*content))
if err2 != nil {
return err2
}
return nil
}
}
func (tm *TemplateManager) RenderAllToFile(pathlike string) {
func (tm *TemplateManager) RenderAllToFile(pathlike string) error {
for _, v := range tm.Templates {
path := path.Join(pathlike, v)
p := path.Join(pathlike, v)
tmpl := tm.templates[v]
buff := strings.Builder{}
err := tmpl.Execute(&buff, tm)
if err != nil {
panic(err)
return err
}
err = ioutil.WriteFile(path, []byte(buff.String()), 0755)
err = ioutil.WriteFile(p, []byte(buff.String()), 0755)
if err != nil {
panic(err)
return err
}
}
return nil
}
func RenderTemplate(w http.ResponseWriter, pathlike string, data any) {
func RenderTemplate(w http.ResponseWriter, pathlike string, data any) error {
templateHelper := template.New(pathlike)
f, err := os.OpenFile(pathlike, os.O_RDONLY, 0777)
if err != nil {
panic(err)
return err
}
defer f.Close()
content, err := ioutil.ReadAll(f)
if err != nil {
panic(err)
return err
}
templ, err := templateHelper.Parse(string(content))
err = templ.Execute(w, data)
if err != nil {
panic(err)
return err
}
return nil
}

View File

@ -7,36 +7,36 @@ import (
"os"
)
func GetDiskContent(filePath string) (*[]byte, bool) {
func GetDiskContent(filePath string) (*[]byte, error) {
_, ferr := os.Stat(filePath)
if os.IsNotExist(ferr) {
return nil, false
return nil, ferr
} else {
f, err := os.OpenFile(filePath, os.O_RDONLY, 0755)
if err != nil {
panic(err)
return nil, err
}
defer f.Close()
content, err := ioutil.ReadAll(f)
if err != nil {
panic(err)
return nil, err
}
return &content, true
return &content, nil
}
}
func GZipBytes(uncompressed *[]byte) *[]byte {
func GZipBytes(uncompressed *[]byte) (*[]byte, error) {
buff := bytes.Buffer{}
gzip := gzip2.NewWriter(&buff)
_, err := gzip.Write(*uncompressed)
if err != nil {
panic(err)
return nil, err
}
err = gzip.Flush()
if err != nil {
panic(err)
return nil, err
}
compressed := buff.Bytes()
return &compressed
return &compressed, nil
}

View File

@ -1,7 +1,6 @@
package vinegarUtil
import (
"errors"
"fmt"
"log"
"strings"
@ -50,17 +49,23 @@ func NewLRU(size int64) *Lru {
return &Lru{&cLru, size, 0}
}
func NewSingleFileCache(pathlike string) *SingleFileCache {
entry := newLRUEntry(pathlike)
func NewSingleFileCache(pathlike string) (*SingleFileCache, error) {
entry, err := newLRUEntry(pathlike)
if err != nil {
return nil, err
}
sfc := SingleFileCache{path: pathlike, entry: entry}
return &sfc
return &sfc, nil
}
func (l *Lru) Get(key string) (*LruEntry, bool) {
entry, exists := (*l.entries)[key]
if exists && entry.expires.Before(time.Now()) {
fmt.Println("Cache miss due to expired content")
entry.Reload()
err := entry.Reload()
if err != nil {
return nil, false
}
return entry, true
} else {
if exists {
@ -73,7 +78,10 @@ func (l *Lru) Get(key string) (*LruEntry, bool) {
func (l *Lru) GetFresh(key string) (*LruEntry, bool) {
entry, exists := l.Get(key)
if exists {
entry.Reload()
err := entry.Reload()
if err != nil {
return nil, false
}
}
return l.Get(key)
}
@ -84,11 +92,13 @@ func (l *Lru) Put(key string, pathlike string) error {
var size int64
if exists {
content, fExists := GetDiskContent(pathlike)
if !fExists {
return errors.New("attempted to refresh cache with file that no longer exists on disk")
content, err := GetDiskContent(pathlike)
if err != nil {
return err
}
zippedContent := *GZipBytes(content)
zippedBytes, err := GZipBytes(content)
zippedContent := *zippedBytes
size = int64(len(*content)) + int64(len(zippedContent))
l.ensureVacancy(size)
@ -99,8 +109,11 @@ func (l *Lru) Put(key string, pathlike string) error {
entry.mostRecentAccess = time.Now()
entry.totalSize = size
} else {
entry = newLRUEntry(pathlike)
nEntry, err := newLRUEntry(pathlike)
if err != nil {
return err
}
entry = nEntry
}
(*l.entries)[key] = entry
@ -143,9 +156,7 @@ func (l *Lru) getLeastRecentlyUsedKey() string {
benchmark = entry
}
}
if leastUsedKey == "" {
panic("Invalid key for LRU: [" + leastUsedKey + "]")
}
return leastUsedKey
}
@ -157,13 +168,23 @@ func (l *Lru) recalcSize() {
l.currentSize = total
}
func (le *LruEntry) Reload() {
content, _ := GetDiskContent(le.path)
func (le *LruEntry) Reload() error {
content, err := GetDiskContent(le.path)
if err != nil {
return err
}
zippedBytes, err := GZipBytes(content)
if err != nil {
return err
}
le.Content = *content
le.CompressedContent = *GZipBytes(content)
le.CompressedContent = *zippedBytes
le.created = time.Now()
le.mostRecentAccess = le.created
le.expires = le.created.Add(time.Minute * DefaultCacheTimeInMinutes)
return nil
}
func (sfc *SingleFileCache) Get(key string) (*LruEntry, bool) {
@ -171,14 +192,20 @@ func (sfc *SingleFileCache) Get(key string) (*LruEntry, bool) {
}
func (sfc *SingleFileCache) GetFresh(key string) (*LruEntry, bool) {
sfc.entry.Reload()
err := sfc.entry.Reload()
if err != nil {
return nil, false
}
return sfc.entry, true
}
// Put THIS WILL DELETE ANYTHING YOU HAVE STORED IN THIS CACHE.
func (sfc *SingleFileCache) Put(key string, pathlike string) error {
//there's a bug in this. we don't return an error from newLRUEntry, so if the file disappears, the server will die
entry := newLRUEntry(pathlike)
entry, err := newLRUEntry(pathlike)
if err != nil {
return err
}
sfc.entry = entry
return nil
}
@ -187,13 +214,16 @@ func (sfc *SingleFileCache) Remove(key string) {
//i'm actually not sure what to do here. why would you ever remove from a single-file cache?
}
func newLRUEntry(pathlike string) *LruEntry {
bits, exists := GetDiskContent(pathlike)
if !exists {
panic("Could not load single file for single file path: " + pathlike)
func newLRUEntry(pathlike string) (*LruEntry, error) {
bits, err := GetDiskContent(pathlike)
if err != nil {
return nil, err
}
compressedBits := GZipBytes(bits)
compressedBits, err := GZipBytes(bits)
if err != nil {
return nil, err
}
size := int64(len(*bits)) + int64(len(*compressedBits))
@ -207,7 +237,7 @@ func newLRUEntry(pathlike string) *LruEntry {
totalSize: size,
MimeType: GuessMimetype(pathlike),
}
return &entry
return &entry, nil
}
func GuessMimetype(filePath string) string {