Merge pull request #8 from infinite-iroha/dev

[engine] add StaticFile && [context] Add Cookie Method, port from gin
This commit is contained in:
WJQSERVER 2025-06-05 19:03:50 +08:00 committed by GitHub
commit f1ff1f935f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 167 additions and 0 deletions

View file

@ -50,6 +50,8 @@ type Context struct {
// 引用所属的 Engine 实例,方便访问 Engine 的配置(如 HTMLRender // 引用所属的 Engine 实例,方便访问 Engine 的配置(如 HTMLRender
engine *Engine engine *Engine
sameSite http.SameSite
} }
// --- Context 相关方法实现 --- // --- Context 相关方法实现 ---
@ -77,6 +79,7 @@ func (c *Context) reset(w http.ResponseWriter, req *http.Request) {
c.queryCache = nil // 清空查询参数缓存 c.queryCache = nil // 清空查询参数缓存
c.formCache = nil // 清空表单数据缓存 c.formCache = nil // 清空表单数据缓存
c.ctx = req.Context() // 使用请求的上下文,继承其取消信号和值 c.ctx = req.Context() // 使用请求的上下文,继承其取消信号和值
c.sameSite = http.SameSiteDefaultMode // 默认 SameSite 模式
// c.HTTPClient 和 c.engine 保持不变,它们引用 Engine 实例的成员 // c.HTTPClient 和 c.engine 保持不变,它们引用 Engine 实例的成员
} }
@ -489,6 +492,58 @@ func (c *Context) GetLogger() *reco.Logger {
return c.engine.LogReco return c.engine.LogReco
} }
// SetSameSite 设置响应的 SameSite cookie 属性。
func (c *Context) SetSameSite(samesite http.SameSite) {
c.sameSite = samesite
}
// SetCookie 设置一个 HTTP cookie。
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
if path == "" {
path = "/"
}
http.SetCookie(c.Writer, &http.Cookie{
Name: name,
Value: url.QueryEscape(value),
MaxAge: maxAge,
Path: path,
Domain: domain,
SameSite: c.sameSite,
Secure: secure,
HttpOnly: httpOnly,
})
}
func (c *Context) SetCookieData(cookie *http.Cookie) {
if cookie.Path == "" {
cookie.Path = "/"
}
if cookie.SameSite == http.SameSiteDefaultMode {
cookie.SameSite = c.sameSite
}
http.SetCookie(c.Writer, cookie)
}
// GetCookie 获取指定名称的 cookie 值。
func (c *Context) GetCookie(name string) (string, error) {
cookie, err := c.Request.Cookie(name)
if err != nil {
return "", err
}
// 对 cookie 值进行 URL 解码
value, err := url.QueryUnescape(cookie.Value)
if err != nil {
return "", fmt.Errorf("failed to unescape cookie value: %w", err)
}
return value, nil
}
// DeleteCookie 删除指定名称的 cookie。
// 通过设置 MaxAge 为 -1 来删除 cookie。
func (c *Context) DeleteCookie(name string) {
c.SetCookie(name, "", -1, "/", "", false, false) // 设置 MaxAge 为 -1 删除 cookie
}
// === 日志记录 === // === 日志记录 ===
func (c *Context) Debugf(format string, args ...any) { func (c *Context) Debugf(format string, args ...any) {
c.engine.LogReco.Debugf(format, args...) c.engine.LogReco.Debugf(format, args...)

112
engine.go
View file

@ -845,3 +845,115 @@ func (group *RouterGroup) Static(relativePath, rootPath string) {
c.Abort() c.Abort()
}) })
} }
// Static File 传入一个文件路径, 使用FileServer进行处理
func (engine *Engine) StaticFile(relativePath, filePath string) {
// 清理路径
relativePath = path.Clean(relativePath)
filePath = path.Clean(filePath)
// 创建一个文件系统处理器,指向包含目标文件的目录
// http.Dir 需要一个目录路径
dir := path.Dir(filePath)
fileName := path.Base(filePath)
fileServer := http.FileServer(http.Dir(dir))
FileHandle := func(c *Context) {
// 检查是否是 GET 或 HEAD 方法
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
// 如果不是,且启用了 MethodNotAllowed 处理,则继续到 MethodNotAllowed 中间件
if engine.HandleMethodNotAllowed {
c.Next()
} else {
// 否则,返回 405 Method Not Allowed
engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
}
return
}
requestPath := c.Request.URL.Path
// 构造文件服务器需要处理的请求路径
// FileServer 会将请求路径与 http.Dir 的根路径结合
// 我们需要将请求路径设置为文件名,以便 FileServer 找到正确的文件
c.Request.URL.Path = "/" + fileName // FileServer 期望路径以 / 开头
// 使用自定义的 ResponseWriter 包装器来捕获 FileServer 可能返回的错误状态码
ecw := AcquireErrorCapturingResponseWriter(c)
defer ReleaseErrorCapturingResponseWriter(ecw)
// 调用 FileServer 处理请求
fileServer.ServeHTTP(ecw, c.Request)
// 在 FileServer 处理完成后,检查是否捕获到错误状态码,并调用 ErrorHandler
ecw.processAfterFileServer()
// 恢复原始请求路径
c.Request.URL.Path = requestPath
// 中止处理链,因为 FileServer 已经处理了响应
c.Abort()
}
// 注册一个精确匹配的路由
engine.GET(relativePath, FileHandle)
engine.HEAD(relativePath, FileHandle)
engine.OPTIONS(relativePath, FileHandle)
}
// Group的StaticFile
func (group *RouterGroup) StaticFile(relativePath, filePath string) {
// 清理路径
relativePath = path.Clean(relativePath)
filePath = path.Clean(filePath)
// 创建一个文件系统处理器,指向包含目标文件的目录
// http.Dir 需要一个目录路径
dir := path.Dir(filePath)
fileName := path.Base(filePath)
fileServer := http.FileServer(http.Dir(dir))
FileHandle := func(c *Context) {
// 检查是否是 GET 或 HEAD 方法
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
// 如果不是,且启用了 MethodNotAllowed 处理,则继续到 MethodNotAllowed 中间件
if group.engine.HandleMethodNotAllowed {
c.Next()
} else {
// 否则,返回 405 Method Not Allowed
group.engine.errorHandle.handler(c, http.StatusMethodNotAllowed)
}
return
}
requestPath := c.Request.URL.Path
// 构造文件服务器需要处理的请求路径
// FileServer 会将请求路径与 http.Dir 的根路径结合
// 我们需要将请求路径设置为文件名,以便 FileServer 找到正确的文件
c.Request.URL.Path = "/" + fileName // FileServer 期望路径以 / 开头
// 使用自定义的 ResponseWriter 包装器来捕获 FileServer 可能返回的错误状态码
ecw := AcquireErrorCapturingResponseWriter(c)
defer ReleaseErrorCapturingResponseWriter(ecw)
// 调用 FileServer 处理请求
fileServer.ServeHTTP(ecw, c.Request)
// 在 FileServer 处理完成后,检查是否捕获到错误状态码,并调用 ErrorHandler
ecw.processAfterFileServer()
// 恢复原始请求路径
c.Request.URL.Path = requestPath
// 中止处理链,因为 FileServer 已经处理了响应
c.Abort()
}
// 注册一个精确匹配的路由
group.GET(relativePath, FileHandle)
group.HEAD(relativePath, FileHandle)
group.OPTIONS(relativePath, FileHandle)
}