mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-17 05:59:42 +02:00
refactor(agent): extract agent pool and HTTP utilities to dedicated package
Moved non-agent-specific logic from agent/pkg/agent/ to internal/agentpool/: - pool.go: Agent pool management (Get, Add, Remove, List, Iter, etc.) - http_requests.go: HTTP utilities (health checks, forwarding, websockets, reverse proxy) - agent.go: Agent struct with HTTP client management This separates general-purpose pool management from agent-specific configuration, improving code organization and making the agent package focused on agent config only.
This commit is contained in:
@@ -23,11 +23,9 @@ require (
|
|||||||
github.com/puzpuzpuz/xsync/v4 v4.2.0
|
github.com/puzpuzpuz/xsync/v4 v4.2.0
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/valyala/fasthttp v1.68.0
|
|
||||||
github.com/yusing/godoxy v0.0.0-00010101000000-000000000000
|
github.com/yusing/godoxy v0.0.0-00010101000000-000000000000
|
||||||
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
|
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
|
||||||
github.com/yusing/goutils v0.7.0
|
github.com/yusing/goutils v0.7.0
|
||||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260103043911-785deb23bd64
|
|
||||||
github.com/yusing/goutils/server v0.0.0-20260103043911-785deb23bd64
|
github.com/yusing/goutils/server v0.0.0-20260103043911-785deb23bd64
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,9 +104,11 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.68.0 // indirect
|
||||||
github.com/vincent-petithory/dataurl v1.0.0 // indirect
|
github.com/vincent-petithory/dataurl v1.0.0 // indirect
|
||||||
github.com/yusing/ds v0.3.1 // indirect
|
github.com/yusing/ds v0.3.1 // indirect
|
||||||
github.com/yusing/gointernals v0.1.16 // indirect
|
github.com/yusing/gointernals v0.1.16 // indirect
|
||||||
|
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260103043911-785deb23bd64 // indirect
|
||||||
github.com/yusing/goutils/http/websocket v0.0.0-20260103043911-785deb23bd64 // indirect
|
github.com/yusing/goutils/http/websocket v0.0.0-20260103043911-785deb23bd64 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"iter"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
var agentPool = xsync.NewMap[string, *AgentConfig](xsync.WithPresize(10))
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if strings.HasSuffix(os.Args[0], ".test") {
|
|
||||||
agentPool.Store("test-agent", &AgentConfig{
|
|
||||||
Addr: "test-agent",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAgent(agentAddrOrDockerHost string) (*AgentConfig, bool) {
|
|
||||||
if !IsDockerHostAgent(agentAddrOrDockerHost) {
|
|
||||||
return getAgentByAddr(agentAddrOrDockerHost)
|
|
||||||
}
|
|
||||||
return getAgentByAddr(GetAgentAddrFromDockerHost(agentAddrOrDockerHost))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAgentByName(name string) (*AgentConfig, bool) {
|
|
||||||
for _, agent := range agentPool.Range {
|
|
||||||
if agent.Name == name {
|
|
||||||
return agent, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddAgent(agent *AgentConfig) {
|
|
||||||
agentPool.Store(agent.Addr, agent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveAgent(agent *AgentConfig) {
|
|
||||||
agentPool.Delete(agent.Addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveAllAgents() {
|
|
||||||
agentPool.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListAgents() []*AgentConfig {
|
|
||||||
agents := make([]*AgentConfig, 0, agentPool.Size())
|
|
||||||
for _, agent := range agentPool.Range {
|
|
||||||
agents = append(agents, agent)
|
|
||||||
}
|
|
||||||
return agents
|
|
||||||
}
|
|
||||||
|
|
||||||
func IterAgents() iter.Seq2[string, *AgentConfig] {
|
|
||||||
return agentPool.Range
|
|
||||||
}
|
|
||||||
|
|
||||||
func NumAgents() int {
|
|
||||||
return agentPool.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAgentByAddr(addr string) (agent *AgentConfig, ok bool) {
|
|
||||||
agent, ok = agentPool.Load(addr)
|
|
||||||
return agent, ok
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -16,11 +18,11 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent/common"
|
"github.com/yusing/godoxy/agent/pkg/agent/common"
|
||||||
agentstream "github.com/yusing/godoxy/agent/pkg/agent/stream"
|
agentstream "github.com/yusing/godoxy/agent/pkg/agent/stream"
|
||||||
"github.com/yusing/godoxy/agent/pkg/certs"
|
"github.com/yusing/godoxy/agent/pkg/certs"
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
|
httputils "github.com/yusing/goutils/http"
|
||||||
"github.com/yusing/goutils/version"
|
"github.com/yusing/goutils/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,9 +33,7 @@ type AgentConfig struct {
|
|||||||
IsTCPStreamSupported bool `json:"supports_tcp_stream"`
|
IsTCPStreamSupported bool `json:"supports_tcp_stream"`
|
||||||
IsUDPStreamSupported bool `json:"supports_udp_stream"`
|
IsUDPStreamSupported bool `json:"supports_udp_stream"`
|
||||||
|
|
||||||
httpClient *http.Client
|
tlsConfig tls.Config
|
||||||
fasthttpClientHealthCheck *fasthttp.Client
|
|
||||||
tlsConfig tls.Config
|
|
||||||
|
|
||||||
// for stream
|
// for stream
|
||||||
caCert *x509.Certificate
|
caCert *x509.Certificate
|
||||||
@@ -106,7 +106,8 @@ func (cfg *AgentConfig) Parse(addr string) error {
|
|||||||
|
|
||||||
var serverVersion = version.Get()
|
var serverVersion = version.Get()
|
||||||
|
|
||||||
func (cfg *AgentConfig) StartWithCerts(ctx context.Context, ca, crt, key []byte) error {
|
// InitWithCerts initializes the agent config with the given CA, certificate, and key.
|
||||||
|
func (cfg *AgentConfig) InitWithCerts(ctx context.Context, ca, crt, key []byte) error {
|
||||||
clientCert, err := tls.X509KeyPair(crt, key)
|
clientCert, err := tls.X509KeyPair(crt, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -134,12 +135,6 @@ func (cfg *AgentConfig) StartWithCerts(ctx context.Context, ca, crt, key []byte)
|
|||||||
ServerName: common.CertsDNSName,
|
ServerName: common.CertsDNSName,
|
||||||
}
|
}
|
||||||
|
|
||||||
// create transport and http client
|
|
||||||
cfg.httpClient = cfg.NewHTTPClient()
|
|
||||||
applyNormalTransportConfig(cfg.httpClient)
|
|
||||||
|
|
||||||
cfg.fasthttpClientHealthCheck = cfg.NewFastHTTPHealthCheckClient()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -233,6 +228,25 @@ func (cfg *AgentConfig) StartWithCerts(ctx context.Context, ca, crt, key []byte)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *AgentConfig) Init(ctx context.Context) error {
|
||||||
|
filepath, ok := certs.AgentCertsFilepath(cfg.Addr)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid agent host: %s", cfg.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
certData, err := os.ReadFile(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read agent certs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ca, crt, key, err := certs.ExtractCert(certData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to extract agent certs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.InitWithCerts(ctx, ca, crt, key)
|
||||||
|
}
|
||||||
|
|
||||||
// NewTCPClient creates a new TCP client for the agent.
|
// NewTCPClient creates a new TCP client for the agent.
|
||||||
//
|
//
|
||||||
// It returns an error if
|
// It returns an error if
|
||||||
@@ -265,50 +279,6 @@ func (cfg *AgentConfig) NewUDPClient(targetAddress string) (net.Conn, error) {
|
|||||||
return agentstream.NewUDPClient(cfg.Addr, targetAddress, cfg.caCert, cfg.clientCert)
|
return agentstream.NewUDPClient(cfg.Addr, targetAddress, cfg.caCert, cfg.clientCert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *AgentConfig) Start(ctx context.Context) error {
|
|
||||||
filepath, ok := certs.AgentCertsFilepath(cfg.Addr)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid agent host: %s", cfg.Addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
certData, err := os.ReadFile(filepath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read agent certs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ca, crt, key, err := certs.ExtractCert(certData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to extract agent certs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg.StartWithCerts(ctx, ca, crt, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *AgentConfig) NewHTTPClient() *http.Client {
|
|
||||||
return &http.Client{
|
|
||||||
Transport: cfg.Transport(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *AgentConfig) NewFastHTTPHealthCheckClient() *fasthttp.Client {
|
|
||||||
return &fasthttp.Client{
|
|
||||||
Dial: func(addr string) (net.Conn, error) {
|
|
||||||
if addr != AgentHost+":443" {
|
|
||||||
return nil, &net.AddrError{Err: "invalid address", Addr: addr}
|
|
||||||
}
|
|
||||||
return net.Dial("tcp", cfg.Addr)
|
|
||||||
},
|
|
||||||
TLSConfig: &cfg.tlsConfig,
|
|
||||||
ReadTimeout: 5 * time.Second,
|
|
||||||
WriteTimeout: 3 * time.Second,
|
|
||||||
DisableHeaderNamesNormalizing: true,
|
|
||||||
DisablePathNormalizing: true,
|
|
||||||
NoDefaultUserAgentHeader: true,
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *AgentConfig) Transport() *http.Transport {
|
func (cfg *AgentConfig) Transport() *http.Transport {
|
||||||
return &http.Transport{
|
return &http.Transport{
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
@@ -324,6 +294,10 @@ func (cfg *AgentConfig) Transport() *http.Transport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *AgentConfig) TLSConfig() *tls.Config {
|
||||||
|
return &cfg.tlsConfig
|
||||||
|
}
|
||||||
|
|
||||||
var dialer = &net.Dialer{Timeout: 5 * time.Second}
|
var dialer = &net.Dialer{Timeout: 5 * time.Second}
|
||||||
|
|
||||||
func (cfg *AgentConfig) DialContext(ctx context.Context) (net.Conn, error) {
|
func (cfg *AgentConfig) DialContext(ctx context.Context) (net.Conn, error) {
|
||||||
@@ -334,10 +308,57 @@ func (cfg *AgentConfig) String() string {
|
|||||||
return cfg.Name + "@" + cfg.Addr
|
return cfg.Name + "@" + cfg.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyNormalTransportConfig(client *http.Client) {
|
func (cfg *AgentConfig) do(ctx context.Context, method, endpoint string, body io.Reader) (*http.Response, error) {
|
||||||
transport := client.Transport.(*http.Transport)
|
req, err := http.NewRequestWithContext(ctx, method, APIBaseURL+endpoint, body)
|
||||||
transport.MaxIdleConns = 100
|
if err != nil {
|
||||||
transport.MaxIdleConnsPerHost = 100
|
return nil, err
|
||||||
transport.ReadBufferSize = 16384
|
}
|
||||||
transport.WriteBufferSize = 16384
|
client := http.Client{
|
||||||
|
Transport: cfg.Transport(),
|
||||||
|
}
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *AgentConfig) fetchString(ctx context.Context, endpoint string) (string, int, error) {
|
||||||
|
resp, err := cfg.do(ctx, "GET", endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, release, err := httputils.ReadAllBody(resp)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
ret := string(data)
|
||||||
|
release(data)
|
||||||
|
return ret, resp.StatusCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchJSON fetches a JSON response from the agent and unmarshals it into the provided struct
|
||||||
|
//
|
||||||
|
// It will return the status code of the response, and error if any.
|
||||||
|
// If the status code is not http.StatusOK, out will be unchanged but error will still be nil.
|
||||||
|
func (cfg *AgentConfig) fetchJSON(ctx context.Context, endpoint string, out any) (int, error) {
|
||||||
|
resp, err := cfg.do(ctx, "GET", endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, release, err := httputils.ReadAllBody(resp)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer release(data)
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return resp.StatusCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, out)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return resp.StatusCode, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bytedance/sonic"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
httputils "github.com/yusing/goutils/http"
|
|
||||||
"github.com/yusing/goutils/http/reverseproxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (cfg *AgentConfig) Do(ctx context.Context, method, endpoint string, body io.Reader) (*http.Response, error) {
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, APIBaseURL+endpoint, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cfg.httpClient.Do(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *AgentConfig) Forward(req *http.Request, endpoint string) (*http.Response, error) {
|
|
||||||
req.URL.Host = AgentHost
|
|
||||||
req.URL.Scheme = "https"
|
|
||||||
req.URL.Path = APIEndpointBase + endpoint
|
|
||||||
req.RequestURI = ""
|
|
||||||
resp, err := cfg.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type HealthCheckResponse struct {
|
|
||||||
Healthy bool `json:"healthy"`
|
|
||||||
Detail string `json:"detail"`
|
|
||||||
Latency time.Duration `json:"latency"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *AgentConfig) DoHealthCheck(timeout time.Duration, query string) (ret HealthCheckResponse, err error) {
|
|
||||||
req := fasthttp.AcquireRequest()
|
|
||||||
defer fasthttp.ReleaseRequest(req)
|
|
||||||
|
|
||||||
resp := fasthttp.AcquireResponse()
|
|
||||||
defer fasthttp.ReleaseResponse(resp)
|
|
||||||
|
|
||||||
req.SetRequestURI(APIBaseURL + EndpointHealth + "?" + query)
|
|
||||||
req.Header.SetMethod(fasthttp.MethodGet)
|
|
||||||
req.Header.Set("Accept-Encoding", "identity")
|
|
||||||
req.SetConnectionClose()
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
err = cfg.fasthttpClientHealthCheck.DoTimeout(req, resp, timeout)
|
|
||||||
ret.Latency = time.Since(start)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if status := resp.StatusCode(); status != http.StatusOK {
|
|
||||||
ret.Detail = fmt.Sprintf("HTTP %d %s", status, resp.Body())
|
|
||||||
return ret, nil
|
|
||||||
} else {
|
|
||||||
err = sonic.Unmarshal(resp.Body(), &ret)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *AgentConfig) fetchString(ctx context.Context, endpoint string) (string, int, error) {
|
|
||||||
resp, err := cfg.Do(ctx, "GET", endpoint, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
data, release, err := httputils.ReadAllBody(resp)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, err
|
|
||||||
}
|
|
||||||
ret := string(data)
|
|
||||||
release(data)
|
|
||||||
return ret, resp.StatusCode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchJSON fetches a JSON response from the agent and unmarshals it into the provided struct
|
|
||||||
//
|
|
||||||
// It will return the status code of the response, and error if any.
|
|
||||||
// If the status code is not http.StatusOK, out will be unchanged but error will still be nil.
|
|
||||||
func (cfg *AgentConfig) fetchJSON(ctx context.Context, endpoint string, out any) (int, error) {
|
|
||||||
resp, err := cfg.Do(ctx, "GET", endpoint, nil)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
data, release, err := httputils.ReadAllBody(resp)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer release(data)
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return resp.StatusCode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sonic.Unmarshal(data, out)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return resp.StatusCode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *AgentConfig) Websocket(ctx context.Context, endpoint string) (*websocket.Conn, *http.Response, error) {
|
|
||||||
transport := cfg.Transport()
|
|
||||||
dialer := websocket.Dialer{
|
|
||||||
NetDialContext: transport.DialContext,
|
|
||||||
NetDialTLSContext: transport.DialTLSContext,
|
|
||||||
}
|
|
||||||
return dialer.DialContext(ctx, APIBaseURL+endpoint, http.Header{
|
|
||||||
"Host": {AgentHost},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReverseProxy reverse proxies the request to the agent
|
|
||||||
//
|
|
||||||
// It will create a new request with the same context, method, and body, but with the agent host and scheme, and the endpoint
|
|
||||||
// If the request has a query, it will be added to the proxy request's URL
|
|
||||||
func (cfg *AgentConfig) ReverseProxy(w http.ResponseWriter, req *http.Request, endpoint string) {
|
|
||||||
rp := reverseproxy.NewReverseProxy("agent", AgentURL, cfg.Transport())
|
|
||||||
req.URL.Host = AgentHost
|
|
||||||
req.URL.Scheme = "https"
|
|
||||||
req.URL.Path = endpoint
|
|
||||||
req.RequestURI = ""
|
|
||||||
rp.ServeHTTP(w, req)
|
|
||||||
}
|
|
||||||
54
internal/agentpool/agent.go
Normal file
54
internal/agentpool/agent.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package agentpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Agent struct {
|
||||||
|
*agent.AgentConfig
|
||||||
|
|
||||||
|
httpClient *http.Client
|
||||||
|
fasthttpHcClient *fasthttp.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAgent(cfg *agent.AgentConfig) *Agent {
|
||||||
|
transport := cfg.Transport()
|
||||||
|
transport.MaxIdleConns = 100
|
||||||
|
transport.MaxIdleConnsPerHost = 100
|
||||||
|
transport.ReadBufferSize = 16384
|
||||||
|
transport.WriteBufferSize = 16384
|
||||||
|
|
||||||
|
return &Agent{
|
||||||
|
AgentConfig: cfg,
|
||||||
|
httpClient: &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
},
|
||||||
|
fasthttpHcClient: &fasthttp.Client{
|
||||||
|
DialTimeout: func(addr string, timeout time.Duration) (net.Conn, error) {
|
||||||
|
if addr != agent.AgentHost+":443" {
|
||||||
|
return nil, &net.AddrError{Err: "invalid address", Addr: addr}
|
||||||
|
}
|
||||||
|
return net.DialTimeout("tcp", cfg.Addr, timeout)
|
||||||
|
},
|
||||||
|
TLSConfig: cfg.TLSConfig(),
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 3 * time.Second,
|
||||||
|
DisableHeaderNamesNormalizing: true,
|
||||||
|
DisablePathNormalizing: true,
|
||||||
|
NoDefaultUserAgentHeader: true,
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (agent *Agent) HTTPClient() *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: agent.Transport(),
|
||||||
|
}
|
||||||
|
}
|
||||||
96
internal/agentpool/http_requests.go
Normal file
96
internal/agentpool/http_requests.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package agentpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bytedance/sonic"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
|
"github.com/yusing/goutils/http/reverseproxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cfg *Agent) Do(ctx context.Context, method, endpoint string, body io.Reader) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, agent.APIBaseURL+endpoint, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cfg.httpClient.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Agent) Forward(req *http.Request, endpoint string) (*http.Response, error) {
|
||||||
|
req.URL.Host = agent.AgentHost
|
||||||
|
req.URL.Scheme = "https"
|
||||||
|
req.URL.Path = agent.APIEndpointBase + endpoint
|
||||||
|
req.RequestURI = ""
|
||||||
|
resp, err := cfg.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HealthCheckResponse struct {
|
||||||
|
Healthy bool `json:"healthy"`
|
||||||
|
Detail string `json:"detail"`
|
||||||
|
Latency time.Duration `json:"latency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Agent) DoHealthCheck(timeout time.Duration, query string) (ret HealthCheckResponse, err error) {
|
||||||
|
req := fasthttp.AcquireRequest()
|
||||||
|
defer fasthttp.ReleaseRequest(req)
|
||||||
|
|
||||||
|
resp := fasthttp.AcquireResponse()
|
||||||
|
defer fasthttp.ReleaseResponse(resp)
|
||||||
|
|
||||||
|
req.SetRequestURI(agent.APIBaseURL + agent.EndpointHealth + "?" + query)
|
||||||
|
req.Header.SetMethod(fasthttp.MethodGet)
|
||||||
|
req.Header.Set("Accept-Encoding", "identity")
|
||||||
|
req.SetConnectionClose()
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
err = cfg.fasthttpHcClient.DoTimeout(req, resp, timeout)
|
||||||
|
ret.Latency = time.Since(start)
|
||||||
|
if err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := resp.StatusCode(); status != http.StatusOK {
|
||||||
|
ret.Detail = fmt.Sprintf("HTTP %d %s", status, resp.Body())
|
||||||
|
return ret, nil
|
||||||
|
} else {
|
||||||
|
err = sonic.Unmarshal(resp.Body(), &ret)
|
||||||
|
if err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Agent) Websocket(ctx context.Context, endpoint string) (*websocket.Conn, *http.Response, error) {
|
||||||
|
transport := cfg.Transport()
|
||||||
|
dialer := websocket.Dialer{
|
||||||
|
NetDialContext: transport.DialContext,
|
||||||
|
NetDialTLSContext: transport.DialTLSContext,
|
||||||
|
}
|
||||||
|
return dialer.DialContext(ctx, agent.APIBaseURL+endpoint, http.Header{
|
||||||
|
"Host": {agent.AgentHost},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReverseProxy reverse proxies the request to the agent
|
||||||
|
//
|
||||||
|
// It will create a new request with the same context, method, and body, but with the agent host and scheme, and the endpoint
|
||||||
|
// If the request has a query, it will be added to the proxy request's URL
|
||||||
|
func (cfg *Agent) ReverseProxy(w http.ResponseWriter, req *http.Request, endpoint string) {
|
||||||
|
rp := reverseproxy.NewReverseProxy("agent", agent.AgentURL, cfg.Transport())
|
||||||
|
req.URL.Host = agent.AgentHost
|
||||||
|
req.URL.Scheme = "https"
|
||||||
|
req.URL.Path = endpoint
|
||||||
|
req.RequestURI = ""
|
||||||
|
rp.ServeHTTP(w, req)
|
||||||
|
}
|
||||||
79
internal/agentpool/pool.go
Normal file
79
internal/agentpool/pool.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package agentpool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/puzpuzpuz/xsync/v4"
|
||||||
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
var agentPool = xsync.NewMap[string, *Agent](xsync.WithPresize(10))
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if strings.HasSuffix(os.Args[0], ".test") {
|
||||||
|
agentPool.Store("test-agent", &Agent{
|
||||||
|
AgentConfig: &agent.AgentConfig{
|
||||||
|
Addr: "test-agent",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(agentAddrOrDockerHost string) (*Agent, bool) {
|
||||||
|
if !agent.IsDockerHostAgent(agentAddrOrDockerHost) {
|
||||||
|
return getAgentByAddr(agentAddrOrDockerHost)
|
||||||
|
}
|
||||||
|
return getAgentByAddr(agent.GetAgentAddrFromDockerHost(agentAddrOrDockerHost))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAgent(name string) (*Agent, bool) {
|
||||||
|
for _, agent := range agentPool.Range {
|
||||||
|
if agent.Name == name {
|
||||||
|
return agent, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func Add(cfg *agent.AgentConfig) (added bool) {
|
||||||
|
_, loaded := agentPool.LoadOrCompute(cfg.Addr, func() (*Agent, bool) {
|
||||||
|
return newAgent(cfg), false
|
||||||
|
})
|
||||||
|
return !loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func Has(cfg *agent.AgentConfig) bool {
|
||||||
|
_, ok := agentPool.Load(cfg.Addr)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func Remove(cfg *agent.AgentConfig) {
|
||||||
|
agentPool.Delete(cfg.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveAll() {
|
||||||
|
agentPool.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func List() []*Agent {
|
||||||
|
agents := make([]*Agent, 0, agentPool.Size())
|
||||||
|
for _, agent := range agentPool.Range {
|
||||||
|
agents = append(agents, agent)
|
||||||
|
}
|
||||||
|
return agents
|
||||||
|
}
|
||||||
|
|
||||||
|
func Iter() iter.Seq2[string, *Agent] {
|
||||||
|
return agentPool.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
func Num() int {
|
||||||
|
return agentPool.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAgentByAddr(addr string) (agent *Agent, ok bool) {
|
||||||
|
agent, ok = agentPool.Load(addr)
|
||||||
|
return agent, ok
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
apitypes "github.com/yusing/goutils/apitypes"
|
apitypes "github.com/yusing/goutils/apitypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ func Create(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostport := net.JoinHostPort(request.Host, strconv.Itoa(request.Port))
|
hostport := net.JoinHostPort(request.Host, strconv.Itoa(request.Port))
|
||||||
if _, ok := agent.GetAgent(hostport); ok {
|
if _, ok := agentpool.Get(hostport); ok {
|
||||||
c.JSON(http.StatusConflict, apitypes.Error("agent already exists"))
|
c.JSON(http.StatusConflict, apitypes.Error("agent already exists"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
"github.com/yusing/goutils/http/httpheaders"
|
"github.com/yusing/goutils/http/httpheaders"
|
||||||
"github.com/yusing/goutils/http/websocket"
|
"github.com/yusing/goutils/http/websocket"
|
||||||
|
|
||||||
@@ -19,15 +19,15 @@ import (
|
|||||||
// @Tags agent,websocket
|
// @Tags agent,websocket
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} Agent
|
// @Success 200 {array} agent.AgentConfig
|
||||||
// @Failure 403 {object} apitypes.ErrorResponse
|
// @Failure 403 {object} apitypes.ErrorResponse
|
||||||
// @Router /agent/list [get]
|
// @Router /agent/list [get]
|
||||||
func List(c *gin.Context) {
|
func List(c *gin.Context) {
|
||||||
if httpheaders.IsWebsocket(c.Request.Header) {
|
if httpheaders.IsWebsocket(c.Request.Header) {
|
||||||
websocket.PeriodicWrite(c, 10*time.Second, func() (any, error) {
|
websocket.PeriodicWrite(c, 10*time.Second, func() (any, error) {
|
||||||
return agent.ListAgents(), nil
|
return agentpool.List(), nil
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusOK, agent.ListAgents())
|
c.JSON(http.StatusOK, agentpool.List())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
"github.com/yusing/godoxy/agent/pkg/certs"
|
"github.com/yusing/godoxy/agent/pkg/certs"
|
||||||
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
config "github.com/yusing/godoxy/internal/config/types"
|
config "github.com/yusing/godoxy/internal/config/types"
|
||||||
"github.com/yusing/godoxy/internal/route/provider"
|
"github.com/yusing/godoxy/internal/route/provider"
|
||||||
apitypes "github.com/yusing/goutils/apitypes"
|
apitypes "github.com/yusing/goutils/apitypes"
|
||||||
@@ -79,21 +80,28 @@ func Verify(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, apitypes.Success(fmt.Sprintf("Added %d routes", nRoutesAdded)))
|
c.JSON(http.StatusOK, apitypes.Success(fmt.Sprintf("Added %d routes", nRoutesAdded)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, gperr.Error) {
|
var errAgentAlreadyExists = gperr.New("agent already exists")
|
||||||
cfgState := config.ActiveState.Load()
|
|
||||||
for _, a := range cfgState.Value().Providers.Agents {
|
|
||||||
if a.Addr == host {
|
|
||||||
return 0, gperr.New("agent already exists")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func verifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, gperr.Error) {
|
||||||
var agentCfg agent.AgentConfig
|
var agentCfg agent.AgentConfig
|
||||||
agentCfg.Addr = host
|
agentCfg.Addr = host
|
||||||
agentCfg.Runtime = containerRuntime
|
agentCfg.Runtime = containerRuntime
|
||||||
|
|
||||||
err := agentCfg.StartWithCerts(cfgState.Context(), ca.Cert, client.Cert, client.Key)
|
// check if agent host exists in the config
|
||||||
|
cfgState := config.ActiveState.Load()
|
||||||
|
for _, a := range cfgState.Value().Providers.Agents {
|
||||||
|
if a.Addr == host {
|
||||||
|
return 0, errAgentAlreadyExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check if agent host exists in the agent pool
|
||||||
|
if agentpool.Has(&agentCfg) {
|
||||||
|
return 0, errAgentAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
err := agentCfg.InitWithCerts(cfgState.Context(), ca.Cert, client.Cert, client.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, gperr.Wrap(err, "failed to start agent")
|
return 0, gperr.Wrap(err, "failed to initialize agent config")
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := provider.NewAgentProvider(&agentCfg)
|
provider := provider.NewAgentProvider(&agentCfg)
|
||||||
@@ -102,11 +110,14 @@ func verifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair, contain
|
|||||||
}
|
}
|
||||||
|
|
||||||
// agent must be added before loading routes
|
// agent must be added before loading routes
|
||||||
agent.AddAgent(&agentCfg)
|
added := agentpool.Add(&agentCfg)
|
||||||
|
if !added {
|
||||||
|
return 0, errAgentAlreadyExists
|
||||||
|
}
|
||||||
err = provider.LoadRoutes()
|
err = provider.LoadRoutes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cfgState.DeleteProvider(provider.String())
|
cfgState.DeleteProvider(provider.String())
|
||||||
agent.RemoveAgent(&agentCfg)
|
agentpool.Remove(&agentCfg)
|
||||||
return 0, gperr.Wrap(err, "failed to load routes")
|
return 0, gperr.Wrap(err, "failed to load routes")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
"github.com/yusing/godoxy/internal/metrics/period"
|
"github.com/yusing/godoxy/internal/metrics/period"
|
||||||
"github.com/yusing/godoxy/internal/metrics/systeminfo"
|
"github.com/yusing/godoxy/internal/metrics/systeminfo"
|
||||||
apitypes "github.com/yusing/goutils/apitypes"
|
apitypes "github.com/yusing/goutils/apitypes"
|
||||||
@@ -79,7 +80,7 @@ func AllSystemInfo(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// leave 5 extra slots for buffering in case new agents are added.
|
// leave 5 extra slots for buffering in case new agents are added.
|
||||||
dataCh := make(chan SystemInfoData, 1+agent.NumAgents()+5)
|
dataCh := make(chan SystemInfoData, 1+agentpool.Num()+5)
|
||||||
defer close(dataCh)
|
defer close(dataCh)
|
||||||
|
|
||||||
ticker := time.NewTicker(req.Interval)
|
ticker := time.NewTicker(req.Interval)
|
||||||
@@ -125,7 +126,7 @@ func AllSystemInfo(c *gin.Context) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, a := range agent.IterAgents() {
|
for _, a := range agentpool.Iter() {
|
||||||
totalAgents++
|
totalAgents++
|
||||||
|
|
||||||
errs.Go(func() error {
|
errs.Go(func() error {
|
||||||
@@ -175,7 +176,7 @@ func AllSystemInfo(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAgentSystemInfo(ctx context.Context, a *agent.AgentConfig, query string) (bytesFromPool, error) {
|
func getAgentSystemInfo(ctx context.Context, a *agentpool.Agent, query string) (bytesFromPool, error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -194,7 +195,7 @@ func getAgentSystemInfo(ctx context.Context, a *agent.AgentConfig, query string)
|
|||||||
return bytesFromPool{json.RawMessage(bytesBuf), release}, nil
|
return bytesFromPool{json.RawMessage(bytesBuf), release}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAgentSystemInfoWithRetry(ctx context.Context, a *agent.AgentConfig, query string) (bytesFromPool, error) {
|
func getAgentSystemInfoWithRetry(ctx context.Context, a *agentpool.Agent, query string) (bytesFromPool, error) {
|
||||||
const maxRetries = 3
|
const maxRetries = 3
|
||||||
var lastErr error
|
var lastErr error
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
agentPkg "github.com/yusing/godoxy/agent/pkg/agent"
|
agentPkg "github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
"github.com/yusing/godoxy/internal/metrics/period"
|
"github.com/yusing/godoxy/internal/metrics/period"
|
||||||
"github.com/yusing/godoxy/internal/metrics/systeminfo"
|
"github.com/yusing/godoxy/internal/metrics/systeminfo"
|
||||||
apitypes "github.com/yusing/goutils/apitypes"
|
apitypes "github.com/yusing/goutils/apitypes"
|
||||||
@@ -49,9 +50,9 @@ func SystemInfo(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.Request.URL.RawQuery = query.Encode()
|
c.Request.URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
agent, ok := agentPkg.GetAgent(agentAddr)
|
agent, ok := agentpool.Get(agentAddr)
|
||||||
if !ok {
|
if !ok {
|
||||||
agent, ok = agentPkg.GetAgentByName(agentName)
|
agent, ok = agentpool.GetAgent(agentName)
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
c.JSON(http.StatusNotFound, apitypes.Error("agent_addr or agent_name not found"))
|
c.JSON(http.StatusNotFound, apitypes.Error("agent_addr or agent_name not found"))
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import (
|
|||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/puzpuzpuz/xsync/v4"
|
"github.com/puzpuzpuz/xsync/v4"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
|
||||||
"github.com/yusing/godoxy/internal/acl"
|
"github.com/yusing/godoxy/internal/acl"
|
||||||
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
"github.com/yusing/godoxy/internal/autocert"
|
"github.com/yusing/godoxy/internal/autocert"
|
||||||
config "github.com/yusing/godoxy/internal/config/types"
|
config "github.com/yusing/godoxy/internal/config/types"
|
||||||
"github.com/yusing/godoxy/internal/entrypoint"
|
"github.com/yusing/godoxy/internal/entrypoint"
|
||||||
@@ -332,7 +332,7 @@ func (state *state) loadRouteProviders() error {
|
|||||||
errs := gperr.NewGroup("route provider errors")
|
errs := gperr.NewGroup("route provider errors")
|
||||||
results := gperr.NewGroup("loaded route providers")
|
results := gperr.NewGroup("loaded route providers")
|
||||||
|
|
||||||
agent.RemoveAllAgents()
|
agentpool.RemoveAll()
|
||||||
|
|
||||||
numProviders := len(providers.Agents) + len(providers.Files) + len(providers.Docker)
|
numProviders := len(providers.Agents) + len(providers.Files) + len(providers.Docker)
|
||||||
providersCh := make(chan types.RouteProvider, numProviders)
|
providersCh := make(chan types.RouteProvider, numProviders)
|
||||||
@@ -352,11 +352,11 @@ func (state *state) loadRouteProviders() error {
|
|||||||
var providersProducer sync.WaitGroup
|
var providersProducer sync.WaitGroup
|
||||||
for _, a := range providers.Agents {
|
for _, a := range providers.Agents {
|
||||||
providersProducer.Go(func() {
|
providersProducer.Go(func() {
|
||||||
if err := a.Start(state.task.Context()); err != nil {
|
if err := a.Init(state.task.Context()); err != nil {
|
||||||
errs.Add(gperr.PrependSubject(a.String(), err))
|
errs.Add(gperr.PrependSubject(a.String(), err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
agent.AddAgent(a)
|
agentpool.Add(a)
|
||||||
p := route.NewAgentProvider(a)
|
p := route.NewAgentProvider(a)
|
||||||
providersCh <- p
|
providersCh <- p
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/moby/moby/client"
|
"github.com/moby/moby/client"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
"github.com/yusing/godoxy/internal/types"
|
"github.com/yusing/godoxy/internal/types"
|
||||||
httputils "github.com/yusing/goutils/http"
|
httputils "github.com/yusing/goutils/http"
|
||||||
"github.com/yusing/goutils/task"
|
"github.com/yusing/goutils/task"
|
||||||
@@ -149,16 +150,16 @@ func NewClient(cfg types.DockerProviderConfig, unique ...bool) (*SharedClient, e
|
|||||||
var dial func(ctx context.Context) (net.Conn, error)
|
var dial func(ctx context.Context) (net.Conn, error)
|
||||||
|
|
||||||
if agent.IsDockerHostAgent(host) {
|
if agent.IsDockerHostAgent(host) {
|
||||||
cfg, ok := agent.GetAgent(host)
|
a, ok := agentpool.Get(host)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Errorf("agent %q not found", host))
|
panic(fmt.Errorf("agent %q not found", host))
|
||||||
}
|
}
|
||||||
opt = []client.Opt{
|
opt = []client.Opt{
|
||||||
client.WithHost(agent.DockerHost),
|
client.WithHost(agent.DockerHost),
|
||||||
client.WithHTTPClient(cfg.NewHTTPClient()),
|
client.WithHTTPClient(a.HTTPClient()),
|
||||||
}
|
}
|
||||||
addr = "tcp://" + cfg.Addr
|
addr = "tcp://" + a.Addr
|
||||||
dial = cfg.DialContext
|
dial = a.DialContext
|
||||||
} else {
|
} else {
|
||||||
helper, err := connhelper.GetConnectionHelper(host)
|
helper, err := connhelper.GetConnectionHelper(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/moby/moby/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/moby/moby/client"
|
"github.com/moby/moby/client"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
"github.com/yusing/godoxy/internal/serialization"
|
"github.com/yusing/godoxy/internal/serialization"
|
||||||
"github.com/yusing/godoxy/internal/types"
|
"github.com/yusing/godoxy/internal/types"
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
@@ -71,7 +72,7 @@ func FromDocker(c *container.Summary, dockerCfg types.DockerProviderConfig) (res
|
|||||||
|
|
||||||
if agent.IsDockerHostAgent(dockerCfg.URL) {
|
if agent.IsDockerHostAgent(dockerCfg.URL) {
|
||||||
var ok bool
|
var ok bool
|
||||||
res.Agent, ok = agent.GetAgent(dockerCfg.URL)
|
res.Agent, ok = agentpool.Get(dockerCfg.URL)
|
||||||
if !ok {
|
if !ok {
|
||||||
addError(res, fmt.Errorf("agent %q not found", dockerCfg.URL))
|
addError(res, fmt.Errorf("agent %q not found", dockerCfg.URL))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
config "github.com/yusing/godoxy/internal/config/types"
|
config "github.com/yusing/godoxy/internal/config/types"
|
||||||
"github.com/yusing/godoxy/internal/docker"
|
"github.com/yusing/godoxy/internal/docker"
|
||||||
"github.com/yusing/godoxy/internal/homepage"
|
"github.com/yusing/godoxy/internal/homepage"
|
||||||
@@ -94,7 +94,7 @@ type (
|
|||||||
|
|
||||||
provider types.RouteProvider
|
provider types.RouteProvider
|
||||||
|
|
||||||
agent *agent.AgentConfig
|
agent *agentpool.Agent
|
||||||
|
|
||||||
started chan struct{}
|
started chan struct{}
|
||||||
onceStart sync.Once
|
onceStart sync.Once
|
||||||
@@ -153,10 +153,10 @@ func (r *Route) validate() gperr.Error {
|
|||||||
}
|
}
|
||||||
var ok bool
|
var ok bool
|
||||||
// by agent address
|
// by agent address
|
||||||
r.agent, ok = agent.GetAgent(r.Agent)
|
r.agent, ok = agentpool.Get(r.Agent)
|
||||||
if !ok {
|
if !ok {
|
||||||
// fallback to get agent by name
|
// fallback to get agent by name
|
||||||
r.agent, ok = agent.GetAgentByName(r.Agent)
|
r.agent, ok = agentpool.GetAgent(r.Agent)
|
||||||
if !ok {
|
if !ok {
|
||||||
return gperr.Errorf("agent %s not found", r.Agent)
|
return gperr.Errorf("agent %s not found", r.Agent)
|
||||||
}
|
}
|
||||||
@@ -510,7 +510,7 @@ func (r *Route) Type() route.RouteType {
|
|||||||
panic(fmt.Errorf("unexpected scheme %s for alias %s", r.Scheme, r.Alias))
|
panic(fmt.Errorf("unexpected scheme %s for alias %s", r.Scheme, r.Alias))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Route) GetAgent() *agent.AgentConfig {
|
func (r *Route) GetAgent() *agentpool.Agent {
|
||||||
if r.Container != nil && r.Container.Agent != nil {
|
if r.Container != nil && r.Container.Agent != nil {
|
||||||
return r.Container.Agent
|
return r.Container.Agent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
|
|
||||||
"github.com/pires/go-proxyproto"
|
"github.com/pires/go-proxyproto"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
|
||||||
"github.com/yusing/godoxy/internal/acl"
|
"github.com/yusing/godoxy/internal/acl"
|
||||||
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
"github.com/yusing/godoxy/internal/entrypoint"
|
"github.com/yusing/godoxy/internal/entrypoint"
|
||||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||||
ioutils "github.com/yusing/goutils/io"
|
ioutils "github.com/yusing/goutils/io"
|
||||||
@@ -19,7 +19,7 @@ type TCPTCPStream struct {
|
|||||||
listener net.Listener
|
listener net.Listener
|
||||||
laddr *net.TCPAddr
|
laddr *net.TCPAddr
|
||||||
dst *net.TCPAddr
|
dst *net.TCPAddr
|
||||||
agent *agent.AgentConfig
|
agent *agentpool.Agent
|
||||||
|
|
||||||
preDial nettypes.HookFunc
|
preDial nettypes.HookFunc
|
||||||
onRead nettypes.HookFunc
|
onRead nettypes.HookFunc
|
||||||
@@ -27,7 +27,7 @@ type TCPTCPStream struct {
|
|||||||
closed atomic.Bool
|
closed atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPTCPStream(network, listenAddr, dstAddr string, agentCfg *agent.AgentConfig) (nettypes.Stream, error) {
|
func NewTCPTCPStream(network, listenAddr, dstAddr string, agent *agentpool.Agent) (nettypes.Stream, error) {
|
||||||
dst, err := net.ResolveTCPAddr(network, dstAddr)
|
dst, err := net.ResolveTCPAddr(network, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -36,7 +36,7 @@ func NewTCPTCPStream(network, listenAddr, dstAddr string, agentCfg *agent.AgentC
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &TCPTCPStream{network: network, laddr: laddr, dst: dst, agent: agentCfg}, nil
|
return &TCPTCPStream{network: network, laddr: laddr, dst: dst, agent: agent}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TCPTCPStream) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) {
|
func (s *TCPTCPStream) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
|
||||||
"github.com/yusing/godoxy/internal/acl"
|
"github.com/yusing/godoxy/internal/acl"
|
||||||
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||||
"github.com/yusing/goutils/synk"
|
"github.com/yusing/goutils/synk"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
@@ -23,7 +23,7 @@ type UDPUDPStream struct {
|
|||||||
|
|
||||||
laddr *net.UDPAddr
|
laddr *net.UDPAddr
|
||||||
dst *net.UDPAddr
|
dst *net.UDPAddr
|
||||||
agent *agent.AgentConfig
|
agent *agentpool.Agent
|
||||||
|
|
||||||
preDial nettypes.HookFunc
|
preDial nettypes.HookFunc
|
||||||
onRead nettypes.HookFunc
|
onRead nettypes.HookFunc
|
||||||
@@ -53,7 +53,7 @@ const (
|
|||||||
|
|
||||||
var bufPool = synk.GetSizedBytesPool()
|
var bufPool = synk.GetSizedBytesPool()
|
||||||
|
|
||||||
func NewUDPUDPStream(network, listenAddr, dstAddr string, agentCfg *agent.AgentConfig) (nettypes.Stream, error) {
|
func NewUDPUDPStream(network, listenAddr, dstAddr string, agent *agentpool.Agent) (nettypes.Stream, error) {
|
||||||
dst, err := net.ResolveUDPAddr(network, dstAddr)
|
dst, err := net.ResolveUDPAddr(network, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -66,7 +66,7 @@ func NewUDPUDPStream(network, listenAddr, dstAddr string, agentCfg *agent.AgentC
|
|||||||
network: network,
|
network: network,
|
||||||
laddr: laddr,
|
laddr: laddr,
|
||||||
dst: dst,
|
dst: dst,
|
||||||
agent: agentCfg,
|
agent: agent,
|
||||||
conns: make(map[string]*udpUDPConn),
|
conns: make(map[string]*udpUDPConn),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/bytedance/sonic"
|
"github.com/bytedance/sonic"
|
||||||
"github.com/moby/moby/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/yusing/ds/ordered"
|
"github.com/yusing/ds/ordered"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ type (
|
|||||||
|
|
||||||
State container.ContainerState `json:"state"`
|
State container.ContainerState `json:"state"`
|
||||||
|
|
||||||
Agent *agent.AgentConfig `json:"agent"`
|
Agent *agentpool.Agent `json:"agent"`
|
||||||
|
|
||||||
Labels map[string]string `json:"-"` // for creating routes
|
Labels map[string]string `json:"-"` // for creating routes
|
||||||
ActualLabels map[string]string `json:"labels"` // for displaying in UI
|
ActualLabels map[string]string `json:"labels"` // for displaying in UI
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package types
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
"github.com/yusing/godoxy/internal/homepage"
|
"github.com/yusing/godoxy/internal/homepage"
|
||||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||||
provider "github.com/yusing/godoxy/internal/route/provider/types"
|
provider "github.com/yusing/godoxy/internal/route/provider/types"
|
||||||
@@ -35,7 +35,7 @@ type (
|
|||||||
DisplayName() string
|
DisplayName() string
|
||||||
ContainerInfo() *Container
|
ContainerInfo() *Container
|
||||||
|
|
||||||
GetAgent() *agent.AgentConfig
|
GetAgent() *agentpool.Agent
|
||||||
|
|
||||||
IsDocker() bool
|
IsDocker() bool
|
||||||
IsAgent() bool
|
IsAgent() bool
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package monitor
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
agentPkg "github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/internal/agentpool"
|
||||||
"github.com/yusing/godoxy/internal/types"
|
"github.com/yusing/godoxy/internal/types"
|
||||||
"github.com/yusing/goutils/synk"
|
"github.com/yusing/goutils/synk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
AgentProxiedMonitor struct {
|
AgentProxiedMonitor struct {
|
||||||
agent *agentPkg.AgentConfig
|
agent *agentpool.Agent
|
||||||
query synk.Value[string]
|
query synk.Value[string]
|
||||||
*monitor
|
*monitor
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ func (target *AgentCheckHealthTarget) displayURL() *url.URL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAgentProxiedMonitor(agent *agentPkg.AgentConfig, config types.HealthCheckConfig, target *AgentCheckHealthTarget) *AgentProxiedMonitor {
|
func NewAgentProxiedMonitor(agent *agentpool.Agent, config types.HealthCheckConfig, target *AgentCheckHealthTarget) *AgentProxiedMonitor {
|
||||||
mon := &AgentProxiedMonitor{
|
mon := &AgentProxiedMonitor{
|
||||||
agent: agent,
|
agent: agent,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user