github.com/kotovmak/go-admin@v1.1.1/context/context.go (about)

     1  // Copyright 2019 GoAdmin Core Team. All rights reserved.
     2  // Use of this source code is governed by a Apache-2.0 style
     3  // license that can be found in the LICENSE file.
     4  
     5  package context
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"math"
    15  	"net"
    16  	"net/http"
    17  	"net/url"
    18  	"os"
    19  	"path"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/kotovmak/go-admin/modules/constant"
    24  )
    25  
    26  const abortIndex int8 = math.MaxInt8 / 2
    27  
    28  // Context is the simplify version of web framework context.
    29  // But it is important which will be used in plugins to custom
    30  // the request and response. And adapter will help to transform
    31  // the Context to the web framework`s context. It has three attributes.
    32  // Request and response are belongs to net/http package. UserValue
    33  // is the custom key-value store of context.
    34  type Context struct {
    35  	Request   *http.Request
    36  	Response  *http.Response
    37  	UserValue map[string]interface{}
    38  	index     int8
    39  	handlers  Handlers
    40  }
    41  
    42  // Path is used in the matching of request and response. Url stores the
    43  // raw register url. RegUrl contains the wildcard which on behalf of
    44  // the route params.
    45  type Path struct {
    46  	URL    string
    47  	Method string
    48  }
    49  
    50  type RouterMap map[string]Router
    51  
    52  func (r RouterMap) Get(name string) Router {
    53  	return r[name]
    54  }
    55  
    56  type Router struct {
    57  	Methods []string
    58  	Patten  string
    59  }
    60  
    61  func (r Router) Method() string {
    62  	return r.Methods[0]
    63  }
    64  
    65  func (r Router) GetURL(value ...string) string {
    66  	u := r.Patten
    67  	for i := 0; i < len(value); i += 2 {
    68  		u = strings.ReplaceAll(u, ":__"+value[i], value[i+1])
    69  	}
    70  	return u
    71  }
    72  
    73  type NodeProcessor func(...Node)
    74  
    75  type Node struct {
    76  	Path     string
    77  	Method   string
    78  	Handlers []Handler
    79  	Value    map[string]interface{}
    80  }
    81  
    82  // SetUserValue set the value of user context.
    83  func (ctx *Context) SetUserValue(key string, value interface{}) {
    84  	ctx.UserValue[key] = value
    85  }
    86  
    87  // Path return the url path.
    88  func (ctx *Context) Path() string {
    89  	return ctx.Request.URL.Path
    90  }
    91  
    92  // Abort abort the context.
    93  func (ctx *Context) Abort() {
    94  	ctx.index = abortIndex
    95  }
    96  
    97  // Next should be used only inside middleware.
    98  func (ctx *Context) Next() {
    99  	ctx.index++
   100  	for s := int8(len(ctx.handlers)); ctx.index < s; ctx.index++ {
   101  		ctx.handlers[ctx.index](ctx)
   102  	}
   103  }
   104  
   105  // SetHandlers set the handlers of Context.
   106  func (ctx *Context) SetHandlers(handlers Handlers) *Context {
   107  	ctx.handlers = handlers
   108  	return ctx
   109  }
   110  
   111  // Method return the request method.
   112  func (ctx *Context) Method() string {
   113  	return ctx.Request.Method
   114  }
   115  
   116  // NewContext used in adapter which return a Context with request
   117  // and slice of UserValue and a default Response.
   118  func NewContext(req *http.Request) *Context {
   119  
   120  	return &Context{
   121  		Request:   req,
   122  		UserValue: make(map[string]interface{}),
   123  		Response: &http.Response{
   124  			StatusCode: http.StatusOK,
   125  			Header:     make(http.Header),
   126  		},
   127  		index: -1,
   128  	}
   129  }
   130  
   131  const (
   132  	HeaderContentType = "Content-Type"
   133  
   134  	HeaderLastModified    = "Last-Modified"
   135  	HeaderIfModifiedSince = "If-Modified-Since"
   136  	HeaderCacheControl    = "Cache-Control"
   137  	HeaderETag            = "ETag"
   138  
   139  	HeaderContentDisposition = "Content-Disposition"
   140  	HeaderContentLength      = "Content-Length"
   141  	HeaderContentEncoding    = "Content-Encoding"
   142  
   143  	GzipHeaderValue      = "gzip"
   144  	HeaderAcceptEncoding = "Accept-Encoding"
   145  	HeaderVary           = "Vary"
   146  )
   147  
   148  func (ctx *Context) BindJSON(data interface{}) error {
   149  	if ctx.Request.Body != nil {
   150  		b, err := ioutil.ReadAll(ctx.Request.Body)
   151  		if err == nil {
   152  			return json.Unmarshal(b, data)
   153  		}
   154  		return err
   155  	}
   156  	return errors.New("empty request body")
   157  }
   158  
   159  func (ctx *Context) MustBindJSON(data interface{}) {
   160  	if ctx.Request.Body != nil {
   161  		b, err := ioutil.ReadAll(ctx.Request.Body)
   162  		if err != nil {
   163  			panic(err)
   164  		}
   165  		err = json.Unmarshal(b, data)
   166  		if err != nil {
   167  			panic(err)
   168  		}
   169  	}
   170  	panic("empty request body")
   171  }
   172  
   173  // Write save the given status code, headers and body string into the response.
   174  func (ctx *Context) Write(code int, header map[string]string, Body string) {
   175  	ctx.Response.StatusCode = code
   176  	for key, head := range header {
   177  		ctx.AddHeader(key, head)
   178  	}
   179  	ctx.Response.Body = ioutil.NopCloser(strings.NewReader(Body))
   180  }
   181  
   182  // JSON serializes the given struct as JSON into the response body.
   183  // It also sets the Content-Type as "application/json".
   184  func (ctx *Context) JSON(code int, Body map[string]interface{}) {
   185  	ctx.Response.StatusCode = code
   186  	ctx.SetContentType("application/json")
   187  	BodyStr, err := json.Marshal(Body)
   188  	if err != nil {
   189  		panic(err)
   190  	}
   191  	ctx.Response.Body = ioutil.NopCloser(bytes.NewReader(BodyStr))
   192  }
   193  
   194  // DataWithHeaders save the given status code, headers and body data into the response.
   195  func (ctx *Context) DataWithHeaders(code int, header map[string]string, data []byte) {
   196  	ctx.Response.StatusCode = code
   197  	for key, head := range header {
   198  		ctx.AddHeader(key, head)
   199  	}
   200  	ctx.Response.Body = ioutil.NopCloser(bytes.NewBuffer(data))
   201  }
   202  
   203  // Data writes some data into the body stream and updates the HTTP code.
   204  func (ctx *Context) Data(code int, contentType string, data []byte) {
   205  	ctx.Response.StatusCode = code
   206  	ctx.SetContentType(contentType)
   207  	ctx.Response.Body = ioutil.NopCloser(bytes.NewBuffer(data))
   208  }
   209  
   210  // Redirect add redirect url to header.
   211  func (ctx *Context) Redirect(path string) {
   212  	ctx.Response.StatusCode = http.StatusFound
   213  	ctx.SetContentType("text/html; charset=utf-8")
   214  	ctx.AddHeader("Location", path)
   215  }
   216  
   217  // HTML output html response.
   218  func (ctx *Context) HTML(code int, body string) {
   219  	ctx.SetContentType("text/html; charset=utf-8")
   220  	ctx.SetStatusCode(code)
   221  	ctx.WriteString(body)
   222  }
   223  
   224  // HTMLByte output html response.
   225  func (ctx *Context) HTMLByte(code int, body []byte) {
   226  	ctx.SetContentType("text/html; charset=utf-8")
   227  	ctx.SetStatusCode(code)
   228  	ctx.Response.Body = ioutil.NopCloser(bytes.NewBuffer(body))
   229  }
   230  
   231  // WriteString save the given body string into the response.
   232  func (ctx *Context) WriteString(body string) {
   233  	ctx.Response.Body = ioutil.NopCloser(strings.NewReader(body))
   234  }
   235  
   236  // SetStatusCode save the given status code into the response.
   237  func (ctx *Context) SetStatusCode(code int) {
   238  	ctx.Response.StatusCode = code
   239  }
   240  
   241  // SetContentType save the given content type header into the response header.
   242  func (ctx *Context) SetContentType(contentType string) {
   243  	ctx.AddHeader(HeaderContentType, contentType)
   244  }
   245  
   246  func (ctx *Context) SetLastModified(modtime time.Time) {
   247  	if !IsZeroTime(modtime) {
   248  		ctx.AddHeader(HeaderLastModified, modtime.UTC().Format(http.TimeFormat)) // or modtime.UTC()?
   249  	}
   250  }
   251  
   252  var unixEpochTime = time.Unix(0, 0)
   253  
   254  // IsZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
   255  func IsZeroTime(t time.Time) bool {
   256  	return t.IsZero() || t.Equal(unixEpochTime)
   257  }
   258  
   259  // ParseTime parses a time header (such as the Date: header),
   260  // trying each forth formats
   261  // that are allowed by HTTP/1.1:
   262  // time.RFC850, and time.ANSIC.
   263  var ParseTime = func(text string) (t time.Time, err error) {
   264  	t, err = time.Parse(http.TimeFormat, text)
   265  	if err != nil {
   266  		return http.ParseTime(text)
   267  	}
   268  
   269  	return
   270  }
   271  
   272  func (ctx *Context) WriteNotModified() {
   273  	// RFC 7232 section 4.1:
   274  	// a sender SHOULD NOT generate representation metadata other than the
   275  	// above listed fields unless said metadata exists for the purpose of
   276  	// guiding cache updates (e.g.," Last-Modified" might be useful if the
   277  	// response does not have an ETag field).
   278  	delete(ctx.Response.Header, HeaderContentType)
   279  	delete(ctx.Response.Header, HeaderContentLength)
   280  	if ctx.Headers(HeaderETag) != "" {
   281  		delete(ctx.Response.Header, HeaderLastModified)
   282  	}
   283  	ctx.SetStatusCode(http.StatusNotModified)
   284  }
   285  
   286  func (ctx *Context) CheckIfModifiedSince(modtime time.Time) (bool, error) {
   287  	if method := ctx.Method(); method != http.MethodGet && method != http.MethodHead {
   288  		return false, errors.New("skip: method")
   289  	}
   290  	ims := ctx.Headers(HeaderIfModifiedSince)
   291  	if ims == "" || IsZeroTime(modtime) {
   292  		return false, errors.New("skip: zero time")
   293  	}
   294  	t, err := ParseTime(ims)
   295  	if err != nil {
   296  		return false, errors.New("skip: " + err.Error())
   297  	}
   298  	// sub-second precision, so
   299  	// use mtime < t+1s instead of mtime <= t to check for unmodified.
   300  	if modtime.UTC().Before(t.Add(1 * time.Second)) {
   301  		return false, nil
   302  	}
   303  	return true, nil
   304  }
   305  
   306  // LocalIP return the request client ip.
   307  func (ctx *Context) LocalIP() string {
   308  	xForwardedFor := ctx.Request.Header.Get("X-Forwarded-For")
   309  	ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0])
   310  	if ip != "" {
   311  		return ip
   312  	}
   313  
   314  	ip = strings.TrimSpace(ctx.Request.Header.Get("X-Real-Ip"))
   315  	if ip != "" {
   316  		return ip
   317  	}
   318  
   319  	if ip, _, err := net.SplitHostPort(strings.TrimSpace(ctx.Request.RemoteAddr)); err == nil {
   320  		return ip
   321  	}
   322  
   323  	return "127.0.0.1"
   324  }
   325  
   326  // SetCookie save the given cookie obj into the response Set-Cookie header.
   327  func (ctx *Context) SetCookie(cookie *http.Cookie) {
   328  	if v := cookie.String(); v != "" {
   329  		ctx.AddHeader("Set-Cookie", v)
   330  	}
   331  }
   332  
   333  // Query get the query parameter of url.
   334  func (ctx *Context) Query(key string) string {
   335  	return ctx.Request.URL.Query().Get(key)
   336  }
   337  
   338  // QueryAll get the query parameters of url.
   339  func (ctx *Context) QueryAll(key string) []string {
   340  	return ctx.Request.URL.Query()[key]
   341  }
   342  
   343  // QueryDefault get the query parameter of url. If it is empty, return the default.
   344  func (ctx *Context) QueryDefault(key, def string) string {
   345  	value := ctx.Query(key)
   346  	if value == "" {
   347  		return def
   348  	}
   349  	return value
   350  }
   351  
   352  // Lang get the query parameter of url with given key __ga_lang.
   353  func (ctx *Context) Lang() string {
   354  	return ctx.Query("__ga_lang")
   355  }
   356  
   357  // Headers get the value of request headers key.
   358  func (ctx *Context) Headers(key string) string {
   359  	return ctx.Request.Header.Get(key)
   360  }
   361  
   362  // Referer get the url string of request header Referer.
   363  func (ctx *Context) Referer() string {
   364  	return ctx.Headers("Referer")
   365  }
   366  
   367  // RefererURL get the url.URL object of request header Referer.
   368  func (ctx *Context) RefererURL() *url.URL {
   369  	ref := ctx.Headers("Referer")
   370  	if ref == "" {
   371  		return nil
   372  	}
   373  	u, err := url.Parse(ref)
   374  	if err != nil {
   375  		return nil
   376  	}
   377  	return u
   378  }
   379  
   380  // RefererQuery retrieve the value of given key from url.URL object of request header Referer.
   381  func (ctx *Context) RefererQuery(key string) string {
   382  	if u := ctx.RefererURL(); u != nil {
   383  		return u.Query().Get(key)
   384  	}
   385  	return ""
   386  }
   387  
   388  // FormValue get the value of request form key.
   389  func (ctx *Context) FormValue(key string) string {
   390  	return ctx.Request.FormValue(key)
   391  }
   392  
   393  // PostForm get the values of request form.
   394  func (ctx *Context) PostForm() url.Values {
   395  	_ = ctx.Request.ParseMultipartForm(32 << 20)
   396  	return ctx.Request.PostForm
   397  }
   398  
   399  func (ctx *Context) WantHTML() bool {
   400  	return ctx.Method() == "GET" && strings.Contains(ctx.Headers("Accept"), "html")
   401  }
   402  
   403  func (ctx *Context) WantJSON() bool {
   404  	return strings.Contains(ctx.Headers("Accept"), "json")
   405  }
   406  
   407  // AddHeader adds the key, value pair to the header.
   408  func (ctx *Context) AddHeader(key, value string) {
   409  	ctx.Response.Header.Add(key, value)
   410  }
   411  
   412  // PjaxUrl add pjax url header.
   413  func (ctx *Context) PjaxUrl(url string) {
   414  	ctx.Response.Header.Add(constant.PjaxUrlHeader, url)
   415  }
   416  
   417  // IsPjax check request is pjax or not.
   418  func (ctx *Context) IsPjax() bool {
   419  	return ctx.Headers(constant.PjaxHeader) == "true"
   420  }
   421  
   422  // IsIframe check request is iframe or not.
   423  func (ctx *Context) IsIframe() bool {
   424  	return ctx.Query(constant.IframeKey) == "true" || ctx.Headers(constant.IframeKey) == "true"
   425  }
   426  
   427  // SetHeader set the key, value pair to the header.
   428  func (ctx *Context) SetHeader(key, value string) {
   429  	ctx.Response.Header.Set(key, value)
   430  }
   431  
   432  func (ctx *Context) GetContentType() string {
   433  	return ctx.Request.Header.Get("Content-Type")
   434  }
   435  
   436  func (ctx *Context) Cookie(name string) string {
   437  	for _, ck := range ctx.Request.Cookies() {
   438  		if ck.Name == name {
   439  			return ck.Value
   440  		}
   441  	}
   442  	return ""
   443  }
   444  
   445  // User return the current login user.
   446  func (ctx *Context) User() interface{} {
   447  	return ctx.UserValue["user"]
   448  }
   449  
   450  // ServeContent serves content, headers are autoset
   451  // receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string)
   452  //
   453  // You can define your own "Content-Type" header also, after this function call
   454  // Doesn't implements resuming (by range), use ctx.SendFile instead
   455  func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error {
   456  	if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil {
   457  		ctx.WriteNotModified()
   458  		return nil
   459  	}
   460  
   461  	if ctx.GetContentType() == "" {
   462  		ctx.SetContentType(filename)
   463  	}
   464  
   465  	buf, _ := ioutil.ReadAll(content)
   466  	ctx.Response.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
   467  	return nil
   468  }
   469  
   470  // ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
   471  func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
   472  	f, err := os.Open(filename)
   473  	if err != nil {
   474  		return fmt.Errorf("%d", http.StatusNotFound)
   475  	}
   476  	defer func() {
   477  		_ = f.Close()
   478  	}()
   479  	fi, _ := f.Stat()
   480  	if fi.IsDir() {
   481  		return ctx.ServeFile(path.Join(filename, "index.html"), gzipCompression)
   482  	}
   483  
   484  	return ctx.ServeContent(f, fi.Name(), fi.ModTime(), gzipCompression)
   485  }
   486  
   487  type HandlerMap map[Path]Handlers
   488  
   489  // App is the key struct of the package. App as a member of plugin
   490  // entity contains the request and the corresponding handler. Prefix
   491  // is the url prefix and MiddlewareList is for control flow.
   492  type App struct {
   493  	Requests    []Path
   494  	Handlers    HandlerMap
   495  	Middlewares Handlers
   496  	Prefix      string
   497  
   498  	Routers    RouterMap
   499  	routeIndex int
   500  	routeANY   bool
   501  }
   502  
   503  // NewApp return an empty app.
   504  func NewApp() *App {
   505  	return &App{
   506  		Requests:    make([]Path, 0),
   507  		Handlers:    make(HandlerMap),
   508  		Prefix:      "/",
   509  		Middlewares: make([]Handler, 0),
   510  		routeIndex:  -1,
   511  		Routers:     make(RouterMap),
   512  	}
   513  }
   514  
   515  // Handler defines the handler used by the middleware as return value.
   516  type Handler func(ctx *Context)
   517  
   518  // Handlers is the array of Handler
   519  type Handlers []Handler
   520  
   521  // AppendReqAndResp stores the request info and handle into app.
   522  // support the route parameter. The route parameter will be recognized as
   523  // wildcard store into the RegUrl of Path struct. For example:
   524  //
   525  //	/user/:id      => /user/(.*)
   526  //	/user/:id/info => /user/(.*?)/info
   527  //
   528  // The RegUrl will be used to recognize the incoming path and find
   529  // the handler.
   530  func (app *App) AppendReqAndResp(url, method string, handler []Handler) {
   531  
   532  	app.Requests = append(app.Requests, Path{
   533  		URL:    join(app.Prefix, url),
   534  		Method: method,
   535  	})
   536  	app.routeIndex++
   537  
   538  	app.Handlers[Path{
   539  		URL:    join(app.Prefix, url),
   540  		Method: method,
   541  	}] = append(app.Middlewares, handler...)
   542  }
   543  
   544  // Find is public helper method for findPath of tree.
   545  func (app *App) Find(url, method string) []Handler {
   546  	app.routeANY = false
   547  	return app.Handlers[Path{URL: url, Method: method}]
   548  }
   549  
   550  // POST is a shortcut for app.AppendReqAndResp(url, "post", handler).
   551  func (app *App) POST(url string, handler ...Handler) *App {
   552  	app.routeANY = false
   553  	app.AppendReqAndResp(url, "post", handler)
   554  	return app
   555  }
   556  
   557  // GET is a shortcut for app.AppendReqAndResp(url, "get", handler).
   558  func (app *App) GET(url string, handler ...Handler) *App {
   559  	app.routeANY = false
   560  	app.AppendReqAndResp(url, "get", handler)
   561  	return app
   562  }
   563  
   564  // DELETE is a shortcut for app.AppendReqAndResp(url, "delete", handler).
   565  func (app *App) DELETE(url string, handler ...Handler) *App {
   566  	app.routeANY = false
   567  	app.AppendReqAndResp(url, "delete", handler)
   568  	return app
   569  }
   570  
   571  // PUT is a shortcut for app.AppendReqAndResp(url, "put", handler).
   572  func (app *App) PUT(url string, handler ...Handler) *App {
   573  	app.routeANY = false
   574  	app.AppendReqAndResp(url, "put", handler)
   575  	return app
   576  }
   577  
   578  // OPTIONS is a shortcut for app.AppendReqAndResp(url, "options", handler).
   579  func (app *App) OPTIONS(url string, handler ...Handler) *App {
   580  	app.routeANY = false
   581  	app.AppendReqAndResp(url, "options", handler)
   582  	return app
   583  }
   584  
   585  // HEAD is a shortcut for app.AppendReqAndResp(url, "head", handler).
   586  func (app *App) HEAD(url string, handler ...Handler) *App {
   587  	app.routeANY = false
   588  	app.AppendReqAndResp(url, "head", handler)
   589  	return app
   590  }
   591  
   592  // ANY registers a route that matches all the HTTP methods.
   593  // GET, POST, PUT, HEAD, OPTIONS, DELETE.
   594  func (app *App) ANY(url string, handler ...Handler) *App {
   595  	app.routeANY = true
   596  	app.AppendReqAndResp(url, "post", handler)
   597  	app.AppendReqAndResp(url, "get", handler)
   598  	app.AppendReqAndResp(url, "delete", handler)
   599  	app.AppendReqAndResp(url, "put", handler)
   600  	app.AppendReqAndResp(url, "options", handler)
   601  	app.AppendReqAndResp(url, "head", handler)
   602  	return app
   603  }
   604  
   605  func (app *App) Name(name string) {
   606  	if app.routeANY {
   607  		app.Routers[name] = Router{
   608  			Methods: []string{"POST", "GET", "DELETE", "PUT", "OPTIONS", "HEAD"},
   609  			Patten:  app.Requests[app.routeIndex].URL,
   610  		}
   611  	} else {
   612  		app.Routers[name] = Router{
   613  			Methods: []string{app.Requests[app.routeIndex].Method},
   614  			Patten:  app.Requests[app.routeIndex].URL,
   615  		}
   616  	}
   617  }
   618  
   619  // Group add middlewares and prefix for App.
   620  func (app *App) Group(prefix string, middleware ...Handler) *RouterGroup {
   621  	return &RouterGroup{
   622  		app:         app,
   623  		Middlewares: append(app.Middlewares, middleware...),
   624  		Prefix:      slash(prefix),
   625  	}
   626  }
   627  
   628  // RouterGroup is a group of routes.
   629  type RouterGroup struct {
   630  	app         *App
   631  	Middlewares Handlers
   632  	Prefix      string
   633  }
   634  
   635  // AppendReqAndResp stores the request info and handle into app.
   636  // support the route parameter. The route parameter will be recognized as
   637  // wildcard store into the RegUrl of Path struct. For example:
   638  //
   639  //	/user/:id      => /user/(.*)
   640  //	/user/:id/info => /user/(.*?)/info
   641  //
   642  // The RegUrl will be used to recognize the incoming path and find
   643  // the handler.
   644  func (g *RouterGroup) AppendReqAndResp(url, method string, handler []Handler) {
   645  
   646  	g.app.Requests = append(g.app.Requests, Path{
   647  		URL:    join(g.Prefix, url),
   648  		Method: method,
   649  	})
   650  	g.app.routeIndex++
   651  
   652  	var h = make([]Handler, len(g.Middlewares))
   653  	copy(h, g.Middlewares)
   654  
   655  	g.app.Handlers[Path{
   656  		URL:    join(g.Prefix, url),
   657  		Method: method,
   658  	}] = append(h, handler...)
   659  }
   660  
   661  // POST is a shortcut for app.AppendReqAndResp(url, "post", handler).
   662  func (g *RouterGroup) POST(url string, handler ...Handler) *RouterGroup {
   663  	g.app.routeANY = false
   664  	g.AppendReqAndResp(url, "post", handler)
   665  	return g
   666  }
   667  
   668  // GET is a shortcut for app.AppendReqAndResp(url, "get", handler).
   669  func (g *RouterGroup) GET(url string, handler ...Handler) *RouterGroup {
   670  	g.app.routeANY = false
   671  	g.AppendReqAndResp(url, "get", handler)
   672  	return g
   673  }
   674  
   675  // DELETE is a shortcut for app.AppendReqAndResp(url, "delete", handler).
   676  func (g *RouterGroup) DELETE(url string, handler ...Handler) *RouterGroup {
   677  	g.app.routeANY = false
   678  	g.AppendReqAndResp(url, "delete", handler)
   679  	return g
   680  }
   681  
   682  // PUT is a shortcut for app.AppendReqAndResp(url, "put", handler).
   683  func (g *RouterGroup) PUT(url string, handler ...Handler) *RouterGroup {
   684  	g.app.routeANY = false
   685  	g.AppendReqAndResp(url, "put", handler)
   686  	return g
   687  }
   688  
   689  // OPTIONS is a shortcut for app.AppendReqAndResp(url, "options", handler).
   690  func (g *RouterGroup) OPTIONS(url string, handler ...Handler) *RouterGroup {
   691  	g.app.routeANY = false
   692  	g.AppendReqAndResp(url, "options", handler)
   693  	return g
   694  }
   695  
   696  // HEAD is a shortcut for app.AppendReqAndResp(url, "head", handler).
   697  func (g *RouterGroup) HEAD(url string, handler ...Handler) *RouterGroup {
   698  	g.app.routeANY = false
   699  	g.AppendReqAndResp(url, "head", handler)
   700  	return g
   701  }
   702  
   703  // ANY registers a route that matches all the HTTP methods.
   704  // GET, POST, PUT, HEAD, OPTIONS, DELETE.
   705  func (g *RouterGroup) ANY(url string, handler ...Handler) *RouterGroup {
   706  	g.app.routeANY = true
   707  	g.AppendReqAndResp(url, "post", handler)
   708  	g.AppendReqAndResp(url, "get", handler)
   709  	g.AppendReqAndResp(url, "delete", handler)
   710  	g.AppendReqAndResp(url, "put", handler)
   711  	g.AppendReqAndResp(url, "options", handler)
   712  	g.AppendReqAndResp(url, "head", handler)
   713  	return g
   714  }
   715  
   716  func (g *RouterGroup) Name(name string) {
   717  	g.app.Name(name)
   718  }
   719  
   720  // Group add middlewares and prefix for RouterGroup.
   721  func (g *RouterGroup) Group(prefix string, middleware ...Handler) *RouterGroup {
   722  	return &RouterGroup{
   723  		app:         g.app,
   724  		Middlewares: append(g.Middlewares, middleware...),
   725  		Prefix:      join(slash(g.Prefix), slash(prefix)),
   726  	}
   727  }
   728  
   729  // slash fix the path which has wrong format problem.
   730  //
   731  //	""      => "/"
   732  //	"abc/"  => "/abc"
   733  //	"/abc/" => "/abc"
   734  //	"/abc"  => "/abc"
   735  //	"/"     => "/"
   736  func slash(prefix string) string {
   737  	prefix = strings.TrimSpace(prefix)
   738  	if prefix == "" || prefix == "/" {
   739  		return "/"
   740  	}
   741  	if prefix[0] != '/' {
   742  		if prefix[len(prefix)-1] == '/' {
   743  			return "/" + prefix[:len(prefix)-1]
   744  		}
   745  		return "/" + prefix
   746  	}
   747  	if prefix[len(prefix)-1] == '/' {
   748  		return prefix[:len(prefix)-1]
   749  	}
   750  	return prefix
   751  }
   752  
   753  // join join the path.
   754  func join(prefix, suffix string) string {
   755  	if prefix == "/" {
   756  		return suffix
   757  	}
   758  	if suffix == "/" {
   759  		return prefix
   760  	}
   761  	return prefix + suffix
   762  }