Initial commit of library
This commit is contained in:
commit
35c87e82d4
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/.idea/
|
||||||
|
/vinegar-server.iml
|
||||||
6
go.mod
Normal file
6
go.mod
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module vinegar-server
|
||||||
|
require (
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
|
||||||
|
google.golang.org/api v0.30.0
|
||||||
|
)
|
||||||
|
go 1.18
|
||||||
211
vinegar/WebLRU.go
Normal file
211
vinegar/WebLRU.go
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
package vinegar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
gzip2 "compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultCacheTimeInMinutes = 15
|
||||||
|
ContentTypeHeaderKey = "Content-Type"
|
||||||
|
ContentEncodingHeaderKey = "Content-Encoding"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
lru map[string]*LruEntry
|
||||||
|
Lru struct {
|
||||||
|
entries *lru
|
||||||
|
limit int64
|
||||||
|
currentSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
LruEntry struct {
|
||||||
|
Content []byte
|
||||||
|
CompressedContent []byte
|
||||||
|
created time.Time
|
||||||
|
expires time.Time
|
||||||
|
mostRecentAccess time.Time
|
||||||
|
totalSize int64
|
||||||
|
MimeType string
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleFileCache struct {
|
||||||
|
Content *[]byte
|
||||||
|
CompressedContent *[]byte
|
||||||
|
timeCreated time.Time
|
||||||
|
timeExpires time.Time
|
||||||
|
Mimetype string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLRU(size int64) *Lru {
|
||||||
|
cLru := make(lru)
|
||||||
|
return &Lru{&cLru, size, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
cache := SingleFileCache{
|
||||||
|
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) {
|
||||||
|
entry, exists := (*l.entries)[key]
|
||||||
|
if exists && entry.expires.Before(time.Now()) {
|
||||||
|
fmt.Println("Cache miss due to expired content")
|
||||||
|
l.Remove(key)
|
||||||
|
return nil, false
|
||||||
|
} else {
|
||||||
|
if exists {
|
||||||
|
entry.mostRecentAccess = time.Now()
|
||||||
|
} else {
|
||||||
|
fmt.Printf("cache miss for '%s'\n", key)
|
||||||
|
}
|
||||||
|
return entry, exists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lru) Remove(key string) {
|
||||||
|
entry, exists := (*l.entries)[key]
|
||||||
|
if exists {
|
||||||
|
size := entry.totalSize
|
||||||
|
delete(*l.entries, key)
|
||||||
|
l.currentSize = l.currentSize - size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lru) ensureVacancy(required int64) {
|
||||||
|
if l.currentSize == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for l.currentSize+required > l.limit {
|
||||||
|
leastKey := l.getLeastRecentlyUsedKey()
|
||||||
|
l.Remove(leastKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lru) getLeastRecentlyUsedKey() string {
|
||||||
|
started := false
|
||||||
|
var benchmark *LruEntry
|
||||||
|
leastUsedKey := ""
|
||||||
|
for key, entry := range *l.entries {
|
||||||
|
if !started {
|
||||||
|
started = true
|
||||||
|
benchmark = entry
|
||||||
|
leastUsedKey = key
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if benchmark.mostRecentAccess.After(entry.mostRecentAccess) {
|
||||||
|
benchmark = entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if leastUsedKey == "" {
|
||||||
|
panic("Invalid key for LRU: [" + leastUsedKey + "]")
|
||||||
|
}
|
||||||
|
return leastUsedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func GuessMimetype(filePath string) string {
|
||||||
|
dotIndex := strings.LastIndex(filePath, ".")
|
||||||
|
ext := filePath[dotIndex+1:]
|
||||||
|
switch ext {
|
||||||
|
case "htm":
|
||||||
|
case "html":
|
||||||
|
return "text/html"
|
||||||
|
case "ico":
|
||||||
|
return "image/vnd.microsoft.icon"
|
||||||
|
case "jpg":
|
||||||
|
case "jpeg":
|
||||||
|
return "image/jpeg"
|
||||||
|
case "png":
|
||||||
|
return "image/png"
|
||||||
|
case "svg":
|
||||||
|
return "image/svg+xml"
|
||||||
|
default:
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
9
vinegar/campaign.go
Normal file
9
vinegar/campaign.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package vinegar
|
||||||
|
|
||||||
|
type Campaign struct {
|
||||||
|
Targets *[]string
|
||||||
|
HostName string
|
||||||
|
ListeningPort int
|
||||||
|
WebRoot string
|
||||||
|
EmailTemplate string
|
||||||
|
}
|
||||||
86
vinegar/dynamicRoute.go
Normal file
86
vinegar/dynamicRoute.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package vinegar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ApiRoute struct {
|
||||||
|
ServletRoute *ServletRoute
|
||||||
|
HttpMethodRoutes *map[HttpMethod]ServletHandleFunction
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
HttpMethod int
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GET HttpMethod = iota
|
||||||
|
POST
|
||||||
|
PUT
|
||||||
|
PATCH
|
||||||
|
DELETE
|
||||||
|
UNDEFINED
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewApiRoute(pattern string) *ApiRoute {
|
||||||
|
functionMap := make(map[HttpMethod]ServletHandleFunction)
|
||||||
|
ancestorRoute := NewServletRoute(pattern, createMethodHandler(&functionMap))
|
||||||
|
route := ApiRoute{
|
||||||
|
ancestorRoute,
|
||||||
|
&functionMap,
|
||||||
|
}
|
||||||
|
return &route
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMethodHandler(m *map[HttpMethod]ServletHandleFunction) ServletHandleFunction {
|
||||||
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
method := getHttpMethod(req)
|
||||||
|
fn, exists := (*m)[method]
|
||||||
|
if exists {
|
||||||
|
fn(w, req)
|
||||||
|
} else {
|
||||||
|
SendApiError(
|
||||||
|
w,
|
||||||
|
200,
|
||||||
|
404,
|
||||||
|
"Method ["+req.Method+"] does not exist for endpoint["+req.URL.Path+"].",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ApiRoute) RegisterHttpMethodHandler(method HttpMethod, handler ServletHandleFunction) {
|
||||||
|
(*api.HttpMethodRoutes)[method] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHttpMethod(req *http.Request) HttpMethod {
|
||||||
|
switch req.Method {
|
||||||
|
case "GET":
|
||||||
|
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 SendApiError(w http.ResponseWriter, httpCode int, messageCode int, message string) {
|
||||||
|
respMessage := fmt.Sprintf("{\"code\":%d, \"message\":\"%s\"}", messageCode, message)
|
||||||
|
w.WriteHeader(httpCode)
|
||||||
|
|
||||||
|
_, err := w.Write([]byte(respMessage))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
161
vinegar/email.go
Normal file
161
vinegar/email.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package vinegar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
"google.golang.org/api/gmail/v1"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
//<editor-fold name="secure gmail">
|
||||||
|
|
||||||
|
/*======================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
secure gmail
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
======================================================================================*/
|
||||||
|
|
||||||
|
// Retrieve a token, saves the token, then returns the generated client.
|
||||||
|
|
||||||
|
func getClient(config *oauth2.Config) *http.Client {
|
||||||
|
|
||||||
|
// The file token.json stores the user's access and refresh tokens, and is
|
||||||
|
|
||||||
|
// created automatically when the authorization flow completes for the first
|
||||||
|
|
||||||
|
// time.
|
||||||
|
|
||||||
|
tokFile := "token.json"
|
||||||
|
|
||||||
|
tok, err := tokenFromFile(tokFile)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
tok = getTokenFromWeb(config)
|
||||||
|
|
||||||
|
saveToken(tokFile, tok)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Client(context.Background(), tok)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request a token from the web, then returns the retrieved token.
|
||||||
|
|
||||||
|
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
|
||||||
|
|
||||||
|
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
|
||||||
|
|
||||||
|
fmt.Printf("Go to the following link in your browser then type the "+
|
||||||
|
"authorization code: \n%v\n", authURL)
|
||||||
|
|
||||||
|
var authCode string
|
||||||
|
|
||||||
|
if _, err := fmt.Scan(&authCode); err != nil {
|
||||||
|
|
||||||
|
log.Fatalf("Unable to read authorization code: %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tok, err := config.Exchange(context.TODO(), authCode)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
log.Fatalf("Unable to retrieve token from web: %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return tok
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves a token from a local file.
|
||||||
|
|
||||||
|
func tokenFromFile(file string) (*oauth2.Token, error) {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
tok := &oauth2.Token{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(f).Decode(tok)
|
||||||
|
|
||||||
|
return tok, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves a token to a file path.
|
||||||
|
|
||||||
|
func saveToken(path string, token *oauth2.Token) {
|
||||||
|
fmt.Printf("Saving credential file to: %s\n", path)
|
||||||
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to cache oauth token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
json.NewEncoder(f).Encode(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMailService() *gmail.Service {
|
||||||
|
ctx := context.Background()
|
||||||
|
b, err := ioutil.ReadFile("gmail-creds.json")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to read client secret file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If modifying these scopes, delete your previously saved token.json.
|
||||||
|
|
||||||
|
config, err := google.ConfigFromJSON(b, gmail.GmailSendScope, gmail.GmailComposeScope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to parse client secret file to config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := getClient(config)
|
||||||
|
|
||||||
|
srv, err := gmail.NewService(ctx, option.WithHTTPClient(client))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to retrieve Gmail client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend() {
|
||||||
|
srv := getMailService()
|
||||||
|
|
||||||
|
payload := []byte("testing")
|
||||||
|
|
||||||
|
msg := gmail.Message{Raw: base64.URLEncoding.EncodeToString(payload)}
|
||||||
|
|
||||||
|
msgResp, err := srv.Users.Messages.Send("me", &msg).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v\n", msgResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
125
vinegar/server.go
Normal file
125
vinegar/server.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package vinegar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultLruSize = int64(1024 * 1024 * 50)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Code int
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Servlet struct {
|
||||||
|
Port string
|
||||||
|
Routes []*ServletRoute
|
||||||
|
Cache *Lru
|
||||||
|
}
|
||||||
|
|
||||||
|
ServletRoute struct {
|
||||||
|
Pattern *regexp.Regexp
|
||||||
|
Handler ServletHandleFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
ServletHandleFunction func(w http.ResponseWriter, req *http.Request)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewServlet(port string) *Servlet {
|
||||||
|
lru := NewLRU(defaultLruSize)
|
||||||
|
srv := Servlet{Port: port, Cache: lru}
|
||||||
|
|
||||||
|
return &srv
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServletRoute(routePattern string, handleFunc ServletHandleFunction) *ServletRoute {
|
||||||
|
route := ServletRoute{}
|
||||||
|
|
||||||
|
pattern := regexp.MustCompile(routePattern)
|
||||||
|
|
||||||
|
route.Pattern = pattern
|
||||||
|
route.Handler = handleFunc
|
||||||
|
|
||||||
|
return &route
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Servlet) AddRoute(route *ServletRoute) {
|
||||||
|
s.Routes = append(s.Routes, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Servlet) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
path := req.URL.Path
|
||||||
|
fmt.Println("Attempting to match request for " + path)
|
||||||
|
for _, route := range s.Routes {
|
||||||
|
if route.Pattern.MatchString(path) {
|
||||||
|
route.Handler(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartServerGood(s *Servlet) {
|
||||||
|
err := http.ListenAndServe(s.Port, s)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendError(w http.ResponseWriter, code int, msg string) {
|
||||||
|
errorFile := "templates/error.html"
|
||||||
|
errorResp := ErrorResponse{code, msg}
|
||||||
|
tmplManager := template.New("error")
|
||||||
|
|
||||||
|
f, err := os.OpenFile(errorFile, os.O_RDONLY, 0777)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := tmplManager.Parse(string(content))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(code)
|
||||||
|
err = tmpl.Execute(w, errorResp)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
118
vinegar/staticRoute.go
Normal file
118
vinegar/staticRoute.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package vinegar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
FileRoute struct {
|
||||||
|
*ServletRoute
|
||||||
|
fileRoot string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewImageRoute(urlPattern string, pathlike string, servlet *Servlet) *FileRoute {
|
||||||
|
defaultPrune := strings.Replace(urlPattern, ".*", "", -1)
|
||||||
|
rootRoute := NewServletRoute(urlPattern, createUncompressedFileServletFunction(defaultPrune, pathlike, servlet.Cache))
|
||||||
|
imgRoute := FileRoute{rootRoute, pathlike}
|
||||||
|
return &imgRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTextRoute(urlPattern string, pathlike string, servlet *Servlet) *FileRoute {
|
||||||
|
defaultPrune := strings.Replace(urlPattern, ".*", "", -1)
|
||||||
|
rootRoute := NewServletRoute(urlPattern, createCompressibleFileServletFunction(defaultPrune, pathlike, servlet.Cache))
|
||||||
|
fr := FileRoute{rootRoute, pathlike}
|
||||||
|
return &fr
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSingleFileRoute(urlPattern string, pathlike string, servlet *Servlet) *FileRoute {
|
||||||
|
fun := createSingleFileServletFunction(pathlike)
|
||||||
|
route := FileRoute{
|
||||||
|
ServletRoute: NewServletRoute("^"+urlPattern+"$", fun),
|
||||||
|
fileRoot: pathlike,
|
||||||
|
}
|
||||||
|
return &route
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSingleFileServletFunction(pathlike string) ServletHandleFunction {
|
||||||
|
cache := NewSingleFileCache(pathlike)
|
||||||
|
var fun ServletHandleFunction = func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Header().Add(ContentTypeHeaderKey, cache.Mimetype)
|
||||||
|
if clientAcceptsGzip(req) {
|
||||||
|
w.Header().Add(ContentEncodingHeaderKey, "gzip")
|
||||||
|
w.Write(*cache.CompressedContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fun
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCompressibleFileServletFunction(basePattern string, pathlike string, cache *Lru) ServletHandleFunction {
|
||||||
|
var fun ServletHandleFunction = func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
stub := strings.Replace(req.URL.Path, basePattern, "", 1)
|
||||||
|
cachedContent, exists := cache.Get(stub)
|
||||||
|
if !exists {
|
||||||
|
content, fileExists := GetDiskContent(path.Join(pathlike, stub))
|
||||||
|
if fileExists {
|
||||||
|
cache.Put(stub, content)
|
||||||
|
cachedContent, _ = cache.Get(stub)
|
||||||
|
} else {
|
||||||
|
SendError(w, 404, "Oops! Something went wrong.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Add(ContentTypeHeaderKey, cachedContent.MimeType)
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
|
if clientAcceptsGzip(req) {
|
||||||
|
w.Header().Add(ContentEncodingHeaderKey, "gzip")
|
||||||
|
_, err = w.Write(cachedContent.CompressedContent)
|
||||||
|
} else {
|
||||||
|
_, err = w.Write(cachedContent.Content)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return fun
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUncompressedFileServletFunction(basePattern string, pathlike string, cache *Lru) ServletHandleFunction {
|
||||||
|
var fun ServletHandleFunction = func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
stub := strings.Replace(req.URL.Path, basePattern, "", 1)
|
||||||
|
|
||||||
|
entry, exists := cache.Get(stub)
|
||||||
|
if !exists {
|
||||||
|
fileContent, fExists := GetDiskContent(path.Join(pathlike, stub))
|
||||||
|
if fExists {
|
||||||
|
cache.Put(stub, fileContent)
|
||||||
|
entry, exists = cache.Get(stub)
|
||||||
|
} else {
|
||||||
|
SendError(w, 404, "Oops! Something went wrong!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
SendError(w, 404, "Oops! Something went wrong.")
|
||||||
|
} else {
|
||||||
|
_, err := w.Write(entry.Content)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fun
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientAcceptsGzip(req *http.Request) bool {
|
||||||
|
encodings := req.Header.Get("Accept-Encoding")
|
||||||
|
if strings.Contains(encodings, "gzip") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user