feat(rewriter): implement plugin-based Rewriter module with gRPC and HTTP backends
Add the first implementation of core/rewriter.Rewriter as a plugin-only component following the bypass/admission pattern (single-value, no RewriterObject). Includes: - plugin/rewriter/proto/: protobuf definition with Rewrite RPC returning transformed data in the reply - x/rewriter/plugin/: gRPC and HTTP plugin clients - x/registry/rewriter.go: hot-reload-safe RewriterRegistry with wrapper - x/config/config.go: RewriterConfig, Config.Rewriters, ServiceConfig.Rewriter - x/config/parsing/rewriter/parse.go: config parser (plugin backends only) - core/handler/option.go: Rewriter field + RewriterOption on handler.Options - x/config/loader/loader.go: rewriter registration during startup - x/config/parsing/service/parse.go: inject rewriter into handler options
This commit is contained in:
+9
-2
@@ -316,6 +316,11 @@ type RecorderObject struct {
|
|||||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RewriterConfig struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type LimiterConfig struct {
|
type LimiterConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Limits []string `yaml:",omitempty" json:"limits,omitempty"`
|
Limits []string `yaml:",omitempty" json:"limits,omitempty"`
|
||||||
@@ -487,8 +492,9 @@ type ServiceConfig struct {
|
|||||||
Logger string `yaml:",omitempty" json:"logger,omitempty"`
|
Logger string `yaml:",omitempty" json:"logger,omitempty"`
|
||||||
Loggers []string `yaml:",omitempty" json:"loggers,omitempty"`
|
Loggers []string `yaml:",omitempty" json:"loggers,omitempty"`
|
||||||
Observer string `yaml:",omitempty" json:"observer,omitempty"`
|
Observer string `yaml:",omitempty" json:"observer,omitempty"`
|
||||||
Recorders []*RecorderObject `yaml:",omitempty" json:"recorders,omitempty"`
|
Rewriter string `yaml:",omitempty" json:"rewriter,omitempty"`
|
||||||
Handler *HandlerConfig `yaml:",omitempty" json:"handler,omitempty"`
|
Recorders []*RecorderObject `yaml:",omitempty" json:"recorders,omitempty"`
|
||||||
|
Handler *HandlerConfig `yaml:",omitempty" json:"handler,omitempty"`
|
||||||
Listener *ListenerConfig `yaml:",omitempty" json:"listener,omitempty"`
|
Listener *ListenerConfig `yaml:",omitempty" json:"listener,omitempty"`
|
||||||
Forwarder *ForwarderConfig `yaml:",omitempty" json:"forwarder,omitempty"`
|
Forwarder *ForwarderConfig `yaml:",omitempty" json:"forwarder,omitempty"`
|
||||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||||
@@ -637,6 +643,7 @@ type Config struct {
|
|||||||
Routers []*RouterConfig `yaml:",omitempty" json:"routers,omitempty"`
|
Routers []*RouterConfig `yaml:",omitempty" json:"routers,omitempty"`
|
||||||
SDs []*SDConfig `yaml:"sds,omitempty" json:"sds,omitempty"`
|
SDs []*SDConfig `yaml:"sds,omitempty" json:"sds,omitempty"`
|
||||||
Recorders []*RecorderConfig `yaml:",omitempty" json:"recorders,omitempty"`
|
Recorders []*RecorderConfig `yaml:",omitempty" json:"recorders,omitempty"`
|
||||||
|
Rewriters []*RewriterConfig `yaml:",omitempty" json:"rewriters,omitempty"`
|
||||||
Limiters []*LimiterConfig `yaml:",omitempty" json:"limiters,omitempty"`
|
Limiters []*LimiterConfig `yaml:",omitempty" json:"limiters,omitempty"`
|
||||||
Quotas []*QuotaConfig `yaml:",omitempty" json:"quotas,omitempty"`
|
Quotas []*QuotaConfig `yaml:",omitempty" json:"quotas,omitempty"`
|
||||||
CLimiters []*LimiterConfig `yaml:"climiters,omitempty" json:"climiters,omitempty"`
|
CLimiters []*LimiterConfig `yaml:"climiters,omitempty" json:"climiters,omitempty"`
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
reg "github.com/go-gost/core/registry"
|
reg "github.com/go-gost/core/registry"
|
||||||
"github.com/go-gost/core/resolver"
|
"github.com/go-gost/core/resolver"
|
||||||
"github.com/go-gost/core/router"
|
"github.com/go-gost/core/router"
|
||||||
|
"github.com/go-gost/core/rewriter"
|
||||||
"github.com/go-gost/core/sd"
|
"github.com/go-gost/core/sd"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
"github.com/go-gost/x/config/parsing"
|
"github.com/go-gost/x/config/parsing"
|
||||||
@@ -38,6 +39,7 @@ import (
|
|||||||
quota_parser "github.com/go-gost/x/config/parsing/quota"
|
quota_parser "github.com/go-gost/x/config/parsing/quota"
|
||||||
recorder_parser "github.com/go-gost/x/config/parsing/recorder"
|
recorder_parser "github.com/go-gost/x/config/parsing/recorder"
|
||||||
resolver_parser "github.com/go-gost/x/config/parsing/resolver"
|
resolver_parser "github.com/go-gost/x/config/parsing/resolver"
|
||||||
|
rewriter_parser "github.com/go-gost/x/config/parsing/rewriter"
|
||||||
router_parser "github.com/go-gost/x/config/parsing/router"
|
router_parser "github.com/go-gost/x/config/parsing/router"
|
||||||
sd_parser "github.com/go-gost/x/config/parsing/sd"
|
sd_parser "github.com/go-gost/x/config/parsing/sd"
|
||||||
service_parser "github.com/go-gost/x/config/parsing/service"
|
service_parser "github.com/go-gost/x/config/parsing/service"
|
||||||
@@ -244,6 +246,16 @@ func register(cfg *config.Config) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var entries []named[rewriter.Rewriter]
|
||||||
|
for _, c := range cfg.Rewriters {
|
||||||
|
entries = append(entries, named[rewriter.Rewriter]{c.Name, rewriter_parser.ParseRewriter(c)})
|
||||||
|
}
|
||||||
|
if err := registerGroup(entries, registry.RewriterRegistry()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var entries []named[traffic.TrafficLimiter]
|
var entries []named[traffic.TrafficLimiter]
|
||||||
for _, c := range cfg.Limiters {
|
for _, c := range cfg.Limiters {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package rewriter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/rewriter"
|
||||||
|
"github.com/go-gost/x/config"
|
||||||
|
"github.com/go-gost/x/internal/plugin"
|
||||||
|
rewriter_plugin "github.com/go-gost/x/rewriter/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseRewriter converts a RewriterConfig into a rewriter.Rewriter.
|
||||||
|
// It currently supports plugin backends only (HTTP or gRPC).
|
||||||
|
// Returns nil when cfg is nil or no backend is configured.
|
||||||
|
func ParseRewriter(cfg *config.RewriterConfig) rewriter.Rewriter {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Plugin != nil {
|
||||||
|
var tlsCfg *tls.Config
|
||||||
|
if cfg.Plugin.TLS != nil {
|
||||||
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return rewriter_plugin.NewHTTPPlugin(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return rewriter_plugin.NewGRPCPlugin(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/go-gost/core/chain"
|
"github.com/go-gost/core/chain"
|
||||||
"github.com/go-gost/core/handler"
|
"github.com/go-gost/core/handler"
|
||||||
"github.com/go-gost/core/hop"
|
"github.com/go-gost/core/hop"
|
||||||
|
"github.com/go-gost/core/rewriter"
|
||||||
"github.com/go-gost/core/listener"
|
"github.com/go-gost/core/listener"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/core/observer/stats"
|
"github.com/go-gost/core/observer/stats"
|
||||||
@@ -353,6 +354,11 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rew rewriter.Rewriter
|
||||||
|
if cfg.Rewriter != "" {
|
||||||
|
rew = registry.RewriterRegistry().Get(cfg.Rewriter)
|
||||||
|
}
|
||||||
|
|
||||||
routerOpts = []chain.RouterOption{
|
routerOpts = []chain.RouterOption{
|
||||||
chain.RetriesRouterOption(cfg.Handler.Retries),
|
chain.RetriesRouterOption(cfg.Handler.Retries),
|
||||||
chain.TimeoutRouterOption(dialTimeout),
|
chain.TimeoutRouterOption(dialTimeout),
|
||||||
@@ -382,6 +388,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
|||||||
handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)),
|
handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)),
|
||||||
handler.ObserverOption(registry.ObserverRegistry().Get(cfg.Handler.Observer)),
|
handler.ObserverOption(registry.ObserverRegistry().Get(cfg.Handler.Observer)),
|
||||||
handler.RecordersOption(recorders...),
|
handler.RecordersOption(recorders...),
|
||||||
|
handler.RewriterOption(rew),
|
||||||
handler.LoggerOption(handlerLogger),
|
handler.LoggerOption(handlerLogger),
|
||||||
handler.ServiceOption(cfg.Name),
|
handler.ServiceOption(cfg.Name),
|
||||||
handler.NetnsOption(netnsIn),
|
handler.NetnsOption(netnsIn),
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ require (
|
|||||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||||
github.com/gin-contrib/cors v1.7.2
|
github.com/gin-contrib/cors v1.7.2
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/go-gost/core v0.4.3
|
github.com/go-gost/core v0.5.0
|
||||||
github.com/go-gost/go-shadowsocks2 v0.1.3
|
github.com/go-gost/go-shadowsocks2 v0.1.3
|
||||||
github.com/go-gost/gosocks4 v0.1.0
|
github.com/go-gost/gosocks4 v0.1.0
|
||||||
github.com/go-gost/gosocks5 v0.5.0
|
github.com/go-gost/gosocks5 v0.5.0
|
||||||
github.com/go-gost/plugin v0.3.0
|
github.com/go-gost/plugin v0.4.0
|
||||||
github.com/go-gost/relay v0.6.1
|
github.com/go-gost/relay v0.6.1
|
||||||
github.com/go-gost/tls-dissector v0.2.0
|
github.com/go-gost/tls-dissector v0.2.0
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
|
|||||||
@@ -51,16 +51,16 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
|||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-gost/core v0.4.3 h1:9VEHRzuSq/MVdDZYrp/TtVG0+0r6NVGUQDXsJozrvJs=
|
github.com/go-gost/core v0.5.0 h1:3SkPojjqw2Av9l5OSyRVlBpRvWhaXgU5BWMXB9aR4SM=
|
||||||
github.com/go-gost/core v0.4.3/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A=
|
github.com/go-gost/core v0.5.0/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A=
|
||||||
github.com/go-gost/go-shadowsocks2 v0.1.3 h1:6CUZLp+mTWXnKP2aK8/Z9ZP+ERMX9gSbywmPu4kGX/A=
|
github.com/go-gost/go-shadowsocks2 v0.1.3 h1:6CUZLp+mTWXnKP2aK8/Z9ZP+ERMX9gSbywmPu4kGX/A=
|
||||||
github.com/go-gost/go-shadowsocks2 v0.1.3/go.mod h1:866zFNNI3He6Wef1M/IvAjTal74WhcfKfBgRpTlkKys=
|
github.com/go-gost/go-shadowsocks2 v0.1.3/go.mod h1:866zFNNI3He6Wef1M/IvAjTal74WhcfKfBgRpTlkKys=
|
||||||
github.com/go-gost/gosocks4 v0.1.0 h1:eAzev6qw4fzkFQKC9uCHLVNnnPdHyqCggbnfNN80Pmk=
|
github.com/go-gost/gosocks4 v0.1.0 h1:eAzev6qw4fzkFQKC9uCHLVNnnPdHyqCggbnfNN80Pmk=
|
||||||
github.com/go-gost/gosocks4 v0.1.0/go.mod h1:hzVjwijJuZR1pp3GqpTj+AKcSGrx68RlWTrQMFMYBP0=
|
github.com/go-gost/gosocks4 v0.1.0/go.mod h1:hzVjwijJuZR1pp3GqpTj+AKcSGrx68RlWTrQMFMYBP0=
|
||||||
github.com/go-gost/gosocks5 v0.5.0 h1:YE37l1MJwde8diIQdynStqogMotG5enoTdborhA5yic=
|
github.com/go-gost/gosocks5 v0.5.0 h1:YE37l1MJwde8diIQdynStqogMotG5enoTdborhA5yic=
|
||||||
github.com/go-gost/gosocks5 v0.5.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
|
github.com/go-gost/gosocks5 v0.5.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
|
||||||
github.com/go-gost/plugin v0.3.0 h1:pmll8nNd9PX92BWMB5+b2y2SEkBAWJLxD2ANfX+WHuw=
|
github.com/go-gost/plugin v0.4.0 h1:M7MR5PL7QAFCrdWfcXVX+5iLNTVIZlhl8FfdV/K8dZY=
|
||||||
github.com/go-gost/plugin v0.3.0/go.mod h1:oN23l+yGDCIP9G3KnDl/I/0zVGOobZUDCB2Z5yYYXts=
|
github.com/go-gost/plugin v0.4.0/go.mod h1:oN23l+yGDCIP9G3KnDl/I/0zVGOobZUDCB2Z5yYYXts=
|
||||||
github.com/go-gost/relay v0.6.1 h1:7SqnHFbY8x/DzvjpK03a5zcVH9+TbJAcnW/RT6s1ecc=
|
github.com/go-gost/relay v0.6.1 h1:7SqnHFbY8x/DzvjpK03a5zcVH9+TbJAcnW/RT6s1ecc=
|
||||||
github.com/go-gost/relay v0.6.1/go.mod h1:Dku0f5sfjOClrZFiDmQUrYYJ4uof7rnkCUBfsl0PSAI=
|
github.com/go-gost/relay v0.6.1/go.mod h1:Dku0f5sfjOClrZFiDmQUrYYJ4uof7rnkCUBfsl0PSAI=
|
||||||
github.com/go-gost/tls-dissector v0.2.0 h1:9tE6WOzzpurATTBWn60DU4R8gibpGNY8/qVcc1SicVg=
|
github.com/go-gost/tls-dissector v0.2.0 h1:9tE6WOzzpurATTBWn60DU4R8gibpGNY8/qVcc1SicVg=
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
reg "github.com/go-gost/core/registry"
|
reg "github.com/go-gost/core/registry"
|
||||||
"github.com/go-gost/core/resolver"
|
"github.com/go-gost/core/resolver"
|
||||||
"github.com/go-gost/core/router"
|
"github.com/go-gost/core/router"
|
||||||
|
"github.com/go-gost/core/rewriter"
|
||||||
"github.com/go-gost/core/sd"
|
"github.com/go-gost/core/sd"
|
||||||
"github.com/go-gost/core/service"
|
"github.com/go-gost/core/service"
|
||||||
"github.com/go-gost/x/limiter/quota"
|
"github.com/go-gost/x/limiter/quota"
|
||||||
@@ -46,6 +47,7 @@ var (
|
|||||||
resolverReg reg.Registry[resolver.Resolver] = new(resolverRegistry)
|
resolverReg reg.Registry[resolver.Resolver] = new(resolverRegistry)
|
||||||
hostsReg reg.Registry[hosts.HostMapper] = new(hostsRegistry)
|
hostsReg reg.Registry[hosts.HostMapper] = new(hostsRegistry)
|
||||||
recorderReg reg.Registry[recorder.Recorder] = new(recorderRegistry)
|
recorderReg reg.Registry[recorder.Recorder] = new(recorderRegistry)
|
||||||
|
rewriterReg reg.Registry[rewriter.Rewriter] = new(rewriterRegistry)
|
||||||
|
|
||||||
trafficLimiterReg reg.Registry[traffic.TrafficLimiter] = new(trafficLimiterRegistry)
|
trafficLimiterReg reg.Registry[traffic.TrafficLimiter] = new(trafficLimiterRegistry)
|
||||||
connLimiterReg reg.Registry[conn.ConnLimiter] = new(connLimiterRegistry)
|
connLimiterReg reg.Registry[conn.ConnLimiter] = new(connLimiterRegistry)
|
||||||
@@ -187,6 +189,11 @@ func RecorderRegistry() reg.Registry[recorder.Recorder] {
|
|||||||
return recorderReg
|
return recorderReg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RewriterRegistry returns the global registry of rewriter instances.
|
||||||
|
func RewriterRegistry() reg.Registry[rewriter.Rewriter] {
|
||||||
|
return rewriterReg
|
||||||
|
}
|
||||||
|
|
||||||
// TrafficLimiterRegistry returns the global registry of traffic limiter instances.
|
// TrafficLimiterRegistry returns the global registry of traffic limiter instances.
|
||||||
func TrafficLimiterRegistry() reg.Registry[traffic.TrafficLimiter] {
|
func TrafficLimiterRegistry() reg.Registry[traffic.TrafficLimiter] {
|
||||||
return trafficLimiterReg
|
return trafficLimiterReg
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/rewriter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rewriterRegistry implements a hot-reload-safe registry for rewriter.Rewriter.
|
||||||
|
type rewriterRegistry struct {
|
||||||
|
registry[rewriter.Rewriter]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register stores a Rewriter under the given name.
|
||||||
|
func (r *rewriterRegistry) Register(name string, v rewriter.Rewriter) error {
|
||||||
|
return r.registry.Register(name, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a wrapper that delegates to the currently registered Rewriter.
|
||||||
|
// Returns nil if name is empty.
|
||||||
|
func (r *rewriterRegistry) Get(name string) rewriter.Rewriter {
|
||||||
|
if name != "" {
|
||||||
|
return &rewriterWrapper{name: name, r: r}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rewriterRegistry) get(name string) rewriter.Rewriter {
|
||||||
|
return r.registry.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type rewriterWrapper struct {
|
||||||
|
name string
|
||||||
|
r *rewriterRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rewriterWrapper) Rewrite(ctx context.Context, b []byte, opts ...rewriter.RewriteOption) ([]byte, error) {
|
||||||
|
v := w.r.get(w.name)
|
||||||
|
if v == nil {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
return v.Rewrite(ctx, b, opts...)
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package rewriter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
"github.com/go-gost/core/rewriter"
|
||||||
|
"github.com/go-gost/plugin/rewriter/proto"
|
||||||
|
"github.com/go-gost/x/internal/plugin"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type grpcPlugin struct {
|
||||||
|
conn grpc.ClientConnInterface
|
||||||
|
client proto.RewriterClient
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGRPCPlugin creates a Rewriter plugin based on gRPC.
|
||||||
|
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) rewriter.Rewriter {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "rewriter",
|
||||||
|
"rewriter": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &grpcPlugin{
|
||||||
|
conn: conn,
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
if conn != nil {
|
||||||
|
p.client = proto.NewRewriterClient(conn)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *grpcPlugin) Rewrite(ctx context.Context, b []byte, opts ...rewriter.RewriteOption) ([]byte, error) {
|
||||||
|
if p.client == nil {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var options rewriter.RewriteOptions
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := json.Marshal(options.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := p.client.Rewrite(ctx,
|
||||||
|
&proto.RewriteRequest{
|
||||||
|
Data: b,
|
||||||
|
Metadata: md,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
p.log.Error(err)
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
if reply == nil || !reply.Ok {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
if reply.Data != nil {
|
||||||
|
return reply.Data, nil
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *grpcPlugin) Close() error {
|
||||||
|
if p.conn == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if closer, ok := p.conn.(io.Closer); ok {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package rewriter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
"github.com/go-gost/core/rewriter"
|
||||||
|
"github.com/go-gost/x/internal/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type httpPluginRequest struct {
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
Metadata []byte `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPluginResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpPlugin struct {
|
||||||
|
url string
|
||||||
|
client *http.Client
|
||||||
|
header http.Header
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPPlugin creates a Rewriter plugin based on HTTP.
|
||||||
|
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) rewriter.Rewriter {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpPlugin{
|
||||||
|
url: url,
|
||||||
|
client: plugin.NewHTTPClient(&options),
|
||||||
|
header: options.Header,
|
||||||
|
log: logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "rewriter",
|
||||||
|
"rewriter": name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *httpPlugin) Rewrite(ctx context.Context, b []byte, opts ...rewriter.RewriteOption) ([]byte, error) {
|
||||||
|
if p.client == nil {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var options rewriter.RewriteOptions
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := json.Marshal(options.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := httpPluginRequest{
|
||||||
|
Data: b,
|
||||||
|
Metadata: md,
|
||||||
|
}
|
||||||
|
v, err := json.Marshal(&rb)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.header != nil {
|
||||||
|
req.Header = p.header.Clone()
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := p.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := httpPluginResponse{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !res.OK {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
if res.Data != nil {
|
||||||
|
return res.Data, nil
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user