2024-01-12 19:18:33 +08:00
|
|
|
package nf
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2024-04-19 16:56:40 +08:00
|
|
|
"errors"
|
2024-01-12 21:42:29 +08:00
|
|
|
"fmt"
|
2024-04-19 16:56:40 +08:00
|
|
|
"github.com/loveuer/nf/internal/sse"
|
2024-01-12 19:18:33 +08:00
|
|
|
"io"
|
2024-01-12 21:42:29 +08:00
|
|
|
"mime/multipart"
|
|
|
|
"net"
|
2024-01-12 19:18:33 +08:00
|
|
|
"net/http"
|
|
|
|
"strings"
|
2024-04-13 10:46:23 +08:00
|
|
|
"sync"
|
2024-01-12 19:18:33 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
type Ctx struct {
|
2024-04-13 10:46:23 +08:00
|
|
|
lock sync.Mutex
|
|
|
|
writermem responseWriter
|
|
|
|
writer http.ResponseWriter
|
|
|
|
Request *http.Request
|
|
|
|
path string
|
|
|
|
method string
|
2024-01-12 19:18:33 +08:00
|
|
|
StatusCode int
|
|
|
|
|
2024-02-20 15:34:00 +08:00
|
|
|
app *App
|
|
|
|
params *Params
|
|
|
|
index int
|
|
|
|
handlers []HandlerFunc
|
|
|
|
locals map[string]interface{}
|
|
|
|
skippedNodes *[]skippedNode
|
|
|
|
fullPath string
|
2024-01-12 19:18:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ctx {
|
2024-02-20 15:34:00 +08:00
|
|
|
|
|
|
|
skippedNodes := make([]skippedNode, 0, app.maxSections)
|
|
|
|
v := make(Params, 0, app.maxParams)
|
|
|
|
|
|
|
|
ctx := &Ctx{
|
2024-04-13 10:46:23 +08:00
|
|
|
lock: sync.Mutex{},
|
2024-01-14 19:10:05 +08:00
|
|
|
writer: writer,
|
|
|
|
Request: request,
|
|
|
|
path: request.URL.Path,
|
2024-02-20 15:34:00 +08:00
|
|
|
method: request.Method,
|
2024-01-14 19:10:05 +08:00
|
|
|
StatusCode: 200,
|
2024-01-12 19:18:33 +08:00
|
|
|
|
2024-02-20 15:34:00 +08:00
|
|
|
app: app,
|
|
|
|
index: -1,
|
|
|
|
locals: map[string]interface{}{},
|
|
|
|
handlers: make([]HandlerFunc, 0),
|
|
|
|
skippedNodes: &skippedNodes,
|
|
|
|
params: &v,
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.writermem = responseWriter{
|
|
|
|
ResponseWriter: ctx.writer,
|
|
|
|
size: -1,
|
|
|
|
status: 0,
|
2024-01-12 19:18:33 +08:00
|
|
|
}
|
2024-02-20 15:34:00 +08:00
|
|
|
|
|
|
|
return ctx
|
2024-01-12 19:18:33 +08:00
|
|
|
}
|
|
|
|
|
2024-01-13 20:44:20 +08:00
|
|
|
func (c *Ctx) Locals(key string, value ...interface{}) interface{} {
|
2024-01-12 19:18:33 +08:00
|
|
|
data := c.locals[key]
|
|
|
|
if len(value) > 0 {
|
|
|
|
c.locals[key] = value[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
2024-02-20 15:34:00 +08:00
|
|
|
func (c *Ctx) Method(overWrite ...string) string {
|
|
|
|
method := c.Request.Method
|
|
|
|
|
|
|
|
if len(overWrite) > 0 && overWrite[0] != "" {
|
|
|
|
c.Request.Method = overWrite[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
return method
|
|
|
|
}
|
|
|
|
|
2024-01-12 19:18:33 +08:00
|
|
|
func (c *Ctx) Path(overWrite ...string) string {
|
|
|
|
path := c.Request.URL.Path
|
|
|
|
if len(overWrite) > 0 && overWrite[0] != "" {
|
|
|
|
c.Request.URL.Path = overWrite[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
2024-01-29 19:16:36 +08:00
|
|
|
func (c *Ctx) Cookies(key string, defaultValue ...string) string {
|
|
|
|
var (
|
|
|
|
dv = ""
|
|
|
|
)
|
|
|
|
|
|
|
|
if len(defaultValue) > 0 {
|
|
|
|
dv = defaultValue[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
cookie, err := c.Request.Cookie(key)
|
|
|
|
if err != nil || cookie.Value == "" {
|
|
|
|
return dv
|
|
|
|
}
|
|
|
|
|
|
|
|
return cookie.Value
|
|
|
|
}
|
|
|
|
|
2024-01-12 19:18:33 +08:00
|
|
|
func (c *Ctx) Next() error {
|
|
|
|
c.index++
|
2024-01-30 11:01:58 +08:00
|
|
|
|
2024-02-27 16:19:19 +08:00
|
|
|
if c.index >= len(c.handlers) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-20 16:04:44 +08:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
handler = c.handlers[c.index]
|
|
|
|
)
|
2024-01-30 11:01:58 +08:00
|
|
|
|
2024-02-20 16:04:44 +08:00
|
|
|
if handler != nil {
|
|
|
|
if err = handler(c); err != nil {
|
|
|
|
return err
|
2024-02-20 15:34:00 +08:00
|
|
|
}
|
2024-01-12 19:18:33 +08:00
|
|
|
}
|
|
|
|
|
2024-02-20 16:04:44 +08:00
|
|
|
c.index++
|
|
|
|
|
2024-02-20 15:34:00 +08:00
|
|
|
return nil
|
2024-01-12 19:18:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ===============================================================
|
|
|
|
|| Handle Ctx Request Part
|
|
|
|
=============================================================== */
|
|
|
|
|
|
|
|
func (c *Ctx) verify() error {
|
|
|
|
// 验证 body size
|
|
|
|
if c.app.config.BodyLimit != -1 && c.Request.ContentLength > c.app.config.BodyLimit {
|
|
|
|
return NewNFError(413, "Content Too Large")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) Param(key string) string {
|
2024-04-10 18:03:38 +08:00
|
|
|
return c.params.ByName(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) SetParam(key, value string) {
|
2024-04-13 10:46:23 +08:00
|
|
|
c.lock.Lock()
|
|
|
|
defer c.lock.Unlock()
|
|
|
|
|
2024-04-10 18:03:38 +08:00
|
|
|
params := append(*c.params, Param{Key: key, Value: value})
|
|
|
|
c.params = ¶ms
|
2024-01-12 19:18:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) Form(key string) string {
|
|
|
|
return c.Request.FormValue(key)
|
2024-02-27 16:19:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// FormValue fiber ctx function
|
|
|
|
func (c *Ctx) FormValue(key string) string {
|
|
|
|
return c.Request.FormValue(key)
|
2024-01-12 19:18:33 +08:00
|
|
|
}
|
|
|
|
|
2024-01-12 21:42:29 +08:00
|
|
|
func (c *Ctx) FormFile(key string) (*multipart.FileHeader, error) {
|
|
|
|
_, fh, err := c.Request.FormFile(key)
|
|
|
|
return fh, err
|
|
|
|
}
|
|
|
|
|
2024-03-11 16:28:33 +08:00
|
|
|
func (c *Ctx) MultipartForm() (*multipart.Form, error) {
|
|
|
|
if err := c.Request.ParseMultipartForm(c.app.config.BodyLimit); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.Request.MultipartForm, nil
|
|
|
|
}
|
|
|
|
|
2024-01-12 19:18:33 +08:00
|
|
|
func (c *Ctx) Query(key string) string {
|
|
|
|
return c.Request.URL.Query().Get(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) Get(key string, defaultValue ...string) string {
|
|
|
|
value := c.Request.Header.Get(key)
|
|
|
|
if value == "" && len(defaultValue) > 0 {
|
|
|
|
return defaultValue[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
2024-01-12 21:42:29 +08:00
|
|
|
func (c *Ctx) IP() string {
|
|
|
|
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return ip
|
|
|
|
}
|
|
|
|
|
2024-01-12 19:18:33 +08:00
|
|
|
func (c *Ctx) BodyParser(out interface{}) error {
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
ctype = strings.ToLower(c.Request.Header.Get("Content-Type"))
|
|
|
|
)
|
|
|
|
|
|
|
|
ctype = parseVendorSpecificContentType(ctype)
|
|
|
|
|
|
|
|
ctypeEnd := strings.IndexByte(ctype, ';')
|
|
|
|
if ctypeEnd != -1 {
|
|
|
|
ctype = ctype[:ctypeEnd]
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasSuffix(ctype, "json") {
|
|
|
|
bs, err := io.ReadAll(c.Request.Body)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-01 18:06:07 +08:00
|
|
|
_ = c.Request.Body.Close()
|
2024-01-12 19:18:33 +08:00
|
|
|
|
|
|
|
c.Request.Body = io.NopCloser(bytes.NewReader(bs))
|
|
|
|
|
|
|
|
return json.Unmarshal(bs, out)
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasPrefix(ctype, MIMEApplicationForm) {
|
|
|
|
|
|
|
|
if err = c.Request.ParseForm(); err != nil {
|
|
|
|
return NewNFError(400, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return parseToStruct("form", out, c.Request.Form)
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasPrefix(ctype, MIMEMultipartForm) {
|
|
|
|
if err = c.Request.ParseMultipartForm(c.app.config.BodyLimit); err != nil {
|
|
|
|
return NewNFError(400, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return parseToStruct("form", out, c.Request.PostForm)
|
|
|
|
}
|
|
|
|
|
|
|
|
return NewNFError(422, "Unprocessable Content")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) QueryParser(out interface{}) error {
|
|
|
|
return parseToStruct("query", out, c.Request.URL.Query())
|
|
|
|
}
|
2024-01-12 21:42:29 +08:00
|
|
|
|
|
|
|
/* ===============================================================
|
|
|
|
|| Handle Ctx Response Part
|
|
|
|
=============================================================== */
|
|
|
|
|
|
|
|
func (c *Ctx) Status(code int) *Ctx {
|
2024-04-13 10:46:23 +08:00
|
|
|
c.lock.Lock()
|
|
|
|
defer c.lock.Unlock()
|
|
|
|
|
2024-05-22 14:08:34 +08:00
|
|
|
c.writermem.WriteHeader(code)
|
|
|
|
c.StatusCode = c.writermem.status
|
2024-04-13 10:46:23 +08:00
|
|
|
|
2024-01-12 21:42:29 +08:00
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) Set(key string, value string) {
|
2024-02-20 15:34:00 +08:00
|
|
|
c.writermem.Header().Set(key, value)
|
2024-01-12 21:42:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) SetHeader(key string, value string) {
|
2024-02-20 15:34:00 +08:00
|
|
|
c.writermem.Header().Set(key, value)
|
2024-01-12 21:42:29 +08:00
|
|
|
}
|
|
|
|
|
2024-04-10 11:24:17 +08:00
|
|
|
func (c *Ctx) SendStatus(code int) error {
|
2024-04-13 10:46:23 +08:00
|
|
|
c.Status(code)
|
|
|
|
c.writermem.WriteHeaderNow()
|
2024-04-10 11:24:17 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-12 21:42:29 +08:00
|
|
|
func (c *Ctx) SendString(data string) error {
|
|
|
|
c.SetHeader("Content-Type", "text/plain")
|
|
|
|
_, err := c.Write([]byte(data))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) Writef(format string, values ...interface{}) (int, error) {
|
|
|
|
c.SetHeader("Content-Type", "text/plain")
|
2024-04-19 16:56:40 +08:00
|
|
|
return c.Write([]byte(fmt.Sprintf(format, values...)))
|
2024-01-12 21:42:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) JSON(data interface{}) error {
|
2024-01-19 20:15:36 +08:00
|
|
|
c.SetHeader("Content-Type", MIMEApplicationJSON)
|
2024-01-12 21:42:29 +08:00
|
|
|
|
2024-02-20 15:34:00 +08:00
|
|
|
encoder := json.NewEncoder(&c.writermem)
|
2024-01-12 21:42:29 +08:00
|
|
|
|
|
|
|
if err := encoder.Encode(data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-19 16:56:40 +08:00
|
|
|
func (c *Ctx) SSEvent(event string, data interface{}) error {
|
|
|
|
c.Set("Content-Type", "text/event-stream")
|
|
|
|
c.Set("Cache-Control", "no-cache")
|
|
|
|
c.Set("Transfer-Encoding", "chunked")
|
|
|
|
|
|
|
|
return sse.Encode(c.writer, sse.Event{Event: event, Data: data})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Ctx) Flush() error {
|
|
|
|
if f, ok := c.writer.(http.Flusher); ok {
|
|
|
|
f.Flush()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.New("http.Flusher is not implemented")
|
|
|
|
}
|
|
|
|
|
2024-01-14 19:10:05 +08:00
|
|
|
func (c *Ctx) RawWriter() http.ResponseWriter {
|
|
|
|
return c.writer
|
|
|
|
}
|
|
|
|
|
2024-01-12 21:42:29 +08:00
|
|
|
func (c *Ctx) HTML(html string) error {
|
|
|
|
c.SetHeader("Content-Type", "text/html")
|
2024-01-14 19:10:05 +08:00
|
|
|
_, err := c.writer.Write([]byte(html))
|
2024-01-12 21:42:29 +08:00
|
|
|
return err
|
|
|
|
}
|
2024-04-13 10:46:23 +08:00
|
|
|
|
|
|
|
func (c *Ctx) Write(data []byte) (int, error) {
|
|
|
|
return c.writermem.Write(data)
|
|
|
|
}
|