Compare commits
10 Commits
change-con
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ebc95b9d8 | |||
| c5c6058c66 | |||
|
|
cd5be4ee7a | ||
|
|
d4d7d3e067 | ||
|
|
ea8f84f0d7 | ||
|
|
e90c211d0f | ||
|
|
f4ca559a26 | ||
|
|
28d9c58a66 | ||
|
|
572e1177ef | ||
|
|
66f2811fff |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,7 @@
|
|||||||
*.crt
|
*.crt
|
||||||
*.key
|
*.key
|
||||||
bin/
|
bin/
|
||||||
data.json
|
data.json
|
||||||
|
*.json
|
||||||
|
config.yaml
|
||||||
|
|
||||||
|
|||||||
66
README.md
66
README.md
@@ -4,10 +4,68 @@ Reverse Proxy for routing subdomains to different ports on same host machine
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
```csv
|
```csv
|
||||||
test.pablu.de;8181
|
server:
|
||||||
manga.pablu.de;8282
|
port: 80
|
||||||
pablu.de;8080
|
ssl:
|
||||||
<Url>;<local Port>
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|||||||
41
acme/acme.go
41
acme/acme.go
@@ -19,7 +19,8 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
domainrouter "github.com/pablu23/domain-router"
|
domainrouter "git.pablu.de/pablu/domain-router"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Acme struct {
|
type Acme struct {
|
||||||
@@ -32,7 +33,8 @@ type Acme struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CertDomainStorage struct {
|
type CertDomainStorage struct {
|
||||||
Domains map[string]time.Time
|
IsUserRegistered bool
|
||||||
|
Domains map[string]time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupAcme(config *domainrouter.Config) (*Acme, error) {
|
func SetupAcme(config *domainrouter.Config) (*Acme, error) {
|
||||||
@@ -93,12 +95,6 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
|
|||||||
return nil, err
|
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)
|
domains := make([]string, 0)
|
||||||
for _, host := range config.Hosts {
|
for _, host := range config.Hosts {
|
||||||
domains = append(domains, host.Domains...)
|
domains = append(domains, host.Domains...)
|
||||||
@@ -113,6 +109,7 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
|
|||||||
renewTicker: time.NewTicker(d),
|
renewTicker: time.NewTicker(d),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUserRegistered := false
|
||||||
_, err = os.Stat("data.json")
|
_, err = os.Stat("data.json")
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -127,6 +124,7 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
isUserRegistered = data.IsUserRegistered
|
||||||
|
|
||||||
mustRenew := false
|
mustRenew := false
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
@@ -135,6 +133,9 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
|
|||||||
mustRenew = true
|
mustRenew = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
mustRenew = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +144,24 @@ 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{
|
request := certificate.ObtainRequest{
|
||||||
Domains: domains,
|
Domains: domains,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
@@ -169,8 +188,10 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
|
|||||||
dataDomains[domain] = now
|
dataDomains[domain] = now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User registration is hella scuffed
|
||||||
data := CertDomainStorage{
|
data := CertDomainStorage{
|
||||||
Domains: dataDomains,
|
Domains: dataDomains,
|
||||||
|
IsUserRegistered: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Create("data.json")
|
file, err := os.Create("data.json")
|
||||||
@@ -187,11 +208,11 @@ func SetupAcme(config *domainrouter.Config) (*Acme, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Acme) RenewAcme() error {
|
func (a *Acme) RenewAcme() error {
|
||||||
|
|
||||||
request := certificate.ObtainRequest{
|
request := certificate.ObtainRequest{
|
||||||
Domains: a.domains,
|
Domains: a.domains,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
certificates, err := a.client.Certificate.Obtain(request)
|
certificates, err := a.client.Certificate.Obtain(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
domainrouter "github.com/pablu23/domain-router"
|
domainrouter "git.pablu.de/pablu/domain-router"
|
||||||
"github.com/pablu23/domain-router/acme"
|
"git.pablu.de/pablu/domain-router/acme"
|
||||||
"github.com/pablu23/domain-router/middleware"
|
"git.pablu.de/pablu/domain-router/middleware"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
@@ -37,17 +42,38 @@ func main() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sigs := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
router := domainrouter.New(config, client)
|
router := domainrouter.New(config, client)
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/", router.ServeHTTP)
|
mux.HandleFunc("/", router.ServeHTTP)
|
||||||
|
|
||||||
pipeline := configureMiddleware(config)
|
pipeline := configureMiddleware(config)
|
||||||
|
|
||||||
|
pipeline.Manage()
|
||||||
server := http.Server{
|
server := http.Server{
|
||||||
Addr: fmt.Sprintf(":%d", config.Server.Port),
|
Addr: fmt.Sprintf(":%d", config.Server.Port),
|
||||||
Handler: pipeline(mux),
|
// this is rather bad looking
|
||||||
|
Handler: pipeline.Use()(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 {
|
if config.Server.Ssl.Enabled {
|
||||||
server.TLSConfig = &tls.Config{
|
server.TLSConfig = &tls.Config{
|
||||||
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
@@ -71,16 +97,23 @@ func main() {
|
|||||||
}
|
}
|
||||||
log.Info().Int("port", config.Server.Port).Str("cert", config.Server.Ssl.CertFile).Str("key", config.Server.Ssl.KeyFile).Msg("Starting server")
|
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("", "")
|
err := server.ListenAndServeTLS("", "")
|
||||||
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")
|
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")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Info().Int("port", config.Server.Port).Msg("Starting server")
|
log.Info().Int("port", config.Server.Port).Msg("Starting server")
|
||||||
err := server.ListenAndServe()
|
err := server.ListenAndServe()
|
||||||
log.Fatal().Err(err).Int("port", config.Server.Port).Msg("Could not start server")
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
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.Middleware {
|
func configureMiddleware(config *domainrouter.Config) *middleware.Pipeline {
|
||||||
middlewares := make([]middleware.Middleware, 0)
|
pipeline := middleware.NewPipeline()
|
||||||
|
|
||||||
if config.RateLimit.Enabled {
|
if config.RateLimit.Enabled {
|
||||||
refillTicker, err := time.ParseDuration(config.RateLimit.RefillTicker)
|
refillTicker, err := time.ParseDuration(config.RateLimit.RefillTicker)
|
||||||
@@ -93,15 +126,22 @@ func configureMiddleware(config *domainrouter.Config) middleware.Middleware {
|
|||||||
log.Fatal().Err(err).Str("cleanup", config.RateLimit.CleanupTicker).Msg("Could not parse cleanup Ticker")
|
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)
|
limiter := middleware.NewLimiter(config.RateLimit.BucketSize, config.RateLimit.BucketRefill, refillTicker, cleanupTicker)
|
||||||
limiter.Start()
|
pipeline.AddMiddleware(limiter)
|
||||||
middlewares = append(middlewares, limiter.RateLimiter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Logging.Requests {
|
if config.Logging.Requests {
|
||||||
middlewares = append(middlewares, middleware.RequestLogger)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeline := middleware.Pipeline(middlewares...)
|
|
||||||
return pipeline
|
return pipeline
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
47
config.go
47
config.go
@@ -1,28 +1,31 @@
|
|||||||
package domainrouter
|
package domainrouter
|
||||||
|
|
||||||
type Location struct {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server struct {
|
Server struct {
|
||||||
Port int `yaml:"port" kdl:"port"`
|
Port int `yaml:"port"`
|
||||||
Ssl struct {
|
Ssl struct {
|
||||||
Enabled bool `yaml:"enabled" kdl:"enabled"`
|
Enabled bool `yaml:"enabled"`
|
||||||
CertFile string `yaml:"certFile" kdl:"cert-file"`
|
CertFile string `yaml:"certFile"`
|
||||||
KeyFile string `yaml:"keyFile" kdl:"key-file"`
|
KeyFile string `yaml:"keyFile"`
|
||||||
Acme struct {
|
Acme struct {
|
||||||
Enabled bool `yaml:"enabled" kdl:"enabled"`
|
Enabled bool `yaml:"enabled"`
|
||||||
Email string `yaml:"email" kdl:"email"`
|
Email string `yaml:"email"`
|
||||||
KeyFile string `yaml:"keyFile" kdl:"key-file"`
|
KeyFile string `yaml:"keyFile"`
|
||||||
CADirURL string `yaml:"caDirUrl" kdl:"ca-dir-url"`
|
CADirURL string `yaml:"caDirUrl"`
|
||||||
Http01Port string `yaml:"http01Port" kdl:"http-01-port"`
|
Http01Port string `yaml:"http01Port"`
|
||||||
TlsAlpn01Port string `yaml:"tlsAlpn01Port" kdl:"tls-apln01-port"`
|
TlsAlpn01Port string `yaml:"tlsAlpn01Port"`
|
||||||
RenewTime string `yaml:"renewTime" kdl:"renew-time"`
|
RenewTime string `yaml:"renewTime"`
|
||||||
} `yaml:"acme" kdl:"acme"`
|
} `yaml:"acme"`
|
||||||
} `yaml:"ssl" kdl:"ssl"`
|
} `yaml:"ssl"`
|
||||||
} `yaml:"server" kdl:"server"`
|
} `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"`
|
||||||
RateLimit struct {
|
RateLimit struct {
|
||||||
Enabled bool `yaml:"enabled"`
|
Enabled bool `yaml:"enabled"`
|
||||||
BucketSize int `yaml:"bucketSize"`
|
BucketSize int `yaml:"bucketSize"`
|
||||||
@@ -41,4 +44,10 @@ type Config struct {
|
|||||||
MaxBackups int `yamls:"maxBackups"`
|
MaxBackups int `yamls:"maxBackups"`
|
||||||
} `yaml:"file"`
|
} `yaml:"file"`
|
||||||
} `yaml:"logging"`
|
} `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
71
config.kdl
@@ -1,71 +0,0 @@
|
|||||||
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
85
config.yaml
@@ -1,85 +0,0 @@
|
|||||||
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
|
|
||||||
61
example.config.yaml
Normal file
61
example.config.yaml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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
5
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module github.com/pablu23/domain-router
|
module git.pablu.de/pablu/domain-router
|
||||||
|
|
||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
@@ -7,7 +7,6 @@ toolchain go1.24.4
|
|||||||
require github.com/rs/zerolog v1.34.0
|
require github.com/rs/zerolog v1.34.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||||
github.com/miekg/dns v1.1.67 // indirect
|
github.com/miekg/dns v1.1.67 // indirect
|
||||||
@@ -17,11 +16,9 @@ require (
|
|||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/text v0.27.0 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
golang.org/x/tools v0.34.0 // indirect
|
golang.org/x/tools v0.34.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-acme/lego v2.7.2+incompatible
|
|
||||||
github.com/go-acme/lego/v4 v4.24.0
|
github.com/go-acme/lego/v4 v4.24.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
|||||||
20
go.sum
20
go.sum
@@ -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 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
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/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/go-acme/lego v2.7.2+incompatible h1:ThhpPBgf6oa9X/vRd0kEmWOsX7+vmYdckmGZSb+FEp0=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/go-acme/lego v2.7.2+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-acme/lego/v4 v4.24.0 h1:pe0q49JKxfSGEP3lkgkMVQrZM1KbD+e0dpJ2McYsiVw=
|
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-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 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
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.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.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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
||||||
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
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.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
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 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
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 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
|
||||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
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=
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
@@ -40,7 +40,6 @@ golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
|||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
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.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.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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
@@ -48,10 +47,9 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
|||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
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 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
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/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 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -9,7 +10,16 @@ import (
|
|||||||
"github.com/urfave/negroni"
|
"github.com/urfave/negroni"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RequestLogger(next http.Handler) http.Handler {
|
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 {
|
||||||
log.Info().Msg("Enabling Logging")
|
log.Info().Msg("Enabling Logging")
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
@@ -22,6 +32,7 @@ func RequestLogger(next http.Handler) http.Handler {
|
|||||||
Str("uri", r.RequestURI).
|
Str("uri", r.RequestURI).
|
||||||
Str("method", r.Method).
|
Str("method", r.Method).
|
||||||
Str("uuid", uuid).
|
Str("uuid", uuid).
|
||||||
|
Str("remote_address", r.RemoteAddr).
|
||||||
Msg("Received Request")
|
Msg("Received Request")
|
||||||
|
|
||||||
next.ServeHTTP(lrw, r)
|
next.ServeHTTP(lrw, r)
|
||||||
|
|||||||
152
middleware/metrics.go
Normal file
152
middleware/metrics.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
@@ -1,18 +1,57 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Middleware func(http.Handler) http.Handler
|
type Middleware interface {
|
||||||
|
Use(http.Handler) http.Handler
|
||||||
|
Manage()
|
||||||
|
Stop(context.Context)
|
||||||
|
}
|
||||||
|
|
||||||
func Pipeline(funcs ...Middleware) Middleware {
|
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 {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
for _, m := range slices.Backward(funcs) {
|
for _, m := range slices.Backward(p.middleware) {
|
||||||
next = m(next)
|
next = m.Use(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -18,10 +19,11 @@ type Limiter struct {
|
|||||||
bucketRefill int
|
bucketRefill int
|
||||||
rwLock *sync.RWMutex
|
rwLock *sync.RWMutex
|
||||||
rateChannel chan string
|
rateChannel chan string
|
||||||
|
stop chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLimiter(maxRequests int, refills int, refillInterval time.Duration, cleanupInterval time.Duration) Limiter {
|
func NewLimiter(maxRequests int, refills int, refillInterval time.Duration, cleanupInterval time.Duration) *Limiter {
|
||||||
return Limiter{
|
return &Limiter{
|
||||||
currentBuckets: make(map[string]*atomic.Int64),
|
currentBuckets: make(map[string]*atomic.Int64),
|
||||||
bucketSize: maxRequests,
|
bucketSize: maxRequests,
|
||||||
refillTicker: time.NewTicker(refillInterval),
|
refillTicker: time.NewTicker(refillInterval),
|
||||||
@@ -32,14 +34,15 @@ func NewLimiter(maxRequests int, refills int, refillInterval time.Duration, clea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Limiter) Start() {
|
|
||||||
go l.Manage()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Limiter) UpdateCleanupTime(new time.Duration) {
|
func (l *Limiter) UpdateCleanupTime(new time.Duration) {
|
||||||
l.cleanupTicker.Reset(new)
|
l.cleanupTicker.Reset(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Limiter) Stop(ctx context.Context) {
|
||||||
|
l.stop <- struct{}{}
|
||||||
|
log.Info().Msg("Stopped Ratelimits")
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Limiter) Manage() {
|
func (l *Limiter) Manage() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -77,6 +80,8 @@ func (l *Limiter) Manage() {
|
|||||||
l.rwLock.Unlock()
|
l.rwLock.Unlock()
|
||||||
duration := time.Since(start)
|
duration := time.Since(start)
|
||||||
log.Debug().Str("duration", duration.String()).Int("deleted_buckets", deletedBuckets).Msg("Cleaned up Buckets")
|
log.Debug().Str("duration", duration.String()).Int("deleted_buckets", deletedBuckets).Msg("Cleaned up Buckets")
|
||||||
|
case <-l.stop:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +98,7 @@ func (l *Limiter) AddIfExists(ip string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Limiter) RateLimiter(next http.Handler) http.Handler {
|
func (l *Limiter) Use(next http.Handler) http.Handler {
|
||||||
log.Info().Int("bucket_size", l.bucketSize).Int("bucket_refill", l.bucketRefill).Msg("Enabling Ratelimits")
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
addr := strings.Split(r.RemoteAddr, ":")[0]
|
addr := strings.Split(r.RemoteAddr, ":")[0]
|
||||||
|
|||||||
47
router.go
47
router.go
@@ -24,17 +24,28 @@ type Router struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Host struct {
|
type Host struct {
|
||||||
Port int
|
Port int
|
||||||
Remotes []string
|
Remotes []string
|
||||||
Secure bool
|
Secure bool
|
||||||
Current *atomic.Uint32
|
Current *atomic.Uint32
|
||||||
|
Rewrites map[string]*Host
|
||||||
|
AdditionalHeaders map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config *Config, client *http.Client) Router {
|
func New(config *Config, client *http.Client) Router {
|
||||||
m := make(map[string]Host)
|
m := make(map[string]Host)
|
||||||
for _, host := range config.Hosts {
|
for _, host := range config.Hosts {
|
||||||
for _, domain := range host.Domains {
|
for _, domain := range host.Domains {
|
||||||
m[domain] = Host{host.Port, host.Remotes, host.Secure, &atomic.Uint32{}}
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +98,26 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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()]
|
remote := host.Remotes[host.Current.Load()]
|
||||||
go router.roundRobin(&host)
|
go router.roundRobin(&host)
|
||||||
|
|
||||||
@@ -171,7 +202,11 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
removeHopByHopHeaders(res.Header)
|
removeHopByHopHeaders(res.Header)
|
||||||
|
|
||||||
copyHeader(w.Header(), res.Header)
|
resultHeader := w.Header()
|
||||||
|
copyHeader(resultHeader, res.Header)
|
||||||
|
for name, val := range host.AdditionalHeaders {
|
||||||
|
resultHeader.Add(name, val)
|
||||||
|
}
|
||||||
|
|
||||||
w.WriteHeader(res.StatusCode)
|
w.WriteHeader(res.StatusCode)
|
||||||
err = router.copyResponse(w, res.Body)
|
err = router.copyResponse(w, res.Body)
|
||||||
|
|||||||
Reference in New Issue
Block a user