| | |
| | |
| | |
| |
|
| | package context |
| |
|
| | import ( |
| | "bytes" |
| | "encoding/json" |
| | "errors" |
| | "fmt" |
| | "io" |
| | "math" |
| | "net" |
| | "net/http" |
| | "net/url" |
| | "os" |
| | "path" |
| | "strings" |
| | "time" |
| |
|
| | "github.com/GoAdminGroup/go-admin/modules/constant" |
| | ) |
| |
|
| | const abortIndex int8 = math.MaxInt8 / 2 |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | type Context struct { |
| | Request *http.Request |
| | Response *http.Response |
| | UserValue map[string]interface{} |
| | index int8 |
| | handlers Handlers |
| | } |
| |
|
| | |
| | |
| | |
| | type Path struct { |
| | URL string |
| | Method string |
| | } |
| |
|
| | type RouterMap map[string]Router |
| |
|
| | func (r RouterMap) Get(name string) Router { |
| | return r[name] |
| | } |
| |
|
| | type Router struct { |
| | Methods []string |
| | Patten string |
| | } |
| |
|
| | func (r Router) Method() string { |
| | return r.Methods[0] |
| | } |
| |
|
| | func (r Router) GetURL(value ...string) string { |
| | u := r.Patten |
| | for i := 0; i < len(value); i += 2 { |
| | u = strings.ReplaceAll(u, ":__"+value[i], value[i+1]) |
| | } |
| | return u |
| | } |
| |
|
| | type NodeProcessor func(...Node) |
| |
|
| | type Node struct { |
| | Path string |
| | Method string |
| | Handlers []Handler |
| | Value map[string]interface{} |
| | } |
| |
|
| | |
| | func (ctx *Context) SetUserValue(key string, value interface{}) { |
| | ctx.UserValue[key] = value |
| | } |
| |
|
| | |
| | func (ctx *Context) GetUserValue(key string) interface{} { |
| | return ctx.UserValue[key] |
| | } |
| |
|
| | |
| | func (ctx *Context) Path() string { |
| | return ctx.Request.URL.Path |
| | } |
| |
|
| | |
| | func (ctx *Context) Abort() { |
| | ctx.index = abortIndex |
| | } |
| |
|
| | |
| | func (ctx *Context) Next() { |
| | ctx.index++ |
| | for s := int8(len(ctx.handlers)); ctx.index < s; ctx.index++ { |
| | ctx.handlers[ctx.index](ctx) |
| | } |
| | } |
| |
|
| | |
| | func (ctx *Context) SetHandlers(handlers Handlers) *Context { |
| | ctx.handlers = handlers |
| | return ctx |
| | } |
| |
|
| | |
| | func (ctx *Context) Method() string { |
| | return ctx.Request.Method |
| | } |
| |
|
| | |
| | |
| | func NewContext(req *http.Request) *Context { |
| |
|
| | return &Context{ |
| | Request: req, |
| | UserValue: make(map[string]interface{}), |
| | Response: &http.Response{ |
| | StatusCode: http.StatusOK, |
| | Header: make(http.Header), |
| | }, |
| | index: -1, |
| | } |
| | } |
| |
|
| | const ( |
| | HeaderContentType = "Content-Type" |
| |
|
| | HeaderLastModified = "Last-Modified" |
| | HeaderIfModifiedSince = "If-Modified-Since" |
| | HeaderCacheControl = "Cache-Control" |
| | HeaderETag = "ETag" |
| |
|
| | HeaderContentDisposition = "Content-Disposition" |
| | HeaderContentLength = "Content-Length" |
| | HeaderContentEncoding = "Content-Encoding" |
| |
|
| | GzipHeaderValue = "gzip" |
| | HeaderAcceptEncoding = "Accept-Encoding" |
| | HeaderVary = "Vary" |
| |
|
| | ThemeKey = "__ga_theme" |
| | ) |
| |
|
| | func (ctx *Context) BindJSON(data interface{}) error { |
| | if ctx.Request.Body != nil { |
| | b, err := io.ReadAll(ctx.Request.Body) |
| | if err == nil { |
| | return json.Unmarshal(b, data) |
| | } |
| | return err |
| | } |
| | return errors.New("empty request body") |
| | } |
| |
|
| | func (ctx *Context) MustBindJSON(data interface{}) { |
| | if ctx.Request.Body != nil { |
| | b, err := io.ReadAll(ctx.Request.Body) |
| | if err != nil { |
| | panic(err) |
| | } |
| | err = json.Unmarshal(b, data) |
| | if err != nil { |
| | panic(err) |
| | } |
| | } |
| | panic("empty request body") |
| | } |
| |
|
| | |
| | func (ctx *Context) Write(code int, header map[string]string, Body string) { |
| | ctx.Response.StatusCode = code |
| | for key, head := range header { |
| | ctx.AddHeader(key, head) |
| | } |
| | ctx.Response.Body = io.NopCloser(strings.NewReader(Body)) |
| | } |
| |
|
| | |
| | |
| | func (ctx *Context) JSON(code int, Body map[string]interface{}) { |
| | ctx.Response.StatusCode = code |
| | ctx.SetContentType("application/json") |
| | BodyStr, err := json.Marshal(Body) |
| | if err != nil { |
| | panic(err) |
| | } |
| | ctx.Response.Body = io.NopCloser(bytes.NewReader(BodyStr)) |
| | } |
| |
|
| | |
| | func (ctx *Context) DataWithHeaders(code int, header map[string]string, data []byte) { |
| | ctx.Response.StatusCode = code |
| | for key, head := range header { |
| | ctx.AddHeader(key, head) |
| | } |
| | ctx.Response.Body = io.NopCloser(bytes.NewBuffer(data)) |
| | } |
| |
|
| | |
| | func (ctx *Context) Data(code int, contentType string, data []byte) { |
| | ctx.Response.StatusCode = code |
| | ctx.SetContentType(contentType) |
| | ctx.Response.Body = io.NopCloser(bytes.NewBuffer(data)) |
| | } |
| |
|
| | |
| | func (ctx *Context) Redirect(path string) { |
| | ctx.Response.StatusCode = http.StatusFound |
| | ctx.SetContentType("text/html; charset=utf-8") |
| | ctx.AddHeader("Location", path) |
| | } |
| |
|
| | |
| | func (ctx *Context) HTML(code int, body string) { |
| | ctx.SetContentType("text/html; charset=utf-8") |
| | ctx.SetStatusCode(code) |
| | ctx.WriteString(body) |
| | } |
| |
|
| | |
| | func (ctx *Context) HTMLByte(code int, body []byte) { |
| | ctx.SetContentType("text/html; charset=utf-8") |
| | ctx.SetStatusCode(code) |
| | ctx.Response.Body = io.NopCloser(bytes.NewBuffer(body)) |
| | } |
| |
|
| | |
| | func (ctx *Context) WriteString(body string) { |
| | ctx.Response.Body = io.NopCloser(strings.NewReader(body)) |
| | } |
| |
|
| | |
| | func (ctx *Context) SetStatusCode(code int) { |
| | ctx.Response.StatusCode = code |
| | } |
| |
|
| | |
| | func (ctx *Context) SetContentType(contentType string) { |
| | ctx.AddHeader(HeaderContentType, contentType) |
| | } |
| |
|
| | func (ctx *Context) SetLastModified(modtime time.Time) { |
| | if !IsZeroTime(modtime) { |
| | ctx.AddHeader(HeaderLastModified, modtime.UTC().Format(http.TimeFormat)) |
| | } |
| | } |
| |
|
| | var unixEpochTime = time.Unix(0, 0) |
| |
|
| | |
| | func IsZeroTime(t time.Time) bool { |
| | return t.IsZero() || t.Equal(unixEpochTime) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | var ParseTime = func(text string) (t time.Time, err error) { |
| | t, err = time.Parse(http.TimeFormat, text) |
| | if err != nil { |
| | return http.ParseTime(text) |
| | } |
| |
|
| | return |
| | } |
| |
|
| | func (ctx *Context) WriteNotModified() { |
| | |
| | |
| | |
| | |
| | |
| | delete(ctx.Response.Header, HeaderContentType) |
| | delete(ctx.Response.Header, HeaderContentLength) |
| | if ctx.Headers(HeaderETag) != "" { |
| | delete(ctx.Response.Header, HeaderLastModified) |
| | } |
| | ctx.SetStatusCode(http.StatusNotModified) |
| | } |
| |
|
| | func (ctx *Context) CheckIfModifiedSince(modtime time.Time) (bool, error) { |
| | if method := ctx.Method(); method != http.MethodGet && method != http.MethodHead { |
| | return false, errors.New("skip: method") |
| | } |
| | ims := ctx.Headers(HeaderIfModifiedSince) |
| | if ims == "" || IsZeroTime(modtime) { |
| | return false, errors.New("skip: zero time") |
| | } |
| | t, err := ParseTime(ims) |
| | if err != nil { |
| | return false, errors.New("skip: " + err.Error()) |
| | } |
| | |
| | |
| | if modtime.UTC().Before(t.Add(1 * time.Second)) { |
| | return false, nil |
| | } |
| | return true, nil |
| | } |
| |
|
| | |
| | func (ctx *Context) LocalIP() string { |
| | xForwardedFor := ctx.Request.Header.Get("X-Forwarded-For") |
| | ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0]) |
| | if ip != "" { |
| | return ip |
| | } |
| |
|
| | ip = strings.TrimSpace(ctx.Request.Header.Get("X-Real-Ip")) |
| | if ip != "" { |
| | return ip |
| | } |
| |
|
| | if ip, _, err := net.SplitHostPort(strings.TrimSpace(ctx.Request.RemoteAddr)); err == nil { |
| | return ip |
| | } |
| |
|
| | return "127.0.0.1" |
| | } |
| |
|
| | |
| | func (ctx *Context) SetCookie(cookie *http.Cookie) { |
| | if v := cookie.String(); v != "" { |
| | ctx.AddHeader("Set-Cookie", v) |
| | } |
| | } |
| |
|
| | |
| | func (ctx *Context) Query(key string) string { |
| | return ctx.Request.URL.Query().Get(key) |
| | } |
| |
|
| | |
| | func (ctx *Context) QueryAll(key string) []string { |
| | return ctx.Request.URL.Query()[key] |
| | } |
| |
|
| | |
| | func (ctx *Context) QueryDefault(key, def string) string { |
| | value := ctx.Query(key) |
| | if value == "" { |
| | return def |
| | } |
| | return value |
| | } |
| |
|
| | |
| | func (ctx *Context) Lang() string { |
| | return ctx.Query("__ga_lang") |
| | } |
| |
|
| | |
| | func (ctx *Context) Theme() string { |
| | queryTheme := ctx.Query(ThemeKey) |
| | if queryTheme != "" { |
| | return queryTheme |
| | } |
| | cookieTheme := ctx.Cookie(ThemeKey) |
| | if cookieTheme != "" { |
| | return cookieTheme |
| | } |
| | return ctx.RefererQuery(ThemeKey) |
| | } |
| |
|
| | |
| | func (ctx *Context) Headers(key string) string { |
| | return ctx.Request.Header.Get(key) |
| | } |
| |
|
| | |
| | func (ctx *Context) Referer() string { |
| | return ctx.Headers("Referer") |
| | } |
| |
|
| | |
| | func (ctx *Context) RefererURL() *url.URL { |
| | ref := ctx.Headers("Referer") |
| | if ref == "" { |
| | return nil |
| | } |
| | u, err := url.Parse(ref) |
| | if err != nil { |
| | return nil |
| | } |
| | return u |
| | } |
| |
|
| | |
| | func (ctx *Context) RefererQuery(key string) string { |
| | if u := ctx.RefererURL(); u != nil { |
| | return u.Query().Get(key) |
| | } |
| | return "" |
| | } |
| |
|
| | |
| | func (ctx *Context) FormValue(key string) string { |
| | return ctx.Request.FormValue(key) |
| | } |
| |
|
| | |
| | func (ctx *Context) PostForm() url.Values { |
| | _ = ctx.Request.ParseMultipartForm(32 << 20) |
| | return ctx.Request.PostForm |
| | } |
| |
|
| | func (ctx *Context) WantHTML() bool { |
| | return ctx.Method() == "GET" && strings.Contains(ctx.Headers("Accept"), "html") |
| | } |
| |
|
| | func (ctx *Context) WantJSON() bool { |
| | return strings.Contains(ctx.Headers("Accept"), "json") |
| | } |
| |
|
| | |
| | func (ctx *Context) AddHeader(key, value string) { |
| | ctx.Response.Header.Add(key, value) |
| | } |
| |
|
| | |
| | func (ctx *Context) PjaxUrl(url string) { |
| | ctx.Response.Header.Add(constant.PjaxUrlHeader, url) |
| | } |
| |
|
| | |
| | func (ctx *Context) IsPjax() bool { |
| | return ctx.Headers(constant.PjaxHeader) == "true" |
| | } |
| |
|
| | |
| | func (ctx *Context) IsIframe() bool { |
| | return ctx.Query(constant.IframeKey) == "true" || ctx.Headers(constant.IframeKey) == "true" |
| | } |
| |
|
| | |
| | func (ctx *Context) SetHeader(key, value string) { |
| | ctx.Response.Header.Set(key, value) |
| | } |
| |
|
| | func (ctx *Context) GetContentType() string { |
| | return ctx.Request.Header.Get("Content-Type") |
| | } |
| |
|
| | func (ctx *Context) Cookie(name string) string { |
| | for _, ck := range ctx.Request.Cookies() { |
| | if ck.Name == name { |
| | return ck.Value |
| | } |
| | } |
| | return "" |
| | } |
| |
|
| | |
| | func (ctx *Context) User() interface{} { |
| | return ctx.UserValue["user"] |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error { |
| | if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil { |
| | ctx.WriteNotModified() |
| | return nil |
| | } |
| |
|
| | if ctx.GetContentType() == "" { |
| | ctx.SetContentType(filename) |
| | } |
| |
|
| | buf, _ := io.ReadAll(content) |
| | ctx.Response.Body = io.NopCloser(bytes.NewBuffer(buf)) |
| | return nil |
| | } |
| |
|
| | |
| | func (ctx *Context) ServeFile(filename string, gzipCompression bool) error { |
| | f, err := os.Open(filename) |
| | if err != nil { |
| | return fmt.Errorf("%d", http.StatusNotFound) |
| | } |
| | defer func() { |
| | _ = f.Close() |
| | }() |
| | fi, _ := f.Stat() |
| | if fi.IsDir() { |
| | return ctx.ServeFile(path.Join(filename, "index.html"), gzipCompression) |
| | } |
| |
|
| | return ctx.ServeContent(f, fi.Name(), fi.ModTime(), gzipCompression) |
| | } |
| |
|
| | type HandlerMap map[Path]Handlers |
| |
|
| | |
| | |
| | |
| | type App struct { |
| | Requests []Path |
| | Handlers HandlerMap |
| | Middlewares Handlers |
| | Prefix string |
| |
|
| | Routers RouterMap |
| | routeIndex int |
| | routeANY bool |
| | } |
| |
|
| | |
| | func NewApp() *App { |
| | return &App{ |
| | Requests: make([]Path, 0), |
| | Handlers: make(HandlerMap), |
| | Prefix: "/", |
| | Middlewares: make([]Handler, 0), |
| | routeIndex: -1, |
| | Routers: make(RouterMap), |
| | } |
| | } |
| |
|
| | |
| | type Handler func(ctx *Context) |
| |
|
| | |
| | type Handlers []Handler |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func (app *App) AppendReqAndResp(url, method string, handler []Handler) { |
| |
|
| | app.Requests = append(app.Requests, Path{ |
| | URL: join(app.Prefix, url), |
| | Method: method, |
| | }) |
| | app.routeIndex++ |
| |
|
| | app.Handlers[Path{ |
| | URL: join(app.Prefix, url), |
| | Method: method, |
| | }] = append(app.Middlewares, handler...) |
| | } |
| |
|
| | |
| | func (app *App) Find(url, method string) []Handler { |
| | app.routeANY = false |
| | return app.Handlers[Path{URL: url, Method: method}] |
| | } |
| |
|
| | |
| | func (app *App) POST(url string, handler ...Handler) *App { |
| | app.routeANY = false |
| | app.AppendReqAndResp(url, "post", handler) |
| | return app |
| | } |
| |
|
| | |
| | func (app *App) GET(url string, handler ...Handler) *App { |
| | app.routeANY = false |
| | app.AppendReqAndResp(url, "get", handler) |
| | return app |
| | } |
| |
|
| | |
| | func (app *App) DELETE(url string, handler ...Handler) *App { |
| | app.routeANY = false |
| | app.AppendReqAndResp(url, "delete", handler) |
| | return app |
| | } |
| |
|
| | |
| | func (app *App) PUT(url string, handler ...Handler) *App { |
| | app.routeANY = false |
| | app.AppendReqAndResp(url, "put", handler) |
| | return app |
| | } |
| |
|
| | |
| | func (app *App) OPTIONS(url string, handler ...Handler) *App { |
| | app.routeANY = false |
| | app.AppendReqAndResp(url, "options", handler) |
| | return app |
| | } |
| |
|
| | |
| | func (app *App) HEAD(url string, handler ...Handler) *App { |
| | app.routeANY = false |
| | app.AppendReqAndResp(url, "head", handler) |
| | return app |
| | } |
| |
|
| | |
| | |
| | func (app *App) ANY(url string, handler ...Handler) *App { |
| | app.routeANY = true |
| | app.AppendReqAndResp(url, "post", handler) |
| | app.AppendReqAndResp(url, "get", handler) |
| | app.AppendReqAndResp(url, "delete", handler) |
| | app.AppendReqAndResp(url, "put", handler) |
| | app.AppendReqAndResp(url, "options", handler) |
| | app.AppendReqAndResp(url, "head", handler) |
| | return app |
| | } |
| |
|
| | func (app *App) Name(name string) { |
| | if app.routeANY { |
| | app.Routers[name] = Router{ |
| | Methods: []string{"POST", "GET", "DELETE", "PUT", "OPTIONS", "HEAD"}, |
| | Patten: app.Requests[app.routeIndex].URL, |
| | } |
| | } else { |
| | app.Routers[name] = Router{ |
| | Methods: []string{app.Requests[app.routeIndex].Method}, |
| | Patten: app.Requests[app.routeIndex].URL, |
| | } |
| | } |
| | } |
| |
|
| | |
| | func (app *App) Group(prefix string, middleware ...Handler) *RouterGroup { |
| | return &RouterGroup{ |
| | app: app, |
| | Middlewares: append(app.Middlewares, middleware...), |
| | Prefix: slash(prefix), |
| | } |
| | } |
| |
|
| | |
| | type RouterGroup struct { |
| | app *App |
| | Middlewares Handlers |
| | Prefix string |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func (g *RouterGroup) AppendReqAndResp(url, method string, handler []Handler) { |
| |
|
| | g.app.Requests = append(g.app.Requests, Path{ |
| | URL: join(g.Prefix, url), |
| | Method: method, |
| | }) |
| | g.app.routeIndex++ |
| |
|
| | var h = make([]Handler, len(g.Middlewares)) |
| | copy(h, g.Middlewares) |
| |
|
| | g.app.Handlers[Path{ |
| | URL: join(g.Prefix, url), |
| | Method: method, |
| | }] = append(h, handler...) |
| | } |
| |
|
| | |
| | func (g *RouterGroup) POST(url string, handler ...Handler) *RouterGroup { |
| | g.app.routeANY = false |
| | g.AppendReqAndResp(url, "post", handler) |
| | return g |
| | } |
| |
|
| | |
| | func (g *RouterGroup) GET(url string, handler ...Handler) *RouterGroup { |
| | g.app.routeANY = false |
| | g.AppendReqAndResp(url, "get", handler) |
| | return g |
| | } |
| |
|
| | |
| | func (g *RouterGroup) DELETE(url string, handler ...Handler) *RouterGroup { |
| | g.app.routeANY = false |
| | g.AppendReqAndResp(url, "delete", handler) |
| | return g |
| | } |
| |
|
| | |
| | func (g *RouterGroup) PUT(url string, handler ...Handler) *RouterGroup { |
| | g.app.routeANY = false |
| | g.AppendReqAndResp(url, "put", handler) |
| | return g |
| | } |
| |
|
| | |
| | func (g *RouterGroup) OPTIONS(url string, handler ...Handler) *RouterGroup { |
| | g.app.routeANY = false |
| | g.AppendReqAndResp(url, "options", handler) |
| | return g |
| | } |
| |
|
| | |
| | func (g *RouterGroup) HEAD(url string, handler ...Handler) *RouterGroup { |
| | g.app.routeANY = false |
| | g.AppendReqAndResp(url, "head", handler) |
| | return g |
| | } |
| |
|
| | |
| | |
| | func (g *RouterGroup) ANY(url string, handler ...Handler) *RouterGroup { |
| | g.app.routeANY = true |
| | g.AppendReqAndResp(url, "post", handler) |
| | g.AppendReqAndResp(url, "get", handler) |
| | g.AppendReqAndResp(url, "delete", handler) |
| | g.AppendReqAndResp(url, "put", handler) |
| | g.AppendReqAndResp(url, "options", handler) |
| | g.AppendReqAndResp(url, "head", handler) |
| | return g |
| | } |
| |
|
| | func (g *RouterGroup) Name(name string) { |
| | g.app.Name(name) |
| | } |
| |
|
| | |
| | func (g *RouterGroup) Group(prefix string, middleware ...Handler) *RouterGroup { |
| | return &RouterGroup{ |
| | app: g.app, |
| | Middlewares: append(g.Middlewares, middleware...), |
| | Prefix: join(slash(g.Prefix), slash(prefix)), |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func slash(prefix string) string { |
| | prefix = strings.TrimSpace(prefix) |
| | if prefix == "" || prefix == "/" { |
| | return "/" |
| | } |
| | if prefix[0] != '/' { |
| | if prefix[len(prefix)-1] == '/' { |
| | return "/" + prefix[:len(prefix)-1] |
| | } |
| | return "/" + prefix |
| | } |
| | if prefix[len(prefix)-1] == '/' { |
| | return prefix[:len(prefix)-1] |
| | } |
| | return prefix |
| | } |
| |
|
| | |
| | func join(prefix, suffix string) string { |
| | if prefix == "/" { |
| | return suffix |
| | } |
| | if suffix == "/" { |
| | return prefix |
| | } |
| | return prefix + suffix |
| | } |
| |
|