massive refactor. StaticRoute.go is still a mess. needs a big overhaul still.

This commit is contained in:
dtookey 2022-07-20 15:16:05 -04:00
parent 0e6bc71322
commit 9ebc43e072
5 changed files with 257 additions and 193 deletions

View File

@ -1,19 +1,19 @@
package servlet package servlet
import ( import (
"fmt"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"regexp" "regexp"
"vinegar/cacheutil" "vinegar/vinegarUtil"
) )
const ( const (
defaultLruSize = int64(1024 * 1024 * 50) defaultLruSize = int64(1024 * 1024 * 50)
ContentTypeHeaderKey = "Content-Type" ContentTypeHeaderKey = "Content-Type"
ContentEncodingHeaderKey = "Content-Encoding" ContentEncodingHeaderKey = "Content-Encoding"
AcceptEncodingHeaderKey = "Accept-Encoding"
) )
type ErrorResponse struct { type ErrorResponse struct {
@ -25,32 +25,28 @@ type (
VinegarServlet struct { VinegarServlet struct {
Port string Port string
Routes []*VinegarRoute Routes []*VinegarRoute
Cache *cacheutil.Lru
} }
VinegarRoute struct { VinegarRoute struct {
Pattern *regexp.Regexp Pattern *regexp.Regexp
Handler VinegarHandlerFunction Handler VinegarHandlerFunction
Cache vinegarUtil.Cache
} }
VinegarHandlerFunction func(w http.ResponseWriter, req *http.Request) VinegarHandlerFunction func(w http.ResponseWriter, req *http.Request)
) )
func NewServlet(port string) *VinegarServlet { func NewServlet(port string) *VinegarServlet {
lru := cacheutil.NewLRU(defaultLruSize) srv := VinegarServlet{Port: port}
srv := VinegarServlet{Port: port, Cache: lru}
return &srv return &srv
} }
func NewServletRoute(routePattern string, handleFunc VinegarHandlerFunction) *VinegarRoute { func NewServletRoute(routePattern string, handleFunc VinegarHandlerFunction) *VinegarRoute {
route := VinegarRoute{}
pattern := regexp.MustCompile(routePattern) pattern := regexp.MustCompile(routePattern)
route.Pattern = pattern route := VinegarRoute{Pattern: pattern, Handler: handleFunc, Cache: vinegarUtil.NewLRU(defaultLruSize)}
route.Handler = handleFunc
return &route return &route
} }
@ -60,7 +56,6 @@ func (s *VinegarServlet) AddRoute(route *VinegarRoute) {
func (s *VinegarServlet) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (s *VinegarServlet) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := req.URL.Path path := req.URL.Path
fmt.Println("Attempting to match request for " + path)
for _, route := range s.Routes { for _, route := range s.Routes {
if route.Pattern.MatchString(path) { if route.Pattern.MatchString(path) {
route.Handler(w, req) route.Handler(w, req)
@ -69,7 +64,11 @@ func (s *VinegarServlet) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
} }
func StartServerGood(s *VinegarServlet) { func (s *VinegarServlet) Start() {
if len(s.Routes) < 1 {
}
err := http.ListenAndServe(s.Port, s) err := http.ListenAndServe(s.Port, s)
if err != nil { if err != nil {
@ -105,24 +104,3 @@ func SendError(w http.ResponseWriter, code int, msg string) {
panic(err) panic(err)
} }
} }
func renderTemplate(w http.ResponseWriter, pathlike string, data any) {
templateHelper := template.New(pathlike)
f, err := os.OpenFile(pathlike, os.O_RDONLY, 0777)
if err != nil {
panic(err)
}
defer f.Close()
content, err := ioutil.ReadAll(f)
if err != nil {
panic(err)
}
templ, err := templateHelper.Parse(string(content))
err = templ.Execute(w, data)
if err != nil {
panic(err)
}
}

View File

@ -1,96 +1,107 @@
package servlet package servlet
import ( import (
"fmt"
"net/http" "net/http"
"path" "path"
"strings" "strings"
"vinegar/cacheutil" util "vinegar/vinegarUtil"
) )
type ( type (
FileRoute struct { FileRoute struct {
*VinegarRoute *VinegarRoute
srv *VinegarServlet
fileRoot string fileRoot string
UseCache bool
} }
) )
func NewImageRoute(urlPattern string, pathlike string, servlet *VinegarServlet) *FileRoute { func NewImageRoute(servlet *VinegarServlet, urlPattern string, pathlike string, useCache bool) *FileRoute {
defaultPrune := strings.Replace(urlPattern, ".*", "", -1) defaultPrune := strings.Replace(urlPattern, ".*", "", -1)
rootRoute := NewServletRoute(urlPattern, createUncompressedFileServletFunction(defaultPrune, pathlike, servlet.Cache)) imgRoute := FileRoute{srv: servlet, fileRoot: pathlike, UseCache: useCache}
imgRoute := FileRoute{rootRoute, pathlike} rootRoute := NewServletRoute(urlPattern, createUncompressedFileServletFunction(&imgRoute, defaultPrune, pathlike))
imgRoute.VinegarRoute = rootRoute //i *kinda* don't like this pattern
return &imgRoute return &imgRoute
} }
func NewTextRoute(urlPattern string, pathlike string, servlet *VinegarServlet) *FileRoute { func NewTextRoute(servlet *VinegarServlet, urlPattern string, pathlike string, useCache bool) *FileRoute {
defaultPrune := strings.Replace(urlPattern, ".*", "", -1) defaultPrune := strings.Replace(urlPattern, ".*", "", -1)
rootRoute := NewServletRoute(urlPattern, createCompressibleFileServletFunction(defaultPrune, pathlike, servlet.Cache)) fr := FileRoute{srv: servlet, fileRoot: pathlike, UseCache: useCache}
fr := FileRoute{rootRoute, pathlike} textRouteHandler := createCompressibleFileServletFunction(&fr, defaultPrune, pathlike)
rootRoute := NewServletRoute(urlPattern, textRouteHandler) //i *still* kinda don't like this pattern
fr.VinegarRoute = rootRoute
return &fr return &fr
} }
func NewSingleFileRoute(urlPattern string, pathlike string, servlet *VinegarServlet) *FileRoute { func NewSingleFileRoute(servlet *VinegarServlet, urlPattern string, pathlike string, useCache bool) *FileRoute {
fun := createSingleFileServletFunction(pathlike)
route := FileRoute{ route := FileRoute{
VinegarRoute: NewServletRoute("^"+urlPattern+"$", fun), srv: servlet,
fileRoot: pathlike, fileRoot: pathlike,
UseCache: useCache,
} }
singleFileServletHandler := createSingleFileServletFunction(&route)
sfCache := util.NewSingleFileCache(pathlike)
parentRoute := NewServletRoute(urlPattern, singleFileServletHandler)
parentRoute.Handler = singleFileServletHandler
parentRoute.Cache = sfCache
route.VinegarRoute = parentRoute
return &route return &route
} }
func createSingleFileServletFunction(pathlike string) VinegarHandlerFunction { func createSingleFileServletFunction(route *FileRoute) VinegarHandlerFunction {
cache := cacheutil.NewSingleFileCache(pathlike)
var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) {
acceptsGzip := clientAcceptsGzip(req)
if cacheutil.UseCache { var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) {
var content []byte var cache *util.LruEntry
if acceptsGzip { var exists bool
w.Header().Add(ContentEncodingHeaderKey, "gzip")
content = *cache.CompressedContent if route.UseCache {
cache, exists = route.Cache.Get("")
} else { } else {
content = *cache.Content cache, exists = route.Cache.GetFresh("")
} }
w.Header().Add(ContentTypeHeaderKey, cache.Mimetype)
if !exists {
SendError(w, 404, "File not found.")
return
}
var content []byte
if clientAcceptsGzip(req) {
content = cache.CompressedContent
w.Header().Add(ContentEncodingHeaderKey, "gzip")
} else {
content = cache.Content
}
_, err := w.Write(content) _, err := w.Write(content)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} else {
contents, exists := cacheutil.GetDiskContent(pathlike)
if exists {
fmt.Printf("returning contents: len of %d ", len(*contents))
w.Header().Add(ContentTypeHeaderKey, cache.Mimetype)
_, err := w.Write(*contents)
if err != nil {
panic(err)
}
return
} else {
w.WriteHeader(404)
w.Write(nil)
fmt.Println("returning nothing")
return
}
}
} }
return fun return fun
} }
func createCompressibleFileServletFunction(basePattern string, pathlike string, cache *cacheutil.Lru) VinegarHandlerFunction { func createCompressibleFileServletFunction(route *FileRoute, basePattern string, pathlike string) VinegarHandlerFunction {
var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) { var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) {
stub := strings.Replace(req.URL.Path, basePattern, "", 1) stub := strings.Replace(req.URL.Path, basePattern, "", 1)
cachedContent, exists := cache.Get(stub) cachedContent, exists := route.Cache.Get(stub)
//i don't like this logic below. we need to streamline this a lot better. it's a twisty jungle right now //i don't like this logic below. we need to streamline this a lot better. it's a twisty jungle right now
resourcePath := path.Join(pathlike, stub)
if !exists { if !exists {
content, fileExists := cacheutil.GetDiskContent(path.Join(pathlike, stub)) content, fileExists := util.GetDiskContent(resourcePath)
if fileExists { if fileExists {
if cacheutil.UseCache { if route.UseCache {
cache.Put(stub, content) route.Cache.Put(stub, resourcePath)
cachedContent, _ = cache.Get(stub) cachedContent, _ = route.Cache.Get(stub)
} else { } else {
w.Header().Add(ContentTypeHeaderKey, cacheutil.GuessMimetype(stub)) w.Header().Add(ContentTypeHeaderKey, util.GuessMimetype(stub))
w.Write(*content) w.Write(*content)
return return
} }
@ -116,21 +127,18 @@ func createCompressibleFileServletFunction(basePattern string, pathlike string,
return fun return fun
} }
func createUncompressedFileServletFunction(basePattern string, pathlike string, cache *cacheutil.Lru) VinegarHandlerFunction { func createUncompressedFileServletFunction(route *FileRoute, basePattern string, pathlike string) VinegarHandlerFunction {
var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) { var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) {
stub := strings.Replace(req.URL.Path, basePattern, "", 1) stub := strings.Replace(req.URL.Path, basePattern, "", 1)
resourcePath := path.Join(pathlike, stub)
entry, exists := cache.Get(stub) entry, exists := route.Cache.Get(stub)
if !exists { if !exists {
fileContent, fExists := cacheutil.GetDiskContent(path.Join(pathlike, stub)) route.Cache.Put(stub, resourcePath)
if fExists { entry, exists = route.Cache.Get(stub)
cache.Put(stub, fileContent)
entry, exists = cache.Get(stub)
} else { } else {
SendError(w, 404, "Oops! Something went wrong!") SendError(w, 404, "Oops! Something went wrong!")
return return
} }
}
if !exists { if !exists {
SendError(w, 404, "Oops! Something went wrong.") SendError(w, 404, "Oops! Something went wrong.")
@ -146,9 +154,6 @@ func createUncompressedFileServletFunction(basePattern string, pathlike string,
} }
func clientAcceptsGzip(req *http.Request) bool { func clientAcceptsGzip(req *http.Request) bool {
encodings := req.Header.Get("Accept-Encoding") encodings := req.Header.Get(AcceptEncodingHeaderKey)
if strings.Contains(encodings, "gzip") { return strings.Contains(encodings, "gzip")
return true
}
return false
} }

9
tmp.go Normal file
View File

@ -0,0 +1,9 @@
package main
import "vinegar/servlet"
func main() {
serv := servlet.NewServlet(":8080")
serv.Start()
}

View File

@ -1,11 +1,8 @@
package cacheutil package vinegarUtil
import ( import (
"bytes" "errors"
gzip2 "compress/gzip"
"fmt" "fmt"
"io/ioutil"
"os"
"strings" "strings"
"time" "time"
) )
@ -14,10 +11,6 @@ const (
DefaultCacheTimeInMinutes = 15 DefaultCacheTimeInMinutes = 15
) )
var (
UseCache = true
)
type ( type (
lru map[string]*LruEntry lru map[string]*LruEntry
Lru struct { Lru struct {
@ -27,6 +20,7 @@ type (
} }
LruEntry struct { LruEntry struct {
path string
Content []byte Content []byte
CompressedContent []byte CompressedContent []byte
created time.Time created time.Time
@ -37,11 +31,15 @@ type (
} }
SingleFileCache struct { SingleFileCache struct {
Content *[]byte path string
CompressedContent *[]byte entry *LruEntry
timeCreated time.Time }
timeExpires time.Time
Mimetype string Cache interface {
Get(key string) (*LruEntry, bool)
GetFresh(key string) (*LruEntry, bool)
Put(key string, pathlike string) error
Remove(key string)
} }
) )
@ -51,59 +49,13 @@ func NewLRU(size int64) *Lru {
} }
func NewSingleFileCache(pathlike string) *SingleFileCache { func NewSingleFileCache(pathlike string) *SingleFileCache {
bits, exists := GetDiskContent(pathlike)
if !exists {
panic("Could not load single file for single file path: " + pathlike)
}
compressedBits := GZipBytes(bits) entry := newLRUEntry(pathlike)
sfc := SingleFileCache{path: pathlike, entry: entry}
cache := SingleFileCache{ return &sfc
Content: bits,
CompressedContent: compressedBits,
timeCreated: time.Now(),
timeExpires: time.Now().Add(DefaultCacheTimeInMinutes),
Mimetype: GuessMimetype(pathlike),
}
return &cache
}
func (l *Lru) Put(key string, content *[]byte) {
entry, exists := (*l.entries)[key]
zippedContent := *GZipBytes(content)
size := int64(len(*content)) + int64(len(zippedContent))
l.ensureVacancy(size)
if exists {
entry.Content = *content
entry.CompressedContent = zippedContent
entry.created = time.Now()
entry.expires = time.Now().Add(DefaultCacheTimeInMinutes * time.Minute)
entry.mostRecentAccess = time.Now()
entry.totalSize = size
} else {
mimetype := GuessMimetype(key)
entry = &LruEntry{
Content: *content,
CompressedContent: zippedContent,
created: time.Now(),
expires: time.Now().Add(DefaultCacheTimeInMinutes * time.Minute),
mostRecentAccess: time.Now(),
totalSize: size,
MimeType: mimetype,
}
}
(*l.entries)[key] = entry
l.currentSize = l.currentSize + size
} }
func (l *Lru) Get(key string) (*LruEntry, bool) { func (l *Lru) Get(key string) (*LruEntry, bool) {
if !UseCache {
return nil, false
}
entry, exists := (*l.entries)[key] entry, exists := (*l.entries)[key]
if exists && entry.expires.Before(time.Now()) { if exists && entry.expires.Before(time.Now()) {
fmt.Println("Cache miss due to expired content") fmt.Println("Cache miss due to expired content")
@ -113,12 +65,48 @@ func (l *Lru) Get(key string) (*LruEntry, bool) {
if exists { if exists {
entry.mostRecentAccess = time.Now() entry.mostRecentAccess = time.Now()
} else { } else {
fmt.Printf("cacheutil miss for '%s'\n", key) fmt.Printf("vinegarUtil miss for '%s'\n", key)
} }
return entry, exists return entry, exists
} }
} }
func (l *Lru) GetFresh(key string) (*LruEntry, bool) {
l.Remove(key)
return l.Get(key)
}
func (l *Lru) Put(key string, pathlike string) error {
entry, exists := (*l.entries)[key]
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")
}
zippedContent := *GZipBytes(content)
size = int64(len(*content)) + int64(len(zippedContent))
l.ensureVacancy(size)
entry.Content = *content
entry.CompressedContent = zippedContent
entry.created = time.Now()
entry.expires = time.Now().Add(DefaultCacheTimeInMinutes * time.Minute)
entry.mostRecentAccess = time.Now()
entry.totalSize = size
} else {
entry = newLRUEntry(pathlike)
}
(*l.entries)[key] = entry
l.recalcSize()
return nil
}
func (l *Lru) Remove(key string) { func (l *Lru) Remove(key string) {
entry, exists := (*l.entries)[key] entry, exists := (*l.entries)[key]
if exists { if exists {
@ -159,19 +147,57 @@ func (l *Lru) getLeastRecentlyUsedKey() string {
return leastUsedKey return leastUsedKey
} }
func GZipBytes(uncompressed *[]byte) *[]byte { func (l *Lru) recalcSize() {
buff := bytes.Buffer{} var total int64
gzip := gzip2.NewWriter(&buff) for _, entry := range *l.entries {
_, err := gzip.Write(*uncompressed) total += entry.totalSize
if err != nil {
panic(err)
} }
err = gzip.Flush() l.currentSize = total
if err != nil {
panic(err)
} }
compressed := buff.Bytes()
return &compressed func (sfc *SingleFileCache) Get(key string) (*LruEntry, bool) {
return sfc.entry, true
}
func (sfc *SingleFileCache) GetFresh(key string) (*LruEntry, bool) {
entry := newLRUEntry(sfc.entry.path)
sfc.entry = entry
return 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)
sfc.entry = entry
return nil
}
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)
}
compressedBits := GZipBytes(bits)
size := int64(len(*bits)) + int64(len(*compressedBits))
entry := LruEntry{
path: pathlike,
Content: *bits,
CompressedContent: *compressedBits,
created: time.Now(),
expires: time.Now().Add(DefaultCacheTimeInMinutes * time.Minute),
mostRecentAccess: time.Now(),
totalSize: size,
MimeType: GuessMimetype(pathlike),
}
return &entry
} }
func GuessMimetype(filePath string) string { func GuessMimetype(filePath string) string {
@ -197,22 +223,3 @@ func GuessMimetype(filePath string) string {
} }
return "application/octet-stream" return "application/octet-stream"
} }
func GetDiskContent(filePath string) (*[]byte, bool) {
_, ferr := os.Stat(filePath)
if os.IsNotExist(ferr) {
return nil, false
} else {
f, err := os.OpenFile(filePath, os.O_RDONLY, 0755)
if err != nil {
panic(err)
}
defer f.Close()
content, err := ioutil.ReadAll(f)
if err != nil {
panic(err)
}
return &content, true
}
}

65
vinegarUtil/files.go Normal file
View File

@ -0,0 +1,65 @@
package vinegarUtil
import (
"bytes"
gzip2 "compress/gzip"
"html/template"
"io/ioutil"
"net/http"
"os"
)
func RenderTemplate(w http.ResponseWriter, pathlike string, data any) {
templateHelper := template.New(pathlike)
f, err := os.OpenFile(pathlike, os.O_RDONLY, 0777)
if err != nil {
panic(err)
}
defer f.Close()
content, err := ioutil.ReadAll(f)
if err != nil {
panic(err)
}
templ, err := templateHelper.Parse(string(content))
err = templ.Execute(w, data)
if err != nil {
panic(err)
}
}
func GetDiskContent(filePath string) (*[]byte, bool) {
_, ferr := os.Stat(filePath)
if os.IsNotExist(ferr) {
return nil, false
} else {
f, err := os.OpenFile(filePath, os.O_RDONLY, 0755)
if err != nil {
panic(err)
}
defer f.Close()
content, err := ioutil.ReadAll(f)
if err != nil {
panic(err)
}
return &content, true
}
}
func GZipBytes(uncompressed *[]byte) *[]byte {
buff := bytes.Buffer{}
gzip := gzip2.NewWriter(&buff)
_, err := gzip.Write(*uncompressed)
if err != nil {
panic(err)
}
err = gzip.Flush()
if err != nil {
panic(err)
}
compressed := buff.Bytes()
return &compressed
}