refactor(utils): 提取 HTTP 错误响应辅助函数
新增 SendError 和 SendErrorWithDetail 函数,统一处理 HTTP 错误响应,减少重复代码。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8a533ba0ca
commit
e1df0ec205
@ -28,6 +28,7 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
"rua.plus/lolly/internal/cache"
|
||||
"rua.plus/lolly/internal/middleware/compression"
|
||||
"rua.plus/lolly/internal/utils"
|
||||
)
|
||||
|
||||
// StaticHandler 静态文件处理器。
|
||||
@ -212,7 +213,7 @@ func (h *StaticHandler) Handle(ctx *fasthttp.RequestCtx) {
|
||||
|
||||
// 安全检查:防止目录遍历
|
||||
if strings.Contains(reqPath, "..") {
|
||||
ctx.Error("Forbidden", fasthttp.StatusForbidden)
|
||||
utils.SendError(ctx, utils.ErrForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
@ -290,7 +291,7 @@ func (h *StaticHandler) handleTryFiles(ctx *fasthttp.RequestCtx, reqPath string)
|
||||
}
|
||||
|
||||
// 所有 try_files 都未找到
|
||||
ctx.Error("Not Found", fasthttp.StatusNotFound)
|
||||
utils.SendError(ctx, utils.ErrNotFound)
|
||||
}
|
||||
|
||||
// resolveTryFilePath 解析 try_files 中的占位符。
|
||||
@ -369,11 +370,11 @@ func (h *StaticHandler) handleInternalRedirect(ctx *fasthttp.RequestCtx, targetP
|
||||
}
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
ctx.Error("Not Found", fasthttp.StatusNotFound)
|
||||
utils.SendError(ctx, utils.ErrNotFound)
|
||||
return
|
||||
}
|
||||
if info.IsDir() {
|
||||
ctx.Error("Forbidden", fasthttp.StatusForbidden)
|
||||
utils.SendError(ctx, utils.ErrForbidden)
|
||||
return
|
||||
}
|
||||
h.serveFile(ctx, filePath, info)
|
||||
@ -419,7 +420,7 @@ func (h *StaticHandler) handleStandard(ctx *fasthttp.RequestCtx, reqPath string)
|
||||
// 检查文件/目录是否存在
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
ctx.Error("Not Found", fasthttp.StatusNotFound)
|
||||
utils.SendError(ctx, utils.ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
@ -432,7 +433,7 @@ func (h *StaticHandler) handleStandard(ctx *fasthttp.RequestCtx, reqPath string)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Error("Forbidden", fasthttp.StatusForbidden)
|
||||
utils.SendError(ctx, utils.ErrForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
@ -492,7 +493,7 @@ func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, inf
|
||||
// 读取文件内容
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
|
||||
utils.SendError(ctx, utils.ErrInternalError)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -40,6 +40,7 @@ import (
|
||||
"rua.plus/lolly/internal/config"
|
||||
"rua.plus/lolly/internal/middleware"
|
||||
"rua.plus/lolly/internal/netutil"
|
||||
"rua.plus/lolly/internal/utils"
|
||||
)
|
||||
|
||||
const rateLimitHeader = "header"
|
||||
@ -174,7 +175,7 @@ func (s *SlidingWindowLimiterWrapper) Process(next fasthttp.RequestHandler) fast
|
||||
key := s.keyFunc(ctx)
|
||||
|
||||
if !s.limiter.Allow(key) {
|
||||
ctx.Error("Too Many Requests", fasthttp.StatusTooManyRequests)
|
||||
utils.SendError(ctx, utils.ErrTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
@ -208,7 +209,7 @@ func (rl *RateLimiter) Process(next fasthttp.RequestHandler) fasthttp.RequestHan
|
||||
// 计算重试等待时间
|
||||
retryAfter := rl.getRetryAfter(key)
|
||||
ctx.Response.Header.Set("Retry-After", fmt.Sprintf("%d", retryAfter))
|
||||
ctx.Error("Too Many Requests", fasthttp.StatusTooManyRequests)
|
||||
utils.SendError(ctx, utils.ErrTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
@ -615,7 +616,7 @@ func (m *connLimiterMiddleware) Name() string {
|
||||
func (m *connLimiterMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
if !m.limiter.Acquire(ctx) {
|
||||
ctx.Error("Service Unavailable: Connection limit exceeded", fasthttp.StatusServiceUnavailable)
|
||||
utils.SendErrorWithDetail(ctx, utils.ErrServiceUnavailable, "Connection limit exceeded")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -48,6 +48,7 @@ import (
|
||||
"rua.plus/lolly/internal/logging"
|
||||
"rua.plus/lolly/internal/netutil"
|
||||
"rua.plus/lolly/internal/resolver"
|
||||
"rua.plus/lolly/internal/utils"
|
||||
"rua.plus/lolly/internal/variable"
|
||||
)
|
||||
|
||||
@ -337,7 +338,7 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
||||
// 没有可用后端
|
||||
upstreamAddr = "FAILED"
|
||||
upstreamStatus = 502
|
||||
ctx.Error("Bad Gateway: no healthy upstream", fasthttp.StatusBadGateway)
|
||||
utils.SendErrorWithDetail(ctx, utils.ErrBadGateway, "no healthy upstream")
|
||||
return
|
||||
}
|
||||
// 没有更多可用目标,返回最后一次错误
|
||||
@ -522,18 +523,18 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
||||
// 处理不同类型的错误
|
||||
if errors.Is(lastErr, fasthttp.ErrTimeout) {
|
||||
upstreamStatus = 504
|
||||
ctx.Error("Gateway Timeout", fasthttp.StatusGatewayTimeout)
|
||||
utils.SendError(ctx, utils.ErrGatewayTimeout)
|
||||
} else if errors.Is(lastErr, fasthttp.ErrConnectionClosed) {
|
||||
upstreamStatus = 502
|
||||
ctx.Error("Bad Gateway: upstream connection closed", fasthttp.StatusBadGateway)
|
||||
utils.SendErrorWithDetail(ctx, utils.ErrBadGateway, "upstream connection closed")
|
||||
} else {
|
||||
upstreamStatus = 502
|
||||
ctx.Error("Bad Gateway", fasthttp.StatusBadGateway)
|
||||
utils.SendError(ctx, utils.ErrBadGateway)
|
||||
}
|
||||
} else {
|
||||
upstreamAddr = "FAILED"
|
||||
upstreamStatus = 502
|
||||
ctx.Error("Bad Gateway: all upstreams failed", fasthttp.StatusBadGateway)
|
||||
utils.SendErrorWithDetail(ctx, utils.ErrBadGateway, "all upstreams failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
"rua.plus/lolly/internal/config"
|
||||
"rua.plus/lolly/internal/netutil"
|
||||
"rua.plus/lolly/internal/utils"
|
||||
)
|
||||
|
||||
// StatusHandler 状态监控处理器。
|
||||
@ -182,7 +183,7 @@ func (h *StatusHandler) Path() string {
|
||||
func (h *StatusHandler) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
||||
// 步骤1: 检查 IP 访问权限
|
||||
if !h.checkAccess(ctx) {
|
||||
ctx.Error("Forbidden: Access denied", fasthttp.StatusForbidden)
|
||||
utils.SendErrorWithDetail(ctx, utils.ErrForbidden, "Access denied")
|
||||
return
|
||||
}
|
||||
|
||||
@ -201,7 +202,7 @@ func (h *StatusHandler) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
||||
|
||||
data, err := json.MarshalIndent(status, "", " ")
|
||||
if err != nil {
|
||||
ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
|
||||
utils.SendError(ctx, utils.ErrInternalError)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
39
internal/utils/httperror.go
Normal file
39
internal/utils/httperror.go
Normal file
@ -0,0 +1,39 @@
|
||||
// Package utils provides utility functions for HTTP error handling.
|
||||
//
|
||||
// This file implements a unified HTTP error response helper to reduce
|
||||
// the scattered pattern of ctx.Error throughout the codebase.
|
||||
package utils
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// HTTPError represents an HTTP error with a message and status code.
|
||||
type HTTPError struct {
|
||||
Message string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
// Predefined common HTTP errors.
|
||||
var (
|
||||
ErrNotFound = HTTPError{Message: "Not Found", StatusCode: fasthttp.StatusNotFound}
|
||||
ErrForbidden = HTTPError{Message: "Forbidden", StatusCode: fasthttp.StatusForbidden}
|
||||
ErrUnauthorized = HTTPError{Message: "Unauthorized", StatusCode: fasthttp.StatusUnauthorized}
|
||||
ErrBadGateway = HTTPError{Message: "Bad Gateway", StatusCode: fasthttp.StatusBadGateway}
|
||||
ErrGatewayTimeout = HTTPError{Message: "Gateway Timeout", StatusCode: fasthttp.StatusGatewayTimeout}
|
||||
ErrInternalError = HTTPError{Message: "Internal Server Error", StatusCode: fasthttp.StatusInternalServerError}
|
||||
ErrTooManyRequests = HTTPError{Message: "Too Many Requests", StatusCode: fasthttp.StatusTooManyRequests}
|
||||
ErrServiceUnavailable = HTTPError{Message: "Service Unavailable", StatusCode: fasthttp.StatusServiceUnavailable}
|
||||
)
|
||||
|
||||
// SendError sends an HTTP error response to the client.
|
||||
func SendError(ctx *fasthttp.RequestCtx, err HTTPError) {
|
||||
ctx.Error(err.Message, err.StatusCode)
|
||||
}
|
||||
|
||||
// SendErrorWithDetail sends an HTTP error response with additional detail.
|
||||
func SendErrorWithDetail(ctx *fasthttp.RequestCtx, err HTTPError, detail string) {
|
||||
if detail != "" {
|
||||
ctx.Error(err.Message+": "+detail, err.StatusCode)
|
||||
} else {
|
||||
SendError(ctx, err)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user