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"
|
"github.com/valyala/fasthttp"
|
||||||
"rua.plus/lolly/internal/cache"
|
"rua.plus/lolly/internal/cache"
|
||||||
"rua.plus/lolly/internal/middleware/compression"
|
"rua.plus/lolly/internal/middleware/compression"
|
||||||
|
"rua.plus/lolly/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StaticHandler 静态文件处理器。
|
// StaticHandler 静态文件处理器。
|
||||||
@ -212,7 +213,7 @@ func (h *StaticHandler) Handle(ctx *fasthttp.RequestCtx) {
|
|||||||
|
|
||||||
// 安全检查:防止目录遍历
|
// 安全检查:防止目录遍历
|
||||||
if strings.Contains(reqPath, "..") {
|
if strings.Contains(reqPath, "..") {
|
||||||
ctx.Error("Forbidden", fasthttp.StatusForbidden)
|
utils.SendError(ctx, utils.ErrForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +291,7 @@ func (h *StaticHandler) handleTryFiles(ctx *fasthttp.RequestCtx, reqPath string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 所有 try_files 都未找到
|
// 所有 try_files 都未找到
|
||||||
ctx.Error("Not Found", fasthttp.StatusNotFound)
|
utils.SendError(ctx, utils.ErrNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveTryFilePath 解析 try_files 中的占位符。
|
// resolveTryFilePath 解析 try_files 中的占位符。
|
||||||
@ -369,11 +370,11 @@ func (h *StaticHandler) handleInternalRedirect(ctx *fasthttp.RequestCtx, targetP
|
|||||||
}
|
}
|
||||||
info, err := os.Stat(filePath)
|
info, err := os.Stat(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error("Not Found", fasthttp.StatusNotFound)
|
utils.SendError(ctx, utils.ErrNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
ctx.Error("Forbidden", fasthttp.StatusForbidden)
|
utils.SendError(ctx, utils.ErrForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.serveFile(ctx, filePath, info)
|
h.serveFile(ctx, filePath, info)
|
||||||
@ -419,7 +420,7 @@ func (h *StaticHandler) handleStandard(ctx *fasthttp.RequestCtx, reqPath string)
|
|||||||
// 检查文件/目录是否存在
|
// 检查文件/目录是否存在
|
||||||
info, err := os.Stat(filePath)
|
info, err := os.Stat(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error("Not Found", fasthttp.StatusNotFound)
|
utils.SendError(ctx, utils.ErrNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,7 +433,7 @@ func (h *StaticHandler) handleStandard(ctx *fasthttp.RequestCtx, reqPath string)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.Error("Forbidden", fasthttp.StatusForbidden)
|
utils.SendError(ctx, utils.ErrForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,7 +493,7 @@ func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, inf
|
|||||||
// 读取文件内容
|
// 读取文件内容
|
||||||
data, err := os.ReadFile(filePath)
|
data, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
|
utils.SendError(ctx, utils.ErrInternalError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import (
|
|||||||
"rua.plus/lolly/internal/config"
|
"rua.plus/lolly/internal/config"
|
||||||
"rua.plus/lolly/internal/middleware"
|
"rua.plus/lolly/internal/middleware"
|
||||||
"rua.plus/lolly/internal/netutil"
|
"rua.plus/lolly/internal/netutil"
|
||||||
|
"rua.plus/lolly/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const rateLimitHeader = "header"
|
const rateLimitHeader = "header"
|
||||||
@ -174,7 +175,7 @@ func (s *SlidingWindowLimiterWrapper) Process(next fasthttp.RequestHandler) fast
|
|||||||
key := s.keyFunc(ctx)
|
key := s.keyFunc(ctx)
|
||||||
|
|
||||||
if !s.limiter.Allow(key) {
|
if !s.limiter.Allow(key) {
|
||||||
ctx.Error("Too Many Requests", fasthttp.StatusTooManyRequests)
|
utils.SendError(ctx, utils.ErrTooManyRequests)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +209,7 @@ func (rl *RateLimiter) Process(next fasthttp.RequestHandler) fasthttp.RequestHan
|
|||||||
// 计算重试等待时间
|
// 计算重试等待时间
|
||||||
retryAfter := rl.getRetryAfter(key)
|
retryAfter := rl.getRetryAfter(key)
|
||||||
ctx.Response.Header.Set("Retry-After", fmt.Sprintf("%d", retryAfter))
|
ctx.Response.Header.Set("Retry-After", fmt.Sprintf("%d", retryAfter))
|
||||||
ctx.Error("Too Many Requests", fasthttp.StatusTooManyRequests)
|
utils.SendError(ctx, utils.ErrTooManyRequests)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -615,7 +616,7 @@ func (m *connLimiterMiddleware) Name() string {
|
|||||||
func (m *connLimiterMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
func (m *connLimiterMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
return func(ctx *fasthttp.RequestCtx) {
|
return func(ctx *fasthttp.RequestCtx) {
|
||||||
if !m.limiter.Acquire(ctx) {
|
if !m.limiter.Acquire(ctx) {
|
||||||
ctx.Error("Service Unavailable: Connection limit exceeded", fasthttp.StatusServiceUnavailable)
|
utils.SendErrorWithDetail(ctx, utils.ErrServiceUnavailable, "Connection limit exceeded")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -48,6 +48,7 @@ import (
|
|||||||
"rua.plus/lolly/internal/logging"
|
"rua.plus/lolly/internal/logging"
|
||||||
"rua.plus/lolly/internal/netutil"
|
"rua.plus/lolly/internal/netutil"
|
||||||
"rua.plus/lolly/internal/resolver"
|
"rua.plus/lolly/internal/resolver"
|
||||||
|
"rua.plus/lolly/internal/utils"
|
||||||
"rua.plus/lolly/internal/variable"
|
"rua.plus/lolly/internal/variable"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -337,7 +338,7 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
// 没有可用后端
|
// 没有可用后端
|
||||||
upstreamAddr = "FAILED"
|
upstreamAddr = "FAILED"
|
||||||
upstreamStatus = 502
|
upstreamStatus = 502
|
||||||
ctx.Error("Bad Gateway: no healthy upstream", fasthttp.StatusBadGateway)
|
utils.SendErrorWithDetail(ctx, utils.ErrBadGateway, "no healthy upstream")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 没有更多可用目标,返回最后一次错误
|
// 没有更多可用目标,返回最后一次错误
|
||||||
@ -522,18 +523,18 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
// 处理不同类型的错误
|
// 处理不同类型的错误
|
||||||
if errors.Is(lastErr, fasthttp.ErrTimeout) {
|
if errors.Is(lastErr, fasthttp.ErrTimeout) {
|
||||||
upstreamStatus = 504
|
upstreamStatus = 504
|
||||||
ctx.Error("Gateway Timeout", fasthttp.StatusGatewayTimeout)
|
utils.SendError(ctx, utils.ErrGatewayTimeout)
|
||||||
} else if errors.Is(lastErr, fasthttp.ErrConnectionClosed) {
|
} else if errors.Is(lastErr, fasthttp.ErrConnectionClosed) {
|
||||||
upstreamStatus = 502
|
upstreamStatus = 502
|
||||||
ctx.Error("Bad Gateway: upstream connection closed", fasthttp.StatusBadGateway)
|
utils.SendErrorWithDetail(ctx, utils.ErrBadGateway, "upstream connection closed")
|
||||||
} else {
|
} else {
|
||||||
upstreamStatus = 502
|
upstreamStatus = 502
|
||||||
ctx.Error("Bad Gateway", fasthttp.StatusBadGateway)
|
utils.SendError(ctx, utils.ErrBadGateway)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
upstreamAddr = "FAILED"
|
upstreamAddr = "FAILED"
|
||||||
upstreamStatus = 502
|
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"
|
"github.com/valyala/fasthttp"
|
||||||
"rua.plus/lolly/internal/config"
|
"rua.plus/lolly/internal/config"
|
||||||
"rua.plus/lolly/internal/netutil"
|
"rua.plus/lolly/internal/netutil"
|
||||||
|
"rua.plus/lolly/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusHandler 状态监控处理器。
|
// StatusHandler 状态监控处理器。
|
||||||
@ -182,7 +183,7 @@ func (h *StatusHandler) Path() string {
|
|||||||
func (h *StatusHandler) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
func (h *StatusHandler) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
||||||
// 步骤1: 检查 IP 访问权限
|
// 步骤1: 检查 IP 访问权限
|
||||||
if !h.checkAccess(ctx) {
|
if !h.checkAccess(ctx) {
|
||||||
ctx.Error("Forbidden: Access denied", fasthttp.StatusForbidden)
|
utils.SendErrorWithDetail(ctx, utils.ErrForbidden, "Access denied")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +202,7 @@ func (h *StatusHandler) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
|
|
||||||
data, err := json.MarshalIndent(status, "", " ")
|
data, err := json.MarshalIndent(status, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
|
utils.SendError(ctx, utils.ErrInternalError)
|
||||||
return
|
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