added readme

This commit is contained in:
dtookey 2023-08-01 12:53:09 -04:00
parent 5c7143ffd7
commit 0ae5afa452
6 changed files with 106 additions and 13 deletions

48
README.md Normal file
View File

@ -0,0 +1,48 @@
# Vinegar
### Vinegar is a simple web application framework written in Go.
## Features
Routing with support for static files, templates, APIs
Template rendering
Caching
Gzip compression
Custom error pages
Getting Started
Prerequisites
Go 1.18+
### Installation
```go get geniuscartel.xyz/vinegar```
### Usage
Create a config.json file to define routes and options
### Import Vinegar and create a new server instance:
```
import "geniuscartel.xyz/vinegar"
func main(){
server := vinegar.NewServer(config)
Add handlers for API routes:
api := server.NewApiRoute("/api")
api.Get(handleGet)
Start the server:
server.Start(":8080")
}
```
See the examples folder for more usage examples.
## Configuration
The config.json file defines the routes and options for Vinegar. It supports the following route types:
* static - Map a URL pattern to a static file or directory
* template - Render a Go template on a route
* api - Add API routes with custom handlers
* See config_example.json for a sample configuration file.
## License
Vinegar is closed source. Copyright 2023 David Tookey. All rights reserved

View File

@ -1,8 +1,10 @@
package servlet package servlet
import ( import (
"encoding/json"
"errors" "errors"
"geniuscartel.xyz/vinegar/vinegarUtil" "geniuscartel.xyz/vinegar/vinegarUtil"
"log"
"net/http" "net/http"
"regexp" "regexp"
) )
@ -28,12 +30,25 @@ func (r *VinegarWebRouter) RouteRequest(w http.ResponseWriter, req *http.Request
path := req.URL.Path path := req.URL.Path
for _, route := range r.Routes { for _, route := range r.Routes {
if route.Pattern.MatchString(path) { if route.Pattern.MatchString(path) {
//fmt.Printf("SERVING: [%s]=>{%s}\n", path, route.Pattern.String()) log.Printf("SERVING: [%s]=>{%s}\n", path, route.Pattern.String())
go route.Handler(w, req) go route.Handler(w, req)
return nil return nil
} }
} }
notFoundHandler(w, req)
return errors.New("failed to match route for [" + path + "]") return errors.New("failed to match route for [" + path + "]")
} }
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
errorResponse := ErrorResponse{
Code: http.StatusNotFound,
Message: "The requested resource could not be found.",
}
jsonBytes, _ := json.Marshal(errorResponse)
w.Header().Add("Content-Type", "application/json")
w.Write(jsonBytes)
}

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"geniuscartel.xyz/vinegar/vinegarUtil" "geniuscartel.xyz/vinegar/vinegarUtil"
"io/ioutil" "io/ioutil"
"log"
) )
type ( type (
@ -150,7 +151,7 @@ func GenerateBlankConfig() error {
if err != nil { if err != nil {
return err return err
} }
fmt.Println("Generating a blank configuration file at ") log.Println("Generating a blank configuration file at ")
ioutil.WriteFile(defaultTemplateFileName, content, 0755) ioutil.WriteFile(defaultTemplateFileName, content, 0755)
return nil return nil
} }

View File

@ -28,6 +28,30 @@ type (
VinegarWebServlet struct { VinegarWebServlet struct {
Port string Port string
Router VinegarWebRouter 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 ErrorRoutes map[int]*TemplateRoute
interrupts chan struct{} interrupts chan struct{}
errors chan error errors chan error
@ -107,9 +131,10 @@ func (r *VinegarWebRoute) Announce() {
} }
func (s *VinegarWebServlet) 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) log.Printf("[%d][%s]. Rendering template for code %d with message: %s\n", code, req.URL.Path, code, msg)
out, _ := json.Marshal(aErr) out, _ := json.Marshal(aErr)
fmt.Println(string(out))
log.Println(string(out))
tmpl, exists := s.ErrorRoutes[code] tmpl, exists := s.ErrorRoutes[code]
if exists { if exists {
err := tmpl.TemplateManager.AddMixin("code", strconv.Itoa(code)) err := tmpl.TemplateManager.AddMixin("code", strconv.Itoa(code))
@ -124,6 +149,11 @@ func (s *VinegarWebServlet) SendError(w http.ResponseWriter, req *http.Request,
} }
w.WriteHeader(code) w.WriteHeader(code)
bitties, err := tmpl.TemplateManager.RenderTemplate(fmt.Sprintf("%d.html", 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)) _, err = w.Write([]byte(bitties))
if err != nil { if err != nil {
http.Error(w, msg, code) http.Error(w, msg, code)

View File

@ -1,7 +1,6 @@
package servlet package servlet
import ( import (
"fmt"
"io" "io"
"log" "log"
"net/http" "net/http"
@ -38,10 +37,11 @@ func TestServletServeTraffic(t *testing.T) {
srv.Router.AddRoute(&VinegarWebRoute{ srv.Router.AddRoute(&VinegarWebRoute{
Pattern: regexp.MustCompile("/test"), Pattern: regexp.MustCompile("/test"),
Handler: func(w http.ResponseWriter, r *http.Request) { Handler: func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("We got into the handler\n") log.Printf("We got into the handler\n")
msg := []byte("hello") msg := []byte("hello")
l, err := w.Write(msg) l, err := w.Write(msg)
fmt.Printf("Wrote %d bytes\n", l)
log.Printf("Wrote %d bytes\n", l)
if err != nil { if err != nil {
srv.errors <- err srv.errors <- err
} }

View File

@ -1,7 +1,6 @@
package vinegarUtil package vinegarUtil
import ( import (
"fmt"
"log" "log"
"strings" "strings"
"time" "time"
@ -61,7 +60,7 @@ func NewSingleFileCache(pathlike string) (*SingleFileCache, error) {
func (l *Lru) Get(key string) (*LruEntry, bool) { func (l *Lru) Get(key string) (*LruEntry, bool) {
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") log.Println("Cache miss due to expired content")
err := entry.Reload() err := entry.Reload()
if err != nil { if err != nil {
return nil, false return nil, false