refactor(http): remove dead internal/util HTTP client wrapper

Drop internal/util (unused httppool wrapper and dead StringArray) and
rely on pkg/httppool plus oauth context injection for HTTP clients.
This commit is contained in:
ryan
2026-07-24 15:49:52 +08:00
parent d99c5b7c43
commit 23a5488203
6 changed files with 4 additions and 122 deletions
+2 -3
View File
@@ -51,7 +51,7 @@
## 严格遵循事项 (Guardrails)
- 切勿删除 `frontend/node_modules`
- 保持 `internal/util/` 绝对纯净且不引入任何框架。禁止从 `internal/util/` 及其子包中导入 Gin、GORM、sessions 等 HTTP/Web/数据库相关框架包(例如,Web 会话选项已收敛至 `internal/apps/oauth/session.go`)。
- 保持 `pkg/util/` 绝对纯净且不引入任何框架。禁止从 `pkg/util/` 及其子包中导入 Gin、GORM、sessions 等 HTTP/Web/数据库相关框架包(例如,Web 会话选项已收敛至 `internal/apps/oauth/session.go`)。
- 编写测试用例时,禁止使用硬编码的相对路径(如 `"uploads/test_cache"`)在源码目录下创建临时测试目录,必须统一使用 Go 内置的 `t.TempDir()` 以避免污染源码目录。
- 所有 HTTP 路由仅在 `internal/router/router.go` 中注册。
- 当 API Handler 发生变化时,更新 Swagger 文档(运行 `make swagger`)。
@@ -121,7 +121,7 @@
- `internal/infra/objectstore/`:S3 兼容对象存储适配,提供对象上传、读取、删除、CDN/代理读取及远端对象本地缓存。
- `internal/infra/task/`:Asynq 任务框架;参见 `new-async-task` 了解变更。
- `internal/shared/`:共享的通用模型及响应(如 `internal/shared/response`)、绑定(bind)、常量以及通用错误。
- `internal/util/`:纯底层工具包,无任何 HTTP/数据库框架依赖。
- `pkg/util/`:纯底层无副作用的系统工具(Crypto/Password/UUID、格式化、网络、版本比较等)。
- `internal/listener/`:域事件分发层。核心域(auth、user 等)在此定义并发射事件(如 `EmitAdminLoggedIn`);运维模块(push、webhook 等)在 bootstrap 阶段订阅,实现跨模块解耦。
- `internal/otel_trace/`:链路追踪(tracing)助手。
- `internal/testhelper/`:后端测试共享辅助能力。
@@ -135,7 +135,6 @@
- `pkg/push/`:推送渠道客户端集成(Lark/Telegram/Email)。
- `pkg/mail/`:邮件发送客户端。
- `pkg/trace/`:OpenTelemetry 链路追踪配置。
- `pkg/util/`:纯底层无副作用的系统工具(Crypto/Password/UUID、格式化、网络、版本比较等)。
前端目录:
+2 -2
View File
@@ -1,6 +1,6 @@
/**
* Cap PoW (Proof-of-Work) 人机验证前端实现
* 与后端 internal/util/cap 算法完全对应
* 与后端 pkg/cap 算法完全对应
*
* 求解在 Web Worker 中执行,不阻塞主线程 UI。
*/
@@ -24,7 +24,7 @@ export interface RedeemResponse {
// ——— Worker 代码(内联 Blob,避免独立文件的打包配置问题)———
//
// 算法与 Go 后端 internal/util/cap/cap.go + prng.go 完全对应:
// 算法与 Go 后端 pkg/cap/cap.go + prng.go 完全对应:
// · FNV-1a 32-bit (Math.imul 保证 32-bit 截断)
// · xorshift32 PRNG → hex 字符串
// · SubtleCrypto SHA-256 校验答案
-10
View File
@@ -37,7 +37,6 @@ import (
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/Rain-kl/Wavelet/internal/testhelper"
"github.com/Rain-kl/Wavelet/internal/util"
)
// -----------------------------------------------------------------------------
@@ -453,7 +452,6 @@ func TestGetLoginSources(t *testing.T) {
},
},
}
util.SetHTTPClient(httpMock)
router := setupTestRouter(dbConn, mockRedis, httpMock)
// Inject OIDC login enabled config
@@ -527,7 +525,6 @@ func TestGetLoginURL(t *testing.T) {
},
},
}
util.SetHTTPClient(httpMock)
router := setupTestRouter(dbConn, mockRedis, httpMock)
// Case 1: Default Login URL
@@ -616,7 +613,6 @@ func TestAuthorize(t *testing.T) {
},
},
}
util.SetHTTPClient(httpMock)
router := setupTestRouter(dbConn, mockRedis, httpMock)
// Case 1a: Active Source Authorize with purpose=bind without login -> 401
@@ -694,7 +690,6 @@ func TestCallbackLoginAndUserInfo(t *testing.T) {
// 1. Mock the outgoing HTTP client for token exchange and user info fetching
httpMock := newMockOIDCClient(testIssuerURL, testClientID, &state, "88888", "test_oauth_user", "oauth@linux.do", "Oauth Test User")
util.SetHTTPClient(httpMock)
router := setupTestRouter(dbConn, mockRedis, httpMock)
// Get Login URL first to initialize the session and generate the state
@@ -774,7 +769,6 @@ func TestCallbackLoginAndUserInfo(t *testing.T) {
var state2 string
// Callback with same username but different external ID (99999)
httpMock2 := newMockOIDCClient(testIssuerURL, testClientID, &state2, "99999", "test_oauth_user", "another@linux.do", "Another User")
util.SetHTTPClient(httpMock2)
// Create another router for this mock client
router2 := setupTestRouter(dbConn, mockRedis, httpMock2)
@@ -826,7 +820,6 @@ func TestCallbackLoginAndUserInfo(t *testing.T) {
var state4 string
httpMock4 := newMockOIDCClient(testIssuerURL, testClientID, &state4, "77777", "need_bind_user", "needbind@linux.do", "Need Bind User")
util.SetHTTPClient(httpMock4)
router4 := setupTestRouter(dbConn, mockRedis, httpMock4)
wLogin4 := performRequest(router4, http.MethodGet, "/api/v1/oauth/login?source="+testSourceName, nil, nil, nil)
@@ -898,7 +891,6 @@ func TestCallbackBind(t *testing.T) {
var state string
// Mock OIDC discovery, JWKS, and Token exchange for custom source (GitHub)
httpMock := newMockOIDCClient("https://github.com", "gh_client", &state, "github_user_123", "github_tester", "tester@github.com", "GitHub Tester")
util.SetHTTPClient(httpMock)
router := setupTestRouter(dbConn, mockRedis, httpMock)
// Set up login helper
@@ -1017,7 +1009,6 @@ func TestCallbackBind(t *testing.T) {
var state3 string
// Re-sign token for new state (since state serves as OIDC Nonce)
httpMock3 := newMockOIDCClient("https://github.com", "gh_client", &state3, "github_user_123", "github_tester", "tester@github.com", "GitHub Tester")
util.SetHTTPClient(httpMock3)
router3 := setupTestRouter(dbConn, mockRedis, httpMock3)
// Generate state3 and SessionHash using activeCookie2
@@ -1131,7 +1122,6 @@ func TestOIDCPolicyEnforcement(t *testing.T) {
// Set up mock client & router
var state string
httpMock := newMockOIDCClient(testIssuerURL, testClientID, &state, "88888", "test_oauth_user", "oauth@linux.do", "Oauth Test User")
util.SetHTTPClient(httpMock)
router := setupTestRouter(dbConn, mockRedis, httpMock)
// --- 1. Test GetLoginURL enforcement ---
-29
View File
@@ -1,29 +0,0 @@
// Copyright 2025 linux.do
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package util provides framework-agnostic helper types and HTTP utilities.
package util
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
// StringArray custom type for handling JSON arrays
type StringArray []string
// Scan 实现 sql.Scanner 接口,从数据库读取 JSON 数组
func (sa *StringArray) Scan(value interface{}) error {
bytesValue, ok := value.([]byte)
if !ok {
return fmt.Errorf(errInvalidCustomValue, value)
}
return json.Unmarshal(bytesValue, sa)
}
// Value 实现 driver.Valuer 接口,将 JSON 数组序列化为数据库存储值
func (sa StringArray) Value() (driver.Value, error) {
return json.Marshal(sa)
}
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package util
const (
errCreateHTTPRequestFailed = "创建HTTP请求失败: %w"
errHTTPRequestFailed = "请求%s接口失败: %w"
errInvalidCustomValue = "invalid value: %v"
)
-68
View File
@@ -1,68 +0,0 @@
// Copyright 2025 linux.do
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package util
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/Rain-kl/Wavelet/pkg/httppool"
)
// IsLocalhost 检查 URL 是否为 localhost
func IsLocalhost(urlStr string) bool {
u, err := url.Parse(urlStr)
if err != nil {
return false
}
hostname := u.Hostname()
return hostname == "localhost" || hostname == "127.0.0.1" || hostname == "::1"
}
// HTTP 客户端配置常量
const (
httpClientTimeout = 10 // HTTP 客户端超时时间(秒)
httpMaxIdleConns = 100
httpMaxIdleConnsPerHost = 20
httpIdleConnTimeout = 60 // 空闲连接超时(秒)
)
// 配置HTTP客户端 使用 otelhttp 自动注入 trace span
var httpClient = &http.Client{
Timeout: httpClientTimeout * time.Second,
Transport: httppool.DefaultTransport(),
}
// SetHTTPClient 替换全局 HTTP 客户端实例
func SetHTTPClient(c *http.Client) {
httpClient = c
}
// Request 发送 HTTP 请求,支持自定义 Headers 和 Cookies
func Request(ctx context.Context, method, url string, body io.Reader, headers, cookies map[string]string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, method, url, body)
if err != nil {
return nil, fmt.Errorf(errCreateHTTPRequestFailed, err)
}
for key, value := range cookies {
req.AddCookie(&http.Cookie{Name: key, Value: value}) //nolint:gosec // client-side cookies do not require server attributes (Secure/HttpOnly)
}
for key, value := range headers {
req.Header.Set(key, value)
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf(errHTTPRequestFailed, url, err)
}
return resp, nil
}