fix(matcher,plugin): case-insensitive matching, port accumulation, nil guards, doc comments

matcher: fix IPv6 normalization via netip.ParseAddr, port-range overwrite
by switching to []*PortRange map, case-insensitive domain/host matching,
glob.MustCompile→Compile to avoid panic on invalid patterns.

plugin: guard nil *Options in NewGRPCConn and NewHTTPClient; add
HTTPClientTransport helper for idle-connection cleanup.

net/*: add doc comments for all exported symbols (no code changes).

Add 30 tests (matcher_test.go 22, plugin_test.go 8) covering all
matcher types, nil safety, case insensitivity, IPv6 normalization,
port ranges, and plugin option composition.
This commit is contained in:
ginuerzh
2026-05-24 18:18:42 +08:00
parent 51455da96f
commit 0e96f602fa
17 changed files with 719 additions and 31 deletions
+37
View File
@@ -1,3 +1,6 @@
// Package plugin provides shared utilities for building gRPC and HTTP plugin
// clients. Both transport types support authentication tokens, TLS
// configuration, custom headers (HTTP), and connection timeouts.
package plugin
import (
@@ -13,10 +16,13 @@ import (
)
const (
// GRPC is the transport type identifier for gRPC-based plugins.
GRPC string = "grpc"
// HTTP is the transport type identifier for HTTP-based plugins.
HTTP string = "http"
)
// Options holds the common configuration for gRPC and HTTP plugin clients.
type Options struct {
Token string
TLSConfig *tls.Config
@@ -24,32 +30,40 @@ type Options struct {
Timeout time.Duration
}
// Option configures Options.
type Option func(opts *Options)
// TokenOption sets the authentication token sent with each request.
func TokenOption(token string) Option {
return func(opts *Options) {
opts.Token = token
}
}
// TLSConfigOption sets the TLS configuration for transport security.
func TLSConfigOption(cfg *tls.Config) Option {
return func(opts *Options) {
opts.TLSConfig = cfg
}
}
// HeaderOption sets custom HTTP headers sent with each request (HTTP transport only).
func HeaderOption(header http.Header) Option {
return func(opts *Options) {
opts.Header = header
}
}
// TimeoutOption sets the request timeout (HTTP transport only).
func TimeoutOption(timeout time.Duration) Option {
return func(opts *Options) {
opts.Timeout = timeout
}
}
// NewGRPCConn creates a gRPC client connection to addr. If opts is nil or
// opts.TLSConfig is nil, the connection uses insecure transport. When
// opts.Token is set, it is attached as per-RPC credentials.
func NewGRPCConn(addr string, opts *Options) (*grpc.ClientConn, error) {
grpcOpts := []grpc.DialOption{
// grpc.WithBlock(),
@@ -57,6 +71,10 @@ func NewGRPCConn(addr string, opts *Options) (*grpc.ClientConn, error) {
Backoff: backoff.DefaultConfig,
}),
}
if opts == nil {
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
return grpc.NewClient(addr, grpcOpts...)
}
if opts.TLSConfig != nil {
grpcOpts = append(grpcOpts,
grpc.WithAuthority(opts.TLSConfig.ServerName),
@@ -85,7 +103,14 @@ func (c *rpcCredentials) RequireTransportSecurity() bool {
return false
}
// NewHTTPClient creates an http.Client from opts. If opts is nil, a default
// client with no timeout is returned. Use HTTPClientTransport to access the
// underlying transport and close idle connections when the client is no longer
// needed.
func NewHTTPClient(opts *Options) *http.Client {
if opts == nil {
return &http.Client{}
}
return &http.Client{
Timeout: opts.Timeout,
Transport: &http.Transport{
@@ -93,3 +118,15 @@ func NewHTTPClient(opts *Options) *http.Client {
},
}
}
// HTTPClientTransport returns the *http.Transport underlying c, or nil if c's
// transport is not an *http.Transport. Callers that hold the client for a
// long time should call tr.CloseIdleConnections() when the client is no longer
// needed.
func HTTPClientTransport(c *http.Client) *http.Transport {
if c == nil {
return nil
}
tr, _ := c.Transport.(*http.Transport)
return tr
}
+112
View File
@@ -0,0 +1,112 @@
package plugin
import (
"net/http"
"testing"
"time"
)
func TestNewGRPCConn_NilOpts(t *testing.T) {
conn, err := NewGRPCConn("localhost:0", nil)
if err != nil {
t.Fatal(err)
}
if conn == nil {
t.Fatal("expected non-nil conn")
}
conn.Close()
}
func TestNewGRPCConn_NoTLS(t *testing.T) {
conn, err := NewGRPCConn("localhost:0", &Options{})
if err != nil {
t.Fatal(err)
}
if conn == nil {
t.Fatal("expected non-nil conn")
}
conn.Close()
}
func TestNewGRPCConn_WithTLS(t *testing.T) {
conn, err := NewGRPCConn("localhost:0", &Options{
TLSConfig: nil, // nil TLSConfig -> insecure
})
if err != nil {
t.Fatal(err)
}
if conn == nil {
t.Fatal("expected non-nil conn")
}
conn.Close()
}
func TestNewHTTPClient_NilOpts(t *testing.T) {
c := NewHTTPClient(nil)
if c == nil {
t.Fatal("expected non-nil client")
}
}
func TestNewHTTPClient_Defaults(t *testing.T) {
c := NewHTTPClient(&Options{})
if c == nil {
t.Fatal("expected non-nil client")
}
if c.Timeout != 0 {
t.Errorf("expected zero timeout, got %v", c.Timeout)
}
}
func TestNewHTTPClient_WithTimeout(t *testing.T) {
c := NewHTTPClient(&Options{Timeout: 5 * time.Second})
if c.Timeout != 5*time.Second {
t.Errorf("expected 5s timeout, got %v", c.Timeout)
}
}
func TestHTTPClientTransport_Nil(t *testing.T) {
if tr := HTTPClientTransport(nil); tr != nil {
t.Error("expected nil transport for nil client")
}
}
func TestHTTPClientTransport_Valid(t *testing.T) {
c := NewHTTPClient(&Options{})
tr := HTTPClientTransport(c)
if tr == nil {
t.Fatal("expected non-nil transport")
}
// Verify we can call CloseIdleConnections without panic.
tr.CloseIdleConnections()
}
func TestOption(t *testing.T) {
opts := &Options{}
TokenOption("tok")(opts)
TLSConfigOption(nil)(opts)
HeaderOption(http.Header{"X": []string{"y"}})(opts)
TimeoutOption(time.Second)(opts)
if opts.Token != "tok" {
t.Errorf("expected Token 'tok', got %q", opts.Token)
}
if opts.Timeout != time.Second {
t.Errorf("expected 1s timeout, got %v", opts.Timeout)
}
if opts.Header.Get("X") != "y" {
t.Errorf("expected header X=y, got %v", opts.Header)
}
}
func TestNewHTTPClient_TransportTLSConfig(t *testing.T) {
// nil TLSConfig — transport should still exist.
c := NewHTTPClient(&Options{})
tr := HTTPClientTransport(c)
if tr == nil {
t.Fatal("expected non-nil transport")
}
if tr.TLSClientConfig != nil {
t.Error("expected nil TLSClientConfig")
}
}