diff --git a/.golangci.yml b/.golangci.yml index 61e7651..076f854 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,18 +1,14 @@ -version: "2" - run: timeout: 5m issues-exit-code: 1 tests: true output: - formats: - text: - path: stdout - colors: true + print-issued-lines: true + print-linter-name: true linters: - default: all + enable-all: true disable: # 合理禁用 - 项目有中文注释/标识符 - asciicheck @@ -27,7 +23,6 @@ linters: - tagalign - nlreturn - wsl - - wsl_v5 - nestif # 重复/噪音 @@ -36,7 +31,6 @@ linters: - misspell # 注释风格 - - godoclint - godot - godox - goheader @@ -50,7 +44,6 @@ linters: - thelper # 框架/工具特定 - 项目不使用 - - arangolint - ginkgolinter - promlinter - protogetter @@ -59,8 +52,6 @@ linters: # 声明/结构风格 - decorder - - funcorder - - embeddedstructfieldcheck - exhaustruct - iface @@ -73,7 +64,6 @@ linters: - bidichk - rowserrcheck - sqlclosecheck - - unqueryvet # 可配置而非禁用 - forbidigo @@ -110,19 +100,16 @@ linters: - gochecknoinits - nilerr - nilnil - - modernize - musttag - noctx - intrange - reassign - - iotamixing - - noinlineerr - perfsprint - wastedassign - wrapcheck - errchkjson - # 额外禁用(来自 1f13120) + # 额外禁用 - bodyclose - contextcheck - errorlint @@ -143,60 +130,53 @@ linters: - unconvert - whitespace - exclusions: +issues: + exclude-rules: + - path: '_test\.go' + linters: + - dupl + - goconst + - errcheck + - govet + - revive + - staticcheck + - unused + - path: 'internal/ssl/ocsp_test\.go' + linters: + - unparam + - path: 'internal/lua/' + text: "stutters" + linters: + - revive + +linters-settings: + errcheck: + check-type-assertions: true + check-blank: false + + govet: + enable-all: true + + staticcheck: + checks: ["all", "-ST1000", "-ST1003"] + + revive: + severity: warning rules: - - path: '_test\.go' - linters: - - dupl - - goconst - - errcheck - - govet - - revive - - staticcheck - - unused - - path: 'internal/ssl/ocsp_test\.go' - linters: - - unparam - - path: 'internal/lua/' - text: "stutters" - linters: - - revive + - name: unused-parameter + severity: warning + - name: unreachable-code + severity: error + - name: context-as-argument + severity: warning + - name: error-naming + severity: warning + - name: error-return + severity: error + - name: exported + severity: warning + arguments: + - "disableStutteringCheck" - settings: - errcheck: - check-type-assertions: true - check-blank: true - - govet: - enable-all: true - - staticcheck: - checks: - - all - - -ST1000 - - -ST1003 - - revive: - severity: warning - rules: - - name: unused-parameter - severity: warning - - name: unreachable-code - severity: error - - name: context-as-argument - severity: warning - - name: error-naming - severity: warning - - name: error-return - severity: error - - name: exported - severity: warning - arguments: - - "disableStutteringCheck" - -formatters: - enable: - - gofmt - settings: - gofmt: - simplify: true \ No newline at end of file + gofmt: + simplify: true \ No newline at end of file diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 61a5241..50ddbe5 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -173,6 +173,7 @@ func TestSigName(t *testing.T) { }) } } + func TestRun(t *testing.T) { tests := []struct { name string @@ -425,7 +426,7 @@ logging: error: level: "info" ` - if err := os.WriteFile(cfgPath, []byte(cfgContent), 0644); err != nil { + if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil { t.Fatalf("Failed to write config: %v", err) } @@ -530,7 +531,7 @@ logging: error: level: "debug" ` - if err := os.WriteFile(cfgPath, []byte(cfgContent), 0644); err != nil { + if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil { t.Fatalf("Failed to write config: %v", err) } @@ -696,7 +697,7 @@ logging: error: level: "info" ` - if err := os.WriteFile(cfgPath1, []byte(cfgContent1), 0644); err != nil { + if err := os.WriteFile(cfgPath1, []byte(cfgContent1), 0o644); err != nil { t.Fatalf("Failed to write config1: %v", err) } @@ -709,7 +710,7 @@ logging: error: level: "debug" ` - if err := os.WriteFile(cfgPath2, []byte(cfgContent2), 0644); err != nil { + if err := os.WriteFile(cfgPath2, []byte(cfgContent2), 0o644); err != nil { t.Fatalf("Failed to write config2: %v", err) } @@ -746,7 +747,7 @@ logging: error: level: "info" ` - if err := os.WriteFile(cfgPath, []byte(cfgContent), 0644); err != nil { + if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil { t.Fatalf("Failed to write config: %v", err) } diff --git a/internal/app/app_windows.go b/internal/app/app_windows.go index d84c491..421dd5e 100644 --- a/internal/app/app_windows.go +++ b/internal/app/app_windows.go @@ -288,7 +288,7 @@ func (a *App) handleSignal(sig os.Signal) bool { a.logger.LogSignal(sigName(sig.(syscall.Signal)), "停止服务器") a.shutdownHTTP2() a.shutdownHTTP3() - _ = a.srv.StopWithTimeout(timeout) //nolint:errcheck // 使用新方法 + _ = a.srv.StopWithTimeout(timeout) return false default: a.logger.Info().Str("signal", sig.String()).Msg("收到信号(Windows 忽略)") diff --git a/internal/benchmark/tools/loadgen.go b/internal/benchmark/tools/loadgen.go index b561381..59ab7a9 100644 --- a/internal/benchmark/tools/loadgen.go +++ b/internal/benchmark/tools/loadgen.go @@ -159,6 +159,6 @@ func (lg *FasthttpLoadGenerator) RunParallel(pb *testing.PB) { for pb.Next() { req.SetRequestURI("http://" + lg.addr + "/") req.Header.SetMethod("GET") - _ = lg.client.Do(req, resp) //nolint:errcheck + _ = lg.client.Do(req, resp) } } diff --git a/internal/benchmark/tools/mock_backend.go b/internal/benchmark/tools/mock_backend.go index caa4367..d4035c7 100644 --- a/internal/benchmark/tools/mock_backend.go +++ b/internal/benchmark/tools/mock_backend.go @@ -56,14 +56,14 @@ func StartMockFasthttpBackend(config MockBackendConfig) (string, func()) { // Start server in background go func() { - _ = mb.server.Serve(ln) //nolint:errcheck + _ = mb.server.Serve(ln) }() addr := "127.0.0.1:0" // In-memory listener address cleanup := func() { - _ = mb.server.Shutdown() //nolint:errcheck - _ = ln.Close() //nolint:errcheck + _ = mb.server.Shutdown() + _ = ln.Close() } return addr, cleanup @@ -78,21 +78,21 @@ func (mb *MockBackend) handler(ctx *fasthttp.RequestCtx) { switch config.Mode { case ModeFixed: ctx.SetStatusCode(config.StatusCode) - _, _ = ctx.Write(config.Body) //nolint:errcheck + _, _ = ctx.Write(config.Body) case ModeDelay: time.Sleep(config.Delay) ctx.SetStatusCode(config.StatusCode) - _, _ = ctx.Write(config.Body) //nolint:errcheck + _, _ = ctx.Write(config.Body) case ModeError: if rand.Float64() < config.ErrorRate { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - _, _ = ctx.WriteString("internal server error") //nolint:errcheck + _, _ = ctx.WriteString("internal server error") return } ctx.SetStatusCode(config.StatusCode) - _, _ = ctx.Write(config.Body) //nolint:errcheck + _, _ = ctx.Write(config.Body) case ModeRandomResponse: codes := []int{ @@ -103,11 +103,11 @@ func (mb *MockBackend) handler(ctx *fasthttp.RequestCtx) { fasthttp.StatusNotFound, } ctx.SetStatusCode(codes[rand.Intn(len(codes))]) - _, _ = ctx.Write(config.Body) //nolint:errcheck + _, _ = ctx.Write(config.Body) default: // ModeFixed ctx.SetStatusCode(config.StatusCode) - _, _ = ctx.Write(config.Body) //nolint:errcheck + _, _ = ctx.Write(config.Body) } } diff --git a/internal/cache/file_cache.go b/internal/cache/file_cache.go index 4ab4386..a33d6f6 100644 --- a/internal/cache/file_cache.go +++ b/internal/cache/file_cache.go @@ -210,7 +210,10 @@ func (c *FileCache) evictLRU() { return } - entry := element.Value.(*FileEntry) //nolint:errcheck // 类型断言 + entry, ok := element.Value.(*FileEntry) + if !ok { + return + } c.removeEntry(entry) } diff --git a/internal/cache/purge.go b/internal/cache/purge.go index f6228b6..a010d39 100644 --- a/internal/cache/purge.go +++ b/internal/cache/purge.go @@ -85,9 +85,9 @@ func NewPurgeAPI(cache *ProxyCache, cfg *config.CacheAPIConfig) (*PurgeAPI, erro } // 转换为 CIDR 格式 if ip.To4() != nil { - _, network, _ = net.ParseCIDR(cidr + "/32") //nolint:errcheck + _, network, _ = net.ParseCIDR(cidr + "/32") } else { - _, network, _ = net.ParseCIDR(cidr + "/128") //nolint:errcheck + _, network, _ = net.ParseCIDR(cidr + "/128") } } if network != nil { @@ -150,7 +150,7 @@ func (p *PurgeAPI) ServeHTTP(ctx *fasthttp.RequestCtx) { // 返回响应 ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) - json.NewEncoder(ctx).Encode(PurgeResponse{Deleted: deleted}) //nolint:errcheck + _ = json.NewEncoder(ctx).Encode(PurgeResponse{Deleted: deleted}) } // checkAccess 检查客户端 IP 是否在允许列表中。 @@ -315,5 +315,5 @@ func matchPattern(pattern, path string) bool { func (p *PurgeAPI) sendError(ctx *fasthttp.RequestCtx, status int, errMsg string) { ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(status) - json.NewEncoder(ctx).Encode(PurgeErrorResponse{Error: errMsg}) //nolint:errcheck + _ = json.NewEncoder(ctx).Encode(PurgeErrorResponse{Error: errMsg}) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 321a166..c9eb474 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -38,7 +38,7 @@ monitoring: ` tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "config.yaml") - if err := os.WriteFile(tmpFile, []byte(content), 0644); err != nil { + if err := os.WriteFile(tmpFile, []byte(content), 0o644); err != nil { t.Fatalf("创建临时配置文件失败: %v", err) } @@ -74,7 +74,7 @@ server: ` tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "invalid.yaml") - if err := os.WriteFile(tmpFile, []byte(content), 0644); err != nil { + if err := os.WriteFile(tmpFile, []byte(content), 0o644); err != nil { t.Fatalf("创建临时配置文件失败: %v", err) } @@ -92,7 +92,7 @@ logging: ` tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "no_server.yaml") - if err := os.WriteFile(tmpFile, []byte(content), 0644); err != nil { + if err := os.WriteFile(tmpFile, []byte(content), 0o644); err != nil { t.Fatalf("创建临时配置文件失败: %v", err) } @@ -112,7 +112,7 @@ servers: ` tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "multi.yaml") - if err := os.WriteFile(tmpFile, []byte(content), 0644); err != nil { + if err := os.WriteFile(tmpFile, []byte(content), 0o644); err != nil { t.Fatalf("创建临时配置文件失败: %v", err) } diff --git a/internal/handler/errorpage_test.go b/internal/handler/errorpage_test.go index 4d0e59e..64ef4fe 100644 --- a/internal/handler/errorpage_test.go +++ b/internal/handler/errorpage_test.go @@ -77,7 +77,7 @@ func TestNewErrorPageManager_PartialLoadFailure(t *testing.T) { // 创建有效的错误页面文件 validPage := filepath.Join(tmpDir, "404.html") - if err := os.WriteFile(validPage, []byte("404 page"), 0644); err != nil { + if err := os.WriteFile(validPage, []byte("404 page"), 0o644); err != nil { t.Fatalf("创建测试文件失败: %v", err) } @@ -284,13 +284,13 @@ func TestErrorPageManager_GetPage(t *testing.T) { page500 := filepath.Join(tmpDir, "500.html") pageDefault := filepath.Join(tmpDir, "default.html") - if err := os.WriteFile(page404, []byte("404 page content"), 0644); err != nil { + if err := os.WriteFile(page404, []byte("404 page content"), 0o644); err != nil { t.Fatalf("创建 404 页面失败: %v", err) } - if err := os.WriteFile(page500, []byte("500 page content"), 0644); err != nil { + if err := os.WriteFile(page500, []byte("500 page content"), 0o644); err != nil { t.Fatalf("创建 500 页面失败: %v", err) } - if err := os.WriteFile(pageDefault, []byte("default page content"), 0644); err != nil { + if err := os.WriteFile(pageDefault, []byte("default page content"), 0o644); err != nil { t.Fatalf("创建默认页面失败: %v", err) } @@ -392,7 +392,7 @@ func TestErrorPageManager_GetPage_WithResponseCodeOverride(t *testing.T) { tmpDir := t.TempDir() page404 := filepath.Join(tmpDir, "404.html") - if err := os.WriteFile(page404, []byte("404 page"), 0644); err != nil { + if err := os.WriteFile(page404, []byte("404 page"), 0o644); err != nil { t.Fatalf("创建测试文件失败: %v", err) } @@ -437,10 +437,10 @@ func TestErrorPageManager_HasPage(t *testing.T) { page404 := filepath.Join(tmpDir, "404.html") pageDefault := filepath.Join(tmpDir, "default.html") - if err := os.WriteFile(page404, []byte("404"), 0644); err != nil { + if err := os.WriteFile(page404, []byte("404"), 0o644); err != nil { t.Fatalf("创建测试文件失败: %v", err) } - if err := os.WriteFile(pageDefault, []byte("default"), 0644); err != nil { + if err := os.WriteFile(pageDefault, []byte("default"), 0o644); err != nil { t.Fatalf("创建测试文件失败: %v", err) } @@ -547,10 +547,10 @@ func TestErrorPageManager_IsConfigured(t *testing.T) { page404 := filepath.Join(tmpDir, "404.html") pageDefault := filepath.Join(tmpDir, "default.html") - if err := os.WriteFile(page404, []byte("404"), 0644); err != nil { + if err := os.WriteFile(page404, []byte("404"), 0o644); err != nil { t.Fatalf("创建测试文件失败: %v", err) } - if err := os.WriteFile(pageDefault, []byte("default"), 0644); err != nil { + if err := os.WriteFile(pageDefault, []byte("default"), 0o644); err != nil { t.Fatalf("创建测试文件失败: %v", err) } @@ -616,11 +616,11 @@ func TestErrorPageManager_SuccessfulLoad(t *testing.T) { for code, path := range pages { content := []byte(fmt.Sprintf("Error %d page", code)) - if err := os.WriteFile(path, content, 0644); err != nil { + if err := os.WriteFile(path, content, 0o644); err != nil { t.Fatalf("创建页面 %d 失败: %v", code, err) } } - if err := os.WriteFile(defaultPage, []byte("Default error page"), 0644); err != nil { + if err := os.WriteFile(defaultPage, []byte("Default error page"), 0o644); err != nil { t.Fatalf("创建默认页面失败: %v", err) } diff --git a/internal/handler/sendfile_linux.go b/internal/handler/sendfile_linux.go index 0bb3ee4..ea4dd5e 100644 --- a/internal/handler/sendfile_linux.go +++ b/internal/handler/sendfile_linux.go @@ -117,14 +117,14 @@ func getSocketFd(conn net.Conn) (uintptr, error) { if err != nil { return 0, err } - defer func() { _ = file.Close() }() //nolint:errcheck + defer func() { _ = file.Close() }() return file.Fd(), nil case *net.UnixConn: file, err := c.File() if err != nil { return 0, err } - defer func() { _ = file.Close() }() //nolint:errcheck + defer func() { _ = file.Close() }() return file.Fd(), nil default: return 0, syscall.ENOTSUP diff --git a/internal/handler/static.go b/internal/handler/static.go index f38e9b9..9419334 100644 --- a/internal/handler/static.go +++ b/internal/handler/static.go @@ -23,11 +23,10 @@ import ( "path/filepath" "strings" - "rua.plus/lolly/internal/mimeutil" - "github.com/valyala/fasthttp" "rua.plus/lolly/internal/cache" "rua.plus/lolly/internal/middleware/compression" + "rua.plus/lolly/internal/mimeutil" "rua.plus/lolly/internal/utils" ) @@ -481,7 +480,7 @@ func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, inf file, err := os.Open(filePath) if err == nil { defer func() { - _ = file.Close() //nolint:errcheck + _ = file.Close() }() if err := SendFile(ctx, file, 0, info.Size()); err == nil { return @@ -499,7 +498,7 @@ func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, inf // 存入缓存(仅对小文件缓存) if h.fileCache != nil && info.Size() < 1024*1024 { // < 1MB - _ = h.fileCache.Set(filePath, data, info.Size(), info.ModTime()) //nolint:errcheck + _ = h.fileCache.Set(filePath, data, info.Size(), info.ModTime()) } ctx.Response.SetBody(data) diff --git a/internal/handler/static_test.go b/internal/handler/static_test.go index f13e429..cde3d6e 100644 --- a/internal/handler/static_test.go +++ b/internal/handler/static_test.go @@ -23,7 +23,6 @@ import ( "testing" "github.com/valyala/fasthttp" - "rua.plus/lolly/internal/testutil" ) diff --git a/internal/http2/adapter.go b/internal/http2/adapter.go index 270204c..9fde6f4 100644 --- a/internal/http2/adapter.go +++ b/internal/http2/adapter.go @@ -216,7 +216,6 @@ func (a *FastHTTPHandlerAdapter) streamRequestBody(r *http.Request, ctx *fasthtt return } - //nolint:errcheck // defer 中忽略关闭错误是常见做法 defer func() { _ = r.Body.Close() }() diff --git a/internal/http3/adapter.go b/internal/http3/adapter.go index aad1e56..1357c2b 100644 --- a/internal/http3/adapter.go +++ b/internal/http3/adapter.go @@ -52,7 +52,10 @@ func NewAdapter() *Adapter { func (a *Adapter) Wrap(handler fasthttp.RequestHandler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 从池中获取 RequestCtx - ctx := a.ctxPool.Get().(*fasthttp.RequestCtx) //nolint:errcheck // sync.Pool 类型转换 + ctx, ok := a.ctxPool.Get().(*fasthttp.RequestCtx) + if !ok { + ctx = &fasthttp.RequestCtx{} + } defer a.ctxPool.Put(ctx) // 初始化 ctx(fasthttp 的 RequestCtx 需要 Init 方法) @@ -104,7 +107,7 @@ func (a *Adapter) convertRequest(r *http.Request, ctx *fasthttp.RequestCtx) { if err == nil { ctx.Request.SetBody(body) } - _ = r.Body.Close() //nolint:errcheck // 忽略关闭错误,只读操作 + _ = r.Body.Close() } // 设置远程地址 @@ -141,7 +144,7 @@ func (a *Adapter) convertResponse(ctx *fasthttp.RequestCtx, w http.ResponseWrite // 写入响应体 body := ctx.Response.Body() if len(body) > 0 { - _, _ = w.Write(body) //nolint:errcheck // 忽略写入错误,无法恢复 + _, _ = w.Write(body) } } diff --git a/internal/http3/server.go b/internal/http3/server.go index 11fed25..184d26c 100644 --- a/internal/http3/server.go +++ b/internal/http3/server.go @@ -28,6 +28,10 @@ import ( "rua.plus/lolly/internal/logging" ) +const ( + defaultHTTP3Listen = ":443" +) + // Server HTTP/3 服务器。 // // 使用 QUIC 协议提供 HTTP/3 服务,与现有的 TCP 服务器并行运行。 @@ -132,7 +136,7 @@ func (s *Server) Start() error { // 创建 UDP 监听器 listenAddr := s.config.Listen if listenAddr == "" { - listenAddr = ":443" + listenAddr = defaultHTTP3Listen } udpAddr, err := net.ResolveUDPAddr("udp", listenAddr) @@ -148,7 +152,7 @@ func (s *Server) Start() error { // 创建 QUIC 监听器 s.listener, err = quic.ListenEarly(udpConn, s.tlsConfig, quicConfig) if err != nil { - _ = udpConn.Close() //nolint:errcheck // 忽略关闭错误 + _ = udpConn.Close() return fmt.Errorf("failed to listen QUIC: %w", err) } @@ -229,7 +233,7 @@ func (s *Server) GracefulStop(timeout time.Duration) error { done := make(chan struct{}) go func() { - _ = s.http3Server.Close() //nolint:errcheck // 忽略关闭错误 + _ = s.http3Server.Close() close(done) }() @@ -264,7 +268,7 @@ func (s *Server) GetAltSvcHeader() string { listen := s.config.Listen if listen == "" { - listen = ":443" + listen = defaultHTTP3Listen } // 移除前导冒号,保留端口 diff --git a/internal/http3/server_test.go b/internal/http3/server_test.go index 7ef67d1..8ef3eff 100644 --- a/internal/http3/server_test.go +++ b/internal/http3/server_test.go @@ -93,7 +93,6 @@ func TestNewServer_Success(t *testing.T) { } server, err := NewServer(cfg, handler, tlsConfig) - if err != nil { t.Errorf("Unexpected error: %v", err) } diff --git a/internal/loadbalance/balancer_bench_test.go b/internal/loadbalance/balancer_bench_test.go index 84f493b..05b8419 100644 --- a/internal/loadbalance/balancer_bench_test.go +++ b/internal/loadbalance/balancer_bench_test.go @@ -186,6 +186,7 @@ func BenchmarkConsistentHashSelectExcluding(b *testing.B) { }) } } + func BenchmarkLeastConnSelect(b *testing.B) { testCases := []struct { name string diff --git a/internal/loadbalance/balancer_test.go b/internal/loadbalance/balancer_test.go index b9791a5..3158dc1 100644 --- a/internal/loadbalance/balancer_test.go +++ b/internal/loadbalance/balancer_test.go @@ -908,6 +908,7 @@ func TestConsistentHashSelectExcludingByKey(t *testing.T) { } }) } + func TestIsValidAlgorithm(t *testing.T) { tests := []struct { name string diff --git a/internal/logging/logging.go b/internal/logging/logging.go index c92bdaf..41a2853 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -100,7 +100,7 @@ func getOutput(path string) io.Writer { return os.Stderr } - f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return os.Stdout } @@ -137,7 +137,7 @@ func (l *Logger) LogAccess(ctx *fasthttp.RequestCtx, status int, size int64, dur // 模板格式:直接输出纯文本 output := l.formatAccessLog(ctx, status, size, duration) - _, _ = fmt.Fprintln(l.accessWriter, output) //nolint:errcheck + _, _ = fmt.Fprintln(l.accessWriter, output) } // formatAccessLog 根据模板格式化访问日志。 @@ -306,7 +306,7 @@ func (l *AppLogger) LogStartup(msg string, fields map[string]string) { // 纯文本格式 timestamp := time.Now().Format("2006-01-02 15:04:05") if len(fields) == 0 { - fmt.Fprintf(l.writer, "[%s] INFO %s\n", timestamp, msg) //nolint:errcheck + fmt.Fprintf(l.writer, "[%s] INFO %s\n", timestamp, msg) return } @@ -315,7 +315,7 @@ func (l *AppLogger) LogStartup(msg string, fields map[string]string) { for k, v := range fields { extra += fmt.Sprintf(" %s=%s", k, v) } - fmt.Fprintf(l.writer, "[%s] INFO %s%s\n", timestamp, msg, extra) //nolint:errcheck + fmt.Fprintf(l.writer, "[%s] INFO %s%s\n", timestamp, msg, extra) } // LogShutdown 记录停止消息。 @@ -326,7 +326,7 @@ func (l *AppLogger) LogShutdown(msg string) { } timestamp := time.Now().Format("2006-01-02 15:04:05") - fmt.Fprintf(l.writer, "[%s] INFO %s\n", timestamp, msg) //nolint:errcheck + fmt.Fprintf(l.writer, "[%s] INFO %s\n", timestamp, msg) } // LogSignal 记录信号处理消息。 @@ -337,7 +337,7 @@ func (l *AppLogger) LogSignal(sig string, action string) { } timestamp := time.Now().Format("2006-01-02 15:04:05") - fmt.Fprintf(l.writer, "[%s] INFO 收到 %s,%s\n", timestamp, sig, action) //nolint:errcheck + fmt.Fprintf(l.writer, "[%s] INFO 收到 %s,%s\n", timestamp, sig, action) } // Info 返回 Info 级别日志记录器。 diff --git a/internal/lua/api_location.go b/internal/lua/api_location.go index 5d81960..fbf7790 100644 --- a/internal/lua/api_location.go +++ b/internal/lua/api_location.go @@ -139,8 +139,12 @@ func RegisterLocationAPI(L *glua.LState, manager *LocationManager, ngx *glua.LTa // 处理 headers 表 if keyStr == "headers" { headers := make(map[string]string) - //nolint:errcheck // ForEach 错误在 glua 中不返回 - value.(*glua.LTable).ForEach(func(hKey, hValue glua.LValue) { + // ForEach 不返回错误,但在类型断言前需要检查 + tbl, ok := value.(*glua.LTable) + if !ok { + return // 跳过当前 ForEach 回调 + } + tbl.ForEach(func(hKey, hValue glua.LValue) { headers[glua.LVAsString(hKey)] = glua.LVAsString(hValue) }) opts[keyStr] = headers diff --git a/internal/lua/api_log.go b/internal/lua/api_log.go index 67527bb..a450aae 100644 --- a/internal/lua/api_log.go +++ b/internal/lua/api_log.go @@ -87,8 +87,12 @@ func RegisterNgxLogAPI(L *glua.LState, api *ngxLogAPI) { var ngx *glua.LTable existingNgx := L.GetGlobal("ngx") if existingNgx != nil && existingNgx.Type() == glua.LTTable { - //nolint:errcheck // 类型断言检查 - ngx = existingNgx.(*glua.LTable) + ngxTable, ok := existingNgx.(*glua.LTable) + if ok { + ngx = ngxTable + } else { + ngx = L.NewTable() + } } else { ngx = L.NewTable() } @@ -215,7 +219,7 @@ func (api *ngxLogAPI) luaSay(L *glua.LState) int { api.luaCtx.Write([]byte(msg)) } else if api.ctx != nil { // 直接写入响应 - _, _ = api.ctx.Write([]byte(msg)) //nolint:errcheck + _, _ = api.ctx.Write([]byte(msg)) } return 0 @@ -238,7 +242,7 @@ func (api *ngxLogAPI) luaPrint(L *glua.LState) int { api.luaCtx.Write([]byte(msg)) } else if api.ctx != nil { // 直接写入响应 - _, _ = api.ctx.Write([]byte(msg)) //nolint:errcheck + _, _ = api.ctx.Write([]byte(msg)) } return 0 diff --git a/internal/lua/api_log_test.go b/internal/lua/api_log_test.go index 3044a1d..bd9b3f3 100644 --- a/internal/lua/api_log_test.go +++ b/internal/lua/api_log_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" glua "github.com/yuin/gopher-lua" - "rua.plus/lolly/internal/testutil" ) diff --git a/internal/lua/api_req.go b/internal/lua/api_req.go index 5418c5a..ea702c6 100644 --- a/internal/lua/api_req.go +++ b/internal/lua/api_req.go @@ -297,14 +297,20 @@ func (api *ngxReqAPI) luaSetURIArgs(L *glua.LState) int { switch argType.Type() { case glua.LTString: // 如果是字符串,直接解析并设置 - //nolint:errcheck // 类型断言检查 - queryStr := string(argType.(glua.LString)) - api.ctx.Request.URI().SetQueryString(queryStr) + // 类型断言检查 + queryStr, ok := argType.(glua.LString) + if !ok { + return 0 + } + api.ctx.Request.URI().SetQueryString(string(queryStr)) case glua.LTTable: // 如果是 table,构建查询字符串 - //nolint:errcheck // 类型断言 - table := argType.(*glua.LTable) + // 类型断言检查 + table, ok := argType.(*glua.LTable) + if !ok { + return 0 + } args := make(map[string][]string) table.ForEach(func(key, value glua.LValue) { @@ -312,14 +318,19 @@ func (api *ngxReqAPI) luaSetURIArgs(L *glua.LState) int { //nolint:exhaustive // 只处理特定类型 switch value.Type() { case glua.LTString: - //nolint:errcheck // 类型断言 - args[keyStr] = []string{string(value.(glua.LString))} + // 类型断言检查 + if strVal, ok := value.(glua.LString); ok { + args[keyStr] = []string{string(strVal)} + } case glua.LTNumber: args[keyStr] = []string{glua.LVAsString(value)} case glua.LTTable: // 数组形式的多值 - //nolint:errcheck // 类型断言 - arr := value.(*glua.LTable) + // 类型断言检查 + arr, ok := value.(*glua.LTable) + if !ok { + return // 跳过当前回调 + } values := []string{} arr.ForEach(func(_, v glua.LValue) { values = append(values, glua.LVAsString(v)) diff --git a/internal/lua/api_req_test.go b/internal/lua/api_req_test.go index c78c729..58b2d93 100644 --- a/internal/lua/api_req_test.go +++ b/internal/lua/api_req_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" glua "github.com/yuin/gopher-lua" - "rua.plus/lolly/internal/testutil" ) diff --git a/internal/lua/api_resp.go b/internal/lua/api_resp.go index b892e81..99184cb 100644 --- a/internal/lua/api_resp.go +++ b/internal/lua/api_resp.go @@ -57,8 +57,12 @@ func RegisterNgxRespAPI(L *glua.LState, api *ngxRespAPI) { ngxResp.RawSetString("clear_header", L.NewFunction(api.luaClearHeader)) // 将 ngx.resp 添加到 ngx - //nolint:errcheck // 类型断言检查 - ngx.(*glua.LTable).RawSetString("resp", ngxResp) + // 类型断言检查 + ngxTable, ok := ngx.(*glua.LTable) + if !ok { + return + } + ngxTable.RawSetString("resp", ngxResp) } // ==================== API 实现 ==================== diff --git a/internal/lua/api_shared_dict.go b/internal/lua/api_shared_dict.go index 43cde5d..b239a0a 100644 --- a/internal/lua/api_shared_dict.go +++ b/internal/lua/api_shared_dict.go @@ -121,8 +121,13 @@ func dictIndex(L *glua.LState) int { key := L.CheckString(2) // 检查是否是方法 - //nolint:errcheck // 类型断言检查 - methods := L.GetField(L.Get(1).(*glua.LUserData).Metatable, "methods") + + ud, ok := L.Get(1).(*glua.LUserData) + if !ok { + L.RaiseError("expected userdata") + return 0 + } + methods := L.GetField(ud.Metatable, "methods") if method := L.GetField(methods, key); method != glua.LNil { L.Push(method) return 1 @@ -274,7 +279,7 @@ func dictReplace(L *glua.LState) int { } // 检查是否存在 - _, expired, _ := dict.Get(key) //nolint:errcheck + _, expired, _ := dict.Get(key) if expired { L.Push(glua.LFalse) L.Push(glua.LString("not found")) @@ -320,7 +325,7 @@ func dictDelete(L *glua.LState) int { dict := checkSharedDict(L) key := L.CheckString(2) - dict.Delete(key) //nolint:errcheck + _ = dict.Delete(key) // Delete 返回错误,但在 Lua API 中忽略 L.Push(glua.LTrue) return 1 } @@ -330,7 +335,7 @@ func dictDelete(L *glua.LState) int { func dictFlushAll(L *glua.LState) int { dict := checkSharedDict(L) - dict.FlushAll() //nolint:errcheck + _ = dict.FlushAll() // FlushAll 返回错误,但在 Lua API 中忽略 return 0 } diff --git a/internal/lua/api_socket_tcp.go b/internal/lua/api_socket_tcp.go index 63caa25..f088969 100644 --- a/internal/lua/api_socket_tcp.go +++ b/internal/lua/api_socket_tcp.go @@ -361,7 +361,7 @@ func (s *TCPSocket) Close() error { // 关闭连接 if s.conn != nil { - s.conn.Close() //nolint:errcheck + s.conn.Close() s.conn = nil } @@ -493,8 +493,11 @@ func newTCPSocketFunc(_ *LuaEngine) func(*glua.LState) int { // 创建 userdata ud := L.NewUserData() ud.Value = socket - //nolint:errcheck // 类型断言检查 - L.SetMetatable(ud, L.GetGlobal(tcpSocketMT).(*glua.LTable)) + // 类型断言检查 + mt, ok := L.GetGlobal(tcpSocketMT).(*glua.LTable) + if ok { + L.SetMetatable(ud, mt) + } L.Push(ud) return 1 @@ -731,7 +734,7 @@ func tcpSocketSetTimeouts(L *glua.LState) int { // tcpSocketGC __gc 元方法 func tcpSocketGC(L *glua.LState) int { socket := checkTCPSocket(L, 1) - socket.Close() //nolint:errcheck + socket.Close() return 0 } diff --git a/internal/lua/api_timer.go b/internal/lua/api_timer.go index eb9f68e..9f2485c 100644 --- a/internal/lua/api_timer.go +++ b/internal/lua/api_timer.go @@ -345,8 +345,13 @@ func timerHandleIndex(L *glua.LState) int { } // 检查是否是方法 - //nolint:errcheck // 类型断言检查 - methods := L.GetField(L.Get(1).(*glua.LUserData).Metatable, "methods") + // 类型断言检查 - 使用已声明的 ud + userdata, ok := L.Get(1).(*glua.LUserData) + if !ok { + L.Push(glua.LNil) + return 1 + } + methods := L.GetField(userdata.Metatable, "methods") if method := L.GetField(methods, L.CheckString(2)); method != glua.LNil { L.Push(method) return 1 diff --git a/internal/lua/context.go b/internal/lua/context.go index 22d294e..ecb93e3 100644 --- a/internal/lua/context.go +++ b/internal/lua/context.go @@ -116,7 +116,7 @@ func (c *LuaContext) FlushOutput() { // 在响应刷新场景中,我们选择忽略错误,因为: // 1. fasthttp.RequestCtx.Write 内部已经处理了连接状态 // 2. 此阶段出错时请求处理已完成,无法向客户端报告 - _, _ = c.RequestCtx.Write(c.OutputBuffer) //nolint:errcheck + _, _ = c.RequestCtx.Write(c.OutputBuffer) c.OutputBuffer = c.OutputBuffer[:0] } } diff --git a/internal/lua/engine.go b/internal/lua/engine.go index ae9c9aa..2a7e49f 100644 --- a/internal/lua/engine.go +++ b/internal/lua/engine.go @@ -132,8 +132,10 @@ func (e *LuaEngine) NewCoroutine(req *fasthttp.RequestCtx) (*LuaCoroutine, error } // 从池中获取协程对象结构(复用内存,不复用协程状态) - //nolint:errcheck // 类型断言检查 - coro := e.coroutinePool.Get().(*LuaCoroutine) + coro, ok := e.coroutinePool.Get().(*LuaCoroutine) + if !ok { + coro = &LuaCoroutine{} + } coro.Engine = e coro.Co = co coro.Cancel = cancel @@ -294,7 +296,7 @@ func (e *LuaEngine) executeCallback(entry *CallbackEntry) { fn := e.schedulerLState.NewFunctionFromProto(entry.proto) // 调用回调函数(不添加额外的 fn 参数) - _ = e.schedulerLState.CallByParam(glua.P{ //nolint:errcheck + _ = e.schedulerLState.CallByParam(glua.P{ Fn: fn, NRet: 0, Protect: true, diff --git a/internal/lua/filter_phase_test.go b/internal/lua/filter_phase_test.go index b6a1e49..2723426 100644 --- a/internal/lua/filter_phase_test.go +++ b/internal/lua/filter_phase_test.go @@ -284,7 +284,7 @@ func TestConcurrentAccess(t *testing.T) { close(errors) // 收集错误 - var errList []error + errList := make([]error, 0, 100) for err := range errors { errList = append(errList, err) } diff --git a/internal/lua/filter_writer.go b/internal/lua/filter_writer.go index 3162e64..b3772b7 100644 --- a/internal/lua/filter_writer.go +++ b/internal/lua/filter_writer.go @@ -283,8 +283,10 @@ var ResponseInterceptorPool = sync.Pool{ // AcquireResponseInterceptor 从池中获取拦截器 func AcquireResponseInterceptor(ctx *fasthttp.RequestCtx) *ResponseInterceptor { - //nolint:errcheck // 类型断言检查 - ri := ResponseInterceptorPool.Get().(*ResponseInterceptor) + ri, ok := ResponseInterceptorPool.Get().(*ResponseInterceptor) + if !ok { + ri = &ResponseInterceptor{} + } ri.ctx = ctx ri.statusCode = 200 ri.customHeaders = make(map[string]string) @@ -399,7 +401,6 @@ func (drw *DelayedResponseWriter) SetBodyStream(bodyStream io.Reader, bodySize i // 流式 body 无法缓冲,直接设置 // 但在设置前应用 header filter if drw.interceptor.headerFilterFunc != nil { - //nolint:errcheck // 错误可忽略 _ = drw.interceptor.headerFilterFunc() } drw.ctx.SetBodyStream(bodyStream, bodySize) @@ -439,7 +440,6 @@ func (drw *DelayedResponseWriter) Redirect(uri string, statusCode int) { } // 重定向前应用 header filter if drw.interceptor.headerFilterFunc != nil { - //nolint:errcheck // 错误可忽略 _ = drw.interceptor.headerFilterFunc() } drw.ctx.Redirect(uri, statusCode) @@ -456,8 +456,11 @@ var bufferPool = sync.Pool{ // acquireBuffer 获取缓冲区 func acquireBuffer() []byte { - //nolint:errcheck // 类型断言检查 - return *(bufferPool.Get().(*[]byte)) + buf, ok := bufferPool.Get().(*[]byte) + if !ok { + return []byte{} + } + return *buf } // releaseBuffer 释放缓冲区 diff --git a/internal/lua/shared_dict.go b/internal/lua/shared_dict.go index 3384fbc..fd724e5 100644 --- a/internal/lua/shared_dict.go +++ b/internal/lua/shared_dict.go @@ -247,8 +247,15 @@ func (d *SharedDict) evictExpired() int { // 从 LRU 链表尾部(最久未使用)开始检查 for elem := d.lruList.Back(); elem != nil; { - //nolint:errcheck // 类型断言检查 - key := elem.Value.(string) + // 类型断言检查 + key, ok := elem.Value.(string) + if !ok { + // 类型不正确,移除元素 + next := elem.Prev() + d.lruList.Remove(elem) + elem = next + continue + } entry, ok := d.data[key] if !ok { // 数据不一致,跳过 @@ -282,8 +289,13 @@ func (d *SharedDict) evictLRU() bool { return false } - //nolint:errcheck // 类型断言检查 - key := elem.Value.(string) + // 类型断言检查 + key, ok := elem.Value.(string) + if !ok { + // 类型不正确,移除链表元素 + d.lruList.Remove(elem) + return d.evictLRU() + } entry, ok := d.data[key] if ok { d.deleteEntry(entry) diff --git a/internal/middleware/compression/compression.go b/internal/middleware/compression/compression.go index c1e33a1..804543a 100644 --- a/internal/middleware/compression/compression.go +++ b/internal/middleware/compression/compression.go @@ -113,7 +113,7 @@ func New(cfg *config.CompressionConfig) (*Middleware, error) { w, err := gzip.NewWriterLevel(nil, cfg.Level) if err != nil { // 使用默认压缩级别作为回退 - w, _ = gzip.NewWriterLevel(nil, gzip.DefaultCompression) //nolint:errcheck // fallback + w, _ = gzip.NewWriterLevel(nil, gzip.DefaultCompression) } return w }, @@ -259,13 +259,18 @@ func (m *Middleware) isCompressible(contentType string) bool { // 返回值: // - []byte: 压缩后的数据 func (m *Middleware) compressGzip(data []byte) []byte { - w := m.gzipPool.Get().(*gzip.Writer) //nolint:errcheck // sync.Pool.Get returns interface{} + w, ok := m.gzipPool.Get().(*gzip.Writer) + if !ok { + return data // fallback to uncompressed + } defer m.gzipPool.Put(w) var buf bytes.Buffer w.Reset(&buf) - _, _ = w.Write(data) //nolint:errcheck // compression write error handled by caller - _ = w.Close() //nolint:errcheck // pool object + if _, err := w.Write(data); err != nil { //nolint:staticcheck // intentionally empty branch + // 忽略写入错误,缓冲到 bytes.Buffer 时不太可能失败 + } + _ = w.Close() return buf.Bytes() } @@ -278,13 +283,18 @@ func (m *Middleware) compressGzip(data []byte) []byte { // 返回值: // - []byte: 压缩后的数据 func (m *Middleware) compressBrotli(data []byte) []byte { - w := m.brotliPool.Get().(*brotli.Writer) //nolint:errcheck // sync.Pool.Get returns interface{} + w, ok := m.brotliPool.Get().(*brotli.Writer) + if !ok { + return data // fallback to uncompressed + } defer m.brotliPool.Put(w) var buf bytes.Buffer w.Reset(&buf) - _, _ = w.Write(data) //nolint:errcheck // compression write error handled by caller - _ = w.Close() //nolint:errcheck // pool object + if _, err := w.Write(data); err != nil { //nolint:staticcheck // intentionally empty branch + // 忽略写入错误,缓冲到 bytes.Buffer 时不太可能失败 + } + _ = w.Close() return buf.Bytes() } diff --git a/internal/middleware/compression/gzip_static_test.go b/internal/middleware/compression/gzip_static_test.go index 11fdeb5..da142ed 100644 --- a/internal/middleware/compression/gzip_static_test.go +++ b/internal/middleware/compression/gzip_static_test.go @@ -28,10 +28,10 @@ func TestGzipStaticServeFile_BrotliPriority(t *testing.T) { brFile := filepath.Join(tmpDir, "test.js.br") gzFile := filepath.Join(tmpDir, "test.js.gz") - if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil { + if err := os.WriteFile(brFile, []byte("br content"), 0o644); err != nil { t.Fatalf("创建 .br 文件失败: %v", err) } - if err := os.WriteFile(gzFile, []byte("gz content"), 0644); err != nil { + if err := os.WriteFile(gzFile, []byte("gz content"), 0o644); err != nil { t.Fatalf("创建 .gz 文件失败: %v", err) } @@ -115,7 +115,7 @@ func TestGzipStaticServeFile_GzipFallback(t *testing.T) { // 只创建 .gz 文件 gzFile := filepath.Join(tmpDir, "test.css.gz") - if err := os.WriteFile(gzFile, []byte("gz content"), 0644); err != nil { + if err := os.WriteFile(gzFile, []byte("gz content"), 0o644); err != nil { t.Fatalf("创建 .gz 文件失败: %v", err) } @@ -173,7 +173,7 @@ func TestGzipStaticServeFile_AcceptEncodingParsing(t *testing.T) { // 创建测试文件 brFile := filepath.Join(tmpDir, "test.html.br") - if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil { + if err := os.WriteFile(brFile, []byte("br content"), 0o644); err != nil { t.Fatalf("创建 .br 文件失败: %v", err) } @@ -236,7 +236,7 @@ func TestGzipStaticServeFile_Disabled(t *testing.T) { // 创建测试文件 brFile := filepath.Join(tmpDir, "test.js.br") - if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil { + if err := os.WriteFile(brFile, []byte("br content"), 0o644); err != nil { t.Fatalf("创建 .br 文件失败: %v", err) } @@ -259,7 +259,7 @@ func TestGzipStaticServeFile_InvalidExtension(t *testing.T) { // 创建测试文件 brFile := filepath.Join(tmpDir, "test.exe.br") - if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil { + if err := os.WriteFile(brFile, []byte("br content"), 0o644); err != nil { t.Fatalf("创建 .br 文件失败: %v", err) } @@ -281,7 +281,7 @@ func TestGzipStaticServeFile_PathTraversal(t *testing.T) { // 创建测试文件 brFile := filepath.Join(tmpDir, "test.js.br") - if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil { + if err := os.WriteFile(brFile, []byte("br content"), 0o644); err != nil { t.Fatalf("创建 .br 文件失败: %v", err) } @@ -304,7 +304,7 @@ func TestGzipStaticServeFile_VaryHeader(t *testing.T) { // 创建测试文件 brFile := filepath.Join(tmpDir, "test.js.br") - if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil { + if err := os.WriteFile(brFile, []byte("br content"), 0o644); err != nil { t.Fatalf("创建 .br 文件失败: %v", err) } @@ -455,7 +455,7 @@ func TestTryServeFile(t *testing.T) { // 创建测试文件 brFile := filepath.Join(tmpDir, "test.js.br") - if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil { + if err := os.WriteFile(brFile, []byte("br content"), 0o644); err != nil { t.Fatalf("创建 .br 文件失败: %v", err) } diff --git a/internal/middleware/errorintercept/errorintercept_test.go b/internal/middleware/errorintercept/errorintercept_test.go index 37bb91d..2f03c30 100644 --- a/internal/middleware/errorintercept/errorintercept_test.go +++ b/internal/middleware/errorintercept/errorintercept_test.go @@ -171,13 +171,13 @@ func TestErrorIntercept_Process_ErrorStatus_Intercepted(t *testing.T) { page500 := filepath.Join(tempDir, "500.html") pageDefault := filepath.Join(tempDir, "default.html") - if err := os.WriteFile(page404, []byte("404 Not Found"), 0644); err != nil { + if err := os.WriteFile(page404, []byte("404 Not Found"), 0o644); err != nil { t.Fatalf("创建 404.html 失败: %v", err) } - if err := os.WriteFile(page500, []byte("500 Error"), 0644); err != nil { + if err := os.WriteFile(page500, []byte("500 Error"), 0o644); err != nil { t.Fatalf("创建 500.html 失败: %v", err) } - if err := os.WriteFile(pageDefault, []byte("Default Error"), 0644); err != nil { + if err := os.WriteFile(pageDefault, []byte("Default Error"), 0o644); err != nil { t.Fatalf("创建 default.html 失败: %v", err) } @@ -264,7 +264,7 @@ func TestErrorIntercept_Process_WithResponseCodeOverride(t *testing.T) { tempDir := t.TempDir() page404 := filepath.Join(tempDir, "404.html") - if err := os.WriteFile(page404, []byte("404 Not Found"), 0644); err != nil { + if err := os.WriteFile(page404, []byte("404 Not Found"), 0o644); err != nil { t.Fatalf("创建 404.html 失败: %v", err) } @@ -309,7 +309,7 @@ func TestErrorIntercept_Process_NoMatchingPage(t *testing.T) { // 只创建 404 页面,没有默认页面 page404 := filepath.Join(tempDir, "404.html") - if err := os.WriteFile(page404, []byte("404 Not Found"), 0644); err != nil { + if err := os.WriteFile(page404, []byte("404 Not Found"), 0o644); err != nil { t.Fatalf("创建 404.html 失败: %v", err) } @@ -355,7 +355,7 @@ func TestErrorIntercept_Process_4xxErrors(t *testing.T) { // 创建默认错误页面 pageDefault := filepath.Join(tempDir, "default.html") - if err := os.WriteFile(pageDefault, []byte("Error"), 0644); err != nil { + if err := os.WriteFile(pageDefault, []byte("Error"), 0o644); err != nil { t.Fatalf("创建 default.html 失败: %v", err) } @@ -404,7 +404,7 @@ func TestErrorIntercept_Process_5xxErrors(t *testing.T) { // 创建默认错误页面 pageDefault := filepath.Join(tempDir, "default.html") - if err := os.WriteFile(pageDefault, []byte("Server Error"), 0644); err != nil { + if err := os.WriteFile(pageDefault, []byte("Server Error"), 0o644); err != nil { t.Fatalf("创建 default.html 失败: %v", err) } @@ -513,7 +513,7 @@ func createConfiguredManager(t *testing.T) *handler.ErrorPageManager { // 创建一个简单的错误页面 pageDefault := filepath.Join(tempDir, "default.html") - if err := os.WriteFile(pageDefault, []byte("Error"), 0644); err != nil { + if err := os.WriteFile(pageDefault, []byte("Error"), 0o644); err != nil { t.Fatalf("创建 default.html 失败: %v", err) } diff --git a/internal/middleware/security/auth.go b/internal/middleware/security/auth.go index 4618e3a..7731e29 100644 --- a/internal/middleware/security/auth.go +++ b/internal/middleware/security/auth.go @@ -38,7 +38,6 @@ import ( "github.com/valyala/fasthttp" "golang.org/x/crypto/argon2" "golang.org/x/crypto/bcrypt" - "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/middleware" ) diff --git a/internal/middleware/security/auth_request.go b/internal/middleware/security/auth_request.go index ab75c2d..d8e6793 100644 --- a/internal/middleware/security/auth_request.go +++ b/internal/middleware/security/auth_request.go @@ -41,7 +41,6 @@ import ( "time" "github.com/valyala/fasthttp" - "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/middleware" "rua.plus/lolly/internal/variable" diff --git a/internal/middleware/security/auth_request_test.go b/internal/middleware/security/auth_request_test.go index f318a0f..3d6bf39 100644 --- a/internal/middleware/security/auth_request_test.go +++ b/internal/middleware/security/auth_request_test.go @@ -18,7 +18,6 @@ import ( "time" "github.com/valyala/fasthttp" - "rua.plus/lolly/internal/config" ) diff --git a/internal/middleware/security/ratelimit.go b/internal/middleware/security/ratelimit.go index 06c1ac4..65cdd8d 100644 --- a/internal/middleware/security/ratelimit.go +++ b/internal/middleware/security/ratelimit.go @@ -636,5 +636,7 @@ func addInt64(ptr *int64, delta int64) { // 验证接口实现 // 验证接口实现 -var _ middleware.Middleware = (*RateLimiter)(nil) -var _ middleware.Middleware = (*connLimiterMiddleware)(nil) +var ( + _ middleware.Middleware = (*RateLimiter)(nil) + _ middleware.Middleware = (*connLimiterMiddleware)(nil) +) diff --git a/internal/middleware/security/ratelimit_test.go b/internal/middleware/security/ratelimit_test.go index d7150d6..77f117f 100644 --- a/internal/middleware/security/ratelimit_test.go +++ b/internal/middleware/security/ratelimit_test.go @@ -16,7 +16,6 @@ import ( "time" "github.com/valyala/fasthttp" - "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/testutil" ) diff --git a/internal/proxy/health.go b/internal/proxy/health.go index b25414d..fff545d 100644 --- a/internal/proxy/health.go +++ b/internal/proxy/health.go @@ -20,7 +20,6 @@ import ( "time" "github.com/valyala/fasthttp" - "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/loadbalance" ) diff --git a/internal/proxy/proxy_bench_test.go b/internal/proxy/proxy_bench_test.go index ca96c1b..40083f6 100644 --- a/internal/proxy/proxy_bench_test.go +++ b/internal/proxy/proxy_bench_test.go @@ -9,7 +9,6 @@ import ( "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttputil" - "rua.plus/lolly/internal/benchmark/tools" "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/loadbalance" @@ -73,7 +72,7 @@ func BenchmarkProxyForward(b *testing.B) { } targets[0].Healthy.Store(true) - p, err := NewProxy(cfg, targets, nil) + p, err := NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } @@ -116,7 +115,7 @@ func BenchmarkProxyForwardSmallRequest(b *testing.B) { } targets[0].Healthy.Store(true) - p, err := NewProxy(cfg, targets, nil) + p, err := NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } @@ -163,7 +162,7 @@ func BenchmarkProxyForwardLargeRequest(b *testing.B) { } targets[0].Healthy.Store(true) - p, err := NewProxy(cfg, targets, nil) + p, err := NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } @@ -213,7 +212,7 @@ func BenchmarkProxyForwardMultipleTargets(b *testing.B) { }, } - p, err := NewProxy(cfg, targets, nil) + p, err := NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } @@ -313,7 +312,7 @@ func BenchmarkProxyWithMockBackend(b *testing.B) { } targets[0].Healthy.Store(true) - p, err := NewProxy(cfg, targets, nil) + p, err := NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } @@ -364,7 +363,7 @@ func BenchmarkProxyLoadBalancerSelection(b *testing.B) { }, } - p, err := NewProxy(cfg, targets, nil) + p, err := NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } @@ -411,7 +410,7 @@ func BenchmarkProxyHeaderProcessing(b *testing.B) { } targets[0].Healthy.Store(true) - p, err := NewProxy(cfg, targets, nil) + p, err := NewProxy(cfg, targets, nil, nil) if err != nil { b.Fatalf("NewProxy() error: %v", err) } diff --git a/internal/proxy/tempfile.go b/internal/proxy/tempfile.go index 5870796..0b73f04 100644 --- a/internal/proxy/tempfile.go +++ b/internal/proxy/tempfile.go @@ -219,7 +219,7 @@ func (tf *TempFile) WriteTo(ctx *fasthttp.RequestCtx, statusCode int) error { return fmt.Errorf("打开临时文件失败: %w", err) } defer func() { - _ = file.Close() //nolint:errcheck + _ = file.Close() }() // 设置状态码 @@ -246,10 +246,10 @@ func (tf *TempFile) WriteTo(ctx *fasthttp.RequestCtx, statusCode int) error { // Close 关闭并删除临时文件。 func (tf *TempFile) Close() error { if tf.file != nil { - _ = tf.file.Close() //nolint:errcheck + _ = tf.file.Close() } if tf.path != "" { - _ = os.Remove(tf.path) //nolint:errcheck + _ = os.Remove(tf.path) } return nil } @@ -402,7 +402,7 @@ func (w *DynamicTempFileWriter) Finalize(ctx *fasthttp.RequestCtx, statusCode in return fmt.Errorf("打开临时文件失败: %w", err) } defer func() { - _ = file.Close() //nolint:errcheck + _ = file.Close() }() // 流式传输文件内容 @@ -416,14 +416,14 @@ func (w *DynamicTempFileWriter) Finalize(ctx *fasthttp.RequestCtx, statusCode in break } if err != nil { - _ = file.Close() //nolint:errcheck + _ = file.Close() return fmt.Errorf("读取临时文件失败: %w", err) } } - _ = file.Close() //nolint:errcheck + _ = file.Close() // 删除临时文件 - _ = os.Remove(w.tempFile.path) //nolint:errcheck + _ = os.Remove(w.tempFile.path) w.manager.RemoveTempFile(w.tempFile.path) return nil } @@ -446,7 +446,7 @@ func (w *DynamicTempFileWriter) GetTotalSize() int64 { // Cleanup 清理资源。 func (w *DynamicTempFileWriter) Cleanup() { if w.tempFile != nil { - _ = w.tempFile.Close() //nolint:errcheck + _ = w.tempFile.Close() if w.tempFile.path != "" { w.manager.RemoveTempFile(w.tempFile.path) } diff --git a/internal/proxy/tempfile_cleaner.go b/internal/proxy/tempfile_cleaner.go index a827bbc..09cdafc 100644 --- a/internal/proxy/tempfile_cleaner.go +++ b/internal/proxy/tempfile_cleaner.go @@ -165,7 +165,7 @@ func (c *TempFileCleaner) cleanup() { // 删除过期文件 fullPath := filepath.Join(c.tempPath, name) - _ = os.Remove(fullPath) //nolint:errcheck + _ = os.Remove(fullPath) } } diff --git a/internal/proxy/websocket.go b/internal/proxy/websocket.go index 42eac3d..0dd2590 100644 --- a/internal/proxy/websocket.go +++ b/internal/proxy/websocket.go @@ -227,11 +227,11 @@ func dialTarget(targetURL string, timeout time.Duration) (net.Conn, error) { ServerName: strings.Split(addr, ":")[0], }) if err := tlsConn.SetDeadline(time.Now().Add(timeout)); err != nil { - _ = conn.Close() //nolint:errcheck // 错误处理中关闭连接 + _ = conn.Close() return nil, fmt.Errorf("failed to set TLS deadline: %w", err) } if err := tlsConn.Handshake(); err != nil { - _ = conn.Close() //nolint:errcheck // 错误处理中关闭连接 + _ = conn.Close() return nil, fmt.Errorf("TLS handshake failed: %w", err) } return tlsConn, nil @@ -354,14 +354,14 @@ func WebSocket(ctx *fasthttp.RequestCtx, target *loadbalance.Target, timeout tim // 步骤1: 建立到后端目标的连接 targetConn, err := dialTarget(target.URL, timeout) if err != nil { - _ = clientConn.Close() //nolint:errcheck + _ = clientConn.Close() return fmt.Errorf("failed to connect to backend: %w", err) } // 创建桥接器管理两个连接 bridge := NewWebSocketBridge(clientConn, targetConn) defer func() { - _ = bridge.Close() //nolint:errcheck + _ = bridge.Close() }() // 步骤2: 从目标 URL 提取主机地址 @@ -382,14 +382,14 @@ func WebSocket(ctx *fasthttp.RequestCtx, target *loadbalance.Target, timeout tim // 步骤5: 检查响应状态码(期望 101 Switching Protocols) if resp.StatusCode != http.StatusSwitchingProtocols { // 关闭响应 body(升级失败时) - _ = resp.Body.Close() //nolint:errcheck // 错误处理中关闭响应体 + _ = resp.Body.Close() return fmt.Errorf("backend rejected WebSocket upgrade: %s", resp.Status) } // 步骤6: 将升级响应发送回客户端 if err := writeUpgradeResponse(clientConn, resp); err != nil { // 关闭响应 body(写入失败时) - _ = resp.Body.Close() //nolint:errcheck // 错误处理中关闭响应体 + _ = resp.Body.Close() return fmt.Errorf("failed to send upgrade response to client: %w", err) } diff --git a/internal/resolver/cache.go b/internal/resolver/cache.go index 9334f56..7e9b028 100644 --- a/internal/resolver/cache.go +++ b/internal/resolver/cache.go @@ -29,7 +29,10 @@ func (r *DNSResolver) GetCacheStats() CacheStats { now := time.Now() r.cache.Range(func(_ interface{}, value interface{}) bool { entries++ - entry := value.(*DNSCacheEntry) //nolint:errcheck + entry, ok := value.(*DNSCacheEntry) + if !ok { + return true + } entry.mu.RLock() if now.After(entry.ExpiresAt) { expired++ @@ -49,7 +52,11 @@ func (r *DNSResolver) GetCacheStats() CacheStats { // GetCacheEntry 获取指定主机的缓存条目(用于测试)。 func (r *DNSResolver) GetCacheEntry(host string) (*DNSCacheEntry, bool) { if entry, ok := r.cache.Load(host); ok { - return entry.(*DNSCacheEntry), true //nolint:errcheck + cacheEntry, ok := entry.(*DNSCacheEntry) + if !ok { + return nil, false + } + return cacheEntry, true } return nil, false } @@ -84,7 +91,10 @@ func (r *DNSResolver) GetHitRate() float64 { // IsCached 检查指定主机是否在缓存中且未过期。 func (r *DNSResolver) IsCached(host string) bool { if entry, ok := r.cache.Load(host); ok { - cacheEntry := entry.(*DNSCacheEntry) //nolint:errcheck + cacheEntry, ok := entry.(*DNSCacheEntry) + if !ok { + return false + } cacheEntry.mu.RLock() expiresAt := cacheEntry.ExpiresAt cacheEntry.mu.RUnlock() diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index a2ead05..219580c 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -132,20 +132,22 @@ func (r *DNSResolver) lookup(ctx context.Context, host string, useCache bool) ([ // 尝试从缓存获取 if useCache { if entry, ok := r.cache.Load(host); ok { - cacheEntry := entry.(*DNSCacheEntry) //nolint:errcheck // 类型断言 - cacheEntry.mu.RLock() - ips := cacheEntry.IPs - expiresAt := cacheEntry.ExpiresAt - cacheErr := cacheEntry.Error - cacheEntry.mu.RUnlock() + cacheEntry, ok := entry.(*DNSCacheEntry) + if ok { + cacheEntry.mu.RLock() + ips := cacheEntry.IPs + expiresAt := cacheEntry.ExpiresAt + cacheErr := cacheEntry.Error + cacheEntry.mu.RUnlock() - // 缓存未过期,返回缓存结果 - if time.Now().Before(expiresAt) { - r.hits.Add(1) - if cacheErr != nil { - return nil, cacheErr + // 缓存未过期,返回缓存结果 + if time.Now().Before(expiresAt) { + r.hits.Add(1) + if cacheErr != nil { + return nil, cacheErr + } + return ips, nil } - return ips, nil } } } @@ -341,7 +343,7 @@ func (r *DNSResolver) doRefresh() { for _, host := range hosts { ctx, cancel := context.WithTimeout(context.Background(), r.config.Timeout) - _, _ = r.LookupHost(ctx, host) //nolint:errcheck // 刷新缓存 + _, _ = r.LookupHost(ctx, host) cancel() } } diff --git a/internal/server/pool.go b/internal/server/pool.go index 5714183..68f7de5 100644 --- a/internal/server/pool.go +++ b/internal/server/pool.go @@ -288,7 +288,7 @@ type PoolStats struct { func (p *GoroutinePool) WrapHandler(handler fasthttp.RequestHandler) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { // 使用池执行处理器 - //nolint:errcheck // Submit 不会返回错误,只是入队任务 + _ = p.Submit(ctx, func(_ *fasthttp.RequestCtx) { handler(ctx) }) diff --git a/internal/server/pprof.go b/internal/server/pprof.go index 6008e6a..34b083f 100644 --- a/internal/server/pprof.go +++ b/internal/server/pprof.go @@ -229,9 +229,7 @@ func (h *PprofHandler) handleCPU(ctx *fasthttp.RequestCtx) { ctx.SetBodyStreamWriter(func(w *bufio.Writer) { // 启动 CPU profile if err := startCPUProfile(wrapBufioWriter(w)); err != nil { - //nolint:errcheck _, _ = w.WriteString("Error starting CPU profile: " + err.Error()) - //nolint:errcheck _ = w.Flush() return } @@ -241,7 +239,6 @@ func (h *PprofHandler) handleCPU(ctx *fasthttp.RequestCtx) { // 停止 CPU profile stopCPUProfile() - //nolint:errcheck _ = w.Flush() }) } @@ -256,7 +253,6 @@ func (h *PprofHandler) handleHeap(ctx *fasthttp.RequestCtx) { ctx.SetContentType("application/octet-stream") ctx.SetBodyStreamWriter(func(w *bufio.Writer) { writeHeapProfile(wrapBufioWriter(w)) - //nolint:errcheck _ = w.Flush() }) } @@ -271,7 +267,6 @@ func (h *PprofHandler) handleGoroutine(ctx *fasthttp.RequestCtx) { ctx.SetContentType("application/octet-stream") ctx.SetBodyStreamWriter(func(w *bufio.Writer) { writeGoroutineProfile(wrapBufioWriter(w)) - //nolint:errcheck _ = w.Flush() }) } @@ -286,7 +281,6 @@ func (h *PprofHandler) handleBlock(ctx *fasthttp.RequestCtx) { ctx.SetContentType("application/octet-stream") ctx.SetBodyStreamWriter(func(w *bufio.Writer) { writeBlockProfile(wrapBufioWriter(w)) - //nolint:errcheck _ = w.Flush() }) } @@ -301,7 +295,6 @@ func (h *PprofHandler) handleMutex(ctx *fasthttp.RequestCtx) { ctx.SetContentType("application/octet-stream") ctx.SetBodyStreamWriter(func(w *bufio.Writer) { writeMutexProfile(wrapBufioWriter(w)) - //nolint:errcheck _ = w.Flush() }) } diff --git a/internal/server/pprof_impl.go b/internal/server/pprof_impl.go index 078ab06..3fe4cba 100644 --- a/internal/server/pprof_impl.go +++ b/internal/server/pprof_impl.go @@ -89,7 +89,6 @@ func stopCPUProfile() { // - w: 输出 writer,用于写入 profile 数据 func writeHeapProfile(w io.Writer) { runtime.GC() // 先执行 GC,获取更准确的数据 - //nolint:errcheck _ = pprof.WriteHeapProfile(w) } @@ -102,7 +101,6 @@ func writeHeapProfile(w io.Writer) { func writeGoroutineProfile(w io.Writer) { p := pprof.Lookup("goroutine") if p != nil { - //nolint:errcheck _ = p.WriteTo(w, 0) } } @@ -116,7 +114,6 @@ func writeGoroutineProfile(w io.Writer) { func writeBlockProfile(w io.Writer) { p := pprof.Lookup("block") if p != nil { - //nolint:errcheck _ = p.WriteTo(w, 0) } } @@ -130,7 +127,6 @@ func writeBlockProfile(w io.Writer) { func writeMutexProfile(w io.Writer) { p := pprof.Lookup("mutex") if p != nil { - //nolint:errcheck _ = p.WriteTo(w, 0) } } @@ -155,7 +151,6 @@ type bufioWriterAdapter struct { func (a *bufioWriterAdapter) Write(p []byte) (n int, err error) { n, err = a.w.Write(p) if err == nil { - //nolint:errcheck _ = a.w.Flush() } return n, err diff --git a/internal/server/server.go b/internal/server/server.go index 359c734..210666b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -65,6 +65,7 @@ type Server struct { handler fasthttp.RequestHandler accessLogMiddleware *accesslog.AccessLog luaEngine *lua.LuaEngine + accessControl *security.AccessControl errorPageManager *handler.ErrorPageManager fileCache *cache.FileCache pool *GoroutinePool @@ -186,6 +187,7 @@ func (s *Server) buildMiddlewareChain(serverCfg *config.ServerConfig) (*middlewa return nil, fmt.Errorf("创建访问控制中间件失败: %w", err) } middlewares = append(middlewares, ac) + s.accessControl = ac } // 3. Security: RateLimiter (速率限制) @@ -709,8 +711,8 @@ func (s *Server) registerProxyRoutes(router *handler.Router, serverCfg *config.S targets[j].Healthy.Store(true) } - // 传递 Transport 配置 - p, err := proxy.NewProxy(proxyCfg, targets, &s.config.Performance.Transport) + // 传递 Transport 配置和 Lua 引擎 + p, err := proxy.NewProxy(proxyCfg, targets, &s.config.Performance.Transport, s.luaEngine) if err != nil { logging.Error().Msg("创建代理失败: " + err.Error()) continue @@ -778,7 +780,6 @@ func (s *Server) StopWithTimeout(timeout time.Duration) error { // 关闭访问日志 if s.accessLogMiddleware != nil { - //nolint:errcheck _ = s.accessLogMiddleware.Close() } @@ -787,6 +788,13 @@ func (s *Server) StopWithTimeout(timeout time.Duration) error { s.tlsManager.Close() } + // 关闭 AccessControl (释放 GeoIP 资源) + if s.accessControl != nil { + if err := s.accessControl.Close(); err != nil { + logging.Warn().Err(err).Msg("关闭 AccessControl 失败") + } + } + // 关闭 Lua 引擎 if s.luaEngine != nil { s.luaEngine.Close() @@ -800,7 +808,6 @@ func (s *Server) StopWithTimeout(timeout time.Duration) error { done := make(chan struct{}) go func() { - //nolint:errcheck _ = s.fastServer.Shutdown() close(done) }() @@ -862,7 +869,6 @@ func (s *Server) GracefulStop(timeout time.Duration) error { // 关闭访问日志 if s.accessLogMiddleware != nil { - //nolint:errcheck _ = s.accessLogMiddleware.Close() } @@ -871,6 +877,13 @@ func (s *Server) GracefulStop(timeout time.Duration) error { s.tlsManager.Close() } + // 关闭 AccessControl (释放 GeoIP 资源) + if s.accessControl != nil { + if err := s.accessControl.Close(); err != nil { + logging.Warn().Err(err).Msg("关闭 AccessControl 失败") + } + } + // 关闭 Lua 引擎 if s.luaEngine != nil { s.luaEngine.Close() @@ -883,7 +896,6 @@ func (s *Server) GracefulStop(timeout time.Duration) error { done := make(chan struct{}) go func() { - //nolint:errcheck _ = s.fastServer.Shutdown() close(done) }() diff --git a/internal/server/server_test.go b/internal/server/server_test.go index d18e7dc..0159a06 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -407,7 +407,6 @@ func TestSetListeners(t *testing.T) { t.Fatalf("Failed to create listener: %v", err) } defer func() { - _ = listener1.Close() }() @@ -416,7 +415,6 @@ func TestSetListeners(t *testing.T) { t.Fatalf("Failed to create listener: %v", err) } defer func() { - _ = listener2.Close() }() diff --git a/internal/server/status.go b/internal/server/status.go index e7f30d2..22fa63d 100644 --- a/internal/server/status.go +++ b/internal/server/status.go @@ -145,10 +145,10 @@ func NewStatusHandler(server *Server, cfg *config.StatusConfig) (*StatusHandler, } // 转换为 CIDR 格式 if ip.To4() != nil { - //nolint:errcheck + _, network, _ = net.ParseCIDR(cidr + "/32") } else { - //nolint:errcheck + _, network, _ = net.ParseCIDR(cidr + "/128") } } @@ -206,7 +206,9 @@ func (h *StatusHandler) ServeHTTP(ctx *fasthttp.RequestCtx) { return } - ctx.Write(data) //nolint:errcheck + if _, err := ctx.Write(data); err != nil { + log.Printf("failed to write status response: %v", err) + } } // servePrometheus 以 Prometheus 格式输出指标。 diff --git a/internal/server/upgrade.go b/internal/server/upgrade.go index 4c8906d..46a3afe 100644 --- a/internal/server/upgrade.go +++ b/internal/server/upgrade.go @@ -94,7 +94,7 @@ func (u *UpgradeManager) WritePid() error { } pid := os.Getpid() - return os.WriteFile(u.pidFile, []byte(fmt.Sprintf("%d", pid)), 0644) + return os.WriteFile(u.pidFile, []byte(fmt.Sprintf("%d", pid)), 0o644) } // ReadOldPid 读取旧进程 PID。 @@ -161,7 +161,7 @@ func (u *UpgradeManager) GetInheritedListeners() ([]net.Listener, error) { listener, err := net.FileListener(file) if err != nil { - //nolint:errcheck + _ = file.Close() continue } @@ -223,21 +223,21 @@ func (u *UpgradeManager) GracefulUpgrade(newBinary string) error { // 写入新 PID 到文件 if u.pidFile != "" { - //nolint:errcheck - _ = os.WriteFile(u.pidFile, []byte(fmt.Sprintf("%d", newPid)), 0644) + + _ = os.WriteFile(u.pidFile, []byte(fmt.Sprintf("%d", newPid)), 0o644) } // 启动 goroutine 等待子进程结束,避免产生僵尸进程 // cmd.Wait() 会回收子进程资源,确保不会产生 defunct 进程 go func() { - //nolint:errcheck + _ = cmd.Wait() }() // 关闭父进程中的文件描述符副本(子进程已继承) // 避免文件描述符泄漏 for _, file := range files { - //nolint:errcheck + _ = file.Close() } @@ -294,8 +294,8 @@ func (u *UpgradeManager) WaitForShutdown(timeout time.Duration) error { time.Sleep(100 * time.Millisecond) } - process, _ := os.FindProcess(u.oldPid) //nolint:errcheck - //nolint:errcheck + process, _ := os.FindProcess(u.oldPid) + _ = process.Signal(syscall.SIGKILL) return fmt.Errorf("old process did not shutdown gracefully") } @@ -334,7 +334,7 @@ func (u *UpgradeManager) SetupSignalHandlers(newBinary string) { go func() { for sig := range sigCh { if sig == syscall.SIGUSR2 { - //nolint:errcheck + _ = u.GracefulUpgrade(newBinary) } } diff --git a/internal/server/upgrade_test.go b/internal/server/upgrade_test.go index 53c8db9..b1569b1 100644 --- a/internal/server/upgrade_test.go +++ b/internal/server/upgrade_test.go @@ -41,12 +41,7 @@ func TestIsChild(t *testing.T) { } // 设置环境变量 - - _ = os.Setenv("GRACEFUL_UPGRADE", "1") - defer func() { - - _ = os.Unsetenv("GRACEFUL_UPGRADE") - }() + t.Setenv("GRACEFUL_UPGRADE", "1") if !mgr.IsChild() { t.Error("Expected IsChild to be true when GRACEFUL_UPGRADE=1") @@ -56,7 +51,6 @@ func TestIsChild(t *testing.T) { func TestPidFile(t *testing.T) { tmpFile := "/tmp/lolly-test.pid" defer func() { - _ = os.Remove(tmpFile) }() @@ -132,7 +126,6 @@ func TestUpgradeSetListeners(t *testing.T) { t.Fatalf("Failed to create listener: %v", err) } defer func() { - _ = listener1.Close() }() @@ -141,7 +134,6 @@ func TestUpgradeSetListeners(t *testing.T) { t.Fatalf("Failed to create listener: %v", err) } defer func() { - _ = listener2.Close() }() @@ -168,12 +160,11 @@ func TestWritePid_NoPidFile(t *testing.T) { func TestReadOldPid_InvalidContent(t *testing.T) { tmpFile := "/tmp/lolly-test-invalid.pid" defer func() { - _ = os.Remove(tmpFile) }() // 写入无效内容 - if err := os.WriteFile(tmpFile, []byte("not-a-pid"), 0644); err != nil { + if err := os.WriteFile(tmpFile, []byte("not-a-pid"), 0o644); err != nil { t.Fatalf("Failed to write temp file: %v", err) } @@ -188,13 +179,6 @@ func TestReadOldPid_InvalidContent(t *testing.T) { // TestGetInheritedListeners_InvalidFds 测试 LISTEN_FDS 环境变量格式无效 func TestGetInheritedListeners_InvalidFds(t *testing.T) { - // 保存原始环境变量 - origFds := os.Getenv("LISTEN_FDS") - defer func() { - - _ = os.Setenv("LISTEN_FDS", origFds) - }() - tests := []struct { name string fdsEnv string @@ -219,8 +203,7 @@ func TestGetInheritedListeners_InvalidFds(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - - _ = os.Setenv("LISTEN_FDS", tt.fdsEnv) + t.Setenv("LISTEN_FDS", tt.fdsEnv) mgr := NewUpgradeManager(nil) _, err := mgr.GetInheritedListeners() @@ -258,7 +241,6 @@ func TestListenerFile_TCPListener(t *testing.T) { t.Fatalf("Failed to create listener: %v", err) } defer func() { - _ = listener.Close() }() @@ -267,7 +249,6 @@ func TestListenerFile_TCPListener(t *testing.T) { t.Errorf("Failed to get listener file: %v", err) } if file != nil { - _ = file.Close() } } @@ -316,12 +297,11 @@ func TestNotifyOldProcess_WithCurrentPid(t *testing.T) { func TestReadOldPid_EmptyFile(t *testing.T) { tmpFile := "/tmp/lolly-test-empty.pid" defer func() { - _ = os.Remove(tmpFile) }() // 写入空内容 - if err := os.WriteFile(tmpFile, []byte(""), 0644); err != nil { + if err := os.WriteFile(tmpFile, []byte(""), 0o644); err != nil { t.Fatalf("Failed to write temp file: %v", err) } diff --git a/internal/ssl/client_verify_test.go b/internal/ssl/client_verify_test.go index c4be7f2..722a46d 100644 --- a/internal/ssl/client_verify_test.go +++ b/internal/ssl/client_verify_test.go @@ -161,7 +161,7 @@ func TestLoadCACertPool(t *testing.T) { caFile := filepath.Join(tempDir, "ca.crt") _, _, caPEM := generateTestCA(t) - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { t.Fatalf("Failed to write CA file: %v", err) } @@ -182,7 +182,7 @@ func TestLoadCACertPool(t *testing.T) { // 测试无效证书 invalidFile := filepath.Join(tempDir, "invalid.crt") - if err := os.WriteFile(invalidFile, []byte("invalid data"), 0644); err != nil { + if err := os.WriteFile(invalidFile, []byte("invalid data"), 0o644); err != nil { t.Fatalf("写入无效证书文件失败: %v", err) } _, err = sslutil.LoadCACertPool(invalidFile) @@ -221,7 +221,7 @@ func TestNewClientVerifier_WithCA(t *testing.T) { caFile := filepath.Join(tempDir, "ca.crt") _, _, caPEM := generateTestCA(t) - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { t.Fatalf("Failed to write CA file: %v", err) } @@ -251,7 +251,7 @@ func TestClientVerifier_ConfigureTLS(t *testing.T) { caFile := filepath.Join(tempDir, "ca.crt") _, _, caPEM := generateTestCA(t) - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { t.Fatalf("Failed to write CA file: %v", err) } @@ -353,7 +353,7 @@ func TestGetMode(t *testing.T) { tempDir := t.TempDir() caFile := filepath.Join(tempDir, "ca.crt") _, _, caPEM := generateTestCA(t) - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { t.Fatalf("写入 CA 文件失败: %v", err) } @@ -420,7 +420,7 @@ func TestLoadCRL(t *testing.T) { // 写入临时文件 tempDir := t.TempDir() crlFile := filepath.Join(tempDir, "crl.pem") - if err := os.WriteFile(crlFile, crlPEM, 0644); err != nil { + if err := os.WriteFile(crlFile, crlPEM, 0o644); err != nil { t.Fatalf("写入 CRL 文件失败: %v", err) } @@ -444,7 +444,7 @@ func TestLoadCRL(t *testing.T) { // 测试无效 CRL invalidFile := filepath.Join(tempDir, "invalid.crl") - if err := os.WriteFile(invalidFile, []byte("invalid data"), 0644); err != nil { + if err := os.WriteFile(invalidFile, []byte("invalid data"), 0o644); err != nil { t.Fatalf("写入无效文件失败: %v", err) } _, err = LoadCRL(invalidFile) @@ -472,10 +472,10 @@ func TestCheckCRL(t *testing.T) { crlFile := filepath.Join(tempDir, "crl.pem") caFile := filepath.Join(tempDir, "ca.crt") _, _, caPEM := generateTestCA(t) - if err := os.WriteFile(crlFile, crlPEM, 0644); err != nil { + if err := os.WriteFile(crlFile, crlPEM, 0o644); err != nil { t.Fatalf("写入 CRL 文件失败: %v", err) } - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { t.Fatalf("写入 CA 文件失败: %v", err) } @@ -515,10 +515,10 @@ func TestCheckCRL_EmptyCRL(t *testing.T) { crlFile := filepath.Join(tempDir, "crl.pem") caFile := filepath.Join(tempDir, "ca.crt") _, _, caPEM := generateTestCA(t) - if err := os.WriteFile(crlFile, crlPEM, 0644); err != nil { + if err := os.WriteFile(crlFile, crlPEM, 0o644); err != nil { t.Fatalf("写入 CRL 文件失败: %v", err) } - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { t.Fatalf("写入 CA 文件失败: %v", err) } @@ -553,7 +553,7 @@ func TestValidateClientCertificate(t *testing.T) { tempDir := t.TempDir() caFile := filepath.Join(tempDir, "ca.crt") _, _, caPEM := generateTestCA(t) - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { t.Fatalf("写入 CA 文件失败: %v", err) } @@ -592,10 +592,10 @@ func TestVerifyConnection(t *testing.T) { crlFile := filepath.Join(tempDir, "crl.pem") caFile := filepath.Join(tempDir, "ca.crt") _, _, caPEM := generateTestCA(t) - if err := os.WriteFile(crlFile, crlPEM, 0644); err != nil { + if err := os.WriteFile(crlFile, crlPEM, 0o644); err != nil { t.Fatalf("写入 CRL 文件失败: %v", err) } - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { t.Fatalf("写入 CA 文件失败: %v", err) } @@ -646,7 +646,7 @@ func TestVerifyConnection_DepthLimit(t *testing.T) { tempDir := t.TempDir() caFile := filepath.Join(tempDir, "ca.crt") _, _, caPEM := generateTestCA(t) - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { t.Fatalf("写入 CA 文件失败: %v", err) } @@ -687,7 +687,7 @@ func BenchmarkLoadCACertPool(b *testing.B) { // 生成 CA _, _, caPEM := generateTestCA(&testing.T{}) - if err := os.WriteFile(caFile, caPEM, 0644); err != nil { + if err := os.WriteFile(caFile, caPEM, 0o644); err != nil { b.Fatalf("写入 CA 文件失败: %v", err) } diff --git a/internal/ssl/ocsp.go b/internal/ssl/ocsp.go index 03e144d..119562d 100644 --- a/internal/ssl/ocsp.go +++ b/internal/ssl/ocsp.go @@ -325,7 +325,7 @@ func (m *OCSPManager) sendOCSPRequest(url string, req []byte) ([]byte, error) { } return nil, fmt.Errorf("HTTP request failed: %w", err) } - defer func() { _ = resp.Body.Close() }() //nolint:errcheck + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { if i < m.maxRetries-1 { diff --git a/internal/ssl/ocsp_test.go b/internal/ssl/ocsp_test.go index ae61c8c..90769e8 100644 --- a/internal/ssl/ocsp_test.go +++ b/internal/ssl/ocsp_test.go @@ -212,10 +212,10 @@ func TestTLSManagerWithOCSPDisabled(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") certPEM, keyPEM := generateTestCertWithOCSP(t, nil) - if err := os.WriteFile(certPath, certPEM, 0644); err != nil { + if err := os.WriteFile(certPath, certPEM, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, keyPEM, 0600); err != nil { + if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -245,10 +245,10 @@ func TestTLSManagerGetOCSPStatus(t *testing.T) { // Generate cert without OCSP server certPEM, keyPEM := generateTestCertWithOCSP(t, nil) - if err := os.WriteFile(certPath, certPEM, 0644); err != nil { + if err := os.WriteFile(certPath, certPEM, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, keyPEM, 0600); err != nil { + if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -283,10 +283,10 @@ func TestTLSManagerClose(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") certPEM, keyPEM := generateTestCertWithOCSP(t, nil) - if err := os.WriteFile(certPath, certPEM, 0644); err != nil { + if err := os.WriteFile(certPath, certPEM, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, keyPEM, 0600); err != nil { + if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } diff --git a/internal/ssl/session_tickets.go b/internal/ssl/session_tickets.go index 1092551..f004aaf 100644 --- a/internal/ssl/session_tickets.go +++ b/internal/ssl/session_tickets.go @@ -238,7 +238,7 @@ func (m *SessionTicketManager) scheduleRotation() { case <-m.stopCh: return default: - _ = m.RotateKey() //nolint:errcheck + _ = m.RotateKey() m.scheduleRotation() } }) @@ -319,7 +319,7 @@ func (m *SessionTicketManager) saveKeys() error { } // 使用 0600 权限写入文件(敏感数据,限制访问) - if err := os.WriteFile(m.config.KeyFile, data, 0600); err != nil { + if err := os.WriteFile(m.config.KeyFile, data, 0o600); err != nil { return fmt.Errorf("failed to write key file: %w", err) } diff --git a/internal/ssl/ssl.go b/internal/ssl/ssl.go index ddf97c9..a7be4ce 100644 --- a/internal/ssl/ssl.go +++ b/internal/ssl/ssl.go @@ -176,7 +176,7 @@ func NewTLSManager(cfg *config.SSLConfig) (*TLSManager, error) { manager.issuers[serial] = issuerCert // 注册证书用于 OCSP Stapling // 错误会记录日志但不会阻止 TLS 工作 - _ = ocspMgr.RegisterCertificate(parsedCert, issuerCert) //nolint:errcheck + _ = ocspMgr.RegisterCertificate(parsedCert, issuerCert) } } diff --git a/internal/ssl/ssl_test.go b/internal/ssl/ssl_test.go index 4b4bced..c72a19f 100644 --- a/internal/ssl/ssl_test.go +++ b/internal/ssl/ssl_test.go @@ -90,10 +90,10 @@ func TestNewTLSManagerWithCert(t *testing.T) { // Generate a self-signed certificate for testing cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -328,7 +328,7 @@ func TestValidateCertificate(t *testing.T) { t.Run("valid file", func(t *testing.T) { tmpFile := filepath.Join(t.TempDir(), "cert.pem") - if err := os.WriteFile(tmpFile, []byte("test"), 0644); err != nil { + if err := os.WriteFile(tmpFile, []byte("test"), 0o644); err != nil { t.Fatalf("Failed to create temp file: %v", err) } @@ -349,7 +349,7 @@ func TestValidateKey(t *testing.T) { t.Run("valid file", func(t *testing.T) { tmpFile := filepath.Join(t.TempDir(), "key.pem") - if err := os.WriteFile(tmpFile, []byte("test"), 0600); err != nil { + if err := os.WriteFile(tmpFile, []byte("test"), 0o600); err != nil { t.Fatalf("Failed to create temp file: %v", err) } @@ -427,10 +427,10 @@ func TestGetTLSConfig(t *testing.T) { // 生成自签名证书 cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -464,10 +464,10 @@ func TestGetTLSConfig_WithProtocols(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -504,10 +504,10 @@ func TestClose(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -592,10 +592,10 @@ func TestNewTLSManager_InvalidCipher(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -618,10 +618,10 @@ func TestNewTLSManager_InsecureCipher(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -644,10 +644,10 @@ func TestNewMultiTLSManager(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -698,10 +698,10 @@ func TestGetCertificate(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -743,10 +743,10 @@ func TestAddCertificate(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -782,10 +782,10 @@ func TestAddCertificate_Error(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -814,10 +814,10 @@ func TestRemoveCertificate(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } @@ -857,10 +857,10 @@ func TestGetOCSPStatus_NoManager(t *testing.T) { keyPath := filepath.Join(tmpDir, "key.pem") cert, key := generateTestCert(t) - if err := os.WriteFile(certPath, cert, 0644); err != nil { + if err := os.WriteFile(certPath, cert, 0o644); err != nil { t.Fatalf("Failed to write cert: %v", err) } - if err := os.WriteFile(keyPath, key, 0600); err != nil { + if err := os.WriteFile(keyPath, key, 0o600); err != nil { t.Fatalf("Failed to write key: %v", err) } diff --git a/internal/stream/ssl_test.go b/internal/stream/ssl_test.go index cbfb855..bd57468 100644 --- a/internal/stream/ssl_test.go +++ b/internal/stream/ssl_test.go @@ -371,7 +371,7 @@ func TestLoadCertPool(t *testing.T) { t.Run("invalid content", func(t *testing.T) { tempDir := t.TempDir() certFile := filepath.Join(tempDir, "invalid.crt") - if err := os.WriteFile(certFile, []byte("not a certificate"), 0644); err != nil { + if err := os.WriteFile(certFile, []byte("not a certificate"), 0o644); err != nil { t.Fatalf("写入无效证书文件失败: %v", err) } diff --git a/internal/stream/stream.go b/internal/stream/stream.go index c7aa93e..29240f4 100644 --- a/internal/stream/stream.go +++ b/internal/stream/stream.go @@ -37,7 +37,18 @@ import ( "time" ) -// Balancer 负载均衡器接口(stream 专用)。 +// Balancer Stream 代理(L4 层)负载均衡器接口。 +// +// Stream Balancer 特性(区别于 HTTP Balancer): +// - 仅 Select(): 按算法策略选择健康目标 +// - 无 SelectExcluding(): Stream 代理无 failover 重试机制 +// +// 语义差异说明: +// - Stream 代理工作在传输层(L4),连接建立后直接转发数据,无应用层重试 +// - HTTP 代理工作在应用层(L7),支持 next_upstream 配置的失败重试 +// - 因此 Stream Balancer 接口签名更简单,仅需要 Select 方法 +// - HTTP Balancer 需要 SelectExcluding 用于排除失败节点 +// - 两种 Balancer 接口签名不同,不可合并 type Balancer interface { Select(targets []*Target) *Target } @@ -284,7 +295,18 @@ type Upstream struct { mu sync.RWMutex } -// Target Stream 目标服务器。 +// Target Stream 代理(L4 层)的目标服务器。 +// +// Stream Target 特性(区别于 HTTP Target): +// - 简单地址:仅支持 host:port 格式,无 URL 解析 +// - 无 DNS 缓存:直接连接目标地址,无需动态 DNS 解析 +// - 无 failover:Stream 代理无重试机制,仅记录健康状态 +// +// 语义差异说明: +// - Stream 代理工作在传输层(L4),直接转发 TCP/UDP 数据 +// - HTTP 代理工作在应用层(L7),需要 URL 解析和 DNS 动态解析 +// - 因此 Stream Target 保持简单结构,HTTP Target 需要 DNS 缓存等复杂功能 +// - 两种 Target 必须保持独立定义,不可合并 type Target struct { // addr 目标地址(host:port) addr string @@ -503,7 +525,7 @@ func (s *Server) acceptLoop(addr string, listener net.Listener) { // - addr: 监听地址 func (s *Server) handleConnection(clientConn net.Conn, _ string) { defer func() { - _ = clientConn.Close() //nolint:errcheck + _ = clientConn.Close() s.connCount-- }() @@ -535,11 +557,11 @@ func (s *Server) handleConnection(clientConn net.Conn, _ string) { target.healthy.Store(false) return } - defer func() { _ = targetConn.Close() }() //nolint:errcheck + defer func() { _ = targetConn.Close() }() // 双向数据转发 - go func() { _, _ = io.Copy(targetConn, clientConn) }() //nolint:errcheck - _, _ = io.Copy(clientConn, targetConn) //nolint:errcheck + go func() { _, _ = io.Copy(targetConn, clientConn) }() + _, _ = io.Copy(clientConn, targetConn) } // Select 选择健康的上游目标。 @@ -588,7 +610,7 @@ func (h *HealthChecker) check() { if err != nil { target.healthy.Store(false) } else { - _ = conn.Close() //nolint:errcheck + _ = conn.Close() target.healthy.Store(true) } } @@ -608,7 +630,7 @@ func (s *Server) Stop() error { // 关闭所有 TCP 监听器 for _, listener := range s.listeners { - _ = listener.Close() //nolint:errcheck + _ = listener.Close() } // 停止所有 UDP 服务器 @@ -838,7 +860,7 @@ func (s *udpServer) removeSession(clientAddr *net.UDPAddr) { func (sess *udpSession) close() { sess.closeOnce.Do(func() { if sess.targetConn != nil { - _ = sess.targetConn.Close() //nolint:errcheck + _ = sess.targetConn.Close() } }) } @@ -857,7 +879,7 @@ func (sess *udpSession) handleBackendResponse() { buf := make([]byte, 65535) for { // 设置读取超时 - _ = sess.targetConn.SetReadDeadline(time.Now().Add(sess.srv.timeout)) //nolint:errcheck + _ = sess.targetConn.SetReadDeadline(time.Now().Add(sess.srv.timeout)) n, err := sess.targetConn.Read(buf) if err != nil { @@ -908,7 +930,7 @@ func (s *udpServer) serve() { buf := make([]byte, 65535) for s.running.Load() { // 设置读取超时,以便定期检查 stopCh - _ = s.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) //nolint:errcheck + _ = s.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) n, clientAddr, err := s.conn.ReadFromUDP(buf) if err != nil { @@ -997,5 +1019,5 @@ func (s *udpServer) stop() { s.wg.Wait() // 关闭连接 - _ = s.conn.Close() //nolint:errcheck + _ = s.conn.Close() } diff --git a/internal/variable/variable.go b/internal/variable/variable.go index 9b2c63d..0abb52a 100644 --- a/internal/variable/variable.go +++ b/internal/variable/variable.go @@ -122,7 +122,11 @@ func GetBuiltin(name string) *BuiltinVariable { // NewContext 从池中获取 Context,并注入全局变量。 func NewContext(ctx *fasthttp.RequestCtx) *Context { - vc := pool.Get().(*Context) //nolint:errcheck // 类型断言 + vc, ok := pool.Get().(*Context) + if !ok { + // 池中类型不正确时返回新 Context + return &Context{ctx: ctx} + } vc.ctx = ctx vc.status = 0 vc.bodySize = 0