Compare commits

1 Commits

Author SHA1 Message Date
Pablu23
0fa96af3d1 Add kdl config, but not runnable 2025-10-01 09:49:14 +02:00
15 changed files with 236 additions and 509 deletions

3
.gitignore vendored
View File

@@ -2,6 +2,3 @@
*.key
bin/
data.json
*.json
config.yaml

View File

@@ -4,68 +4,10 @@ Reverse Proxy for routing subdomains to different ports on same host machine
## Configuration
```csv
server:
port: 80
ssl:
enabled: false
certFile: server.crt
keyFile: server.key
acme:
enabled: false
email: user@host.dev
keyFile: userKey.key
caDirUrl: https://localhost:14000/dir
tlsAlpn01Port: 5001
http01Port: 5002
renewTime: 30s
logging:
level: info
# Pretty print for human consumption otherwise json
pretty: true
# Log incoming requests
requests: true
# Log to file aswell as stderr
file:
enabled: false
maxAge: 14
maxBackups: 10
path: ~/logs/router
rateLimit:
enabled: false
# How many requests per ip adress are allowed
bucketSize: 50
# How many requests per ip address are refilled
refillSize: 50
# How often requests per ip address are refilled
refillTime: 30s
# How often Ip Addresses get cleaned up (only ip addresses with max allowed requests are cleaned up)
cleanupTime: 45s
# Experimental
metrics:
enabled: false
flushInterval: 1h
bufferSize: 512
file: ~/metrics.json
hosts:
- remotes:
- 127.0.0.1
port: 3000
domains:
- api.hitstar.xyz
- remotes:
- 127.0.0.1
port: 5173
domains:
- localhost
- hitstar.xyz
# api.hitstar.xyz must be declared before rewrite can be used
rewrite:
"/api": api.hitstar.xyz
test.pablu.de;8181
manga.pablu.de;8282
pablu.de;8080
<Url>;<local Port>
```
## Building

View File

