lolly/internal/handler/errorpage.go
xfy cf2fcca7e8 refactor: 提取公共逻辑、消除重复代码、加强错误处理
- 提取 App 公共逻辑到 app_common.go,消除 app.go/app_windows.go 重复定义
- 提取 Server 生命周期/中间件/路由逻辑到独立文件(lifecycle.go/middleware_builder.go/router.go)
- 提取 Proxy 缓存处理/头部修改/目标选择到独立模块
- 提取 CheckIPAccess/CheckTokenAuth 到 utils/httperror.go,消除 status/purge 重复实现
- 修复 stream 双向转发:任一方向完成立即关闭双端,避免连接泄漏
- 修复 SSL/TLS 中静默忽略错误的问题,添加日志记录
- 统一日志消息为英文

💘 Generated with Crush

Assisted-by: GLM 5.1 via Crush <crush@charm.land>
2026-04-28 18:00:48 +08:00

206 lines
5.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package handler 提供 HTTP 请求处理器,包括路由、静态文件服务和零拷贝传输。
//
// 该文件包含自定义错误页面相关的核心逻辑,包括:
// - 错误页面预加载
// - 错误页面内容管理
// - 错误页面查找
//
// 主要用途:
//
// 用于在服务器启动时预加载自定义错误页面文件到内存中,运行时不进行文件 I/O。
//
// 注意事项:
// - 所有错误页面在启动时预加载
// - 全部加载失败时会阻止服务器启动
// - 部分加载失败会记录警告但允许启动
//
// 作者xfy
package handler
import (
"errors"
"fmt"
"os"
"sync"
"rua.plus/lolly/internal/config"
)
// ErrorPageManager 自定义错误页面管理器。
//
// 负责在服务器启动时预加载错误页面文件到内存中,
// 并在运行时提供错误页面内容。
type ErrorPageManager struct {
// pages 预加载的错误页面内容
// key 为 HTTP 状态码value 为页面内容
pages map[int][]byte
// defaultPage 默认错误页面内容
defaultPage []byte
// responseCode 响应状态码覆盖
responseCode int
// mu 保护 pages 的读写锁
mu sync.RWMutex
}
// NewErrorPageManager 创建错误页面管理器。
//
// 根据配置预加载错误页面文件到内存中。
//
// 参数:
// - cfg: 错误页面配置
//
// 返回值:
// - *ErrorPageManager: 创建的错误页面管理器
// - error: 预加载失败时的错误,全部失败时返回错误
//
// 使用示例:
//
// manager, err := handler.NewErrorPageManager(&cfg.ErrorPage)
// if err != nil {
// log.Fatal("加载错误页面失败:", err)
// }
func NewErrorPageManager(cfg *config.ErrorPageConfig) (*ErrorPageManager, error) {
if len(cfg.Pages) == 0 && cfg.Default == "" {
// 没有配置错误页面,返回空管理器
return &ErrorPageManager{
pages: make(map[int][]byte),
responseCode: cfg.ResponseCode,
}, nil
}
manager := &ErrorPageManager{
pages: make(map[int][]byte),
responseCode: cfg.ResponseCode,
}
// 预加载特定状态码的错误页面
loadErrors := make(map[int]error)
for code, path := range cfg.Pages {
content, err := os.ReadFile(path)
if err != nil {
loadErrors[code] = err
continue
}
manager.pages[code] = content
}
// 预加载默认错误页面
if cfg.Default != "" {
content, err := os.ReadFile(cfg.Default)
if err != nil {
loadErrors[0] = err // 使用 0 表示默认页面错误
} else {
manager.defaultPage = content
}
}
// 检查加载结果
if len(loadErrors) > 0 {
// 部分或全部加载失败
totalPages := len(cfg.Pages)
if cfg.Default != "" {
totalPages++
}
if len(loadErrors) == totalPages {
// 全部加载失败,返回错误
errs := make([]error, 0, len(loadErrors))
for _, e := range loadErrors {
errs = append(errs, e)
}
return nil, fmt.Errorf("所有错误页面加载失败: %w", errors.Join(errs...))
}
// 部分失败,记录警告(由调用者处理)
return manager, &PartialLoadError{Errors: loadErrors}
}
return manager, nil
}
// PartialLoadError 部分错误页面加载失败错误。
type PartialLoadError struct {
Errors map[int]error
}
// Error 实现 error 接口。
//
// 返回值:
// - string: 格式化的错误消息,包含失败的数量
func (e *PartialLoadError) Error() string {
return fmt.Sprintf("部分错误页面加载失败: %d 个错误", len(e.Errors))
}
// GetPage 获取指定状态码的错误页面内容。
//
// 参数:
// - code: HTTP 状态码
//
// 返回值:
// - []byte: 错误页面内容,如未找到返回 nil
// - bool: 是否找到
// - int: 响应状态码(可能被覆盖)
func (m *ErrorPageManager) GetPage(code int) ([]byte, bool, int) {
m.mu.RLock()
defer m.mu.RUnlock()
// 查找特定状态码的页面
if content, ok := m.pages[code]; ok {
responseCode := code
if m.responseCode > 0 {
responseCode = m.responseCode
}
return content, true, responseCode
}
// 使用默认页面
if m.defaultPage != nil {
responseCode := code
if m.responseCode > 0 {
responseCode = m.responseCode
}
return m.defaultPage, true, responseCode
}
return nil, false, code
}
// HasPage 检查是否有指定状态码的错误页面。
//
// 参数:
// - code: HTTP 状态码
//
// 返回值:
// - bool: 是否有该状态码的错误页面(包括默认页面)
func (m *ErrorPageManager) HasPage(code int) bool {
m.mu.RLock()
defer m.mu.RUnlock()
if _, ok := m.pages[code]; ok {
return true
}
return m.defaultPage != nil
}
// GetResponseCode 获取响应状态码覆盖值。
//
// 返回值:
// - int: 响应状态码覆盖值0 表示不覆盖
func (m *ErrorPageManager) GetResponseCode() int {
m.mu.RLock()
defer m.mu.RUnlock()
return m.responseCode
}
// IsConfigured 检查是否配置了错误页面。
//
// 返回值:
// - bool: 是否配置了任何错误页面
func (m *ErrorPageManager) IsConfigured() bool {
m.mu.RLock()
defer m.mu.RUnlock()
return len(m.pages) > 0 || m.defaultPage != nil
}