mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-19 23:11:25 +02:00
refactored some stuff, added healthcheck support, fixed 'include file' reload not showing in log
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -14,9 +15,11 @@ import (
|
||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
||||
"github.com/yusing/go-proxy/internal/net/http/loadbalancer"
|
||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
||||
url "github.com/yusing/go-proxy/internal/net/types"
|
||||
P "github.com/yusing/go-proxy/internal/proxy"
|
||||
PT "github.com/yusing/go-proxy/internal/proxy/fields"
|
||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -24,9 +27,10 @@ type (
|
||||
*P.ReverseProxyEntry
|
||||
LoadBalancer *loadbalancer.LoadBalancer `json:"load_balancer"`
|
||||
|
||||
server *loadbalancer.Server
|
||||
handler http.Handler
|
||||
rp *gphttp.ReverseProxy
|
||||
healthMon health.HealthMonitor
|
||||
server *loadbalancer.Server
|
||||
handler http.Handler
|
||||
rp *gphttp.ReverseProxy
|
||||
}
|
||||
|
||||
SubdomainKey = PT.Alias
|
||||
@@ -65,7 +69,7 @@ func NewHTTPRoute(entry *P.ReverseProxyEntry) (*HTTPRoute, E.NestedError) {
|
||||
trans = gphttp.DefaultTransport.Clone()
|
||||
}
|
||||
|
||||
rp := gphttp.NewReverseProxy(entry.URL, trans)
|
||||
rp := gphttp.NewReverseProxy(string(entry.Alias), entry.URL, trans)
|
||||
|
||||
if len(entry.Middlewares) > 0 {
|
||||
err := middleware.PatchReverseProxy(string(entry.Alias), rp, entry.Middlewares)
|
||||
@@ -81,6 +85,18 @@ func NewHTTPRoute(entry *P.ReverseProxyEntry) (*HTTPRoute, E.NestedError) {
|
||||
ReverseProxyEntry: entry,
|
||||
rp: rp,
|
||||
}
|
||||
if entry.LoadBalance.Link != "" && entry.HealthCheck.Disabled {
|
||||
logrus.Warnf("%s.healthCheck.disabled cannot be false when loadbalancer is enabled", entry.Alias)
|
||||
entry.HealthCheck.Disabled = true
|
||||
}
|
||||
if !entry.HealthCheck.Disabled {
|
||||
r.healthMon = health.NewHTTPHealthMonitor(
|
||||
context.Background(),
|
||||
string(entry.Alias),
|
||||
entry.URL,
|
||||
entry.HealthCheck,
|
||||
)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -88,6 +104,10 @@ func (r *HTTPRoute) String() string {
|
||||
return string(r.Alias)
|
||||
}
|
||||
|
||||
func (r *HTTPRoute) URL() url.URL {
|
||||
return r.ReverseProxyEntry.URL
|
||||
}
|
||||
|
||||
func (r *HTTPRoute) Start() E.NestedError {
|
||||
if r.handler != nil {
|
||||
return nil
|
||||
@@ -118,24 +138,13 @@ func (r *HTTPRoute) Start() E.NestedError {
|
||||
|
||||
if r.LoadBalance.Link == "" {
|
||||
httpRoutes.Store(string(r.Alias), r)
|
||||
return nil
|
||||
} else {
|
||||
r.addToLoadBalancer()
|
||||
}
|
||||
|
||||
var lb *loadbalancer.LoadBalancer
|
||||
linked, ok := httpRoutes.Load(r.LoadBalance.Link)
|
||||
if ok {
|
||||
lb = linked.LoadBalancer
|
||||
} else {
|
||||
lb = loadbalancer.New(r.LoadBalance)
|
||||
lb.Start()
|
||||
linked = &HTTPRoute{
|
||||
LoadBalancer: lb,
|
||||
handler: lb,
|
||||
}
|
||||
httpRoutes.Store(r.LoadBalance.Link, linked)
|
||||
if r.healthMon != nil {
|
||||
r.healthMon.Start()
|
||||
}
|
||||
r.server = loadbalancer.NewServer(string(r.Alias), r.rp.TargetURL, r.LoadBalance.Weight, r.handler)
|
||||
lb.AddServer(r.server)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -164,6 +173,10 @@ func (r *HTTPRoute) Stop() (_ E.NestedError) {
|
||||
httpRoutes.Delete(string(r.Alias))
|
||||
}
|
||||
|
||||
if r.healthMon != nil {
|
||||
r.healthMon.Stop()
|
||||
}
|
||||
|
||||
r.handler = nil
|
||||
|
||||
return
|
||||
@@ -173,8 +186,30 @@ func (r *HTTPRoute) Started() bool {
|
||||
return r.handler != nil
|
||||
}
|
||||
|
||||
func (r *HTTPRoute) addToLoadBalancer() {
|
||||
var lb *loadbalancer.LoadBalancer
|
||||
linked, ok := httpRoutes.Load(r.LoadBalance.Link)
|
||||
if ok {
|
||||
lb = linked.LoadBalancer
|
||||
} else {
|
||||
lb = loadbalancer.New(r.LoadBalance)
|
||||
lb.Start()
|
||||
linked = &HTTPRoute{
|
||||
LoadBalancer: lb,
|
||||
handler: lb,
|
||||
}
|
||||
httpRoutes.Store(r.LoadBalance.Link, linked)
|
||||
}
|
||||
r.server = loadbalancer.NewServer(string(r.Alias), r.rp.TargetURL, r.LoadBalance.Weight, r.handler, r.healthMon)
|
||||
lb.AddServer(r.server)
|
||||
}
|
||||
|
||||
func ProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
mux, err := findMuxFunc(r.Host)
|
||||
// Why use StatusNotFound instead of StatusBadRequest or StatusBadGateway?
|
||||
// On nginx, when route for domain does not exist, it returns StatusBadGateway.
|
||||
// Then scraper / scanners will know the subdomain is invalid.
|
||||
// With StatusNotFound, they won't know whether it's the path, or the subdomain that is invalid.
|
||||
if err != nil {
|
||||
if !middleware.ServeStaticErrorPageFile(w, r) {
|
||||
logrus.Error(E.Failure("request").
|
||||
|
||||
@@ -1,35 +1,30 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
url "github.com/yusing/go-proxy/internal/net/types"
|
||||
P "github.com/yusing/go-proxy/internal/proxy"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
U "github.com/yusing/go-proxy/internal/utils"
|
||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||
)
|
||||
|
||||
type (
|
||||
Route interface {
|
||||
RouteImpl
|
||||
Entry() *types.RawEntry
|
||||
Type() RouteType
|
||||
URL() *url.URL
|
||||
RouteType string
|
||||
Route struct {
|
||||
_ U.NoCopy
|
||||
impl
|
||||
Type RouteType
|
||||
Entry *types.RawEntry
|
||||
}
|
||||
Routes = F.Map[string, Route]
|
||||
Routes = F.Map[string, *Route]
|
||||
|
||||
RouteImpl interface {
|
||||
impl interface {
|
||||
Start() E.NestedError
|
||||
Stop() E.NestedError
|
||||
Started() bool
|
||||
String() string
|
||||
}
|
||||
RouteType string
|
||||
route struct {
|
||||
RouteImpl
|
||||
type_ RouteType
|
||||
entry *types.RawEntry
|
||||
URL() url.URL
|
||||
}
|
||||
)
|
||||
|
||||
@@ -38,44 +33,36 @@ const (
|
||||
RouteTypeReverseProxy RouteType = "reverse_proxy"
|
||||
)
|
||||
|
||||
// function alias
|
||||
var NewRoutes = F.NewMapOf[string, Route]
|
||||
// function alias.
|
||||
var NewRoutes = F.NewMap[Routes]
|
||||
|
||||
func NewRoute(en *types.RawEntry) (Route, E.NestedError) {
|
||||
func NewRoute(en *types.RawEntry) (*Route, E.NestedError) {
|
||||
entry, err := P.ValidateEntry(en)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var t RouteType
|
||||
var rt RouteImpl
|
||||
var rt impl
|
||||
|
||||
switch e := entry.(type) {
|
||||
case *P.StreamEntry:
|
||||
rt, err = NewStreamRoute(e)
|
||||
t = RouteTypeStream
|
||||
rt, err = NewStreamRoute(e)
|
||||
case *P.ReverseProxyEntry:
|
||||
rt, err = NewHTTPRoute(e)
|
||||
t = RouteTypeReverseProxy
|
||||
rt, err = NewHTTPRoute(e)
|
||||
default:
|
||||
panic("bug: should not reach here")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &route{RouteImpl: rt, entry: en, type_: t}, nil
|
||||
}
|
||||
|
||||
func (rt *route) Entry() *types.RawEntry {
|
||||
return rt.entry
|
||||
}
|
||||
|
||||
func (rt *route) Type() RouteType {
|
||||
return rt.type_
|
||||
}
|
||||
|
||||
func (rt *route) URL() *url.URL {
|
||||
url, _ := url.Parse(fmt.Sprintf("%s://%s:%s", rt.entry.Scheme, rt.entry.Host, rt.entry.Port))
|
||||
return url
|
||||
return &Route{
|
||||
impl: rt,
|
||||
Type: t,
|
||||
Entry: en,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func FromEntries(entries types.RawEntries) (Routes, E.NestedError) {
|
||||
@@ -85,7 +72,7 @@ func FromEntries(entries types.RawEntries) (Routes, E.NestedError) {
|
||||
entries.RangeAll(func(alias string, entry *types.RawEntry) {
|
||||
entry.Alias = alias
|
||||
r, err := NewRoute(entry)
|
||||
if err.HasError() {
|
||||
if err != nil {
|
||||
b.Add(err.Subject(alias))
|
||||
} else {
|
||||
routes.Store(alias, r)
|
||||
|
||||
@@ -10,14 +10,19 @@ import (
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
url "github.com/yusing/go-proxy/internal/net/types"
|
||||
P "github.com/yusing/go-proxy/internal/proxy"
|
||||
PT "github.com/yusing/go-proxy/internal/proxy/fields"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
)
|
||||
|
||||
type StreamRoute struct {
|
||||
*P.StreamEntry
|
||||
StreamImpl `json:"-"`
|
||||
|
||||
url url.URL
|
||||
healthMon health.HealthMonitor
|
||||
|
||||
wg sync.WaitGroup
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
@@ -40,8 +45,14 @@ func NewStreamRoute(entry *P.StreamEntry) (*StreamRoute, E.NestedError) {
|
||||
if !entry.Scheme.IsCoherent() {
|
||||
return nil, E.Unsupported("scheme", fmt.Sprintf("%v -> %v", entry.Scheme.ListeningScheme, entry.Scheme.ProxyScheme))
|
||||
}
|
||||
url, err := url.ParseURL(fmt.Sprintf("%s://%s:%d", entry.Scheme.ProxyScheme, entry.Host, entry.Port.ProxyPort))
|
||||
if err != nil {
|
||||
// !! should not happen
|
||||
panic(err)
|
||||
}
|
||||
base := &StreamRoute{
|
||||
StreamEntry: entry,
|
||||
url: url,
|
||||
connCh: make(chan any, 100),
|
||||
}
|
||||
if entry.Scheme.ListeningScheme.IsTCP() {
|
||||
@@ -49,6 +60,9 @@ func NewStreamRoute(entry *P.StreamEntry) (*StreamRoute, E.NestedError) {
|
||||
} else {
|
||||
base.StreamImpl = NewUDPRoute(base)
|
||||
}
|
||||
if !entry.Healthcheck.Disabled {
|
||||
base.healthMon = health.NewRawHealthMonitor(base.ctx, string(entry.Alias), url, entry.Healthcheck)
|
||||
}
|
||||
base.l = logrus.WithField("route", base.StreamImpl)
|
||||
return base, nil
|
||||
}
|
||||
@@ -57,6 +71,10 @@ func (r *StreamRoute) String() string {
|
||||
return fmt.Sprintf("%s stream: %s", r.Scheme, r.Alias)
|
||||
}
|
||||
|
||||
func (r *StreamRoute) URL() url.URL {
|
||||
return r.url
|
||||
}
|
||||
|
||||
func (r *StreamRoute) Start() E.NestedError {
|
||||
if r.Port.ProxyPort == PT.NoPort || r.started.Load() {
|
||||
return nil
|
||||
@@ -71,6 +89,9 @@ func (r *StreamRoute) Start() E.NestedError {
|
||||
r.wg.Add(2)
|
||||
go r.grAcceptConnections()
|
||||
go r.grHandleConnections()
|
||||
if r.healthMon != nil {
|
||||
r.healthMon.Start()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -78,7 +99,12 @@ func (r *StreamRoute) Stop() E.NestedError {
|
||||
if !r.started.Load() {
|
||||
return nil
|
||||
}
|
||||
l := r.l
|
||||
r.started.Store(false)
|
||||
|
||||
if r.healthMon != nil {
|
||||
r.healthMon.Stop()
|
||||
}
|
||||
|
||||
r.cancel()
|
||||
r.CloseListeners()
|
||||
|
||||
@@ -92,7 +118,7 @@ func (r *StreamRoute) Stop() E.NestedError {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
l.Debug("stopped listening")
|
||||
r.l.Debug("stopped listening")
|
||||
return nil
|
||||
case <-timeout:
|
||||
return E.FailedWhy("stop", "timed out")
|
||||
|
||||
@@ -27,7 +27,7 @@ type (
|
||||
UDPConnMap = F.Map[string, *UDPConn]
|
||||
)
|
||||
|
||||
var NewUDPConnMap = F.NewMapOf[string, *UDPConn]
|
||||
var NewUDPConnMap = F.NewMap[UDPConnMap]
|
||||
|
||||
func NewUDPRoute(base *StreamRoute) StreamImpl {
|
||||
return &UDPRoute{
|
||||
|
||||
Reference in New Issue
Block a user