@@ -19,8 +19,7 @@ import (
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
domainrouter "git.pablu.de/pablu/domain-router"
"github.com/rs/zerolog/log"
domainrouter "github.com/pablu23/domain-router"
)
type Acme struct {
@@ -33,8 +32,7 @@ type Acme struct {
}
type CertDomainStorage struct {
IsUserRegistered bool
Domains map[string]time.Time
Domains map[string]time.Time
}
func SetupAcme(config *domainrouter.Config) (*Acme, error) {
@@ -95,6 +93,12 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
return nil, err
}
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
user.Registration = reg
domains := make([]string, 0)
for _, host := range config.Hosts {
domains = append(domains, host.Domains...)
@@ -109,7 +113,6 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
renewTicker: time.NewTicker(d),
}
isUserRegistered := false
_, err = os.Stat("data.json")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
@@ -124,7 +127,6 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
if err != nil {
return nil, err
}
isUserRegistered = data.IsUserRegistered
mustRenew := false
for _, domain := range domains {
@@ -133,9 +135,6 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
mustRenew = true
break
}
} else {
mustRenew = true
break
}
}
@@ -144,24 +143,6 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
}
}
if !isUserRegistered {
log.Debug().Str("user", user.Email).Msg("Registering new User")
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
user.Registration = reg
} else {
log.Debug().Str("user", user.Email).Msg("Resolving registration by Key")
reg, err := client.Registration.ResolveAccountByKey()
if err != nil {
return nil, err
}
user.Registration = reg
}
request := certificate.ObtainRequest{
Domains: domains,
Bundle: true,
@@ -188,10 +169,8 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
dataDomains[domain] = now
}
// User registration is hella scuffed
data := CertDomainStorage{
Domains: dataDomains,
IsUserRegistered: true,
Domains: dataDomains,
}
file, err := os.Create("data.json")
@@ -208,11 +187,11 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
}
func (a *Acme) RenewAcme() error {
request := certificate.ObtainRequest{
Domains: a.domains,
Bundle: true,
}
certificates, err := a.client.Certificate.Obtain(request)
if err != nil {
return err

View File

@@ -1,22 +1,17 @@
package main
import (
"context"
"crypto/tls"
"errors"
"flag"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
domainrouter "git.pablu.de/pablu/domain-router"
"git.pablu.de/pablu/domain-router/acme"
"git.pablu.de/pablu/domain-router/middleware"
domainrouter "github.com/pablu23/domain-router"
"github.com/pablu23/domain-router/acme"
"github.com/pablu23/domain-router/middleware"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gopkg.in/natefinch/lumberjack.v2"
@@ -42,38 +37,17 @@ func main() {
},
}
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
router := domainrouter.New(config, client)
mux := http.NewServeMux()
mux.HandleFunc("/", router.ServeHTTP)
pipeline := configureMiddleware(config)
pipeline.Manage()
server := http.Server{
Addr: fmt.Sprintf(":%d", config.Server.Port),
// this is rather bad looking
Handler: pipeline.Use()(mux),
Addr: fmt.Sprintf(":%d", config.Server.Port),
Handler: pipeline(mux),
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
<-sigs
log.Info().Msg("Stopping server")
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
server.Shutdown(ctx)
log.Info().Msg("Http Server stopped")
log.Info().Msg("Stopping pipeline")
pipeline.Stop(ctx)
log.Info().Msg("Pipeline stopped")
}()
if config.Server.Ssl.Enabled {
server.TLSConfig = &tls.Config{
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
@@ -97,23 +71,16 @@ func main() {
}
log.Info().Int("port", config.Server.Port).Str("cert", config.Server.Ssl.CertFile).Str("key", config.Server.Ssl.KeyFile).Msg("Starting server")
err := server.ListenAndServeTLS("", "")
if err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal().Err(err).Str("cert", config.Server.Ssl.CertFile).Str("key", config.Server.Ssl.KeyFile).Int("port", config.Server.Port).Msg("Could not start server")
}
log.Fatal().Err(err).Str("cert", config.Server.Ssl.CertFile).Str("key", config.Server.Ssl.KeyFile).Int("port", config.Server.Port).Msg("Could not start server")
} else {
log.Info().Int("port", config.Server.Port).Msg("Starting server")
err := server.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal().Err(err).Int("port", config.Server.Port).Msg("Could not start server")
}
log.Fatal().Err(err).Int("port", config.Server.Port).Msg("Could not start server")
}
wg.Wait()
log.Info().Msg("Server shutdown completly, have a nice day")
}
func configureMiddleware(config *domainrouter.Config) *middleware.Pipeline {
pipeline := middleware.NewPipeline()
func configureMiddleware(config *domainrouter.Config) middleware.Middleware {
middlewares := make([]middleware.Middleware, 0)
if config.RateLimit.Enabled {
refillTicker, err := time.ParseDuration(config.RateLimit.RefillTicker)
@@ -126,22 +93,15 @@ func configureMiddleware(config *domainrouter.Config) *middleware.Pipeline {
log.Fatal().Err(err).Str("cleanup", config.RateLimit.CleanupTicker).Msg("Could not parse cleanup Ticker")
}
limiter := middleware.NewLimiter(config.RateLimit.BucketSize, config.RateLimit.BucketRefill, refillTicker, cleanupTicker)
pipeline.AddMiddleware(limiter)
limiter.Start()
middlewares = append(middlewares, limiter.RateLimiter)
}
if config.Logging.Requests {
pipeline.AddMiddleware(&middleware.RequestLogger{})
}
if config.Metrics.Enabled {
flushInterval, err := time.ParseDuration(config.Metrics.FlushInterval)
if err != nil {
log.Fatal().Err(err).Str("flush_interval", config.Metrics.FlushInterval).Msg("Could not parse FlushInterval")
}
metrics := middleware.NewMetrics(config.Metrics.BufferSize, flushInterval, config.Metrics.File)
pipeline.AddMiddleware(metrics)
middlewares = append(middlewares, middleware.RequestLogger)
}
pipeline := middleware.Pipeline(middlewares...)
return pipeline
}

View File

@@ -1,31 +1,28 @@
package domainrouter
type Location struct {
}
type Config struct {
Server struct {
Port int `yaml:"port"`
Port int `yaml:"port" kdl:"port"`
Ssl struct {
Enabled bool `yaml:"enabled"`
CertFile string `yaml:"certFile"`
KeyFile string `yaml:"keyFile"`
Enabled bool `yaml:"enabled" kdl:"enabled"`
CertFile string `yaml:"certFile" kdl:"cert-file"`
KeyFile string `yaml:"keyFile" kdl:"key-file"`
Acme struct {
Enabled bool `yaml:"enabled"`
Email string `yaml:"email"`
KeyFile string `yaml:"keyFile"`
CADirURL string `yaml:"caDirUrl"`
Http01Port string `yaml:"http01Port"`
TlsAlpn01Port string `yaml:"tlsAlpn01Port"`
RenewTime string `yaml:"renewTime"`
} `yaml:"acme"`
} `yaml:"ssl"`
} `yaml:"server"`
Hosts []struct {
Port int `yaml:"port"`
Remotes []string `yaml:"remotes"`
Domains []string `yaml:"domains"`
Secure bool `yaml:"secure"`
Rewrite map[string]string `yaml:"rewrite"`
AdditionalHeaders map[string]string `yaml:"extraHeaders"`
} `yaml:"hosts"`
Enabled bool `yaml:"enabled" kdl:"enabled"`
Email string `yaml:"email" kdl:"email"`
KeyFile string `yaml:"keyFile" kdl:"key-file"`
CADirURL string `yaml:"caDirUrl" kdl:"ca-dir-url"`
Http01Port string `yaml:"http01Port" kdl:"http-01-port"`
TlsAlpn01Port string `yaml:"tlsAlpn01Port" kdl:"tls-apln01-port"`
RenewTime string `yaml:"renewTime" kdl:"renew-time"`
} `yaml:"acme" kdl:"acme"`
} `yaml:"ssl" kdl:"ssl"`
} `yaml:"server" kdl:"server"`
RateLimit struct {
Enabled bool `yaml:"enabled"`
BucketSize int `yaml:"bucketSize"`
@@ -44,10 +41,4 @@ type Config struct {
MaxBackups int `yamls:"maxBackups"`
} `yaml:"file"`
} `yaml:"logging"`
Metrics struct {
Enabled bool `yaml:"enabled"`
File string `yaml:"file"`
BufferSize int `yaml:"bufferSize"`
FlushInterval string `yaml:"flushInterval"`
} `yaml:"metrics"`
}

