implemented rate limiting

This commit is contained in:
Maximilian Wagner
2025-12-28 00:31:39 +01:00
parent 2c05415ed1
commit 03c0e3ca77
5 changed files with 74 additions and 41 deletions

View File

@@ -1,6 +1,7 @@
package backend
import (
"embed"
"encoding/json"
"errors"
"fmt"
@@ -13,7 +14,6 @@ import (
"slices"
"strings"
"time"
"embed"
"git.noctra.dev/noctra/servtex/globals"
"github.com/fsnotify/fsnotify"
@@ -22,9 +22,15 @@ import (
//go:embed default_config.json
var defaultConfig embed.FS
// There are no errors if I can't see them
func Must[T any](object T, err error) T {
return object
}
// Returns the current time in the timezone specified in the config file
func GetLocalTime() time.Time {
timezone, _ := time.LoadLocation(globals.AppConfig.Timezone)
timezone := Must(time.LoadLocation(globals.AppConfig.Timezone))
return time.Now().In(timezone)
}
@@ -57,7 +63,7 @@ func LogLine(message string, level int) {
globals.LogFile.WriteString(logline)
}
// Logs client info. Configured log level sets the verbosity.
// Logs client info. Configured log level sets the verbosity. Logs only up to level INFO
//
// Bad Request if err != nil
func VerifyLogRequest(request *http.Request) (err error) {
@@ -66,16 +72,16 @@ func VerifyLogRequest(request *http.Request) (err error) {
if client.Proxied && globals.AppConfig.LogLevelNumeric > 1 {
LogLine(fmt.Sprintf(
"Request: %s via %s - %s %s",
"httpR: %s via %s - %s %s",
client.ClientIP,
client.Proxy,
client.RequestType,
client.RequestPath,
),
4,
2,
)
} else {
LogLine(fmt.Sprintf("Request: %s - %s %s", client.ClientIP, client.RequestType, client.RequestPath), 4)
LogLine(fmt.Sprintf("httpR: %s - %s %s", client.ClientIP, client.RequestType, client.RequestPath), 2)
}
return nil
@@ -84,7 +90,7 @@ func VerifyLogRequest(request *http.Request) (err error) {
// Writes a configuration file populated with defaults
func createConfigWithDefaults(path string) error {
config, _ := defaultConfig.ReadFile("default_config.json")
config := Must(defaultConfig.ReadFile("default_config.json"))
err := os.WriteFile(path, config, 0644)
return err
}
@@ -122,7 +128,7 @@ func configReaderParse(filePath string, configOptionStorage *globals.Config) err
// populates the appconfig with default values
func configPopulator(configOptionStorage *globals.Config) {
jsonData, _ := defaultConfig.ReadFile("default_config.json")
jsonData := Must(defaultConfig.ReadFile("default_config.json"))
json.Unmarshal(jsonData, &configOptionStorage)
}
@@ -175,35 +181,34 @@ func watchLoop(watcher *fsnotify.Watcher, callOnChange func() error) {
defer watcher.Close()
for {
select {
// Errors
case err, ok := <-watcher.Errors:
if !ok {
LogLine(fmt.Sprintf("Watcher crashed on error, manual compile only: %s", err), 4)
return
}
LogLine(fmt.Sprintf("Watcher Error: %s", err), 3)
// Events
case event, ok := <-watcher.Events:
if !ok {
LogLine(fmt.Sprintf("Watcher crashed on event, manual compile only: %s", event), 4)
return
}
LogLine(fmt.Sprintf("File change noticed: %s", event.Name), 2)
err := callOnChange()
if err != nil {
LogLine(fmt.Sprintf("Latex compilation failed: %s", err), 4)
}
if err = watcher.Add(event.Name); err != nil {
LogLine(fmt.Sprintf("File could not be re-added to watcher: %s", event.Name), 4)
}
case err, ok := <-watcher.Errors:
if !ok {
LogLine(fmt.Sprintf("Watcher crashed on error, manual compile only: %s", err), 4)
return
}
LogLine(fmt.Sprintf("Watcher Error: %s", err), 3)
case event, ok := <-watcher.Events:
if !ok {
LogLine(fmt.Sprintf("Watcher crashed on event, manual compile only: %s", event), 4)
return
}
LogLine(fmt.Sprintf("File change noticed: %s", event.Name), 2)
err := callOnChange()
if err != nil {
LogLine(fmt.Sprintf("Latex compilation failed: %s", err), 4)
}
if err = watcher.Add(event.Name); err != nil {
LogLine(fmt.Sprintf("File could not be re-added to watcher: %s", event.Name), 4)
}
}
}
}
// Returns a list of all .tex files recursively found in the passed directory
func texFinder(path string) []string {
var texFiles []string
err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
@@ -216,7 +221,7 @@ func texFinder(path string) []string {
return nil
})
if err != nil {
LogLine("Some paths could not be traversed while search for .tex files", 3)
LogLine(fmt.Sprintf("Some paths could not be traversed while search for .tex files: %s", err), 3)
}
return texFiles
}
@@ -249,19 +254,40 @@ func updateExecutionTimestamp(execution *globals.LatexExecution) {
execution.TimestampRFC = GetLocalTimeRFC()
}
// Returns the necessary time to sleep for rate limiting.
//
// Requires an RFC3339 timestamp. A dog will die if anything else is passed.
func rateLimitTime(timestampRFC string) (sleepTime time.Duration) {
lastExeTime := Must(time.Parse(time.RFC3339, timestampRFC))
rateLimitInterval := Must(time.ParseDuration(fmt.Sprintf("%ds", globals.AppConfig.RateLimitSeconds)))
return time.Until(lastExeTime.Add(rateLimitInterval)).Round(time.Second)
}
// Intended to be run as goroutine
func LatexCompile() error {
execution := &globals.LatexExec
config := globals.AppConfig
// check for already running compilation
if execution.ExecutionLock.TryLock() {
defer execution.ExecutionLock.Unlock()
} else {
LogLine("LaTeX execution already underway", 2)
LogLine("LaTeX: Already running", 2)
return errors.New("Execution already in progress")
}
LogLine("LaTeX execution started", 2)
// rate limit or start right away
rateLimit := rateLimitTime(execution.TimestampRFC)
if rateLimit.Seconds() > 0 {
LogLine(fmt.Sprintf("LaTeX: Rate limit: %d seconds", int(rateLimit.Seconds())), 3)
execution.ExecutionState = globals.LatexExecutionStateLimit
time.Sleep(rateLimit)
} else {
LogLine("LaTeX: Started", 3)
execution.ExecutionState = globals.LatexExecutionStateRunning
}
// build command
var latexCommand *exec.Cmd
switch config.LatexEngine {
case "lualatex":
@@ -277,10 +303,11 @@ func LatexCompile() error {
execution.Output = []byte{}
updateExecutionTimestamp(execution)
LogLine("Unsupported LaTeX Engine", 4)
LogLine("LaTeX: Unsupported engine", 4)
return errors.New("Unsupported LaTeX Engine")
}
// execute command
stdout, err := latexCommand.Output()
execution.Output = stdout
if err == nil {
@@ -290,7 +317,7 @@ func LatexCompile() error {
}
updateExecutionTimestamp(execution)
LogLine("LaTeX execution finished", 2)
LogLine("LaTeX: Finished", 3)
return err
}
@@ -317,7 +344,7 @@ func isTrustedProxy(proxy string, trustedProxies []string) (trusted bool, usedPr
// resolve trustedProxies in case a DNS name was configured
for _, trustedProxyDNS := range trustedProxies {
// get IPs of this dns name
trustedProxyIPs, err := net.LookupAddr(trustedProxyDNS)
trustedProxyIPs, err := net.LookupHost(trustedProxyDNS)
if err != nil { continue }
// set usedProxy to DNS name if proxy is trusted