mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
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:
@@ -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、格式化、网络、版本比较等)。
|
||||
|
||||
前端目录:
|
||||
|
||||
|
||||
@@ -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 校验答案
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user