71
config.kdl Normal file
View File

@@ -0,0 +1,71 @@
server {
port 443
ssl {
enabled true
cert-file "server.crt"
key-file "server.key"
acme {
email "me@pablu.de"
key-file "userKey.key"
ca-dir-url "https://192.168.2.154:14000/dir"
tls-alpn01-port 5001
http-01-port 5002
renew-time "30s"
}
}
}
logging {
level "trace"
pretty true
requests true
file {
enabled false
max-age 14
max-backups 10
path "~/logs/router"
}
}
rate-limit {
enabled false
bucket-size 50
refill-size 50
refill-time "30s"
cleanup-time "45s"
}
location "test.localhost" "localhost" {
port 8181
remote "localhost"
}
location "private.localhost" {
port 8282
remote "localhost"
}
location "api.hitstar.localhost" {
port 9090
remote "localhost"
}
location "hitstar.localhost" "hipstar.localhost" {
port 5173
remote "localhost"
rewrite "/api" {
location "api.hitstar.localhost"
}
rewrite "/public" {
port 9090
remote "localhost"
keep-sub true
}
}
location "chat.localhost" {
port 46009
secure true
remote "localhost" "chat.pablu.de"
}

85
config.yaml Normal file
View File

@@ -0,0 +1,85 @@
server:
port: 443
ssl:
enabled: true
certFile: server.crt
keyFile: server.key
acme:
enabled: false
email: me@pablu.de
keyFile: userKey.key
caDirUrl: https://192.168.2.154:14000/dir
tlsAlpn01Port: 5001
http01Port: 5002
renewTime: 30s
logging:
level: trace
# Pretty print for human consumption otherwise json
pretty: true
# Log incoming requests
requests: true
# Log to file aswell as stderr
file:
enabled: false
maxAge: 14
maxBackups: 10
path: ~/logs/router
rateLimit:
enabled: false
# How many requests per ip adress are allowed
bucketSize: 50
# How many requests per ip address are refilled
refillSize: 50
# How often requests per ip address are refilled
refillTime: 30s
# How often Ip Addresses get cleaned up (only ip addresses with max allowed requests are cleaned up)
cleanupTime: 45s
hosts:
# Remote address to request
- remotes:
- localhost
# Port on which to request
port: 8181
# Domains which get redirected to host
domains:
- localhost
- test.localhost
- remotes:
- localhost
port: 8282
domains:
- private.localhost
- remotes:
- localhost
port: 5173
domains:
- hitstar.localhost
- hipstar.localhost
- remotes:
- 127.0.0.1
port: 46009
domains:
- chat.localhost
- remotes:
- localhost
port: 8080
domains:
- gorilla.localhost
- remotes:
- www.google.com
port: 443
# Uses https under the hood to communicate with the remote host
secure: true
domains:
- google.localhost

