diff --git a/context.go b/context.go index dad0684..ff65ef3 100644 --- a/context.go +++ b/context.go @@ -50,6 +50,8 @@ type Context struct { // 引用所属的 Engine 实例,方便访问 Engine 的配置(如 HTMLRender) engine *Engine + + sameSite http.SameSite } // --- Context 相关方法实现 --- @@ -77,6 +79,7 @@ func (c *Context) reset(w http.ResponseWriter, req *http.Request) { c.queryCache = nil // 清空查询参数缓存 c.formCache = nil // 清空表单数据缓存 c.ctx = req.Context() // 使用请求的上下文,继承其取消信号和值 + c.sameSite = http.SameSiteDefaultMode // 默认 SameSite 模式 // c.HTTPClient 和 c.engine 保持不变,它们引用 Engine 实例的成员 } @@ -489,6 +492,58 @@ func (c *Context) GetLogger() *reco.Logger { 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) { c.engine.LogReco.Debugf(format, args...) diff --git a/engine.go b/engine.go index 0d58bd1..b5d527f 100644 --- a/engine.go +++ b/engine.go @@ -845,3 +845,115 @@ func (group *RouterGroup) Static(relativePath, rootPath string) { 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) + +}