add reload for web api

This commit is contained in:
ginuerzh
2025-01-10 21:39:21 +08:00
parent e480039691
commit aa7528fc1e
11 changed files with 858 additions and 145 deletions
+13 -83
View File
@@ -2,13 +2,11 @@ package api
import (
"embed"
"net"
"net/http"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/service"
)
var (
@@ -21,55 +19,17 @@ type Response struct {
Msg string `json:"msg,omitempty"`
}
type options struct {
accessLog bool
pathPrefix string
auther auth.Authenticator
type Options struct {
AccessLog bool
PathPrefix string
Auther auth.Authenticator
}
type Option func(*options)
func PathPrefixOption(pathPrefix string) Option {
return func(o *options) {
o.pathPrefix = pathPrefix
}
}
func AccessLogOption(enable bool) Option {
return func(o *options) {
o.accessLog = enable
}
}
func AutherOption(auther auth.Authenticator) Option {
return func(o *options) {
o.auther = auther
}
}
type server struct {
s *http.Server
ln net.Listener
cclose chan struct{}
}
func NewService(network, addr string, opts ...Option) (service.Service, error) {
if network == "" {
network = "tcp"
}
ln, err := net.Listen(network, addr)
if err != nil {
return nil, err
func Register(r *gin.Engine, opts *Options) {
if opts == nil {
opts = &Options{}
}
var options options
for _, opt := range opts {
opt(&options)
}
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(
cors.New((cors.Config{
AllowAllOrigins: true,
@@ -79,55 +39,25 @@ func NewService(network, addr string, opts ...Option) (service.Service, error) {
})),
gin.Recovery(),
)
if options.accessLog {
if opts.AccessLog {
r.Use(mwLogger())
}
router := r.Group("")
if options.pathPrefix != "" {
router = router.Group(options.pathPrefix)
if opts.PathPrefix != "" {
router = router.Group(opts.PathPrefix)
}
router.StaticFS("/docs", http.FS(swaggerDoc))
config := router.Group("/config")
config.Use(mwBasicAuth(options.auther))
registerConfig(config)
config.Use(mwBasicAuth(opts.Auther))
return &server{
s: &http.Server{
Handler: r,
},
ln: ln,
cclose: make(chan struct{}),
}, nil
}
func (s *server) Serve() error {
return s.s.Serve(s.ln)
}
func (s *server) Addr() net.Addr {
return s.ln.Addr()
}
func (s *server) Close() error {
return s.s.Close()
}
func (s *server) IsClosed() bool {
select {
case <-s.cclose:
return true
default:
return false
}
}
func registerConfig(config *gin.RouterGroup) {
config.GET("", getConfig)
config.POST("", saveConfig)
config.POST("/reload", reloadConfig)
config.POST("/services", createService)
config.PUT("/services/:service", updateService)
config.DELETE("/services/:service", deleteService)
+56
View File
@@ -0,0 +1,56 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/loader"
"github.com/go-gost/x/config/parsing/parser"
"github.com/go-gost/x/registry"
)
// swagger:parameters reloadConfigRequest
type reloadConfigRequest struct{}
// successful operation.
// swagger:response reloadConfigResponse
type reloadConfigResponse struct {
Data Response
}
func reloadConfig(ctx *gin.Context) {
// swagger:route POST /config/reload Reload reloadConfigRequest
//
// Hot reload config.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: reloadConfigResponse
cfg, err := parser.Parse()
if err != nil {
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, err.Error()))
return
}
config.Set(cfg)
if err := loader.Load(cfg); err != nil {
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, err.Error()))
return
}
for _, svc := range registry.ServiceRegistry().GetAll() {
svc := svc
go func() {
svc.Serve()
}()
}
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
+96
View File
@@ -0,0 +1,96 @@
package service
import (
"net"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/service"
"github.com/go-gost/x/api"
)
type options struct {
accessLog bool
pathPrefix string
auther auth.Authenticator
}
type Option func(*options)
func PathPrefixOption(pathPrefix string) Option {
return func(o *options) {
o.pathPrefix = pathPrefix
}
}
func AccessLogOption(enable bool) Option {
return func(o *options) {
o.accessLog = enable
}
}
func AutherOption(auther auth.Authenticator) Option {
return func(o *options) {
o.auther = auther
}
}
type server struct {
s *http.Server
ln net.Listener
cclose chan struct{}
}
func NewService(network, addr string, opts ...Option) (service.Service, error) {
if network == "" {
network = "tcp"
}
ln, err := net.Listen(network, addr)
if err != nil {
return nil, err
}
var options options
for _, opt := range opts {
opt(&options)
}
gin.SetMode(gin.ReleaseMode)
r := gin.New()
api.Register(r, &api.Options{
AccessLog: options.accessLog,
PathPrefix: options.pathPrefix,
Auther: options.auther,
})
return &server{
s: &http.Server{
Handler: r,
},
ln: ln,
cclose: make(chan struct{}),
}, nil
}
func (s *server) Serve() error {
return s.s.Serve(s.ln)
}
func (s *server) Addr() net.Addr {
return s.ln.Addr()
}
func (s *server) Close() error {
return s.s.Close()
}
func (s *server) IsClosed() bool {
select {
case <-s.cclose:
return true
default:
return false
}
}
+103 -8
View File
@@ -41,7 +41,7 @@ definitions:
reload:
$ref: '#/definitions/Duration'
reverse:
description: DEPRECATED by whitelist since beta.4
description: 'Deprecated: use whitelist instead'
type: boolean
x-go-name: Reverse
whitelist:
@@ -102,7 +102,7 @@ definitions:
reload:
$ref: '#/definitions/Duration'
reverse:
description: DEPRECATED by whitelist since beta.4
description: 'Deprecated: use whitelist instead'
type: boolean
x-go-name: Reverse
whitelist:
@@ -309,11 +309,13 @@ definitions:
filter:
$ref: '#/definitions/NodeFilterConfig'
host:
description: DEPRECATED by filter.host
description: 'Deprecated: use matcher instead'
type: string
x-go-name: Host
http:
$ref: '#/definitions/HTTPNodeConfig'
matcher:
$ref: '#/definitions/NodeMatcherConfig'
metadata:
additionalProperties: {}
type: object
@@ -325,11 +327,11 @@ definitions:
type: string
x-go-name: Network
path:
description: DEPRECATED by filter.path
description: 'Deprecated: use matcher instead'
type: string
x-go-name: Path
protocol:
description: DEPRECATED by filter.protocol
description: 'Deprecated: use matcher instead'
type: string
x-go-name: Protocol
tls:
@@ -343,7 +345,7 @@ definitions:
type: string
x-go-name: Hop
name:
description: DEPRECATED by hop field
description: 'Deprecated: use hop instead'
type: string
x-go-name: Name
nodes:
@@ -355,6 +357,17 @@ definitions:
$ref: '#/definitions/SelectorConfig'
type: object
x-go-package: github.com/go-gost/x/config
HTTPBodyRewriteConfig:
properties:
Match:
type: string
Replacement:
type: string
Type:
description: filter by MIME types
type: string
type: object
x-go-package: github.com/go-gost/x/config
HTTPLoader:
properties:
timeout:
@@ -371,20 +384,52 @@ definitions:
header:
additionalProperties:
type: string
description: 'Deprecated: use requestHeader instead'
type: object
x-go-name: Header
host:
description: rewrite host header
type: string
x-go-name: Host
requestHeader:
additionalProperties:
type: string
description: additional request header
type: object
x-go-name: RequestHeader
responseHeader:
additionalProperties:
type: string
description: additional response header
type: object
x-go-name: ResponseHeader
rewrite:
description: 'Deprecated: use rewriteURL instead'
items:
$ref: '#/definitions/HTTPURLRewriteConfig'
type: array
x-go-name: Rewrite
rewriteBody:
description: rewrite response body
items:
$ref: '#/definitions/HTTPBodyRewriteConfig'
type: array
x-go-name: RewriteBody
rewriteURL:
description: rewrite URL
items:
$ref: '#/definitions/HTTPURLRewriteConfig'
type: array
x-go-name: RewriteURL
type: object
x-go-package: github.com/go-gost/x/config
HTTPRecorder:
properties:
header:
additionalProperties:
type: string
type: object
x-go-name: Header
timeout:
$ref: '#/definitions/Duration'
url:
@@ -458,6 +503,10 @@ definitions:
interface:
type: string
x-go-name: Interface
metadata:
additionalProperties: {}
type: object
x-go-name: Metadata
name:
type: string
x-go-name: Name
@@ -737,6 +786,8 @@ definitions:
interface:
type: string
x-go-name: Interface
matcher:
$ref: '#/definitions/NodeMatcherConfig'
metadata:
additionalProperties: {}
type: object
@@ -744,6 +795,9 @@ definitions:
name:
type: string
x-go-name: Name
netns:
type: string
x-go-name: Netns
network:
type: string
x-go-name: Network
@@ -769,6 +823,17 @@ definitions:
x-go-name: Protocol
type: object
x-go-package: github.com/go-gost/x/config
NodeMatcherConfig:
properties:
priority:
format: int64
type: integer
x-go-name: Priority
rule:
type: string
x-go-name: Rule
type: object
x-go-package: github.com/go-gost/x/config
ObserverConfig:
properties:
name:
@@ -821,9 +886,10 @@ definitions:
x-go-package: github.com/go-gost/x/config
RecorderObject:
properties:
Metadata:
metadata:
additionalProperties: {}
type: object
x-go-name: Metadata
name:
type: string
x-go-name: Name
@@ -850,6 +916,9 @@ definitions:
type:
type: string
x-go-name: Type
username:
type: string
x-go-name: Username
type: object
x-go-package: github.com/go-gost/x/config
RedisRecorder:
@@ -870,6 +939,9 @@ definitions:
type:
type: string
x-go-name: Type
username:
type: string
x-go-name: Username
type: object
x-go-package: github.com/go-gost/x/config
ResolverConfig:
@@ -983,7 +1055,7 @@ definitions:
type: string
x-go-name: Hosts
interface:
description: DEPRECATED by metadata.interface since beta.5
description: 'Deprecated: use metadata.interface instead'
type: string
x-go-name: Interface
limiter:
@@ -1139,6 +1211,11 @@ definitions:
x-go-package: github.com/go-gost/x/config
TLSOptions:
properties:
alpn:
items:
type: string
type: array
x-go-name: ALPN
cipherSuites:
items:
type: string
@@ -1834,6 +1911,18 @@ paths:
summary: Update recorder by name, the recorder must already exist.
tags:
- Recorder
/config/reload:
post:
operationId: reloadConfigRequest
responses:
"200":
$ref: '#/responses/reloadConfigResponse'
security:
- basicAuth:
- '[]'
summary: Hot reload config.
tags:
- Reload
/config/resolvers:
post:
operationId: createResolverRequest
@@ -2325,6 +2414,12 @@ responses:
Config: {}
schema:
$ref: '#/definitions/Config'
reloadConfigResponse:
description: successful operation.
headers:
Data: {}
schema:
$ref: '#/definitions/Response'
saveConfigResponse:
description: successful operation.
headers:
+232
View File
@@ -0,0 +1,232 @@
package loader
import (
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
admission_parser "github.com/go-gost/x/config/parsing/admission"
auth_parser "github.com/go-gost/x/config/parsing/auth"
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
chain_parser "github.com/go-gost/x/config/parsing/chain"
hop_parser "github.com/go-gost/x/config/parsing/hop"
hosts_parser "github.com/go-gost/x/config/parsing/hosts"
ingress_parser "github.com/go-gost/x/config/parsing/ingress"
limiter_parser "github.com/go-gost/x/config/parsing/limiter"
logger_parser "github.com/go-gost/x/config/parsing/logger"
observer_parser "github.com/go-gost/x/config/parsing/observer"
recorder_parser "github.com/go-gost/x/config/parsing/recorder"
resolver_parser "github.com/go-gost/x/config/parsing/resolver"
router_parser "github.com/go-gost/x/config/parsing/router"
sd_parser "github.com/go-gost/x/config/parsing/sd"
service_parser "github.com/go-gost/x/config/parsing/service"
"github.com/go-gost/x/registry"
)
var (
defaultLoader *loader = &loader{}
)
func Load(cfg *config.Config) error {
return defaultLoader.Load(cfg)
}
type loader struct{}
func (l *loader) Load(cfg *config.Config) error {
logCfg := cfg.Log
if logCfg == nil {
logCfg = &config.LogConfig{}
}
logger.SetDefault(logger_parser.ParseLogger(&config.LoggerConfig{Log: logCfg}))
tlsCfg, err := parsing.BuildDefaultTLSConfig(cfg.TLS)
if err != nil {
return err
}
parsing.SetDefaultTLSConfig(tlsCfg)
if err := register(cfg); err != nil {
return err
}
return nil
}
func register(cfg *config.Config) error {
if cfg == nil {
return nil
}
for name := range registry.LoggerRegistry().GetAll() {
registry.LoggerRegistry().Unregister(name)
}
for _, loggerCfg := range cfg.Loggers {
if err := registry.LoggerRegistry().Register(loggerCfg.Name, logger_parser.ParseLogger(loggerCfg)); err != nil {
return err
}
}
for name := range registry.AutherRegistry().GetAll() {
registry.AutherRegistry().Unregister(name)
}
for _, autherCfg := range cfg.Authers {
if err := registry.AutherRegistry().Register(autherCfg.Name, auth_parser.ParseAuther(autherCfg)); err != nil {
return err
}
}
for name := range registry.AdmissionRegistry().GetAll() {
registry.AdmissionRegistry().Unregister(name)
}
for _, admissionCfg := range cfg.Admissions {
if err := registry.AdmissionRegistry().Register(admissionCfg.Name, admission_parser.ParseAdmission(admissionCfg)); err != nil {
return err
}
}
for name := range registry.BypassRegistry().GetAll() {
registry.BypassRegistry().Unregister(name)
}
for _, bypassCfg := range cfg.Bypasses {
if err := registry.BypassRegistry().Register(bypassCfg.Name, bypass_parser.ParseBypass(bypassCfg)); err != nil {
return err
}
}
for name := range registry.ResolverRegistry().GetAll() {
registry.ResolverRegistry().Unregister(name)
}
for _, resolverCfg := range cfg.Resolvers {
r, err := resolver_parser.ParseResolver(resolverCfg)
if err != nil {
return err
}
if err := registry.ResolverRegistry().Register(resolverCfg.Name, r); err != nil {
return err
}
}
for name := range registry.HostsRegistry().GetAll() {
registry.HostsRegistry().Unregister(name)
}
for _, hostsCfg := range cfg.Hosts {
if err := registry.HostsRegistry().Register(hostsCfg.Name, hosts_parser.ParseHostMapper(hostsCfg)); err != nil {
return err
}
}
for name := range registry.IngressRegistry().GetAll() {
registry.IngressRegistry().Unregister(name)
}
for _, ingressCfg := range cfg.Ingresses {
if err := registry.IngressRegistry().Register(ingressCfg.Name, ingress_parser.ParseIngress(ingressCfg)); err != nil {
return err
}
}
for name := range registry.RouterRegistry().GetAll() {
registry.RouterRegistry().Unregister(name)
}
for _, routerCfg := range cfg.Routers {
if err := registry.RouterRegistry().Register(routerCfg.Name, router_parser.ParseRouter(routerCfg)); err != nil {
return err
}
}
for name := range registry.SDRegistry().GetAll() {
registry.SDRegistry().Unregister(name)
}
for _, sdCfg := range cfg.SDs {
if err := registry.SDRegistry().Register(sdCfg.Name, sd_parser.ParseSD(sdCfg)); err != nil {
return err
}
}
for name := range registry.ObserverRegistry().GetAll() {
registry.ObserverRegistry().Unregister(name)
}
for _, observerCfg := range cfg.Observers {
if err := registry.ObserverRegistry().Register(observerCfg.Name, observer_parser.ParseObserver(observerCfg)); err != nil {
return err
}
}
for name := range registry.RecorderRegistry().GetAll() {
registry.RecorderRegistry().Unregister(name)
}
for _, recorderCfg := range cfg.Recorders {
if err := registry.RecorderRegistry().Register(recorderCfg.Name, recorder_parser.ParseRecorder(recorderCfg)); err != nil {
return err
}
}
for name := range registry.TrafficLimiterRegistry().GetAll() {
registry.TrafficLimiterRegistry().Unregister(name)
}
for _, limiterCfg := range cfg.Limiters {
if err := registry.TrafficLimiterRegistry().Register(limiterCfg.Name, limiter_parser.ParseTrafficLimiter(limiterCfg)); err != nil {
return err
}
}
for name := range registry.ConnLimiterRegistry().GetAll() {
registry.ConnLimiterRegistry().Unregister(name)
}
for _, limiterCfg := range cfg.CLimiters {
if err := registry.ConnLimiterRegistry().Register(limiterCfg.Name, limiter_parser.ParseConnLimiter(limiterCfg)); err != nil {
return err
}
}
for name := range registry.RateLimiterRegistry().GetAll() {
registry.RateLimiterRegistry().Unregister(name)
}
for _, limiterCfg := range cfg.RLimiters {
if err := registry.RateLimiterRegistry().Register(limiterCfg.Name, limiter_parser.ParseRateLimiter(limiterCfg)); err != nil {
return err
}
}
for name := range registry.HopRegistry().GetAll() {
registry.HopRegistry().Unregister(name)
}
for _, hopCfg := range cfg.Hops {
hop, err := hop_parser.ParseHop(hopCfg, logger.Default())
if err != nil {
return err
}
if err := registry.HopRegistry().Register(hopCfg.Name, hop); err != nil {
return err
}
}
for name := range registry.ChainRegistry().GetAll() {
registry.ChainRegistry().Unregister(name)
}
for _, chainCfg := range cfg.Chains {
c, err := chain_parser.ParseChain(chainCfg, logger.Default())
if err != nil {
return err
}
if err := registry.ChainRegistry().Register(chainCfg.Name, c); err != nil {
return err
}
}
for name := range registry.ServiceRegistry().GetAll() {
registry.ServiceRegistry().Unregister(name)
}
for _, svcCfg := range cfg.Services {
svc, err := service_parser.ParseService(svcCfg)
if err != nil {
return err
}
if svc != nil {
if err := registry.ServiceRegistry().Register(svcCfg.Name, svc); err != nil {
return err
}
}
}
return nil
}
+206
View File
@@ -0,0 +1,206 @@
package parser
import (
"encoding/json"
"os"
"strings"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/cmd"
xmd "github.com/go-gost/x/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
var (
defaultParser = &parser{}
)
func Init(args Args) {
defaultParser = &parser{
args: args,
}
}
func Parse() (*config.Config, error) {
return defaultParser.Parse()
}
type Args struct {
CfgFile string
Services []string
Nodes []string
Debug bool
Trace bool
ApiAddr string
MetricsAddr string
}
type parser struct {
args Args
}
func (p *parser) Parse() (*config.Config, error) {
cfg := &config.Config{}
cfgFile := p.args.CfgFile
if cfgFile != "" {
cfgFile = strings.TrimSpace(cfgFile)
if strings.HasPrefix(cfgFile, "{") && strings.HasSuffix(cfgFile, "}") {
if err := json.Unmarshal([]byte(cfgFile), cfg); err != nil {
return nil, err
}
} else {
if err := cfg.ReadFile(cfgFile); err != nil {
return nil, err
}
}
}
cmdCfg, err := cmd.BuildConfigFromCmd(p.args.Services, p.args.Nodes)
if err != nil {
return nil, err
}
cfg = mergeConfig(cfg, cmdCfg)
if len(cfg.Services) == 0 && p.args.ApiAddr == "" && cfg.API == nil {
if err := cfg.Load(); err != nil {
return nil, err
}
}
if v := os.Getenv("GOST_LOGGER_LEVEL"); v != "" {
cfg.Log = &config.LogConfig{
Level: v,
}
}
if v := os.Getenv("GOST_API"); v != "" {
cfg.API = &config.APIConfig{
Addr: v,
}
}
if v := os.Getenv("GOST_METRICS"); v != "" {
cfg.Metrics = &config.MetricsConfig{
Addr: v,
}
}
if v := os.Getenv("GOST_PROFILING"); v != "" {
cfg.Profiling = &config.ProfilingConfig{
Addr: v,
}
}
if p.args.Debug || p.args.Trace {
if cfg.Log == nil {
cfg.Log = &config.LogConfig{}
}
cfg.Log.Level = string(logger.DebugLevel)
if p.args.Trace {
cfg.Log.Level = string(logger.TraceLevel)
}
}
if p.args.ApiAddr != "" {
cfg.API = &config.APIConfig{
Addr: p.args.ApiAddr,
}
if url, _ := cmd.Norm(p.args.ApiAddr); url != nil {
cfg.API.Addr = url.Host
if url.User != nil {
username := url.User.Username()
password, _ := url.User.Password()
cfg.API.Auth = &config.AuthConfig{
Username: username,
Password: password,
}
}
m := map[string]any{}
for k, v := range url.Query() {
if len(v) > 0 {
m[k] = v[0]
}
}
md := xmd.NewMetadata(m)
cfg.API.PathPrefix = mdutil.GetString(md, "pathPrefix")
cfg.API.AccessLog = mdutil.GetBool(md, "accesslog")
}
}
if p.args.MetricsAddr != "" {
cfg.Metrics = &config.MetricsConfig{
Addr: p.args.MetricsAddr,
}
if url, _ := cmd.Norm(p.args.MetricsAddr); url != nil {
cfg.Metrics.Addr = url.Host
if url.User != nil {
username := url.User.Username()
password, _ := url.User.Password()
cfg.Metrics.Auth = &config.AuthConfig{
Username: username,
Password: password,
}
}
m := map[string]any{}
for k, v := range url.Query() {
if len(v) > 0 {
m[k] = v[0]
}
}
md := xmd.NewMetadata(m)
cfg.Metrics.Path = mdutil.GetString(md, "path")
}
}
return cfg, nil
}
func mergeConfig(cfg1, cfg2 *config.Config) *config.Config {
if cfg1 == nil {
return cfg2
}
if cfg2 == nil {
return cfg1
}
cfg := &config.Config{
Services: append(cfg1.Services, cfg2.Services...),
Chains: append(cfg1.Chains, cfg2.Chains...),
Hops: append(cfg1.Hops, cfg2.Hops...),
Authers: append(cfg1.Authers, cfg2.Authers...),
Admissions: append(cfg1.Admissions, cfg2.Admissions...),
Bypasses: append(cfg1.Bypasses, cfg2.Bypasses...),
Resolvers: append(cfg1.Resolvers, cfg2.Resolvers...),
Hosts: append(cfg1.Hosts, cfg2.Hosts...),
Ingresses: append(cfg1.Ingresses, cfg2.Ingresses...),
SDs: append(cfg1.SDs, cfg2.SDs...),
Recorders: append(cfg1.Recorders, cfg2.Recorders...),
Limiters: append(cfg1.Limiters, cfg2.Limiters...),
CLimiters: append(cfg1.CLimiters, cfg2.CLimiters...),
RLimiters: append(cfg1.RLimiters, cfg2.RLimiters...),
Loggers: append(cfg1.Loggers, cfg2.Loggers...),
Routers: append(cfg1.Routers, cfg2.Routers...),
Observers: append(cfg1.Observers, cfg2.Observers...),
TLS: cfg1.TLS,
Log: cfg1.Log,
API: cfg1.API,
Metrics: cfg1.Metrics,
Profiling: cfg1.Profiling,
}
if cfg2.TLS != nil {
cfg.TLS = cfg2.TLS
}
if cfg2.Log != nil {
cfg.Log = cfg2.Log
}
if cfg2.API != nil {
cfg.API = cfg2.API
}
if cfg2.Metrics != nil {
cfg.Metrics = cfg2.Metrics
}
if cfg2.Profiling != nil {
cfg.Profiling = cfg2.Profiling
}
return cfg
}
+90
View File
@@ -0,0 +1,90 @@
package api
import (
"context"
"net"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/api"
"github.com/go-gost/x/registry"
)
func init() {
registry.HandlerRegistry().Register("api", NewHandler)
}
type apiHandler struct {
handler http.Handler
md metadata
options handler.Options
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &apiHandler{
options: options,
}
}
func (h *apiHandler) Init(md md.Metadata) (err error) {
if err = h.parseMetadata(md); err != nil {
return
}
gin.SetMode(gin.ReleaseMode)
r := gin.New()
api.Register(r, &api.Options{
AccessLog: h.md.accesslog,
PathPrefix: h.md.pathPrefix,
Auther: h.options.Auther,
})
h.handler = r
return
}
func (h *apiHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
l := &singleConnListener{
conn: make(chan net.Conn, 1),
}
l.send(conn)
s := http.Server{
Handler: h.handler,
}
s.Serve(l)
return s.Shutdown(ctx)
}
type singleConnListener struct {
conn chan net.Conn
}
func (l *singleConnListener) Accept() (net.Conn, error) {
if conn, ok := <-l.conn; ok {
return conn, nil
}
return nil, net.ErrClosed
}
func (l *singleConnListener) Close() error {
return nil
}
func (l *singleConnListener) Addr() net.Addr {
return &net.TCPAddr{}
}
func (l *singleConnListener) send(conn net.Conn) {
l.conn <- conn
close(l.conn)
}
+17
View File
@@ -0,0 +1,17 @@
package api
import (
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
accesslog bool
pathPrefix string
}
func (h *apiHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.accesslog = mdutil.GetBool(md, "api.accessLog", "accessLog")
h.md.pathPrefix = mdutil.GetString(md, "api.pathPrefix", "pathPrefix")
return
}
+22 -41
View File
@@ -1,10 +1,9 @@
package file
package metrics
import (
"context"
"net"
"net/http"
"sync"
"time"
"github.com/go-gost/core/handler"
@@ -20,8 +19,7 @@ func init() {
type metricsHandler struct {
handler http.Handler
server *http.Server
ln *singleConnListener
mux *http.ServeMux
md metadata
options handler.Options
}
@@ -42,32 +40,33 @@ func (h *metricsHandler) Init(md md.Metadata) (err error) {
return
}
xmetrics.Init(xmetrics.NewMetrics())
xmetrics.Enable(true)
h.handler = promhttp.Handler()
mux := http.NewServeMux()
mux.Handle(h.md.path, http.HandlerFunc(h.handleFunc))
h.server = &http.Server{
Handler: mux,
}
h.ln = &singleConnListener{
conn: make(chan net.Conn),
done: make(chan struct{}),
}
go h.server.Serve(h.ln)
h.mux = mux
return
}
func (h *metricsHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
h.ln.send(conn)
l := &singleConnListener{
conn: make(chan net.Conn, 1),
}
l.send(conn)
return nil
s := http.Server{
Handler: h.mux,
}
s.Serve(l)
return s.Shutdown(ctx)
}
func (h *metricsHandler) Close() error {
return h.server.Close()
return nil
}
func (h *metricsHandler) handleFunc(w http.ResponseWriter, r *http.Request) {
@@ -88,47 +87,29 @@ func (h *metricsHandler) handleFunc(w http.ResponseWriter, r *http.Request) {
"remote": r.RemoteAddr,
"duration": time.Since(start),
})
log.Debugf("%s %s", r.Method, r.RequestURI)
log.Infof("%s %s", r.Method, r.RequestURI)
}
type singleConnListener struct {
conn chan net.Conn
addr net.Addr
done chan struct{}
mu sync.Mutex
}
func (l *singleConnListener) Accept() (net.Conn, error) {
select {
case conn := <-l.conn:
if conn, ok := <-l.conn; ok {
return conn, nil
case <-l.done:
return nil, net.ErrClosed
}
return nil, net.ErrClosed
}
func (l *singleConnListener) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
select {
case <-l.done:
default:
close(l.done)
}
return nil
}
func (l *singleConnListener) Addr() net.Addr {
return l.addr
return &net.TCPAddr{}
}
func (l *singleConnListener) send(conn net.Conn) {
select {
case l.conn <- conn:
case <-l.done:
return
}
l.conn <- conn
close(l.conn)
}
+1 -1
View File
@@ -1,4 +1,4 @@
package file
package metrics
import (
mdata "github.com/go-gost/core/metadata"
+22 -12
View File
@@ -1,6 +1,10 @@
package metrics
import "github.com/go-gost/core/metrics"
import (
"sync/atomic"
"github.com/go-gost/core/metrics"
)
const (
// Number of services. Labels: host.
@@ -24,29 +28,35 @@ const (
)
var (
global metrics.Metrics = Noop()
defaultMetrics metrics.Metrics = NewMetrics()
enabled atomic.Bool
)
func Init(m metrics.Metrics) {
if m != nil {
global = m
} else {
global = Noop()
}
func Enable(b bool) {
enabled.Store(b)
}
func IsEnabled() bool {
return global != Noop()
return enabled.Load()
}
func GetCounter(name metrics.MetricName, labels metrics.Labels) metrics.Counter {
return global.Counter(name, labels)
if IsEnabled() {
return defaultMetrics.Counter(name, labels)
}
return noop.Counter(name, labels)
}
func GetGauge(name metrics.MetricName, labels metrics.Labels) metrics.Gauge {
return global.Gauge(name, labels)
if IsEnabled() {
return defaultMetrics.Gauge(name, labels)
}
return noop.Gauge(name, labels)
}
func GetObserver(name metrics.MetricName, labels metrics.Labels) metrics.Observer {
return global.Observer(name, labels)
if IsEnabled() {
return defaultMetrics.Observer(name, labels)
}
return noop.Observer(name, labels)
}