View File

@@ -1,61 +0,0 @@
server:
port: 80
ssl:
enabled: false
certFile: server.crt
keyFile: server.key
acme:
enabled: false
email: user@host.dev
keyFile: userKey.key
caDirUrl: https://localhost:14000/dir
tlsAlpn01Port: 5001
http01Port: 5002
renewTime: 30s
logging:
level: info
# Pretty print for human consumption otherwise json
pretty: true
# Log incoming requests
requests: true
# Log to file aswell as stderr
file:
enabled: false
maxAge: 14
maxBackups: 10
path: ~/logs/router
rateLimit:
enabled: false
# How many requests per ip adress are allowed
bucketSize: 50
# How many requests per ip address are refilled
refillSize: 50
# How often requests per ip address are refilled
refillTime: 30s
# How often Ip Addresses get cleaned up (only ip addresses with max allowed requests are cleaned up)
cleanupTime: 45s
# Experimental
metrics:
enabled: false
flushInterval: 1h
bufferSize: 512
file: ~/metrics.json
hosts:
- remotes:
- 127.0.0.1
port: 3000
domains:
- api.hitstar.xyz
- remotes:
- 127.0.0.1
port: 5173
domains:
- localhost
- hitstar.xyz
rewrite:
"/api": api.hitstar.xyz

5
go.mod
View File

