mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-18 14:39:49 +02:00
refactor(api): restructured API for type safety, maintainability and docs generation
- These changes makes the API incombatible with previous versions - Added new types for error handling, success responses, and health checks. - Updated health check logic to utilize the new types for better clarity and structure. - Refactored existing handlers to improve response consistency and error handling. - Updated Makefile to include a new target for generating API types from Swagger. - Updated "new agent" API to respond an encrypted cert pair
This commit is contained in:
@@ -3,20 +3,21 @@ package route
|
||||
import (
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
)
|
||||
|
||||
func checkExists(r routes.Route) gperr.Error {
|
||||
func checkExists(r types.Route) gperr.Error {
|
||||
if r.UseLoadBalance() { // skip checking for load balanced routes
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
existing routes.Route
|
||||
existing types.Route
|
||||
ok bool
|
||||
)
|
||||
switch r := r.(type) {
|
||||
case routes.HTTPRoute:
|
||||
case types.HTTPRoute:
|
||||
existing, ok = routes.HTTP.Get(r.Key())
|
||||
case routes.StreamRoute:
|
||||
case types.StreamRoute:
|
||||
existing, ok = routes.Stream.Get(r.Key())
|
||||
}
|
||||
if ok {
|
||||
|
||||
@@ -14,7 +14,7 @@ type AgentProvider struct {
|
||||
}
|
||||
|
||||
func (p *AgentProvider) ShortName() string {
|
||||
return p.AgentConfig.Name()
|
||||
return p.AgentConfig.Name
|
||||
}
|
||||
|
||||
func (p *AgentProvider) NewWatcher() watcher.Watcher {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
"github.com/yusing/go-proxy/internal/route"
|
||||
"github.com/yusing/go-proxy/internal/serialization"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
"github.com/yusing/go-proxy/internal/watcher"
|
||||
)
|
||||
@@ -78,7 +79,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
}
|
||||
|
||||
if container.IsHostNetworkMode {
|
||||
err := container.UpdatePorts()
|
||||
err := docker.UpdatePorts(container)
|
||||
if err != nil {
|
||||
errs.Add(gperr.PrependSubject(container.ContainerName, err))
|
||||
continue
|
||||
@@ -111,7 +112,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
|
||||
// Returns a list of proxy entries for a container.
|
||||
// Always non-nil.
|
||||
func (p *DockerProvider) routesFromContainerLabels(container *docker.Container) (route.Routes, gperr.Error) {
|
||||
func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (route.Routes, gperr.Error) {
|
||||
if !container.IsExplicit && p.IsExplicitOnly() {
|
||||
return make(route.Routes, 0), nil
|
||||
}
|
||||
@@ -138,10 +139,10 @@ func (p *DockerProvider) routesFromContainerLabels(container *docker.Container)
|
||||
continue
|
||||
}
|
||||
|
||||
entryMap, ok := entryMapAny.(docker.LabelMap)
|
||||
entryMap, ok := entryMapAny.(types.LabelMap)
|
||||
if !ok {
|
||||
// try to deserialize to map
|
||||
entryMap = make(docker.LabelMap)
|
||||
entryMap = make(types.LabelMap)
|
||||
yamlStr, ok := entryMapAny.(string)
|
||||
if !ok {
|
||||
// should not happen
|
||||
|
||||
@@ -10,11 +10,12 @@ import (
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||
"github.com/yusing/go-proxy/internal/docker"
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
"github.com/yusing/go-proxy/internal/route"
|
||||
provider "github.com/yusing/go-proxy/internal/route/provider/types"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
W "github.com/yusing/go-proxy/internal/watcher"
|
||||
"github.com/yusing/go-proxy/internal/watcher/events"
|
||||
)
|
||||
@@ -45,7 +46,7 @@ const (
|
||||
|
||||
var ErrEmptyProviderName = errors.New("empty provider name")
|
||||
|
||||
var _ routes.Provider = (*Provider)(nil)
|
||||
var _ types.RouteProvider = (*Provider)(nil)
|
||||
|
||||
func newProvider(t provider.Type) *Provider {
|
||||
return &Provider{t: t}
|
||||
@@ -76,7 +77,7 @@ func NewAgentProvider(cfg *agent.AgentConfig) *Provider {
|
||||
p := newProvider(provider.ProviderTypeAgent)
|
||||
agent := &AgentProvider{
|
||||
AgentConfig: cfg,
|
||||
docker: DockerProviderImpl(cfg.Name(), cfg.FakeDockerHost()),
|
||||
docker: DockerProviderImpl(cfg.Name, cfg.FakeDockerHost()),
|
||||
}
|
||||
p.ProviderImpl = agent
|
||||
p.watcher = p.NewWatcher()
|
||||
@@ -145,7 +146,7 @@ func (p *Provider) NumRoutes() int {
|
||||
return len(p.routes)
|
||||
}
|
||||
|
||||
func (p *Provider) IterRoutes(yield func(string, routes.Route) bool) {
|
||||
func (p *Provider) IterRoutes(yield func(string, types.Route) bool) {
|
||||
routes := p.lockCloneRoutes()
|
||||
for alias, r := range routes {
|
||||
if !yield(alias, r.Impl()) {
|
||||
@@ -154,7 +155,7 @@ func (p *Provider) IterRoutes(yield func(string, routes.Route) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) FindService(project, service string) (routes.Route, bool) {
|
||||
func (p *Provider) FindService(project, service string) (types.Route, bool) {
|
||||
switch p.GetType() {
|
||||
case provider.ProviderTypeDocker, provider.ProviderTypeAgent:
|
||||
default:
|
||||
@@ -166,17 +167,17 @@ func (p *Provider) FindService(project, service string) (routes.Route, bool) {
|
||||
routes := p.lockCloneRoutes()
|
||||
for _, r := range routes {
|
||||
cont := r.ContainerInfo()
|
||||
if cont.DockerComposeProject() != project {
|
||||
if docker.DockerComposeProject(cont) != project {
|
||||
continue
|
||||
}
|
||||
if cont.DockerComposeService() == service {
|
||||
if docker.DockerComposeService(cont) == service {
|
||||
return r.Impl(), true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (p *Provider) GetRoute(alias string) (routes.Route, bool) {
|
||||
func (p *Provider) GetRoute(alias string) (types.Route, bool) {
|
||||
r, ok := p.lockGetRoute(alias)
|
||||
if !ok {
|
||||
return nil, false
|
||||
|
||||
@@ -1,61 +1,12 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
R "github.com/yusing/go-proxy/internal/route"
|
||||
provider "github.com/yusing/go-proxy/internal/route/provider/types"
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
)
|
||||
|
||||
type (
|
||||
RouteStats struct {
|
||||
Total uint16 `json:"total"`
|
||||
NumHealthy uint16 `json:"healthy"`
|
||||
NumUnhealthy uint16 `json:"unhealthy"`
|
||||
NumNapping uint16 `json:"napping"`
|
||||
NumError uint16 `json:"error"`
|
||||
NumUnknown uint16 `json:"unknown"`
|
||||
}
|
||||
ProviderStats struct {
|
||||
Total uint16 `json:"total"`
|
||||
RPs RouteStats `json:"reverse_proxies"`
|
||||
Streams RouteStats `json:"streams"`
|
||||
Type provider.Type `json:"type"`
|
||||
}
|
||||
)
|
||||
|
||||
func (stats *RouteStats) Add(r *R.Route) {
|
||||
stats.Total++
|
||||
mon := r.HealthMonitor()
|
||||
if mon == nil {
|
||||
stats.NumUnknown++
|
||||
return
|
||||
}
|
||||
switch mon.Status() {
|
||||
case health.StatusHealthy:
|
||||
stats.NumHealthy++
|
||||
case health.StatusUnhealthy:
|
||||
stats.NumUnhealthy++
|
||||
case health.StatusNapping:
|
||||
stats.NumNapping++
|
||||
case health.StatusError:
|
||||
stats.NumError++
|
||||
default:
|
||||
stats.NumUnknown++
|
||||
}
|
||||
}
|
||||
|
||||
func (stats *RouteStats) AddOther(other RouteStats) {
|
||||
stats.Total += other.Total
|
||||
stats.NumHealthy += other.NumHealthy
|
||||
stats.NumUnhealthy += other.NumUnhealthy
|
||||
stats.NumNapping += other.NumNapping
|
||||
stats.NumError += other.NumError
|
||||
stats.NumUnknown += other.NumUnknown
|
||||
}
|
||||
|
||||
func (p *Provider) Statistics() ProviderStats {
|
||||
var rps, streams RouteStats
|
||||
func (p *Provider) Statistics() types.ProviderStats {
|
||||
var rps, streams types.RouteStats
|
||||
for _, r := range p.routes {
|
||||
switch r.Type() {
|
||||
case route.RouteTypeHTTP:
|
||||
@@ -64,7 +15,7 @@ func (p *Provider) Statistics() ProviderStats {
|
||||
streams.Add(r)
|
||||
}
|
||||
}
|
||||
return ProviderStats{
|
||||
return types.ProviderStats{
|
||||
Total: rps.Total + streams.Total,
|
||||
RPs: rps,
|
||||
Streams: streams,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package provider
|
||||
|
||||
type Type string
|
||||
type Type string // @name ProviderType
|
||||
|
||||
const (
|
||||
ProviderTypeDocker Type = "docker"
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
"github.com/yusing/go-proxy/internal/logging/accesslog"
|
||||
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
|
||||
"github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer"
|
||||
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
|
||||
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
|
||||
nettypes "github.com/yusing/go-proxy/internal/net/types"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ type ReveseProxyRoute struct {
|
||||
rp *reverseproxy.ReverseProxy
|
||||
}
|
||||
|
||||
var _ routes.ReverseProxyRoute = (*ReveseProxyRoute)(nil)
|
||||
var _ types.ReverseProxyRoute = (*ReveseProxyRoute)(nil)
|
||||
|
||||
// var globalMux = http.NewServeMux() // TODO: support regex subdomain matching.
|
||||
|
||||
@@ -196,7 +196,7 @@ func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) {
|
||||
}
|
||||
r.loadBalancer = lb
|
||||
|
||||
server := loadbalance.NewServer(r.task.Name(), r.ProxyURL, r.LoadBalance.Weight, r.handler, r.HealthMon)
|
||||
server := loadbalancer.NewServer(r.task.Name(), r.ProxyURL, r.LoadBalance.Weight, r.handler, r.HealthMon)
|
||||
lb.AddServer(server)
|
||||
r.task.OnCancel("lb_remove_server", func() {
|
||||
lb.RemoveServer(server)
|
||||
|
||||
@@ -14,18 +14,16 @@ import (
|
||||
"github.com/yusing/go-proxy/internal/docker"
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
"github.com/yusing/go-proxy/internal/homepage"
|
||||
idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types"
|
||||
netutils "github.com/yusing/go-proxy/internal/net"
|
||||
nettypes "github.com/yusing/go-proxy/internal/net/types"
|
||||
"github.com/yusing/go-proxy/internal/proxmox"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
config "github.com/yusing/go-proxy/internal/config/types"
|
||||
"github.com/yusing/go-proxy/internal/logging/accesslog"
|
||||
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
"github.com/yusing/go-proxy/internal/route/rules"
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
@@ -43,39 +41,41 @@ type (
|
||||
Root string `json:"root,omitempty"`
|
||||
|
||||
route.HTTPConfig
|
||||
PathPatterns []string `json:"path_patterns,omitempty"`
|
||||
Rules rules.Rules `json:"rules,omitempty" validate:"omitempty,unique=Name"`
|
||||
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
||||
LoadBalance *loadbalance.Config `json:"load_balance,omitempty"`
|
||||
Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty"`
|
||||
Homepage *homepage.ItemConfig `json:"homepage,omitempty"`
|
||||
AccessLog *accesslog.RequestLoggerConfig `json:"access_log,omitempty"`
|
||||
PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"`
|
||||
Rules rules.Rules `json:"rules,omitempty" validate:"omitempty,unique=Name" extension:"x-nullable"`
|
||||
HealthCheck *types.HealthCheckConfig `json:"healthcheck"`
|
||||
LoadBalance *types.LoadBalancerConfig `json:"load_balance,omitempty" extensions:"x-nullable"`
|
||||
Middlewares map[string]types.LabelMap `json:"middlewares,omitempty" extensions:"x-nullable"`
|
||||
Homepage *homepage.ItemConfig `json:"homepage"`
|
||||
AccessLog *accesslog.RequestLoggerConfig `json:"access_log,omitempty" extensions:"x-nullable"`
|
||||
Agent string `json:"agent,omitempty"`
|
||||
|
||||
Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"`
|
||||
HealthMon health.HealthMonitor `json:"health,omitempty"`
|
||||
Idlewatcher *types.IdlewatcherConfig `json:"idlewatcher,omitempty" extensions:"x-nullable"`
|
||||
HealthMon types.HealthMonitor `json:"health,omitempty" swaggerignore:"true"`
|
||||
// for swagger
|
||||
HealthJSON *types.HealthJSON `form:"health"`
|
||||
|
||||
Metadata `deserialize:"-"`
|
||||
}
|
||||
|
||||
Metadata struct {
|
||||
/* Docker only */
|
||||
Container *docker.Container `json:"container,omitempty"`
|
||||
Container *types.Container `json:"container,omitempty" extensions:"x-nullable"`
|
||||
|
||||
Provider string `json:"provider,omitempty"` // for backward compatibility
|
||||
Provider string `json:"provider,omitempty" extensions:"x-nullable"` // for backward compatibility
|
||||
|
||||
// private fields
|
||||
LisURL *nettypes.URL `json:"lurl,omitempty"`
|
||||
ProxyURL *nettypes.URL `json:"purl,omitempty"`
|
||||
LisURL *nettypes.URL `json:"lurl,omitempty" swaggertype:"string" extensions:"x-nullable"`
|
||||
ProxyURL *nettypes.URL `json:"purl,omitempty" swaggertype:"string"`
|
||||
|
||||
Excluded *bool `json:"excluded"`
|
||||
|
||||
impl routes.Route
|
||||
impl types.Route
|
||||
task *task.Task
|
||||
|
||||
isValidated bool
|
||||
lastError gperr.Error
|
||||
provider routes.Provider
|
||||
provider types.RouteProvider
|
||||
|
||||
agent *agent.AgentConfig
|
||||
|
||||
@@ -212,7 +212,7 @@ func (r *Route) Validate() gperr.Error {
|
||||
|
||||
errs := gperr.NewBuilder("entry validation failed")
|
||||
|
||||
var impl routes.Route
|
||||
var impl types.Route
|
||||
var err gperr.Error
|
||||
|
||||
switch r.Scheme {
|
||||
@@ -263,7 +263,7 @@ func (r *Route) Validate() gperr.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Route) Impl() routes.Route {
|
||||
func (r *Route) Impl() types.Route {
|
||||
return r.impl
|
||||
}
|
||||
|
||||
@@ -318,11 +318,11 @@ func (r *Route) Started() <-chan struct{} {
|
||||
return r.started
|
||||
}
|
||||
|
||||
func (r *Route) GetProvider() routes.Provider {
|
||||
func (r *Route) GetProvider() types.RouteProvider {
|
||||
return r.provider
|
||||
}
|
||||
|
||||
func (r *Route) SetProvider(p routes.Provider) {
|
||||
func (r *Route) SetProvider(p types.RouteProvider) {
|
||||
r.provider = p
|
||||
r.Provider = p.ShortName()
|
||||
}
|
||||
@@ -384,26 +384,26 @@ func (r *Route) IsAgent() bool {
|
||||
return r.GetAgent() != nil
|
||||
}
|
||||
|
||||
func (r *Route) HealthMonitor() health.HealthMonitor {
|
||||
func (r *Route) HealthMonitor() types.HealthMonitor {
|
||||
return r.HealthMon
|
||||
}
|
||||
|
||||
func (r *Route) SetHealthMonitor(m health.HealthMonitor) {
|
||||
func (r *Route) SetHealthMonitor(m types.HealthMonitor) {
|
||||
if r.HealthMon != nil && r.HealthMon != m {
|
||||
r.HealthMon.Finish("health monitor replaced")
|
||||
}
|
||||
r.HealthMon = m
|
||||
}
|
||||
|
||||
func (r *Route) IdlewatcherConfig() *idlewatcher.Config {
|
||||
func (r *Route) IdlewatcherConfig() *types.IdlewatcherConfig {
|
||||
return r.Idlewatcher
|
||||
}
|
||||
|
||||
func (r *Route) HealthCheckConfig() *health.HealthCheckConfig {
|
||||
func (r *Route) HealthCheckConfig() *types.HealthCheckConfig {
|
||||
return r.HealthCheck
|
||||
}
|
||||
|
||||
func (r *Route) LoadBalanceConfig() *loadbalance.Config {
|
||||
func (r *Route) LoadBalanceConfig() *types.LoadBalancerConfig {
|
||||
return r.LoadBalance
|
||||
}
|
||||
|
||||
@@ -419,7 +419,7 @@ func (r *Route) HomepageItem() *homepage.Item {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Route) ContainerInfo() *docker.Container {
|
||||
func (r *Route) ContainerInfo() *types.Container {
|
||||
return r.Container
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ func (r *Route) ShouldExclude() bool {
|
||||
return true
|
||||
case r.IsZeroPort() && !r.UseIdleWatcher():
|
||||
return true
|
||||
case !r.Container.IsExplicit && r.Container.IsBlacklisted():
|
||||
case !r.Container.IsExplicit && docker.IsBlacklisted(r.Container):
|
||||
return true
|
||||
case strings.HasPrefix(r.Container.ContainerName, "buildx_"):
|
||||
return true
|
||||
@@ -579,7 +579,7 @@ func (r *Route) Finalize() {
|
||||
r.Port.Listening, r.Port.Proxy = lp, pp
|
||||
|
||||
if r.HealthCheck == nil {
|
||||
r.HealthCheck = health.DefaultHealthConfig()
|
||||
r.HealthCheck = types.DefaultHealthConfig()
|
||||
}
|
||||
|
||||
if !r.HealthCheck.Disable {
|
||||
|
||||
@@ -5,11 +5,9 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"github.com/yusing/go-proxy/internal/docker"
|
||||
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
expect "github.com/yusing/go-proxy/internal/utils/testing"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
)
|
||||
|
||||
func TestRouteValidate(t *testing.T) {
|
||||
@@ -43,10 +41,10 @@ func TestRouteValidate(t *testing.T) {
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: route.Port{Proxy: 80},
|
||||
HealthCheck: &health.HealthCheckConfig{
|
||||
HealthCheck: &types.HealthCheckConfig{
|
||||
Disable: true,
|
||||
},
|
||||
LoadBalance: &loadbalance.Config{
|
||||
LoadBalance: &types.LoadBalancerConfig{
|
||||
Link: "test-link",
|
||||
}, // Minimal LoadBalance config with non-empty Link will be checked by UseLoadBalance
|
||||
}
|
||||
@@ -99,9 +97,9 @@ func TestRouteValidate(t *testing.T) {
|
||||
Host: "example.com",
|
||||
Port: route.Port{Proxy: 80},
|
||||
Metadata: Metadata{
|
||||
Container: &docker.Container{
|
||||
Container: &types.Container{
|
||||
ContainerID: "test-id",
|
||||
Image: &docker.ContainerImage{
|
||||
Image: &types.ContainerImage{
|
||||
Name: "test-image",
|
||||
},
|
||||
},
|
||||
@@ -157,9 +155,9 @@ func TestDockerRouteDisallowAgent(t *testing.T) {
|
||||
Port: route.Port{Proxy: 80},
|
||||
Agent: "test-agent",
|
||||
Metadata: Metadata{
|
||||
Container: &docker.Container{
|
||||
Container: &types.Container{
|
||||
ContainerID: "test-id",
|
||||
Image: &docker.ContainerImage{
|
||||
Image: &types.ContainerImage{
|
||||
Name: "test-image",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,18 +4,20 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
)
|
||||
|
||||
type RouteContext struct{}
|
||||
|
||||
var routeContextKey = RouteContext{}
|
||||
|
||||
func WithRouteContext(r *http.Request, route HTTPRoute) *http.Request {
|
||||
func WithRouteContext(r *http.Request, route types.HTTPRoute) *http.Request {
|
||||
return r.WithContext(context.WithValue(r.Context(), routeContextKey, route))
|
||||
}
|
||||
|
||||
func TryGetRoute(r *http.Request) HTTPRoute {
|
||||
if route, ok := r.Context().Value(routeContextKey).(HTTPRoute); ok {
|
||||
func TryGetRoute(r *http.Request) types.HTTPRoute {
|
||||
if route, ok := r.Context().Value(routeContextKey).(types.HTTPRoute); ok {
|
||||
return route
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -2,84 +2,80 @@ package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/homepage"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
)
|
||||
|
||||
func getHealthInfo(r Route) map[string]string {
|
||||
mon := r.HealthMonitor()
|
||||
if mon == nil {
|
||||
return map[string]string{
|
||||
"status": "unknown",
|
||||
"uptime": "n/a",
|
||||
"latency": "n/a",
|
||||
"detail": "n/a",
|
||||
}
|
||||
}
|
||||
return map[string]string{
|
||||
"status": mon.Status().String(),
|
||||
"uptime": mon.Uptime().Round(time.Second).String(),
|
||||
"latency": mon.Latency().Round(time.Microsecond).String(),
|
||||
"detail": mon.Detail(),
|
||||
}
|
||||
type HealthInfo struct {
|
||||
Status types.HealthStatus `json:"status" swaggertype:"string" enums:"healthy,unhealthy,napping,starting,error,unknown"`
|
||||
Uptime time.Duration `json:"uptime" swaggertype:"number"` // uptime in milliseconds
|
||||
Latency time.Duration `json:"latency" swaggertype:"number"` // latency in microseconds
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
type HealthInfoRaw struct {
|
||||
Status health.Status `json:"status"`
|
||||
Latency time.Duration `json:"latency"`
|
||||
}
|
||||
|
||||
func (info *HealthInfoRaw) MarshalJSON() ([]byte, error) {
|
||||
func (info *HealthInfo) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"status": info.Status.String(),
|
||||
"latency": info.Latency.Milliseconds(),
|
||||
"latency": info.Latency.Microseconds(),
|
||||
"uptime": info.Uptime.Milliseconds(),
|
||||
"detail": info.Detail,
|
||||
})
|
||||
}
|
||||
|
||||
func (info *HealthInfoRaw) UnmarshalJSON(data []byte) error {
|
||||
var v map[string]any
|
||||
func (info *HealthInfo) UnmarshalJSON(data []byte) error {
|
||||
var v struct {
|
||||
Status string `json:"status"`
|
||||
Latency int64 `json:"latency"`
|
||||
Uptime int64 `json:"uptime"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
if status, ok := v["status"].(string); ok {
|
||||
info.Status = health.NewStatus(status)
|
||||
|
||||
// overflow check
|
||||
if math.MaxInt64/time.Microsecond < time.Duration(v.Latency) {
|
||||
return fmt.Errorf("latency overflow: %d", v.Latency)
|
||||
}
|
||||
if latency, ok := v["latency"].(float64); ok {
|
||||
info.Latency = time.Duration(latency)
|
||||
if math.MaxInt64/time.Millisecond < time.Duration(v.Uptime) {
|
||||
return fmt.Errorf("uptime overflow: %d", v.Uptime)
|
||||
}
|
||||
|
||||
info.Status = types.NewHealthStatusFromString(v.Status)
|
||||
info.Latency = time.Duration(v.Latency) * time.Microsecond
|
||||
info.Uptime = time.Duration(v.Uptime) * time.Millisecond
|
||||
info.Detail = v.Detail
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHealthInfoRaw(r Route) *HealthInfoRaw {
|
||||
mon := r.HealthMonitor()
|
||||
if mon == nil {
|
||||
return &HealthInfoRaw{
|
||||
Status: health.StatusUnknown,
|
||||
Latency: time.Duration(0),
|
||||
}
|
||||
}
|
||||
return &HealthInfoRaw{
|
||||
Status: mon.Status(),
|
||||
Latency: mon.Latency(),
|
||||
}
|
||||
}
|
||||
|
||||
func HealthMap() map[string]map[string]string {
|
||||
healthMap := make(map[string]map[string]string, NumRoutes())
|
||||
func GetHealthInfo() map[string]HealthInfo {
|
||||
healthMap := make(map[string]HealthInfo, NumRoutes())
|
||||
for r := range Iter {
|
||||
healthMap[r.Name()] = getHealthInfo(r)
|
||||
}
|
||||
return healthMap
|
||||
}
|
||||
|
||||
func HealthInfo() map[string]*HealthInfoRaw {
|
||||
healthMap := make(map[string]*HealthInfoRaw, NumRoutes())
|
||||
for r := range Iter {
|
||||
healthMap[r.Name()] = getHealthInfoRaw(r)
|
||||
func getHealthInfo(r types.Route) HealthInfo {
|
||||
mon := r.HealthMonitor()
|
||||
if mon == nil {
|
||||
return HealthInfo{
|
||||
Status: types.StatusUnknown,
|
||||
Detail: "n/a",
|
||||
}
|
||||
}
|
||||
return HealthInfo{
|
||||
Status: mon.Status(),
|
||||
Uptime: mon.Uptime(),
|
||||
Latency: mon.Latency(),
|
||||
Detail: mon.Detail(),
|
||||
}
|
||||
return healthMap
|
||||
}
|
||||
|
||||
func HomepageCategories() []string {
|
||||
@@ -99,7 +95,13 @@ func HomepageCategories() []string {
|
||||
return categories
|
||||
}
|
||||
|
||||
func HomepageConfig(categoryFilter, providerFilter string) homepage.Homepage {
|
||||
func HomepageItems(proto, hostname, categoryFilter, providerFilter string) homepage.Homepage {
|
||||
switch proto {
|
||||
case "http", "https":
|
||||
default:
|
||||
proto = "http"
|
||||
}
|
||||
|
||||
hp := make(homepage.Homepage)
|
||||
|
||||
for _, r := range HTTP.Iter {
|
||||
@@ -110,13 +112,32 @@ func HomepageConfig(categoryFilter, providerFilter string) homepage.Homepage {
|
||||
if categoryFilter != "" && item.Category != categoryFilter {
|
||||
continue
|
||||
}
|
||||
|
||||
// clear url if invalid
|
||||
_, err := url.Parse(item.URL)
|
||||
if err != nil {
|
||||
item.URL = ""
|
||||
}
|
||||
|
||||
// append hostname if provided and only if alias is not FQDN
|
||||
if hostname != "" && item.URL == "" {
|
||||
if !strings.Contains(item.Alias, ".") {
|
||||
item.URL = fmt.Sprintf("%s://%s.%s", proto, item.Alias, hostname)
|
||||
}
|
||||
}
|
||||
|
||||
// prepend protocol if not exists
|
||||
if !strings.HasPrefix(item.URL, "http://") && !strings.HasPrefix(item.URL, "https://") {
|
||||
item.URL = fmt.Sprintf("%s://%s", proto, item.URL)
|
||||
}
|
||||
|
||||
hp.Add(item)
|
||||
}
|
||||
return hp
|
||||
}
|
||||
|
||||
func ByProvider() map[string][]Route {
|
||||
rts := make(map[string][]Route)
|
||||
func ByProvider() map[string][]types.Route {
|
||||
rts := make(map[string][]types.Route)
|
||||
for r := range Iter {
|
||||
rts[r.ProviderName()] = append(rts[r.ProviderName()], r)
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||
"github.com/yusing/go-proxy/internal/docker"
|
||||
"github.com/yusing/go-proxy/internal/homepage"
|
||||
idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/utils/pool"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
|
||||
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
|
||||
nettypes "github.com/yusing/go-proxy/internal/net/types"
|
||||
)
|
||||
|
||||
type (
|
||||
//nolint:interfacebloat // this is for avoiding circular imports
|
||||
Route interface {
|
||||
task.TaskStarter
|
||||
task.TaskFinisher
|
||||
pool.Object
|
||||
ProviderName() string
|
||||
GetProvider() Provider
|
||||
TargetURL() *nettypes.URL
|
||||
HealthMonitor() health.HealthMonitor
|
||||
SetHealthMonitor(m health.HealthMonitor)
|
||||
References() []string
|
||||
|
||||
Started() <-chan struct{}
|
||||
|
||||
IdlewatcherConfig() *idlewatcher.Config
|
||||
HealthCheckConfig() *health.HealthCheckConfig
|
||||
LoadBalanceConfig() *loadbalance.Config
|
||||
HomepageConfig() *homepage.ItemConfig
|
||||
HomepageItem() *homepage.Item
|
||||
ContainerInfo() *docker.Container
|
||||
|
||||
GetAgent() *agent.AgentConfig
|
||||
|
||||
IsDocker() bool
|
||||
IsAgent() bool
|
||||
UseLoadBalance() bool
|
||||
UseIdleWatcher() bool
|
||||
UseHealthCheck() bool
|
||||
UseAccessLog() bool
|
||||
}
|
||||
HTTPRoute interface {
|
||||
Route
|
||||
http.Handler
|
||||
}
|
||||
ReverseProxyRoute interface {
|
||||
HTTPRoute
|
||||
ReverseProxy() *reverseproxy.ReverseProxy
|
||||
}
|
||||
StreamRoute interface {
|
||||
Route
|
||||
nettypes.Stream
|
||||
Stream() nettypes.Stream
|
||||
}
|
||||
Provider interface {
|
||||
GetRoute(alias string) (r Route, ok bool)
|
||||
IterRoutes(yield func(alias string, r Route) bool)
|
||||
FindService(project, service string) (r Route, ok bool)
|
||||
ShortName() string
|
||||
}
|
||||
)
|
||||
@@ -1,21 +1,22 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
"github.com/yusing/go-proxy/internal/utils/pool"
|
||||
)
|
||||
|
||||
var (
|
||||
HTTP = pool.New[HTTPRoute]("http_routes")
|
||||
Stream = pool.New[StreamRoute]("stream_routes")
|
||||
HTTP = pool.New[types.HTTPRoute]("http_routes")
|
||||
Stream = pool.New[types.StreamRoute]("stream_routes")
|
||||
// All is a pool of all routes, including HTTP, Stream routes and also excluded routes.
|
||||
All = pool.New[Route]("all_routes")
|
||||
All = pool.New[types.Route]("all_routes")
|
||||
)
|
||||
|
||||
func init() {
|
||||
All.DisableLog()
|
||||
}
|
||||
|
||||
func Iter(yield func(r Route) bool) {
|
||||
func Iter(yield func(r types.Route) bool) {
|
||||
for _, r := range All.Iter {
|
||||
if !yield(r) {
|
||||
break
|
||||
@@ -23,7 +24,7 @@ func Iter(yield func(r Route) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func IterKV(yield func(alias string, r Route) bool) {
|
||||
func IterKV(yield func(alias string, r types.Route) bool) {
|
||||
for k, r := range All.Iter {
|
||||
if !yield(k, r) {
|
||||
break
|
||||
@@ -41,7 +42,7 @@ func Clear() {
|
||||
All.Clear()
|
||||
}
|
||||
|
||||
func GetHTTPRouteOrExact(alias, host string) (HTTPRoute, bool) {
|
||||
func GetHTTPRouteOrExact(alias, host string) (types.HTTPRoute, bool) {
|
||||
r, ok := HTTP.Get(alias)
|
||||
if ok {
|
||||
return r, true
|
||||
@@ -50,6 +51,6 @@ func GetHTTPRouteOrExact(alias, host string) (HTTPRoute, bool) {
|
||||
return HTTP.Get(host)
|
||||
}
|
||||
|
||||
func Get(alias string) (Route, bool) {
|
||||
func Get(alias string) (types.Route, bool) {
|
||||
return All.Get(alias)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
"github.com/yusing/go-proxy/internal/route/stream"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||
)
|
||||
|
||||
@@ -24,7 +25,7 @@ type StreamRoute struct {
|
||||
l zerolog.Logger
|
||||
}
|
||||
|
||||
func NewStreamRoute(base *Route) (routes.Route, gperr.Error) {
|
||||
func NewStreamRoute(base *Route) (types.Route, gperr.Error) {
|
||||
// TODO: support non-coherent scheme
|
||||
return &StreamRoute{
|
||||
Route: base,
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
|
||||
type HTTPConfig struct {
|
||||
NoTLSVerify bool `json:"no_tls_verify,omitempty"`
|
||||
ResponseHeaderTimeout time.Duration `json:"response_header_timeout,omitempty"`
|
||||
ResponseHeaderTimeout time.Duration `json:"response_header_timeout,omitempty" swaggertype:"primitive,integer"`
|
||||
DisableCompression bool `json:"disable_compression,omitempty"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user