v0.5: (BREAKING) simplified config format, improved output formatting, fixed docker watcher

This commit is contained in:
yusing 2024-09-16 03:48:39 +08:00
parent 719693deb7
commit 5be8659a99
34 changed files with 165 additions and 343 deletions

4
.gitignore vendored
View file

@ -13,4 +13,6 @@ log/
go.work.sum go.work.sum
!src/config/ !src/config/
todo.md

View file

@ -1,3 +1,5 @@
go 1.22 go 1.22.0
toolchain go1.22.6
use ./src use ./src

View file

@ -1,7 +1,7 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "go-proxy providers file", "title": "go-proxy providers file",
"anyOf": [ "oneOf": [
{ {
"type": "object" "type": "object"
}, },
@ -16,7 +16,7 @@
"properties": { "properties": {
"scheme": { "scheme": {
"title": "Proxy scheme (http, https, tcp, udp)", "title": "Proxy scheme (http, https, tcp, udp)",
"anyOf": [ "oneOf": [
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -101,7 +101,7 @@
"then": { "then": {
"properties": { "properties": {
"port": { "port": {
"anyOf": [ "oneOf": [
{ {
"type": "string", "type": "string",
"pattern": "^[0-9]{1,5}$", "pattern": "^[0-9]{1,5}$",
@ -118,7 +118,7 @@
] ]
}, },
"path": { "path": {
"anyOf": [ "oneOf": [
{ {
"type": "string", "type": "string",
"description": "Proxy path" "description": "Proxy path"

View file

@ -6,6 +6,7 @@ import (
U "github.com/yusing/go-proxy/api/v1/utils" U "github.com/yusing/go-proxy/api/v1/utils"
"github.com/yusing/go-proxy/config" "github.com/yusing/go-proxy/config"
PT "github.com/yusing/go-proxy/proxy/fields"
R "github.com/yusing/go-proxy/route" R "github.com/yusing/go-proxy/route"
) )
@ -23,20 +24,20 @@ func CheckHealth(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
U.HandleErr(w, r, U.ErrNotFound("target", target), http.StatusNotFound) U.HandleErr(w, r, U.ErrNotFound("target", target), http.StatusNotFound)
return return
case *R.HTTPRoute: case *R.HTTPRoute:
path := r.FormValue("path") path, err := PT.NewPath(r.FormValue("path"))
if path == "" { if err.IsNotNil() {
U.HandleErr(w, r, U.ErrMissingKey("path"), http.StatusBadRequest) U.HandleErr(w, r, err, http.StatusBadRequest)
return return
} }
sr, hasSr := route.GetSubroute(path) sr, hasSr := route.GetSubroute(path)
if !hasSr { if !hasSr {
U.HandleErr(w, r, U.ErrNotFound("path", path), http.StatusNotFound) U.HandleErr(w, r, U.ErrNotFound("path", string(path)), http.StatusNotFound)
return return
} }
ok = U.IsSiteHealthy(sr.TargetURL.String()) ok = U.IsSiteHealthy(sr.TargetURL.String())
case *R.StreamRoute: case *R.StreamRoute:
ok = U.IsStreamHealthy( ok = U.IsStreamHealthy(
route.Scheme.ProxyScheme.String(), string(route.Scheme.ProxyScheme),
fmt.Sprintf("%s:%v", route.Host, route.Port.ProxyPort), fmt.Sprintf("%s:%v", route.Host, route.Port.ProxyPort),
) )
} }

View file

@ -11,7 +11,7 @@ import (
func HandleErr(w http.ResponseWriter, r *http.Request, err error, code ...int) { func HandleErr(w http.ResponseWriter, r *http.Request, err error, code ...int) {
err = E.From(err).Subjectf("%s %s", r.Method, r.URL) err = E.From(err).Subjectf("%s %s", r.Method, r.URL)
logrus.WithField("?", "api").Error(err) logrus.WithField("module", "api").Error(err)
if len(code) > 0 { if len(code) > 0 {
http.Error(w, err.Error(), code[0]) http.Error(w, err.Error(), code[0])
return return

View file

@ -28,4 +28,4 @@ var providersGenMap = map[string]ProviderGenerator{
ProviderDuckdns: providerGenerator(duckdns.NewDefaultConfig, duckdns.NewDNSProviderConfig), ProviderDuckdns: providerGenerator(duckdns.NewDefaultConfig, duckdns.NewDNSProviderConfig),
} }
var Logger = logrus.WithField("?", "autocert") var Logger = logrus.WithField("module", "autocert")

View file

@ -255,4 +255,4 @@ func providerGenerator[CT any, PT challenge.Provider](
} }
} }
var logger = logrus.WithField("?", "autocert") var logger = logrus.WithField("module", "autocert")

View file

@ -32,14 +32,15 @@ type Config struct {
func New() (*Config, E.NestedError) { func New() (*Config, E.NestedError) {
cfg := &Config{ cfg := &Config{
l: logrus.WithField("?", "config"), l: logrus.WithField("module", "config"),
reader: U.NewFileReader(common.ConfigPath), reader: U.NewFileReader(common.ConfigPath),
watcher: W.NewFileWatcher(common.ConfigFileName), watcher: W.NewFileWatcher(common.ConfigFileName),
reloadReq: make(chan struct{}), reloadReq: make(chan struct{}, 1),
} }
if err := cfg.load(); err.IsNotNil() { if err := cfg.load(); err.IsNotNil() {
return nil, err return nil, err
} }
cfg.startProviders()
cfg.watchChanges() cfg.watchChanges()
return cfg, E.Nil() return cfg, E.Nil()
} }
@ -200,7 +201,7 @@ func (cfg *Config) load() E.NestedError {
} }
} }
warnings := E.NewBuilder("errors validating config") warnings := E.NewBuilder("errors loading config")
cfg.l.Debug("starting autocert") cfg.l.Debug("starting autocert")
ap, err := autocert.NewConfig(&model.AutoCert).GetProvider() ap, err := autocert.NewConfig(&model.AutoCert).GetProvider()
@ -211,7 +212,7 @@ func (cfg *Config) load() E.NestedError {
} }
cfg.autocertProvider = ap cfg.autocertProvider = ap
cfg.l.Debug("starting providers") cfg.l.Debug("loading providers")
cfg.proxyProviders = F.NewMap[string, *PR.Provider]() cfg.proxyProviders = F.NewMap[string, *PR.Provider]()
for _, filename := range model.Providers.Files { for _, filename := range model.Providers.Files {
p := PR.NewFileProvider(filename) p := PR.NewFileProvider(filename)
@ -221,12 +222,7 @@ func (cfg *Config) load() E.NestedError {
p := PR.NewDockerProvider(name, dockerHost) p := PR.NewDockerProvider(name, dockerHost)
cfg.proxyProviders.Set(p.GetName(), p) cfg.proxyProviders.Set(p.GetName(), p)
} }
cfg.proxyProviders.EachKV(func(name string, p *PR.Provider) { cfg.l.Debug("loaded providers")
if err := p.StartAllRoutes(); err.IsNotNil() {
warnings.Add(E.Failure("start routes").Subject(p).With(err))
}
})
cfg.l.Debug("started providers")
cfg.value = model cfg.value = model
@ -257,5 +253,5 @@ func (cfg *Config) startProviders() {
} }
func (cfg *Config) stopProviders() { func (cfg *Config) stopProviders() {
cfg.controlProviders("stop", (*PR.Provider).StopAllRoutes) cfg.controlProviders("stop routes", (*PR.Provider).StopAllRoutes)
} }

View file

@ -91,4 +91,4 @@ var clientOptEnvHost = []client.Opt{
client.WithAPIVersionNegotiation(), client.WithAPIVersionNegotiation(),
} }
var logger = logrus.WithField("?", "docker") var logger = logrus.WithField("module", "docker")

View file

@ -1,22 +1,24 @@
module github.com/yusing/go-proxy module github.com/yusing/go-proxy
go 1.22 go 1.22.0
toolchain go1.22.6
require ( require (
github.com/docker/cli v27.1.2+incompatible github.com/docker/cli v27.2.1+incompatible
github.com/docker/docker v27.1.2+incompatible github.com/docker/docker v27.2.1+incompatible
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/go-acme/lego/v4 v4.17.4 github.com/go-acme/lego/v4 v4.18.0
github.com/santhosh-tekuri/jsonschema v1.2.4 github.com/santhosh-tekuri/jsonschema v1.2.4
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
golang.org/x/net v0.28.0 golang.org/x/net v0.29.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudflare/cloudflare-go v0.101.0 // indirect github.com/cloudflare/cloudflare-go v0.104.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
@ -28,25 +30,25 @@ require (
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/miekg/dns v1.1.61 // indirect github.com/miekg/dns v1.1.62 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel v1.30.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.30.0 // indirect
golang.org/x/crypto v0.26.0 // indirect golang.org/x/crypto v0.27.0 // indirect
golang.org/x/mod v0.20.0 // indirect golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.17.0 // indirect golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.6.0 // indirect golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/tools v0.25.0 // indirect
gotest.tools/v3 v3.5.1 // indirect gotest.tools/v3 v3.5.1 // indirect
) )

View file

@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
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/cloudflare/cloudflare-go v0.101.0 h1:SXWNSEDkbdY84iFIZGyTdWQwDfd98ljv0/4UubpleBQ= github.com/cloudflare/cloudflare-go v0.104.0 h1:R/lB0dZupaZbOgibAH/BRrkFbZ6Acn/WsKg2iX2xXuY=
github.com/cloudflare/cloudflare-go v0.101.0/go.mod h1:xXQHnoXKR48JlWbFS42i2al3nVqimVhcYvKnIdXLw9g= github.com/cloudflare/cloudflare-go v0.104.0/go.mod h1:pfUQ4PIG4ISI0/Mmc21Bp86UnFU0ktmPf3iTgbSL+cM=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -13,10 +13,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v27.1.2+incompatible h1:nYviRv5Y+YAKx3dFrTvS1ErkyVVunKOhoweCTE1BsnI= github.com/docker/cli v27.2.1+incompatible h1:U5BPtiD0viUzjGAjV1p0MGB8eVA3L3cbIrnyWmSJI70=
github.com/docker/cli v27.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v27.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI=
github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@ -25,8 +25,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-acme/lego/v4 v4.17.4 h1:h0nePd3ObP6o7kAkndtpTzCw8shOZuWckNYeUQwo36Q= github.com/go-acme/lego/v4 v4.18.0 h1:2hH8KcdRBSb+p5o9VZIm61GAOXYALgILUCSs1Q+OYsk=
github.com/go-acme/lego/v4 v4.17.4/go.mod h1:dU94SvPNqimEeb7EVilGGSnS0nU1O5Exir0pQ4QFL4U= github.com/go-acme/lego/v4 v4.18.0/go.mod h1:Blkg3izvXpl3zxk7WKngIuwR2I/hvYVP3vRnvgBp7m8=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -51,8 +51,8 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
@ -79,37 +79,37 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -119,20 +119,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -20,13 +20,14 @@ import (
R "github.com/yusing/go-proxy/route" R "github.com/yusing/go-proxy/route"
"github.com/yusing/go-proxy/server" "github.com/yusing/go-proxy/server"
F "github.com/yusing/go-proxy/utils/functional" F "github.com/yusing/go-proxy/utils/functional"
W "github.com/yusing/go-proxy/watcher"
) )
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
args := common.GetArgs() args := common.GetArgs()
l := logrus.WithField("?", "init") l := logrus.WithField("module", "main")
if common.IsDebug { if common.IsDebug {
logrus.SetLevel(logrus.DebugLevel) logrus.SetLevel(logrus.DebugLevel)
@ -69,6 +70,7 @@ func main() {
onShutdown.Add(func() { onShutdown.Add(func() {
docker.CloseAllClients() docker.CloseAllClients()
W.StopAllFileWatchers()
cfg.Dispose() cfg.Dispose()
}) })

View file

@ -1,9 +1,9 @@
package proxy package proxy
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
E "github.com/yusing/go-proxy/error" E "github.com/yusing/go-proxy/error"
M "github.com/yusing/go-proxy/models" M "github.com/yusing/go-proxy/models"
@ -39,7 +39,7 @@ func NewEntry(m *M.ProxyEntry) (any, E.NestedError) {
if scheme.IsStream() { if scheme.IsStream() {
return validateStreamEntry(m) return validateStreamEntry(m)
} }
return validateEntry(m, *scheme) return validateEntry(m, scheme)
} }
func validateEntry(m *M.ProxyEntry, s T.Scheme) (*Entry, E.NestedError) { func validateEntry(m *M.ProxyEntry, s T.Scheme) (*Entry, E.NestedError) {
@ -55,7 +55,7 @@ func validateEntry(m *M.ProxyEntry, s T.Scheme) (*Entry, E.NestedError) {
if err.IsNotNil() { if err.IsNotNil() {
return nil, err return nil, err
} }
url, err := E.Check(url.Parse(s.String() + "://" + host.String() + ":" + strconv.Itoa(int(port)))) url, err := E.Check(url.Parse(fmt.Sprintf("%s://%s:%d", s, host, port)))
if err.IsNotNil() { if err.IsNotNil() {
return nil, err return nil, err
} }

View file

@ -6,11 +6,11 @@ import (
F "github.com/yusing/go-proxy/utils/functional" F "github.com/yusing/go-proxy/utils/functional"
) )
type Alias struct{ F.Stringable } type Alias string
type Aliases struct{ *F.Slice[Alias] } type Aliases struct{ *F.Slice[Alias] }
func NewAlias(s string) Alias { func NewAlias(s string) Alias {
return Alias{F.NewStringable(s)} return Alias(s)
} }
func NewAliases(s string) Aliases { func NewAliases(s string) Aliases {

View file

@ -2,19 +2,11 @@ package fields
import ( import (
E "github.com/yusing/go-proxy/error" E "github.com/yusing/go-proxy/error"
F "github.com/yusing/go-proxy/utils/functional"
) )
type Host struct{ F.Stringable } type Host string
type Subdomain = Alias type Subdomain = Alias
func NewHost(s string) (Host, E.NestedError) { func NewHost(s string) (Host, E.NestedError) {
return Host{F.NewStringable(s)}, E.Nil() return Host(s), E.Nil()
}
func (h Host) Subdomain() (*Subdomain, E.NestedError) {
if i := h.IndexRune(':'); i != -1 {
return &Subdomain{h.SubStr(0, i)}, E.Nil()
}
return nil, E.Invalid("host", h)
} }

View file

@ -2,14 +2,13 @@ package fields
import ( import (
E "github.com/yusing/go-proxy/error" E "github.com/yusing/go-proxy/error"
F "github.com/yusing/go-proxy/utils/functional"
) )
type Path struct{ F.Stringable } type Path string
func NewPath(s string) (Path, E.NestedError) { func NewPath(s string) (Path, E.NestedError) {
if s == "" || s[0] == '/' { if s == "" || s[0] == '/' {
return Path{F.NewStringable(s)}, E.Nil() return Path(s), E.Nil()
} }
return Path{}, E.Invalid("path", s).With("must be empty or start with '/'") return "", E.Invalid("path", s).With("must be empty or start with '/'")
} }

View file

@ -1,25 +1,24 @@
package fields package fields
import ( import (
F "github.com/yusing/go-proxy/utils/functional"
E "github.com/yusing/go-proxy/error" E "github.com/yusing/go-proxy/error"
) )
type PathMode struct{ F.Stringable } type PathMode string
func NewPathMode(pm string) (PathMode, E.NestedError) { func NewPathMode(pm string) (PathMode, E.NestedError) {
switch pm { switch pm {
case "", "forward": case "", "forward":
return PathMode{F.NewStringable(pm)}, E.Nil() return PathMode(pm), E.Nil()
default: default:
return PathMode{}, E.Invalid("path mode", pm) return "", E.Invalid("path mode", pm)
} }
} }
func (p PathMode) IsRemove() bool { func (p PathMode) IsRemove() bool {
return p.String() == "" return p == ""
} }
func (p PathMode) IsForward() bool { func (p PathMode) IsForward() bool {
return p.String() == "forward" return p == "forward"
} }

View file

@ -4,20 +4,19 @@ import (
"strings" "strings"
E "github.com/yusing/go-proxy/error" E "github.com/yusing/go-proxy/error"
F "github.com/yusing/go-proxy/utils/functional"
) )
type Scheme struct{ F.Stringable } type Scheme string
func NewScheme(s string) (*Scheme, E.NestedError) { func NewScheme(s string) (Scheme, E.NestedError) {
switch s { switch s {
case "http", "https", "tcp", "udp": case "http", "https", "tcp", "udp":
return &Scheme{F.NewStringable(s)}, E.Nil() return Scheme(s), E.Nil()
} }
return nil, E.Invalid("scheme", s) return "", E.Invalid("scheme", s)
} }
func NewSchemeFromPort(p string) (*Scheme, E.NestedError) { func NewSchemeFromPort(p string) (Scheme, E.NestedError) {
var s string var s string
switch { switch {
case strings.ContainsRune(p, ':'): case strings.ContainsRune(p, ':'):
@ -27,11 +26,11 @@ func NewSchemeFromPort(p string) (*Scheme, E.NestedError) {
default: default:
s = "http" s = "http"
} }
return &Scheme{F.NewStringable(s)}, E.Nil() return Scheme(s), E.Nil()
} }
func (s Scheme) IsHTTP() bool { return s.String() == "http" } func (s Scheme) IsHTTP() bool { return s == "http" }
func (s Scheme) IsHTTPS() bool { return s.String() == "https" } func (s Scheme) IsHTTPS() bool { return s == "https" }
func (s Scheme) IsTCP() bool { return s.String() == "tcp" } func (s Scheme) IsTCP() bool { return s == "tcp" }
func (s Scheme) IsUDP() bool { return s.String() == "udp" } func (s Scheme) IsUDP() bool { return s == "udp" }
func (s Scheme) IsStream() bool { return s.IsTCP() || s.IsUDP() } func (s Scheme) IsStream() bool { return s.IsTCP() || s.IsUDP() }

View file

@ -1,14 +1,15 @@
package fields package fields
import ( import (
"fmt"
"strings" "strings"
E "github.com/yusing/go-proxy/error" E "github.com/yusing/go-proxy/error"
) )
type StreamScheme struct { type StreamScheme struct {
ListeningScheme *Scheme `json:"listening"` ListeningScheme Scheme `json:"listening"`
ProxyScheme *Scheme `json:"proxy"` ProxyScheme Scheme `json:"proxy"`
} }
func NewStreamScheme(s string) (ss *StreamScheme, err E.NestedError) { func NewStreamScheme(s string) (ss *StreamScheme, err E.NestedError) {
@ -31,12 +32,12 @@ func NewStreamScheme(s string) (ss *StreamScheme, err E.NestedError) {
} }
func (s StreamScheme) String() string { func (s StreamScheme) String() string {
return s.ListeningScheme.String() + " -> " + s.ProxyScheme.String() return fmt.Sprintf("%s -> %s", s.ListeningScheme, s.ProxyScheme)
} }
// IsCoherent checks if the ListeningScheme and ProxyScheme of the StreamScheme are equal. // IsCoherent checks if the ListeningScheme and ProxyScheme of the StreamScheme are equal.
// //
// It returns a boolean value indicating whether the ListeningScheme and ProxyScheme are equal. // It returns a boolean value indicating whether the ListeningScheme and ProxyScheme are equal.
func (s StreamScheme) IsCoherent() bool { func (s StreamScheme) IsCoherent() bool {
return *s.ListeningScheme == *s.ProxyScheme return s.ListeningScheme == s.ProxyScheme
} }

View file

@ -100,8 +100,8 @@ func (p *DockerProvider) getEntriesFromLabels(container *types.Container, client
// init entries map for all aliases // init entries map for all aliases
aliases.ForEach(func(a PT.Alias) { aliases.ForEach(func(a PT.Alias) {
entries.Set(a.String(), &M.ProxyEntry{ entries.Set(string(a), &M.ProxyEntry{
Alias: a.String(), Alias: string(a),
Host: clientHost, Host: clientHost,
Port: fmt.Sprint(defaultPort), Port: fmt.Sprint(defaultPort),
}) })

View file

@ -40,13 +40,15 @@ const (
) )
func newProvider(name string, t ProviderType) *Provider { func newProvider(name string, t ProviderType) *Provider {
return &Provider{ p := &Provider{
name: name, name: name,
t: t, t: t,
routes: R.NewRoutes(), routes: R.NewRoutes(),
reloadReqCh: make(chan struct{}, 1), reloadReqCh: make(chan struct{}, 1),
l: logrus.WithField("provider", name),
} }
p.l = logrus.WithField("provider", p)
return p
} }
func NewFileProvider(filename string) *Provider { func NewFileProvider(filename string) *Provider {
name := path.Base(filename) name := path.Base(filename)
@ -72,7 +74,7 @@ func (p *Provider) GetType() ProviderType {
} }
func (p *Provider) String() string { func (p *Provider) String() string {
return fmt.Sprintf("%s (%s provider)", p.name, p.t) return fmt.Sprintf("%s: %s", p.t, p.name)
} }
func (p *Provider) StartAllRoutes() E.NestedError { func (p *Provider) StartAllRoutes() E.NestedError {
@ -103,25 +105,22 @@ func (p *Provider) StartAllRoutes() E.NestedError {
} }
func (p *Provider) StopAllRoutes() E.NestedError { func (p *Provider) StopAllRoutes() E.NestedError {
defer p.routes.Clear()
if p.watcherCancel != nil { if p.watcherCancel != nil {
p.watcherCancel() p.watcherCancel()
} }
errors := E.NewBuilder("errors stopping routes for provider %q", p.name) errors := E.NewBuilder("errors stopping routes for provider %q", p.name)
nStopped := 0 nStopped := 0
nFailed := 0
p.routes.EachKVParallel(func(alias string, r R.Route) { p.routes.EachKVParallel(func(alias string, r R.Route) {
if err := r.Stop(); err.IsNotNil() { if err := r.Stop(); err.IsNotNil() {
errors.Add(err.Subject(r)) errors.Add(err.Subject(r))
nFailed++
} else { } else {
nStopped++ nStopped++
} }
}) })
if err := errors.Build(); err.IsNotNil() { p.l.Infof("%d routes stopped, %d failed", nStopped, nFailed)
return err return errors.Build()
}
p.l.Infof("%d routes stopped", nStopped)
return E.Nil()
} }
func (p *Provider) ReloadRoutes() { func (p *Provider) ReloadRoutes() {
@ -146,7 +145,7 @@ func (p *Provider) GetCurrentRoutes() *R.Routes {
func (p *Provider) watchEvents() { func (p *Provider) watchEvents() {
events, errs := p.watcher.Events(p.watcherCtx) events, errs := p.watcher.Events(p.watcherCtx)
l := logrus.WithField("?", "watcher") l := p.l.WithField("module", "watcher")
for { for {
select { select {
@ -156,12 +155,15 @@ func (p *Provider) watchEvents() {
if !ok { if !ok {
return return
} }
l.Infof("watcher event: %v", event) l.Infof("watcher event: %s", event)
p.reloadReqCh <- struct{}{} p.reloadReqCh <- struct{}{}
case err, ok := <-errs: case err, ok := <-errs:
if !ok { if !ok {
return return
} }
if err.Is(context.Canceled) {
continue
}
l.Errorf("watcher error: %s", err) l.Errorf("watcher error: %s", err)
} }
} }
@ -175,17 +177,15 @@ func (p *Provider) loadRoutes() E.NestedError {
} }
p.routes = R.NewRoutes() p.routes = R.NewRoutes()
errors := E.NewBuilder("errors loading routes from provider %q", p.name) errors := E.NewBuilder("errors loading routes from %s", p)
entries.EachKV(func(a string, e *M.ProxyEntry) { entries.EachKV(func(a string, e *M.ProxyEntry) {
e.Alias = a e.Alias = a
r, err := R.NewRoute(e) r, err := R.NewRoute(e)
if err.IsNotNil() { if err.IsNotNil() {
errors.Add(err.Subject(a)) errors.Add(err.Subject(a))
p.l.Debugf("failed to load route: %s, %s", a, err)
} else { } else {
p.routes.Set(a, r) p.routes.Set(a, r)
} }
}) })
p.l.Debugf("loaded %d routes from %d entries", p.routes.Size(), entries.Size())
return errors.Build() return errors.Build()
} }

View file

@ -535,4 +535,4 @@ func IsPrint(s string) bool {
return true return true
} }
var logger = logrus.WithField("?", "http") var logger = logrus.WithField("module", "http")

View file

@ -1,102 +0,0 @@
package proxy
// import (
// "net/http"
// "net/url"
// "os"
// "reflect"
// "testing"
// "time"
// )
// var proxy Entry
// var proxyUrl, _ = url.Parse("http://127.0.0.1:8181")
// var proxyServer = NewServer(ServerOptions{
// Name: "proxy",
// HTTPAddr: ":8080",
// Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// NewReverseProxy(proxyUrl, &http.Transport{}, &proxy).ServeHTTP(w, r)
// }),
// })
// var testServer = NewServer(ServerOptions{
// Name: "test",
// HTTPAddr: ":8181",
// Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// h := r.Header
// for k, vv := range h {
// for _, v := range vv {
// w.Header().Add(k, v)
// }
// }
// w.WriteHeader(http.StatusOK)
// }),
// })
// var httpClient = http.DefaultClient
// func TestMain(m *testing.M) {
// proxyServer.Start()
// testServer.Start()
// time.Sleep(100 * time.Millisecond)
// code := m.Run()
// proxyServer.Stop()
// testServer.Stop()
// os.Exit(code)
// }
// func TestSetHeader(t *testing.T) {
// hWant := http.Header{"X-Test": []string{"foo", "bar"}, "X-Test2": []string{"baz"}}
// proxy = Entry{
// Alias: "test",
// Scheme: "http",
// Host: "127.0.0.1",
// Port: "8181",
// SetHeaders: hWant,
// }
// req, err := http.NewRequest("HEAD", "http://127.0.0.1:8080", nil)
// if err != nil {
// t.Fatal(err)
// }
// resp, err := httpClient.Do(req)
// if err != nil {
// t.Fatal(err)
// }
// hGot := resp.Header
// t.Log("headers: ", hGot)
// for k, v := range hWant {
// if !reflect.DeepEqual(hGot[k], v) {
// t.Errorf("header %s: expected %v, got %v", k, v, hGot[k])
// }
// }
// }
// func TestHideHeader(t *testing.T) {
// hHide := []string{"X-Test", "X-Test2"}
// proxy = Entry{
// Alias: "test",
// Scheme: "http",
// Host: "127.0.0.1",
// Port: "8181",
// HideHeaders: hHide,
// }
// req, err := http.NewRequest("HEAD", "http://127.0.0.1:8080", nil)
// for _, k := range hHide {
// req.Header.Set(k, "foo")
// }
// if err != nil {
// t.Fatal(err)
// }
// resp, err := httpClient.Do(req)
// if err != nil {
// t.Fatal(err)
// }
// hGot := resp.Header
// t.Log("headers: ", hGot)
// for _, v := range hHide {
// _, ok := hGot[v]
// if ok {
// t.Errorf("header %s: expected hidden, got %v", v, hGot[v])
// }
// }
// }

View file

@ -26,17 +26,15 @@ type (
} }
HTTPSubroute struct { HTTPSubroute struct {
TargetURL URL `json:"targetURL"` TargetURL *URL `json:"targetURL"`
Path PathKey `json:"path"` Path PathKey `json:"path"`
proxy *P.ReverseProxy proxy *P.ReverseProxy
} }
URL struct { URL url.URL
*url.URL PathKey = PT.Path
} SubdomainKey = PT.Alias
PathKey = string
SubdomainKey = string
HTTPSubroutes = map[PathKey]HTTPSubroute HTTPSubroutes = map[PathKey]HTTPSubroute
) )
@ -53,25 +51,25 @@ func NewHTTPRoute(entry *P.Entry) (*HTTPRoute, E.NestedError) {
rp := P.NewReverseProxy(entry.URL, tr, entry) rp := P.NewReverseProxy(entry.URL, tr, entry)
httpRoutes.Lock() httpRoutes.Lock()
defer httpRoutes.Unlock()
var r *HTTPRoute var r *HTTPRoute
r, ok := httpRoutes.UnsafeGet(entry.Alias.String()) r, ok := httpRoutes.UnsafeGet(entry.Alias)
if !ok { if !ok {
r = &HTTPRoute{ r = &HTTPRoute{
Alias: entry.Alias, Alias: entry.Alias,
Subroutes: make(HTTPSubroutes), Subroutes: make(HTTPSubroutes),
mux: http.NewServeMux(), mux: http.NewServeMux(),
} }
httpRoutes.UnsafeSet(entry.Alias.String(), r) httpRoutes.UnsafeSet(entry.Alias, r)
} }
path := entry.Path.String() path := entry.Path
if _, exists := r.Subroutes[path]; exists { if _, exists := r.Subroutes[path]; exists {
httpRoutes.Unlock()
return nil, E.Duplicated("path", path) return nil, E.Duplicated("path", path)
} }
r.mux.HandleFunc(path, rp.ServeHTTP) r.mux.HandleFunc(string(path), rp.ServeHTTP)
if err := recover(); err != nil { if err := recover(); err != nil {
httpRoutes.Unlock()
switch t := err.(type) { switch t := err.(type) {
case error: case error:
// NOTE: likely path pattern error // NOTE: likely path pattern error
@ -82,7 +80,7 @@ func NewHTTPRoute(entry *P.Entry) (*HTTPRoute, E.NestedError) {
} }
sr := HTTPSubroute{ sr := HTTPSubroute{
TargetURL: URL{entry.URL}, TargetURL: (*URL)(entry.URL),
proxy: rp, proxy: rp,
Path: path, Path: path,
} }
@ -102,21 +100,20 @@ func NewHTTPRoute(entry *P.Entry) (*HTTPRoute, E.NestedError) {
} }
r.Subroutes[path] = sr r.Subroutes[path] = sr
httpRoutes.Unlock()
return r, E.Nil() return r, E.Nil()
} }
func (r *HTTPRoute) String() string { func (r *HTTPRoute) String() string {
return fmt.Sprintf("%s (reverse proxy)", r.Alias) return string(r.Alias)
} }
func (r *HTTPRoute) Start() E.NestedError { func (r *HTTPRoute) Start() E.NestedError {
httpRoutes.Set(r.Alias.String(), r) httpRoutes.Set(r.Alias, r)
return E.Nil() return E.Nil()
} }
func (r *HTTPRoute) Stop() E.NestedError { func (r *HTTPRoute) Stop() E.NestedError {
httpRoutes.Delete(r.Alias.String()) httpRoutes.Delete(r.Alias)
return E.Nil() return E.Nil()
} }
@ -125,7 +122,11 @@ func (r *HTTPRoute) GetSubroute(path PathKey) (HTTPSubroute, bool) {
return sr, ok return sr, ok
} }
func (u URL) MarshalText() (text []byte, err error) { func (u *URL) String() string {
return (*url.URL)(u).String()
}
func (u *URL) MarshalText() (text []byte, err error) {
return []byte(u.String()), nil return []byte(u.String()), nil
} }
@ -144,7 +145,7 @@ func ProxyHandler(w http.ResponseWriter, r *http.Request) {
func findMux(host string, path PathKey) (*http.ServeMux, error) { func findMux(host string, path PathKey) (*http.ServeMux, error) {
sd := strings.Split(host, ".")[0] sd := strings.Split(host, ".")[0]
if r, ok := httpRoutes.UnsafeGet(sd); ok { if r, ok := httpRoutes.UnsafeGet(PT.Alias(sd)); ok {
return r.mux, nil return r.mux, nil
} }
return nil, E.NotExists("route", fmt.Sprintf("subdomain: %s, path: %s", sd, path)) return nil, E.NotExists("route", fmt.Sprintf("subdomain: %s, path: %s", sd, path))

View file

@ -39,18 +39,18 @@ func NewStreamRoute(entry *P.StreamEntry) (*StreamRoute, E.NestedError) {
wg: sync.WaitGroup{}, wg: sync.WaitGroup{},
stopCh: make(chan struct{}, 1), stopCh: make(chan struct{}, 1),
connCh: make(chan any), connCh: make(chan any),
l: logger.WithField("alias", entry.Alias),
} }
if entry.Scheme.ListeningScheme.IsTCP() { if entry.Scheme.ListeningScheme.IsTCP() {
base.StreamImpl = NewTCPRoute(base) base.StreamImpl = NewTCPRoute(base)
} else { } else {
base.StreamImpl = NewUDPRoute(base) base.StreamImpl = NewUDPRoute(base)
} }
base.l = logrus.WithField("route", base.StreamImpl)
return base, E.Nil() return base, E.Nil()
} }
func (r *StreamRoute) String() string { func (r *StreamRoute) String() string {
return fmt.Sprintf("%s (%v stream)", r.Alias, r.Scheme) return fmt.Sprintf("%s-stream: %s", r.Scheme, r.Alias)
} }
func (r *StreamRoute) Start() E.NestedError { func (r *StreamRoute) Start() E.NestedError {
@ -131,5 +131,3 @@ func (r *StreamRoute) grHandleConnections() {
} }
} }
} }
var logger = logrus.WithField("?", "stream")

View file

@ -53,7 +53,7 @@ func (route *TCPRoute) Handle(c interface{}) error {
serverAddr := fmt.Sprintf("%s:%v", route.Host, route.Port.ProxyPort) serverAddr := fmt.Sprintf("%s:%v", route.Host, route.Port.ProxyPort)
dialer := &net.Dialer{} dialer := &net.Dialer{}
serverConn, err := dialer.DialContext(ctx, route.Scheme.ProxyScheme.String(), serverAddr) serverConn, err := dialer.DialContext(ctx, string(route.Scheme.ProxyScheme), serverAddr)
if err != nil { if err != nil {
return err return err
} }

View file

@ -36,15 +36,15 @@ func NewUDPRoute(base *StreamRoute) StreamImpl {
} }
func (route *UDPRoute) Setup() error { func (route *UDPRoute) Setup() error {
laddr, err := net.ResolveUDPAddr(route.Scheme.ListeningScheme.String(), fmt.Sprintf(":%v", route.Port.ProxyPort)) laddr, err := net.ResolveUDPAddr(string(route.Scheme.ListeningScheme), fmt.Sprintf(":%v", route.Port.ProxyPort))
if err != nil { if err != nil {
return err return err
} }
source, err := net.ListenUDP(route.Scheme.ListeningScheme.String(), laddr) source, err := net.ListenUDP(string(route.Scheme.ListeningScheme), laddr)
if err != nil { if err != nil {
return err return err
} }
raddr, err := net.ResolveUDPAddr(route.Scheme.ProxyScheme.String(), fmt.Sprintf("%s:%v", route.Host, route.Port.ProxyPort)) raddr, err := net.ResolveUDPAddr(string(route.Scheme.ProxyScheme), fmt.Sprintf("%s:%v", route.Host, route.Port.ProxyPort))
if err != nil { if err != nil {
source.Close() source.Close()
return err return err

View file

@ -158,4 +158,4 @@ func redirectToTLSHandler(port string) http.HandlerFunc {
} }
} }
var logger = logrus.WithField("?", "server") var logger = logrus.WithField("module", "server")

View file

@ -1,68 +0,0 @@
package functional
import (
"fmt"
"strconv"
"strings"
)
type Stringable struct{ string }
func NewStringable(v any) Stringable {
switch vv := v.(type) {
case string:
return Stringable{vv}
case fmt.Stringer:
return Stringable{vv.String()}
default:
return Stringable{fmt.Sprint(vv)}
}
}
func (s Stringable) String() string {
return s.string
}
func (s Stringable) Len() int {
return len(s.string)
}
func (s Stringable) MarshalText() (text []byte, err error) {
return []byte(s.string), nil
}
func (s Stringable) SubStr(start int, end int) Stringable {
return Stringable{s.string[start:end]}
}
func (s Stringable) HasPrefix(p Stringable) bool {
return len(s.string) >= len(p.string) && s.string[0:len(p.string)] == p.string
}
func (s Stringable) HasSuffix(p Stringable) bool {
return len(s.string) >= len(p.string) && s.string[len(s.string)-len(p.string):] == p.string
}
func (s Stringable) IsEmpty() bool {
return len(s.string) == 0
}
func (s Stringable) IndexRune(r rune) int {
return strings.IndexRune(s.string, r)
}
func (s Stringable) ToInt() (int, error) {
return strconv.Atoi(s.string)
}
func (s Stringable) Split(sep string) []Stringable {
return Stringables(strings.Split(s.string, sep))
}
func Stringables(ss []string) []Stringable {
ret := make([]Stringable, len(ss))
for i, s := range ss {
ret[i] = Stringable{s}
}
return ret
}

View file

@ -4,7 +4,7 @@ import (
"context" "context"
"time" "time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
D "github.com/yusing/go-proxy/docker" D "github.com/yusing/go-proxy/docker"
E "github.com/yusing/go-proxy/error" E "github.com/yusing/go-proxy/error"
@ -30,14 +30,14 @@ func (w *DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.Nest
var err E.NestedError var err E.NestedError
for range 3 { for range 3 {
cl, err = D.ConnectClient(w.host) cl, err = D.ConnectClient(w.host)
if err.IsNotNil() { if err.IsNil() {
break break
} }
errCh <- E.From(err) errCh <- E.From(err)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
if err.IsNotNil() { if err.IsNotNil() {
errCh <- E.Failure("connect to docker") errCh <- E.Failure("connecting to docker")
return return
} }
@ -50,14 +50,8 @@ func (w *DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.Nest
errCh <- E.From(<-cErrCh) errCh <- E.From(<-cErrCh)
return return
case msg := <-cEventCh: case msg := <-cEventCh:
containerName, ok := msg.Actor.Attributes["name"]
if !ok {
// NOTE: should not happen
// but if it happens, just ignore it
continue
}
eventCh <- Event{ eventCh <- Event{
ActorName: containerName, ActorName: msg.Actor.Attributes["name"],
Action: ActionModified, Action: ActionModified,
} }
case err := <-cErrCh: case err := <-cErrCh:
@ -79,7 +73,7 @@ func (w *DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.Nest
return eventCh, errCh return eventCh, errCh
} }
var dwOptions = types.EventsOptions{Filters: filters.NewArgs( var dwOptions = events.ListOptions{Filters: filters.NewArgs(
filters.Arg("type", "container"), filters.Arg("type", "container"),
filters.Arg("event", "start"), filters.Arg("event", "start"),
filters.Arg("event", "die"), // 'stop' already triggering 'die' filters.Arg("event", "die"), // 'stop' already triggering 'die'

View file

@ -16,7 +16,7 @@ const (
ActionCreated Action = "CREATED" ActionCreated Action = "CREATED"
) )
func (e *Event) String() string { func (e Event) String() string {
return fmt.Sprintf("%s %s", e.ActorName, e.Action) return fmt.Sprintf("%s %s", e.ActorName, e.Action)
} }

View file

@ -18,6 +18,10 @@ func NewFileWatcher(filename string) Watcher {
return &fileWatcher{filename: filename} return &fileWatcher{filename: filename}
} }
func StopAllFileWatchers() {
fwHelper.close()
}
func (f *fileWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.NestedError) { func (f *fileWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.NestedError) {
return fwHelper.Add(ctx, f) return fwHelper.Add(ctx, f)
} }

View file

@ -129,4 +129,4 @@ func (h *fileWatcherHelper) start() {
} }
} }
var fsLogger = logrus.WithField("?", "fsnotify") var fsLogger = logrus.WithField("module", "fsnotify")

View file

@ -1 +1 @@
0.5.0-beta2 0.5.0-beta4