@@ -1,4 +1,4 @@
module git.pablu.de/pablu/domain-router
module github.com/pablu23/domain-router
go 1.23.0
@@ -7,6 +7,7 @@ toolchain go1.24.4
require github.com/rs/zerolog v1.34.0
require (
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/miekg/dns v1.1.67 // indirect
@@ -16,9 +17,11 @@ require (
golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.34.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
)
require (
github.com/go-acme/lego v2.7.2+incompatible
github.com/go-acme/lego/v4 v4.24.0
github.com/google/uuid v1.6.0
github.com/mattn/go-colorable v0.1.13 // indirect

20
go.sum
View File

@@ -1,33 +1,33 @@
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-acme/lego v2.7.2+incompatible h1:ThhpPBgf6oa9X/vRd0kEmWOsX7+vmYdckmGZSb+FEp0=
github.com/go-acme/lego v2.7.2+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
github.com/go-acme/lego/v4 v4.24.0 h1:pe0q49JKxfSGEP3lkgkMVQrZM1KbD+e0dpJ2McYsiVw=
github.com/go-acme/lego/v4 v4.24.0/go.mod h1:hkstZY6D0jylIrZbuNmEQrWQxTIfaJH7prwaWvKDOjw=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
@@ -40,6 +40,7 @@ golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
@@ -47,9 +48,10 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,7 +1,6 @@
package middleware
import (
"context"
"net/http"
"time"
@@ -10,16 +9,7 @@ import (
"github.com/urfave/negroni"
)
type RequestLogger struct{}
func (_ *RequestLogger) Stop(ctx context.Context) {
log.Info().Msg("Stopped Logging")
}
func (_ *RequestLogger) Manage() {
}
func (_ *RequestLogger) Use(next http.Handler) http.Handler {
func RequestLogger(next http.Handler) http.Handler {
log.Info().Msg("Enabling Logging")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
@@ -32,7 +22,6 @@ func (_ *RequestLogger) Use(next http.Handler) http.Handler {
Str("uri", r.RequestURI).
Str("method", r.Method).
Str("uuid", uuid).
Str("remote_address", r.RemoteAddr).
Msg("Received Request")
next.ServeHTTP(lrw, r)

View File

@@ -1,152 +0,0 @@
package middleware
import (
"cmp"
"context"
"encoding/json"
"net/http"
"os"
"slices"
"strings"
"time"
"github.com/rs/zerolog/log"
)
type Metrics struct {
c chan RequestMetric
endpointMetrics []EndpointMetrics
ticker *time.Ticker
file string
stop chan struct{}
}
type EndpointMetrics struct {
Host string
Endpoint string
AbsoluteDuration time.Duration
Calls uint64
}
type RequestMetric struct {
Start time.Time
Stop time.Time
Host string
Method string
Uri string
Status int
Size int
}
func NewMetrics(bufferSize int, flushTimeout time.Duration, file string) *Metrics {
return &Metrics{
c: make(chan RequestMetric, bufferSize),
ticker: time.NewTicker(flushTimeout),
file: file,
}
}
func (m *Metrics) Use(next http.Handler) http.Handler {
log.Info().Msg("Enabling Request Metrics")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
rm := RequestMetric{
Start: start,
Host: r.Host,
Method: r.Method,
Uri: r.URL.Path,
}
log.Trace().Any("request_metric", rm).Msg("RequestMetric created")
next.ServeHTTP(w, r)
rm.Stop = time.Now()
log.Trace().Any("request_metric", rm).Msg("RequestMetric finished")
m.c <- rm
})
}
func (m *Metrics) Manage() {
for {
select {
case rm := <-m.c:
m.calculateDuration(rm)
case <-m.ticker.C:
m.Flush()
case <-m.stop:
return
}
}
}
func (m *Metrics) calculateDuration(rm RequestMetric) {
duration := rm.Stop.Sub(rm.Start)
// TODO: Replace this with a hash probably
index := slices.IndexFunc(m.endpointMetrics, func(e EndpointMetrics) bool {
if strings.EqualFold(e.Host, rm.Host) && strings.EqualFold(e.Endpoint, rm.Uri) {
return true
}
return false
})
var in EndpointMetrics
if index >= 0 {
in = m.endpointMetrics[index]
} else {
in = EndpointMetrics{
Host: rm.Host,
Endpoint: rm.Uri,
AbsoluteDuration: time.Duration(0),
Calls: 0,
}
}
in.AbsoluteDuration += duration
in.Calls += 1
if index >= 0 {
m.endpointMetrics[index] = in
} else {
m.endpointMetrics = append(m.endpointMetrics, in)
}
}
func (m *Metrics) Flush() {
file, err := os.Create(m.file)
if err != nil {
log.Error().Err(err).Str("file", m.file).Msg("Could not open file for flushing")
return
}
a := make([]EndpointMetrics, len(m.endpointMetrics))
copy(a, m.endpointMetrics)
slices.SortStableFunc(a, func(e1 EndpointMetrics, e2 EndpointMetrics) int {
return cmp.Compare(e1.Calls, e2.Calls)
})
err = json.NewEncoder(file).Encode(a)
if err != nil {
log.Error().Err(err).Str("file", m.file).Msg("Could not json Encode to file")
return
}
log.Debug().Str("file", m.file).Int("count", len(a)).Msg("Completed Metrics flush")
}
func (m *Metrics) Stop(ctx context.Context) {
log.Info().Msg("Stopping Request Metrics")
for len(m.c) > 0 {
select {
case rm := <-m.c:
m.calculateDuration(rm)
case <-ctx.Done():
m.stop <- struct{}{}
log.Warn().Msg("Hard Stopped Request Metrics")
return
}
}
m.Flush()
m.stop <- struct{}{}
log.Info().Msg("Stopped Request Metrics")
}

View File

@@ -1,57 +1,18 @@
package middleware
import (
"context"
"net/http"
"slices"
)
type Middleware interface {
Use(http.Handler) http.Handler
Manage()
Stop(context.Context)
}
type Middleware func(http.Handler) http.Handler
type Pipeline struct {
middleware []Middleware
}
func NewPipeline() *Pipeline {
return &Pipeline{}
}
func (p *Pipeline) AddMiddleware(m Middleware) {
p.middleware = append(p.middleware, m)
}
func (p *Pipeline) Use() func(http.Handler) http.Handler {
func Pipeline(funcs ...Middleware) Middleware {
return func(next http.Handler) http.Handler {
for _, m := range slices.Backward(p.middleware) {
next = m.Use(next)
for _, m := range slices.Backward(funcs) {
next = m(next)
}
return next
}
}
func (p *Pipeline) Stop(ctx context.Context) {
for _, m := range p.middleware {
m.Stop(ctx)
}
}
func (p *Pipeline) Manage() {
for _, m := range p.middleware {
go m.Manage()
}
}
// func Pipeline(funcs ...Middleware) func(http.Handler) http.Handler {
// return func(next http.Handler) http.Handler {
// for _, m := range slices.Backward(funcs) {
// next = m.Use(next)
// }
//
// return next
// }
// }

View File

@@ -1,7 +1,6 @@
package middleware
import (
"context"
"net/http"
"strings"
"sync"
@@ -19,11 +18,10 @@ type Limiter struct {
bucketRefill int
rwLock *sync.RWMutex
rateChannel chan string
stop chan struct{}
}
func NewLimiter(maxRequests int, refills int, refillInterval time.Duration, cleanupInterval time.Duration) *Limiter {
return &Limiter{
func NewLimiter(maxRequests int, refills int, refillInterval time.Duration, cleanupInterval time.Duration) Limiter {
return Limiter{
currentBuckets: make(map[string]*atomic.Int64),
bucketSize: maxRequests,
refillTicker: time.NewTicker(refillInterval),
@@ -34,13 +32,12 @@ func NewLimiter(maxRequests int, refills int, refillInterval time.Duration, clea
}
}
func (l *Limiter) UpdateCleanupTime(new time.Duration) {
l.cleanupTicker.Reset(new)
func (l *Limiter) Start() {
go l.Manage()
}
func (l *Limiter) Stop(ctx context.Context) {
l.stop <- struct{}{}
log.Info().Msg("Stopped Ratelimits")
func (l *Limiter) UpdateCleanupTime(new time.Duration) {
l.cleanupTicker.Reset(new)
}
func (l *Limiter) Manage() {
@@ -80,8 +77,6 @@ func (l *Limiter) Manage() {
l.rwLock.Unlock()
duration := time.Since(start)
log.Debug().Str("duration", duration.String()).Int("deleted_buckets", deletedBuckets).Msg("Cleaned up Buckets")
case <-l.stop:
return
}
}
}
@@ -98,7 +93,7 @@ func (l *Limiter) AddIfExists(ip string) bool {
return false
}
func (l *Limiter) Use(next http.Handler) http.Handler {
func (l *Limiter) RateLimiter(next http.Handler) http.Handler {
log.Info().Int("bucket_size", l.bucketSize).Int("bucket_refill", l.bucketRefill).Msg("Enabling Ratelimits")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
addr := strings.Split(r.RemoteAddr, ":")[0]

View File

@@ -24,28 +24,17 @@ type Router struct {
}
type Host struct {
Port int
Remotes []string
Secure bool
Current *atomic.Uint32
Rewrites map[string]*Host
AdditionalHeaders map[string]string
Port int
Remotes []string
Secure bool
Current *atomic.Uint32
}
func New(config *Config, client *http.Client) Router {
m := make(map[string]Host)
for _, host := range config.Hosts {
for _, domain := range host.Domains {
curr := Host{host.Port, host.Remotes, host.Secure, &atomic.Uint32{}, make(map[string]*Host), host.AdditionalHeaders}
m[domain] = curr
for subUrl, rewriteHost := range host.Rewrite {
rewrite, ok := m[rewriteHost]
if !ok {
panic("WIP: Rewrite location has to be defined before rewrite")
}
curr.Rewrites[subUrl] = &rewrite
}
m[domain] = Host{host.Port, host.Remotes, host.Secure, &atomic.Uint32{}}
}
}
@@ -98,26 +87,6 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
for subUrl, rewriteHost := range host.Rewrites {
if !strings.HasPrefix(r.URL.Path, subUrl) {
break
}
slicedPath, _ := strings.CutPrefix(r.URL.Path, subUrl)
log.Info().
Str("old_host", strings.Join(host.Remotes, ", ")).
Str("new_host", strings.Join(rewriteHost.Remotes, ", ")).
Str("sub_url", subUrl).
Str("requested_path", r.URL.Path).
Str("new_path", slicedPath).
Msg("Rewriting matched url path to different remote")
r.URL.Path = slicedPath
host = *rewriteHost
break
}
remote := host.Remotes[host.Current.Load()]
go router.roundRobin(&host)
@@ -202,11 +171,7 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
removeHopByHopHeaders(res.Header)
resultHeader := w.Header()
copyHeader(resultHeader, res.Header)
for name, val := range host.AdditionalHeaders {
resultHeader.Add(name, val)
}
copyHeader(w.Header(), res.Header)
w.WriteHeader(res.StatusCode)
err = router.copyResponse(w, res.Body)