Compare commits
	
		
			15 Commits
		
	
	
		
			v0.0.2
			...
			7cf7ec32ac
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7cf7ec32ac | ||
| 
						 | 
					039f4cf8c0 | ||
| 
						 | 
					137d4ee5c8 | ||
| 
						 | 
					de3ce47671 | ||
| 
						 | 
					1c9c21e294 | ||
| 
						 | 
					9dcf2f8e28 | ||
| 
						 | 
					083b91bfaa | ||
| 
						 | 
					d2d90e6ffd | ||
| 
						 | 
					79e94dfd21 | ||
| 
						 | 
					7b62a82b42 | ||
| 
						 | 
					7057e232e6 | ||
| 
						 | 
					8f4132f131 | ||
| 
						 | 
					340239fdd9 | ||
| 
						 | 
					53ed37a218 | ||
| 
						 | 
					286f010346 | 
							
								
								
									
										234
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										234
									
								
								app.go
									
									
									
									
									
								
							@@ -5,50 +5,82 @@ import (
 | 
				
			|||||||
	"crypto/tls"
 | 
						"crypto/tls"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"github.com/loveuer/nf/internal/bytesconv"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"strings"
 | 
						"path"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						_ IRouter = (*App)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						regSafePrefix         = regexp.MustCompile("[^a-zA-Z0-9/-]+")
 | 
				
			||||||
 | 
						regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mimePlain = []string{"text/plain"}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type App struct {
 | 
					type App struct {
 | 
				
			||||||
	*RouterGroup
 | 
						RouterGroup
 | 
				
			||||||
	config *Config
 | 
						config *Config
 | 
				
			||||||
	router *router
 | 
					 | 
				
			||||||
	groups []*RouterGroup
 | 
						groups []*RouterGroup
 | 
				
			||||||
	server *http.Server
 | 
						server *http.Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						trees methodTrees
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						maxParams   uint16
 | 
				
			||||||
 | 
						maxSections uint16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						redirectTrailingSlash  bool // true
 | 
				
			||||||
 | 
						redirectFixedPath      bool // false
 | 
				
			||||||
 | 
						handleMethodNotAllowed bool // false
 | 
				
			||||||
 | 
						useRawPath             bool // false
 | 
				
			||||||
 | 
						unescapePathValues     bool // true
 | 
				
			||||||
 | 
						removeExtraSlash       bool // false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
 | 
					func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
 | 
				
			||||||
	c := newContext(a, writer, request)
 | 
						var (
 | 
				
			||||||
 | 
							err error
 | 
				
			||||||
 | 
							c   = newContext(a, writer, request)
 | 
				
			||||||
 | 
							nfe = new(Err)
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, group := range a.groups {
 | 
						if err = c.verify(); err != nil {
 | 
				
			||||||
		if strings.HasPrefix(request.URL.Path, group.prefix) {
 | 
							if errors.As(err, nfe) {
 | 
				
			||||||
			c.handlers = append(c.handlers, group.middlewares...)
 | 
								_ = c.Status(nfe.Status).SendString(nfe.Msg)
 | 
				
			||||||
		}
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := a.router.handle(c); err != nil {
 | 
							_ = c.Status(500).SendString(err.Error())
 | 
				
			||||||
		var ne = &Err{}
 | 
							return
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if errors.As(err, ne) {
 | 
					 | 
				
			||||||
			writer.WriteHeader(ne.Status)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			writer.WriteHeader(500)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_, _ = writer.Write([]byte(err.Error()))
 | 
						a.handleHTTPRequest(c)
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *App) run(ln net.Listener) error {
 | 
					func (a *App) run(ln net.Listener) error {
 | 
				
			||||||
	srv := &http.Server{Handler: a}
 | 
						srv := &http.Server{Handler: a}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.config.DisableHttpErrorLog {
 | 
				
			||||||
 | 
							srv.ErrorLog = log.New(io.Discard, "", 0)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	a.server = srv
 | 
						a.server = srv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !a.config.DisableBanner {
 | 
						if !a.config.DisableBanner {
 | 
				
			||||||
		fmt.Println(banner + "nf serve at: " + ln.Addr().String() + "\n")
 | 
							fmt.Println(banner + "nf serve at: " + ln.Addr().String() + "\n")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return a.server.Serve(ln)
 | 
						err := a.server.Serve(ln)
 | 
				
			||||||
 | 
						if !errors.Is(err, http.ErrServerClosed) || a.config.ErrServeClose {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *App) Run(address string) error {
 | 
					func (a *App) Run(address string) error {
 | 
				
			||||||
@@ -78,3 +110,169 @@ func (a *App) RunListener(ln net.Listener) error {
 | 
				
			|||||||
func (a *App) Shutdown(ctx context.Context) error {
 | 
					func (a *App) Shutdown(ctx context.Context) error {
 | 
				
			||||||
	return a.server.Shutdown(ctx)
 | 
						return a.server.Shutdown(ctx)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) addRoute(method, path string, handlers ...HandlerFunc) {
 | 
				
			||||||
 | 
						elsePanic(path[0] == '/', "path must begin with '/'")
 | 
				
			||||||
 | 
						elsePanic(method != "", "HTTP method can not be empty")
 | 
				
			||||||
 | 
						elsePanic(len(handlers) > 0, "without enable not implement, there must be at least one handler")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !a.config.DisableMessagePrint {
 | 
				
			||||||
 | 
							fmt.Printf("[NF] Add Route: %-8s - %-25s (%2d handlers)\n", method, path, len(handlers))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						root := a.trees.get(method)
 | 
				
			||||||
 | 
						if root == nil {
 | 
				
			||||||
 | 
							root = new(node)
 | 
				
			||||||
 | 
							root.fullPath = "/"
 | 
				
			||||||
 | 
							a.trees = append(a.trees, methodTree{method: method, root: root})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						root.addRoute(path, handlers...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if paramsCount := countParams(path); paramsCount > a.maxParams {
 | 
				
			||||||
 | 
							a.maxParams = paramsCount
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if sectionsCount := countSections(path); sectionsCount > a.maxSections {
 | 
				
			||||||
 | 
							a.maxSections = sectionsCount
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *App) handleHTTPRequest(c *Ctx) {
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							err error
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						httpMethod := c.Request.Method
 | 
				
			||||||
 | 
						rPath := c.Request.URL.Path
 | 
				
			||||||
 | 
						unescape := false
 | 
				
			||||||
 | 
						if a.useRawPath && len(c.Request.URL.RawPath) > 0 {
 | 
				
			||||||
 | 
							rPath = c.Request.URL.RawPath
 | 
				
			||||||
 | 
							unescape = a.unescapePathValues
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.removeExtraSlash {
 | 
				
			||||||
 | 
							rPath = cleanPath(rPath)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Find root of the tree for the given HTTP method
 | 
				
			||||||
 | 
						t := a.trees
 | 
				
			||||||
 | 
						for i, tl := 0, len(t); i < tl; i++ {
 | 
				
			||||||
 | 
							if t[i].method != httpMethod {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							root := t[i].root
 | 
				
			||||||
 | 
							// Find route in tree
 | 
				
			||||||
 | 
							value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
 | 
				
			||||||
 | 
							if value.params != nil {
 | 
				
			||||||
 | 
								c.Params = *value.params
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if value.handlers != nil {
 | 
				
			||||||
 | 
								c.handlers = value.handlers
 | 
				
			||||||
 | 
								c.fullPath = value.fullPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err = c.Next(); err != nil {
 | 
				
			||||||
 | 
									serveError(c, errorHandler)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								c.writermem.WriteHeaderNow()
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if httpMethod != http.MethodConnect && rPath != "/" {
 | 
				
			||||||
 | 
								if value.tsr && a.redirectTrailingSlash {
 | 
				
			||||||
 | 
									redirectTrailingSlash(c)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if a.redirectFixedPath && redirectFixedPath(c, root, a.redirectFixedPath) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.handleMethodNotAllowed {
 | 
				
			||||||
 | 
							// According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response
 | 
				
			||||||
 | 
							// containing a list of the target resource's currently supported methods.
 | 
				
			||||||
 | 
							allowed := make([]string, 0, len(t)-1)
 | 
				
			||||||
 | 
							for _, tree := range a.trees {
 | 
				
			||||||
 | 
								if tree.method == httpMethod {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
 | 
				
			||||||
 | 
									allowed = append(allowed, tree.method)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(allowed) > 0 {
 | 
				
			||||||
 | 
								c.handlers = a.combineHandlers()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								serveError(c, a.config.MethodNotAllowedHandler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.handlers = a.combineHandlers()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						serveError(c, a.config.NotFoundHandler)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func errorHandler(c *Ctx) error {
 | 
				
			||||||
 | 
						return c.Status(500).SendString(_500)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func serveError(c *Ctx, handler HandlerFunc) {
 | 
				
			||||||
 | 
						err := c.Next()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.writermem.Written() {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_ = handler(c)
 | 
				
			||||||
 | 
						_ = err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func redirectTrailingSlash(c *Ctx) {
 | 
				
			||||||
 | 
						req := c.Request
 | 
				
			||||||
 | 
						p := req.URL.Path
 | 
				
			||||||
 | 
						if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
 | 
				
			||||||
 | 
							prefix = regSafePrefix.ReplaceAllString(prefix, "")
 | 
				
			||||||
 | 
							prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							p = prefix + "/" + req.URL.Path
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.URL.Path = p + "/"
 | 
				
			||||||
 | 
						if length := len(p); length > 1 && p[length-1] == '/' {
 | 
				
			||||||
 | 
							req.URL.Path = p[:length-1]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						redirectRequest(c)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func redirectFixedPath(c *Ctx, root *node, trailingSlash bool) bool {
 | 
				
			||||||
 | 
						req := c.Request
 | 
				
			||||||
 | 
						rPath := req.URL.Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
 | 
				
			||||||
 | 
							req.URL.Path = bytesconv.BytesToString(fixedPath)
 | 
				
			||||||
 | 
							redirectRequest(c)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func redirectRequest(c *Ctx) {
 | 
				
			||||||
 | 
						req := c.Request
 | 
				
			||||||
 | 
						//rPath := req.URL.Path
 | 
				
			||||||
 | 
						rURL := req.URL.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						code := http.StatusMovedPermanently // Permanent redirect, request with GET method
 | 
				
			||||||
 | 
						if req.Method != http.MethodGet {
 | 
				
			||||||
 | 
							code = http.StatusTemporaryRedirect
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						http.Redirect(c.writer, req, rURL, code)
 | 
				
			||||||
 | 
						c.writermem.WriteHeaderNow()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										111
									
								
								ctx.go
									
									
									
									
									
								
							
							
						
						
									
										111
									
								
								ctx.go
									
									
									
									
									
								
							@@ -5,7 +5,6 @@ import (
 | 
				
			|||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"mime/multipart"
 | 
						"mime/multipart"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
@@ -13,37 +12,57 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Ctx struct {
 | 
					type Ctx struct {
 | 
				
			||||||
 | 
						writermem responseWriter
 | 
				
			||||||
	// origin objects
 | 
						// origin objects
 | 
				
			||||||
	Writer  http.ResponseWriter
 | 
						writer  http.ResponseWriter
 | 
				
			||||||
	Request *http.Request
 | 
						Request *http.Request
 | 
				
			||||||
	// request info
 | 
						// request info
 | 
				
			||||||
	path   string
 | 
						path   string
 | 
				
			||||||
	Method string
 | 
						method string
 | 
				
			||||||
	// response info
 | 
						// response info
 | 
				
			||||||
	StatusCode int
 | 
						StatusCode int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	app          *App
 | 
						app          *App
 | 
				
			||||||
	params   map[string]string
 | 
						params       *Params
 | 
				
			||||||
	index        int
 | 
						index        int
 | 
				
			||||||
	handlers     []HandlerFunc
 | 
						handlers     []HandlerFunc
 | 
				
			||||||
	locals   map[string]any
 | 
						locals       map[string]interface{}
 | 
				
			||||||
 | 
						skippedNodes *[]skippedNode
 | 
				
			||||||
 | 
						fullPath     string
 | 
				
			||||||
 | 
						Params       Params
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ctx {
 | 
					func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ctx {
 | 
				
			||||||
	return &Ctx{
 | 
					
 | 
				
			||||||
		Writer:  writer,
 | 
						skippedNodes := make([]skippedNode, 0, app.maxSections)
 | 
				
			||||||
 | 
						v := make(Params, 0, app.maxParams)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx := &Ctx{
 | 
				
			||||||
 | 
							writer:     writer,
 | 
				
			||||||
 | 
							writermem:  responseWriter{},
 | 
				
			||||||
		Request:    request,
 | 
							Request:    request,
 | 
				
			||||||
		path:       request.URL.Path,
 | 
							path:       request.URL.Path,
 | 
				
			||||||
		Method:  request.Method,
 | 
							method:     request.Method,
 | 
				
			||||||
 | 
							StatusCode: 200,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		app:          app,
 | 
							app:          app,
 | 
				
			||||||
		index:        -1,
 | 
							index:        -1,
 | 
				
			||||||
		locals:   map[string]any{},
 | 
							locals:       map[string]interface{}{},
 | 
				
			||||||
		handlers:     make([]HandlerFunc, 0),
 | 
							handlers:     make([]HandlerFunc, 0),
 | 
				
			||||||
	}
 | 
							skippedNodes: &skippedNodes,
 | 
				
			||||||
 | 
							params:       &v,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) Locals(key string, value ...any) any {
 | 
						ctx.writermem = responseWriter{
 | 
				
			||||||
 | 
							ResponseWriter: ctx.writer,
 | 
				
			||||||
 | 
							size:           -1,
 | 
				
			||||||
 | 
							status:         0,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ctx
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Ctx) Locals(key string, value ...interface{}) interface{} {
 | 
				
			||||||
	data := c.locals[key]
 | 
						data := c.locals[key]
 | 
				
			||||||
	if len(value) > 0 {
 | 
						if len(value) > 0 {
 | 
				
			||||||
		c.locals[key] = value[0]
 | 
							c.locals[key] = value[0]
 | 
				
			||||||
@@ -52,6 +71,16 @@ func (c *Ctx) Locals(key string, value ...any) any {
 | 
				
			|||||||
	return data
 | 
						return data
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Ctx) Method(overWrite ...string) string {
 | 
				
			||||||
 | 
						method := c.Request.Method
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(overWrite) > 0 && overWrite[0] != "" {
 | 
				
			||||||
 | 
							c.Request.Method = overWrite[0]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return method
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) Path(overWrite ...string) string {
 | 
					func (c *Ctx) Path(overWrite ...string) string {
 | 
				
			||||||
	path := c.Request.URL.Path
 | 
						path := c.Request.URL.Path
 | 
				
			||||||
	if len(overWrite) > 0 && overWrite[0] != "" {
 | 
						if len(overWrite) > 0 && overWrite[0] != "" {
 | 
				
			||||||
@@ -61,15 +90,41 @@ func (c *Ctx) Path(overWrite ...string) string {
 | 
				
			|||||||
	return path
 | 
						return path
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) Next() error {
 | 
					func (c *Ctx) Next() error {
 | 
				
			||||||
	c.index++
 | 
						c.index++
 | 
				
			||||||
	s := len(c.handlers)
 | 
					
 | 
				
			||||||
	for ; c.index < s; c.index++ {
 | 
						var (
 | 
				
			||||||
		if err := c.handlers[c.index](c); err != nil {
 | 
							err     error
 | 
				
			||||||
 | 
							handler = c.handlers[c.index]
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//for c.index < len(c.handlers) {
 | 
				
			||||||
 | 
						if handler != nil {
 | 
				
			||||||
 | 
							if err = handler(c); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.index++
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,7 +142,7 @@ func (c *Ctx) verify() error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) Param(key string) string {
 | 
					func (c *Ctx) Param(key string) string {
 | 
				
			||||||
	return c.params[key]
 | 
						return c.Params.ByName(key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) Form(key string) string {
 | 
					func (c *Ctx) Form(key string) string {
 | 
				
			||||||
@@ -126,8 +181,6 @@ func (c *Ctx) BodyParser(out interface{}) error {
 | 
				
			|||||||
		ctype = strings.ToLower(c.Request.Header.Get("Content-Type"))
 | 
							ctype = strings.ToLower(c.Request.Header.Get("Content-Type"))
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Printf("BodyParser: Content-Type=%s", ctype)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ctype = parseVendorSpecificContentType(ctype)
 | 
						ctype = parseVendorSpecificContentType(ctype)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctypeEnd := strings.IndexByte(ctype, ';')
 | 
						ctypeEnd := strings.IndexByte(ctype, ';')
 | 
				
			||||||
@@ -138,9 +191,9 @@ func (c *Ctx) BodyParser(out interface{}) error {
 | 
				
			|||||||
	if strings.HasSuffix(ctype, "json") {
 | 
						if strings.HasSuffix(ctype, "json") {
 | 
				
			||||||
		bs, err := io.ReadAll(c.Request.Body)
 | 
							bs, err := io.ReadAll(c.Request.Body)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Printf("BodyParser: read all err=%v", err)
 | 
					 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							_ = c.Request.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		c.Request.Body = io.NopCloser(bytes.NewReader(bs))
 | 
							c.Request.Body = io.NopCloser(bytes.NewReader(bs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -181,17 +234,17 @@ func (c *Ctx) QueryParser(out interface{}) error {
 | 
				
			|||||||
=============================================================== */
 | 
					=============================================================== */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) Status(code int) *Ctx {
 | 
					func (c *Ctx) Status(code int) *Ctx {
 | 
				
			||||||
	c.StatusCode = code
 | 
						c.writermem.WriteHeader(code)
 | 
				
			||||||
	c.Writer.WriteHeader(code)
 | 
						c.StatusCode = c.writermem.status
 | 
				
			||||||
	return c
 | 
						return c
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) Set(key string, value string) {
 | 
					func (c *Ctx) Set(key string, value string) {
 | 
				
			||||||
	c.Writer.Header().Set(key, value)
 | 
						c.writermem.Header().Set(key, value)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) SetHeader(key string, value string) {
 | 
					func (c *Ctx) SetHeader(key string, value string) {
 | 
				
			||||||
	c.Writer.Header().Set(key, value)
 | 
						c.writermem.Header().Set(key, value)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) SendString(data string) error {
 | 
					func (c *Ctx) SendString(data string) error {
 | 
				
			||||||
@@ -202,13 +255,13 @@ func (c *Ctx) SendString(data string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) Writef(format string, values ...interface{}) (int, error) {
 | 
					func (c *Ctx) Writef(format string, values ...interface{}) (int, error) {
 | 
				
			||||||
	c.SetHeader("Content-Type", "text/plain")
 | 
						c.SetHeader("Content-Type", "text/plain")
 | 
				
			||||||
	return c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
 | 
						return c.writer.Write([]byte(fmt.Sprintf(format, values...)))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) JSON(data interface{}) error {
 | 
					func (c *Ctx) JSON(data interface{}) error {
 | 
				
			||||||
	c.SetHeader("Content-Type", "application/json")
 | 
						c.SetHeader("Content-Type", MIMEApplicationJSON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	encoder := json.NewEncoder(c.Writer)
 | 
						encoder := json.NewEncoder(&c.writermem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := encoder.Encode(data); err != nil {
 | 
						if err := encoder.Encode(data); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
@@ -217,12 +270,16 @@ func (c *Ctx) JSON(data interface{}) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Ctx) RawWriter() http.ResponseWriter {
 | 
				
			||||||
 | 
						return c.writer
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) Write(data []byte) (int, error) {
 | 
					func (c *Ctx) Write(data []byte) (int, error) {
 | 
				
			||||||
	return c.Writer.Write(data)
 | 
						return c.writermem.Write(data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Ctx) HTML(html string) error {
 | 
					func (c *Ctx) HTML(html string) error {
 | 
				
			||||||
	c.SetHeader("Content-Type", "text/html")
 | 
						c.SetHeader("Content-Type", "text/html")
 | 
				
			||||||
	_, err := c.Writer.Write([]byte(html))
 | 
						_, err := c.writer.Write([]byte(html))
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										61
									
								
								group.go
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								group.go
									
									
									
									
									
								
							@@ -1,61 +0,0 @@
 | 
				
			|||||||
package nf
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type RouterGroup struct {
 | 
					 | 
				
			||||||
	prefix      string
 | 
					 | 
				
			||||||
	middlewares []HandlerFunc // support middleware
 | 
					 | 
				
			||||||
	parent      *RouterGroup  // support nesting
 | 
					 | 
				
			||||||
	app         *App          // all groups share a Engine instance
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Group is defined to create a new RouterGroup
 | 
					 | 
				
			||||||
// remember all groups share the same Engine instance
 | 
					 | 
				
			||||||
func (group *RouterGroup) Group(prefix string) *RouterGroup {
 | 
					 | 
				
			||||||
	app := group.app
 | 
					 | 
				
			||||||
	newGroup := &RouterGroup{
 | 
					 | 
				
			||||||
		prefix: group.prefix + prefix,
 | 
					 | 
				
			||||||
		parent: group,
 | 
					 | 
				
			||||||
		app:    app,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	app.groups = append(app.groups, newGroup)
 | 
					 | 
				
			||||||
	return newGroup
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (group *RouterGroup) addRoute(method string, comp string, handlers ...HandlerFunc) {
 | 
					 | 
				
			||||||
	verifyHandlers(comp, handlers...)
 | 
					 | 
				
			||||||
	pattern := group.prefix + comp
 | 
					 | 
				
			||||||
	log.Printf("Add Route %4s - %s", method, pattern)
 | 
					 | 
				
			||||||
	group.app.router.addRoute(method, pattern, handlers...)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (group *RouterGroup) Get(pattern string, handlers ...HandlerFunc) {
 | 
					 | 
				
			||||||
	group.addRoute(http.MethodGet, pattern, handlers...)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (group *RouterGroup) Post(pattern string, handlers ...HandlerFunc) {
 | 
					 | 
				
			||||||
	group.addRoute(http.MethodPost, pattern, handlers...)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (group *RouterGroup) Put(pattern string, handlers ...HandlerFunc) {
 | 
					 | 
				
			||||||
	group.addRoute(http.MethodPut, pattern, handlers...)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (group *RouterGroup) Delete(pattern string, handlers ...HandlerFunc) {
 | 
					 | 
				
			||||||
	group.addRoute(http.MethodDelete, pattern, handlers...)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (group *RouterGroup) Patch(pattern string, handlers ...HandlerFunc) {
 | 
					 | 
				
			||||||
	group.addRoute(http.MethodPatch, pattern, handlers...)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (group *RouterGroup) Head(pattern string, handlers ...HandlerFunc) {
 | 
					 | 
				
			||||||
	group.addRoute(http.MethodHead, pattern, handlers...)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
 | 
					 | 
				
			||||||
	group.middlewares = append(group.middlewares, middlewares...)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,3 +1,9 @@
 | 
				
			|||||||
package nf
 | 
					package nf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type HandlerFunc func(*Ctx) error
 | 
					type HandlerFunc func(*Ctx) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ToDoHandler(c *Ctx) error {
 | 
				
			||||||
 | 
						return c.Status(501).SendString(fmt.Sprintf("%s - %s Not Implemented", c.Method(), c.Path()))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								internal/bytesconv/bytesconv_1.19.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								internal/bytesconv/bytesconv_1.19.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 Gin Core Team. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:build !go1.20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package bytesconv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"unsafe"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StringToBytes converts string to byte slice without a memory allocation.
 | 
				
			||||||
 | 
					func StringToBytes(s string) []byte {
 | 
				
			||||||
 | 
						return *(*[]byte)(unsafe.Pointer(
 | 
				
			||||||
 | 
							&struct {
 | 
				
			||||||
 | 
								string
 | 
				
			||||||
 | 
								Cap int
 | 
				
			||||||
 | 
							}{s, len(s)},
 | 
				
			||||||
 | 
						))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BytesToString converts byte slice to string without a memory allocation.
 | 
				
			||||||
 | 
					func BytesToString(b []byte) string {
 | 
				
			||||||
 | 
						return *(*string)(unsafe.Pointer(&b))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								internal/bytesconv/bytesconv_1.20.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								internal/bytesconv/bytesconv_1.20.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					// Copyright 2023 Gin Core Team. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:build go1.20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package bytesconv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"unsafe"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StringToBytes converts string to byte slice without a memory allocation.
 | 
				
			||||||
 | 
					// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
 | 
				
			||||||
 | 
					func StringToBytes(s string) []byte {
 | 
				
			||||||
 | 
						return unsafe.Slice(unsafe.StringData(s), len(s))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BytesToString converts byte slice to string without a memory allocation.
 | 
				
			||||||
 | 
					// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
 | 
				
			||||||
 | 
					func BytesToString(b []byte) string {
 | 
				
			||||||
 | 
						return unsafe.String(unsafe.SliceData(b), len(b))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										99
									
								
								internal/bytesconv/bytesconv_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								internal/bytesconv/bytesconv_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 Gin Core Team. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package bytesconv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"math/rand"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere."
 | 
				
			||||||
 | 
					var testBytes = []byte(testString)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func rawBytesToStr(b []byte) string {
 | 
				
			||||||
 | 
						return string(b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func rawStrToBytes(s string) []byte {
 | 
				
			||||||
 | 
						return []byte(s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// go test -v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestBytesToString(t *testing.T) {
 | 
				
			||||||
 | 
						data := make([]byte, 1024)
 | 
				
			||||||
 | 
						for i := 0; i < 100; i++ {
 | 
				
			||||||
 | 
							rand.Read(data)
 | 
				
			||||||
 | 
							if rawBytesToStr(data) != BytesToString(data) {
 | 
				
			||||||
 | 
								t.Fatal("don't match")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						letterIdxBits = 6                    // 6 bits to represent a letter index
 | 
				
			||||||
 | 
						letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
 | 
				
			||||||
 | 
						letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var src = rand.NewSource(time.Now().UnixNano())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func RandStringBytesMaskImprSrcSB(n int) string {
 | 
				
			||||||
 | 
						sb := strings.Builder{}
 | 
				
			||||||
 | 
						sb.Grow(n)
 | 
				
			||||||
 | 
						// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
 | 
				
			||||||
 | 
						for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
 | 
				
			||||||
 | 
							if remain == 0 {
 | 
				
			||||||
 | 
								cache, remain = src.Int63(), letterIdxMax
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
 | 
				
			||||||
 | 
								sb.WriteByte(letterBytes[idx])
 | 
				
			||||||
 | 
								i--
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							cache >>= letterIdxBits
 | 
				
			||||||
 | 
							remain--
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sb.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestStringToBytes(t *testing.T) {
 | 
				
			||||||
 | 
						for i := 0; i < 100; i++ {
 | 
				
			||||||
 | 
							s := RandStringBytesMaskImprSrcSB(64)
 | 
				
			||||||
 | 
							if !bytes.Equal(rawStrToBytes(s), StringToBytes(s)) {
 | 
				
			||||||
 | 
								t.Fatal("don't match")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BenchmarkBytesConvBytesToStrRaw(b *testing.B) {
 | 
				
			||||||
 | 
						for i := 0; i < b.N; i++ {
 | 
				
			||||||
 | 
							rawBytesToStr(testBytes)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BenchmarkBytesConvBytesToStr(b *testing.B) {
 | 
				
			||||||
 | 
						for i := 0; i < b.N; i++ {
 | 
				
			||||||
 | 
							BytesToString(testBytes)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BenchmarkBytesConvStrToBytesRaw(b *testing.B) {
 | 
				
			||||||
 | 
						for i := 0; i < b.N; i++ {
 | 
				
			||||||
 | 
							rawStrToBytes(testString)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BenchmarkBytesConvStrToBytes(b *testing.B) {
 | 
				
			||||||
 | 
						for i := 0; i < b.N; i++ {
 | 
				
			||||||
 | 
							StringToBytes(testString)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,8 +2,10 @@ package nf
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"runtime/debug"
 | 
						"runtime/debug"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewRecover(enableStackTrace bool) HandlerFunc {
 | 
					func NewRecover(enableStackTrace bool) HandlerFunc {
 | 
				
			||||||
@@ -15,9 +17,62 @@ func NewRecover(enableStackTrace bool) HandlerFunc {
 | 
				
			|||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					os.Stderr.WriteString(fmt.Sprintf("recovered from panic: %v\n", r))
 | 
										os.Stderr.WriteString(fmt.Sprintf("recovered from panic: %v\n", r))
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									//serveError(c, 500, []byte(fmt.Sprint(r)))
 | 
				
			||||||
 | 
									_ = c.Status(500).SendString(fmt.Sprint(r))
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}()
 | 
							}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return c.Next()
 | 
							return c.Next()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewLogger() HandlerFunc {
 | 
				
			||||||
 | 
						l := log.New(os.Stdout, "[NF] ", 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						durationFormat := func(num int64) string {
 | 
				
			||||||
 | 
							var (
 | 
				
			||||||
 | 
								unit = "ns"
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if num > 1000 {
 | 
				
			||||||
 | 
								num = num / 1000
 | 
				
			||||||
 | 
								unit = "µs"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if num > 1000 {
 | 
				
			||||||
 | 
								num = num / 1000
 | 
				
			||||||
 | 
								unit = "ms"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if num > 1000 {
 | 
				
			||||||
 | 
								num = num / 1000
 | 
				
			||||||
 | 
								unit = "s"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return fmt.Sprintf("%3d %2s", num, unit)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return func(c *Ctx) error {
 | 
				
			||||||
 | 
							start := time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err := c.Next()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var (
 | 
				
			||||||
 | 
								duration = time.Now().Sub(start).Nanoseconds()
 | 
				
			||||||
 | 
								status   = c.StatusCode
 | 
				
			||||||
 | 
								path     = c.path
 | 
				
			||||||
 | 
								method   = c.Request.Method
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							l.Printf("%s | %5s | %d | %s | %s",
 | 
				
			||||||
 | 
								start.Format("06/01/02T15:04:05"),
 | 
				
			||||||
 | 
								method,
 | 
				
			||||||
 | 
								status,
 | 
				
			||||||
 | 
								durationFormat(duration),
 | 
				
			||||||
 | 
								path,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										53
									
								
								nf.go
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								nf.go
									
									
									
									
									
								
							@@ -2,40 +2,87 @@ package nf
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	banner = "  _  _     _     ___                 _ \n | \\| |___| |_  | __|__ _  _ _ _  __| |\n | .` / _ \\  _| | _/ _ \\ || | ' \\/ _` |\n |_|\\_\\___/\\__| |_|\\___/\\_,_|_||_\\__,_|\n "
 | 
						banner = "  _  _     _     ___                 _ \n | \\| |___| |_  | __|__ _  _ _ _  __| |\n | .` / _ \\  _| | _/ _ \\ || | ' \\/ _` |\n |_|\\_\\___/\\__| |_|\\___/\\_,_|_||_\\__,_|\n "
 | 
				
			||||||
 | 
						_404   = "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1\"><meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"><title>Not Found</title><style>body{background:#333;margin:0;color:#ccc;display:flex;align-items:center;max-height:100vh;height:100vh;justify-content:center}textarea{min-height:5rem;min-width:20rem;text-align:center;border:none;background:0 0;color:#ccc;resize:none;user-input:none;user-select:none;cursor:default;-webkit-user-select:none;-webkit-touch-callout:none;-moz-user-select:none;-ms-user-select:none;outline:0}</style></head><body><textarea id=\"banner\" readonly=\"readonly\"></textarea><script type=\"text/javascript\">let htmlCodes = [\n    ' _  _     _     ___                 _ ',\n    '| \\\\| |___| |_  | __|__ _  _ _ _  __| |',\n    '| .` / _ \\\\  _| | _/ _ \\\\ || | \\' \\\\/ _` |',\n    '|_|\\\\_\\\\___/\\\\__| |_|\\\\___/\\\\_,_|_||_\\\\__,_|'\n].join('\\n');\ndocument.querySelector('#banner').value = htmlCodes</script></body></html>"
 | 
				
			||||||
 | 
						_405   = `405 Method Not Allowed`
 | 
				
			||||||
 | 
						_500   = `500 Internal Server Error`
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Map map[string]interface{}
 | 
					type Map map[string]interface{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Config struct {
 | 
					type Config struct {
 | 
				
			||||||
 | 
						DisableMessagePrint bool `json:"-"`
 | 
				
			||||||
	// Default: 4 * 1024 * 1024
 | 
						// Default: 4 * 1024 * 1024
 | 
				
			||||||
	BodyLimit int64 `json:"-"`
 | 
						BodyLimit int64 `json:"-"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if report http.ErrServerClosed as run err
 | 
				
			||||||
 | 
						ErrServeClose bool `json:"-"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	DisableBanner       bool `json:"-"`
 | 
						DisableBanner       bool `json:"-"`
 | 
				
			||||||
	DisableLogger       bool `json:"-"`
 | 
						DisableLogger       bool `json:"-"`
 | 
				
			||||||
	DisableRecover      bool `json:"-"`
 | 
						DisableRecover      bool `json:"-"`
 | 
				
			||||||
 | 
						DisableHttpErrorLog bool `json:"-"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//EnableNotImplementHandler bool        `json:"-"`
 | 
				
			||||||
 | 
						NotFoundHandler         HandlerFunc `json:"-"`
 | 
				
			||||||
 | 
						MethodNotAllowedHandler HandlerFunc `json:"-"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	defaultConfig = &Config{
 | 
						defaultConfig = &Config{
 | 
				
			||||||
		BodyLimit: 4 * 1024 * 1024,
 | 
							BodyLimit: 4 * 1024 * 1024,
 | 
				
			||||||
 | 
							NotFoundHandler: func(c *Ctx) error {
 | 
				
			||||||
 | 
								c.Set("Content-Type", MIMETextHTML)
 | 
				
			||||||
 | 
								_, err := c.Status(404).Write([]byte(_404))
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							MethodNotAllowedHandler: func(c *Ctx) error {
 | 
				
			||||||
 | 
								c.Set("Content-Type", MIMETextPlain)
 | 
				
			||||||
 | 
								_, err := c.Status(405).Write([]byte(_405))
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func New(config ...Config) *App {
 | 
					func New(config ...Config) *App {
 | 
				
			||||||
	app := &App{
 | 
						app := &App{
 | 
				
			||||||
		router: newRouter(),
 | 
							RouterGroup: RouterGroup{
 | 
				
			||||||
 | 
								Handlers: nil,
 | 
				
			||||||
 | 
								basePath: "/",
 | 
				
			||||||
 | 
								root:     true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							redirectTrailingSlash:  true,  // true
 | 
				
			||||||
 | 
							redirectFixedPath:      false, // false
 | 
				
			||||||
 | 
							handleMethodNotAllowed: true,  // false
 | 
				
			||||||
 | 
							useRawPath:             false, // false
 | 
				
			||||||
 | 
							unescapePathValues:     true,  // true
 | 
				
			||||||
 | 
							removeExtraSlash:       false, // false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(config) > 0 {
 | 
						if len(config) > 0 {
 | 
				
			||||||
		app.config = &config[0]
 | 
							app.config = &config[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if app.config.BodyLimit == 0 {
 | 
							if app.config.BodyLimit == 0 {
 | 
				
			||||||
			app.config.BodyLimit = defaultConfig.BodyLimit
 | 
								app.config.BodyLimit = defaultConfig.BodyLimit
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if app.config.NotFoundHandler == nil {
 | 
				
			||||||
 | 
								app.config.NotFoundHandler = defaultConfig.NotFoundHandler
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if app.config.MethodNotAllowedHandler == nil {
 | 
				
			||||||
 | 
								app.config.MethodNotAllowedHandler = defaultConfig.MethodNotAllowedHandler
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		app.config = defaultConfig
 | 
							app.config = defaultConfig
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	app.RouterGroup = &RouterGroup{app: app}
 | 
						app.RouterGroup.app = app
 | 
				
			||||||
	app.groups = []*RouterGroup{app.RouterGroup}
 | 
					
 | 
				
			||||||
 | 
						if !app.config.DisableLogger {
 | 
				
			||||||
 | 
							app.Use(NewLogger())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !app.config.DisableRecover {
 | 
						if !app.config.DisableRecover {
 | 
				
			||||||
		app.Use(NewRecover(true))
 | 
							app.Use(NewRecover(true))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										69
									
								
								nft/resp/error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								nft/resp/error.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					package resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"github.com/loveuer/nf"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Error struct {
 | 
				
			||||||
 | 
						status uint32
 | 
				
			||||||
 | 
						msg    string
 | 
				
			||||||
 | 
						err    error
 | 
				
			||||||
 | 
						data   any
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e Error) Error() string {
 | 
				
			||||||
 | 
						if e.msg != "" {
 | 
				
			||||||
 | 
							return e.msg
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch e.status {
 | 
				
			||||||
 | 
						case 200:
 | 
				
			||||||
 | 
							return MSG200
 | 
				
			||||||
 | 
						case 202:
 | 
				
			||||||
 | 
							return MSG202
 | 
				
			||||||
 | 
						case 400:
 | 
				
			||||||
 | 
							return MSG400
 | 
				
			||||||
 | 
						case 401:
 | 
				
			||||||
 | 
							return MSG401
 | 
				
			||||||
 | 
						case 403:
 | 
				
			||||||
 | 
							return MSG403
 | 
				
			||||||
 | 
						case 404:
 | 
				
			||||||
 | 
							return MSG404
 | 
				
			||||||
 | 
						case 429:
 | 
				
			||||||
 | 
							return MSG429
 | 
				
			||||||
 | 
						case 500:
 | 
				
			||||||
 | 
							return MSG500
 | 
				
			||||||
 | 
						case 501:
 | 
				
			||||||
 | 
							return MSG501
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return e.err.Error()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewError(statusCode uint32, msg string, rawErr error, data any) Error {
 | 
				
			||||||
 | 
						return Error{
 | 
				
			||||||
 | 
							status: statusCode,
 | 
				
			||||||
 | 
							msg:    msg,
 | 
				
			||||||
 | 
							err:    rawErr,
 | 
				
			||||||
 | 
							data:   data,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func RespError(c *nf.Ctx, err error) error {
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							return Resp(c, 500, MSG500, "response with nil error", nil)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var re = &Error{}
 | 
				
			||||||
 | 
						if errors.As(err, re) {
 | 
				
			||||||
 | 
							if re.err == nil {
 | 
				
			||||||
 | 
								return Resp(c, re.status, re.msg, re.msg, re.data)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return Resp(c, re.status, re.msg, re.err.Error(), re.data)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Resp(c, 500, MSG500, err.Error(), nil)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										127
									
								
								nft/resp/resp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								nft/resp/resp.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
				
			|||||||
 | 
					package resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"github.com/loveuer/nf"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func handleEmptyMsg(status uint32, msg string) string {
 | 
				
			||||||
 | 
						if msg == "" {
 | 
				
			||||||
 | 
							switch status {
 | 
				
			||||||
 | 
							case 200:
 | 
				
			||||||
 | 
								msg = MSG200
 | 
				
			||||||
 | 
							case 202:
 | 
				
			||||||
 | 
								msg = MSG202
 | 
				
			||||||
 | 
							case 400:
 | 
				
			||||||
 | 
								msg = MSG400
 | 
				
			||||||
 | 
							case 401:
 | 
				
			||||||
 | 
								msg = MSG401
 | 
				
			||||||
 | 
							case 403:
 | 
				
			||||||
 | 
								msg = MSG403
 | 
				
			||||||
 | 
							case 404:
 | 
				
			||||||
 | 
								msg = MSG404
 | 
				
			||||||
 | 
							case 429:
 | 
				
			||||||
 | 
								msg = MSG429
 | 
				
			||||||
 | 
							case 500:
 | 
				
			||||||
 | 
								msg = MSG500
 | 
				
			||||||
 | 
							case 501:
 | 
				
			||||||
 | 
								msg = MSG501
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return msg
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Resp(c *nf.Ctx, status uint32, msg string, err string, data any) error {
 | 
				
			||||||
 | 
						msg = handleEmptyMsg(status, msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Set(RealStatusHeader, strconv.Itoa(int(status)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if data == nil {
 | 
				
			||||||
 | 
							return c.JSON(nf.Map{"status": status, "msg": msg, "err": err})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.JSON(nf.Map{"status": status, "msg": msg, "err": err, "data": data})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Resp200(c *nf.Ctx, data any, msgs ...string) error {
 | 
				
			||||||
 | 
						msg := MSG200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(msgs) > 0 && msgs[0] != "" {
 | 
				
			||||||
 | 
							msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Resp(c, 200, msg, "", data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Resp202(c *nf.Ctx, data any, msgs ...string) error {
 | 
				
			||||||
 | 
						msg := MSG202
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(msgs) > 0 && msgs[0] != "" {
 | 
				
			||||||
 | 
							msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Resp(c, 202, msg, "", data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Resp400(c *nf.Ctx, data any, msgs ...string) error {
 | 
				
			||||||
 | 
						msg := MSG400
 | 
				
			||||||
 | 
						err := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(msgs) > 0 && msgs[0] != "" {
 | 
				
			||||||
 | 
							msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
 | 
				
			||||||
 | 
							err = msg
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Resp(c, 400, msg, err, data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Resp401(c *nf.Ctx, data any, msgs ...string) error {
 | 
				
			||||||
 | 
						msg := MSG401
 | 
				
			||||||
 | 
						err := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(msgs) > 0 && msgs[0] != "" {
 | 
				
			||||||
 | 
							msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
 | 
				
			||||||
 | 
							err = msg
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Resp(c, 401, msg, err, data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Resp403(c *nf.Ctx, data any, msgs ...string) error {
 | 
				
			||||||
 | 
						msg := MSG403
 | 
				
			||||||
 | 
						err := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(msgs) > 0 && msgs[0] != "" {
 | 
				
			||||||
 | 
							msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
 | 
				
			||||||
 | 
							err = msg
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Resp(c, 403, msg, err, data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Resp429(c *nf.Ctx, data any, msgs ...string) error {
 | 
				
			||||||
 | 
						msg := MSG429
 | 
				
			||||||
 | 
						err := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(msgs) > 0 && msgs[0] != "" {
 | 
				
			||||||
 | 
							msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
 | 
				
			||||||
 | 
							err = ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Resp(c, 429, msg, err, data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Resp500(c *nf.Ctx, data any, msgs ...string) error {
 | 
				
			||||||
 | 
						msg := MSG500
 | 
				
			||||||
 | 
						err := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(msgs) > 0 && msgs[0] != "" {
 | 
				
			||||||
 | 
							msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
 | 
				
			||||||
 | 
							err = msg
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Resp(c, 500, msg, err, data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								nft/resp/var.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								nft/resp/var.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					package resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						MSG200 = "请求成功"
 | 
				
			||||||
 | 
						MSG202 = "请求成功, 请稍后..."
 | 
				
			||||||
 | 
						MSG400 = "请求参数错误"
 | 
				
			||||||
 | 
						MSG401 = "登录已过期, 请重新登录"
 | 
				
			||||||
 | 
						MSG403 = "请求权限不足"
 | 
				
			||||||
 | 
						MSG404 = "请求资源未找到"
 | 
				
			||||||
 | 
						MSG429 = "请求过于频繁, 请稍后再试"
 | 
				
			||||||
 | 
						MSG500 = "服务器开小差了, 请稍后再试"
 | 
				
			||||||
 | 
						MSG501 = "功能开发中, 尽情期待"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						RealStatusHeader = "NF-STATUS"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -50,7 +50,7 @@ func handlePost(c *nf.Ctx) error {
 | 
				
			|||||||
    var (
 | 
					    var (
 | 
				
			||||||
        err error
 | 
					        err error
 | 
				
			||||||
        req = Req{}
 | 
					        req = Req{}
 | 
				
			||||||
		reqMap = make(map[string]any)
 | 
					        reqMap = make(map[string]interface{})
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
    if err = c.BodyParser(&req); err != nil {
 | 
					    if err = c.BodyParser(&req); err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										132
									
								
								response_writer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								response_writer.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					package nf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						noWritten     = -1
 | 
				
			||||||
 | 
						defaultStatus = http.StatusOK
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResponseWriter ...
 | 
				
			||||||
 | 
					type ResponseWriter interface {
 | 
				
			||||||
 | 
						http.ResponseWriter
 | 
				
			||||||
 | 
						http.Hijacker
 | 
				
			||||||
 | 
						http.Flusher
 | 
				
			||||||
 | 
						http.CloseNotifier
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Status returns the HTTP response status code of the current request.
 | 
				
			||||||
 | 
						Status() int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Size returns the number of bytes already written into the response http body.
 | 
				
			||||||
 | 
						// See Written()
 | 
				
			||||||
 | 
						Size() int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// WriteString writes the string into the response body.
 | 
				
			||||||
 | 
						WriteString(string) (int, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Written returns true if the response body was already written.
 | 
				
			||||||
 | 
						Written() bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// WriteHeaderNow forces to write the http header (status code + headers).
 | 
				
			||||||
 | 
						WriteHeaderNow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Pusher get the http.Pusher for server push
 | 
				
			||||||
 | 
						Pusher() http.Pusher
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type responseWriter struct {
 | 
				
			||||||
 | 
						http.ResponseWriter
 | 
				
			||||||
 | 
						size   int
 | 
				
			||||||
 | 
						status int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ ResponseWriter = (*responseWriter)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) Unwrap() http.ResponseWriter {
 | 
				
			||||||
 | 
						return w.ResponseWriter
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) reset(writer http.ResponseWriter) {
 | 
				
			||||||
 | 
						w.ResponseWriter = writer
 | 
				
			||||||
 | 
						w.size = noWritten
 | 
				
			||||||
 | 
						w.status = defaultStatus
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) WriteHeader(code int) {
 | 
				
			||||||
 | 
						if code > 0 && w.status != code {
 | 
				
			||||||
 | 
							if w.Written() {
 | 
				
			||||||
 | 
								// todo: debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							w.status = code
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) WriteHeaderNow() {
 | 
				
			||||||
 | 
						if !w.Written() {
 | 
				
			||||||
 | 
							w.size = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if w.status == 0 {
 | 
				
			||||||
 | 
								w.status = 200
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							w.ResponseWriter.WriteHeader(w.status)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) Write(data []byte) (n int, err error) {
 | 
				
			||||||
 | 
						w.WriteHeaderNow()
 | 
				
			||||||
 | 
						n, err = w.ResponseWriter.Write(data)
 | 
				
			||||||
 | 
						w.size += n
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) WriteString(s string) (n int, err error) {
 | 
				
			||||||
 | 
						w.WriteHeaderNow()
 | 
				
			||||||
 | 
						n, err = io.WriteString(w.ResponseWriter, s)
 | 
				
			||||||
 | 
						w.size += n
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) Status() int {
 | 
				
			||||||
 | 
						return w.status
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) Size() int {
 | 
				
			||||||
 | 
						return w.size
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) Written() bool {
 | 
				
			||||||
 | 
						return w.size != noWritten
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Hijack implements the http.Hijacker interface.
 | 
				
			||||||
 | 
					func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
 | 
				
			||||||
 | 
						if w.size < 0 {
 | 
				
			||||||
 | 
							w.size = 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return w.ResponseWriter.(http.Hijacker).Hijack()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CloseNotify implements the http.CloseNotifier interface.
 | 
				
			||||||
 | 
					func (w *responseWriter) CloseNotify() <-chan bool {
 | 
				
			||||||
 | 
						return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Flush implements the http.Flusher interface.
 | 
				
			||||||
 | 
					func (w *responseWriter) Flush() {
 | 
				
			||||||
 | 
						w.WriteHeaderNow()
 | 
				
			||||||
 | 
						w.ResponseWriter.(http.Flusher).Flush()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *responseWriter) Pusher() (pusher http.Pusher) {
 | 
				
			||||||
 | 
						if pusher, ok := w.ResponseWriter.(http.Pusher); ok {
 | 
				
			||||||
 | 
							return pusher
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										99
									
								
								router.go
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								router.go
									
									
									
									
									
								
							@@ -1,99 +0,0 @@
 | 
				
			|||||||
package nf
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "strings"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type router struct {
 | 
					 | 
				
			||||||
	roots    map[string]*_node
 | 
					 | 
				
			||||||
	handlers map[string][]HandlerFunc
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func newRouter() *router {
 | 
					 | 
				
			||||||
	return &router{
 | 
					 | 
				
			||||||
		roots:    make(map[string]*_node),
 | 
					 | 
				
			||||||
		handlers: make(map[string][]HandlerFunc),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Only one * is allowed
 | 
					 | 
				
			||||||
func parsePattern(pattern string) []string {
 | 
					 | 
				
			||||||
	vs := strings.Split(pattern, "/")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	parts := make([]string, 0)
 | 
					 | 
				
			||||||
	for _, item := range vs {
 | 
					 | 
				
			||||||
		if item != "" {
 | 
					 | 
				
			||||||
			parts = append(parts, item)
 | 
					 | 
				
			||||||
			if item[0] == '*' {
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return parts
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (r *router) addRoute(method string, pattern string, handlers ...HandlerFunc) {
 | 
					 | 
				
			||||||
	parts := parsePattern(pattern)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	key := method + "-" + pattern
 | 
					 | 
				
			||||||
	_, ok := r.roots[method]
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		r.roots[method] = &_node{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	r.roots[method].insert(pattern, parts, 0)
 | 
					 | 
				
			||||||
	r.handlers[key] = handlers
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (r *router) getRoute(method string, path string) (*_node, map[string]string) {
 | 
					 | 
				
			||||||
	searchParts := parsePattern(path)
 | 
					 | 
				
			||||||
	params := make(map[string]string)
 | 
					 | 
				
			||||||
	root, ok := r.roots[method]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		return nil, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	n := root.search(searchParts, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if n != nil {
 | 
					 | 
				
			||||||
		parts := parsePattern(n.pattern)
 | 
					 | 
				
			||||||
		for index, part := range parts {
 | 
					 | 
				
			||||||
			if part[0] == ':' {
 | 
					 | 
				
			||||||
				params[part[1:]] = searchParts[index]
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if part[0] == '*' && len(part) > 1 {
 | 
					 | 
				
			||||||
				params[part[1:]] = strings.Join(searchParts[index:], "/")
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return n, params
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (r *router) getRoutes(method string) []*_node {
 | 
					 | 
				
			||||||
	root, ok := r.roots[method]
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	nodes := make([]*_node, 0)
 | 
					 | 
				
			||||||
	root.travel(&nodes)
 | 
					 | 
				
			||||||
	return nodes
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (r *router) handle(c *Ctx) error {
 | 
					 | 
				
			||||||
	if err := c.verify(); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	node, params := r.getRoute(c.Method, c.path)
 | 
					 | 
				
			||||||
	if node != nil {
 | 
					 | 
				
			||||||
		c.params = params
 | 
					 | 
				
			||||||
		key := c.Method + "-" + node.pattern
 | 
					 | 
				
			||||||
		c.handlers = append(c.handlers, r.handlers[key]...)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		_, err := c.Writef("404 NOT FOUND: %s\n", c.path)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return c.Next()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										155
									
								
								routergroup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								routergroup.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
				
			|||||||
 | 
					package nf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"math"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						// regEnLetter matches english letters for http method name
 | 
				
			||||||
 | 
						regEnLetter = regexp.MustCompile("^[A-Z]+$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// anyMethods for RouterGroup Any method
 | 
				
			||||||
 | 
						anyMethods = []string{
 | 
				
			||||||
 | 
							http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
 | 
				
			||||||
 | 
							http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
 | 
				
			||||||
 | 
							http.MethodTrace,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IRouter defines all router handle interface includes single and group router.
 | 
				
			||||||
 | 
					type IRouter interface {
 | 
				
			||||||
 | 
						IRoutes
 | 
				
			||||||
 | 
						Group(string, ...HandlerFunc) *RouterGroup
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IRoutes defines all router handle interface.
 | 
				
			||||||
 | 
					type IRoutes interface {
 | 
				
			||||||
 | 
						Use(...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Handle(string, string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
						Any(string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
						Get(string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
						Post(string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
						Delete(string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
						Patch(string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
						Put(string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
						Options(string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
						Head(string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
						Match([]string, string, ...HandlerFunc) IRoutes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//StaticFile(string, string) IRoutes
 | 
				
			||||||
 | 
						//StaticFileFS(string, string, http.FileSystem) IRoutes
 | 
				
			||||||
 | 
						//Static(string, string) IRoutes
 | 
				
			||||||
 | 
						//StaticFS(string, http.FileSystem) IRoutes
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RouterGroup struct {
 | 
				
			||||||
 | 
						Handlers []HandlerFunc
 | 
				
			||||||
 | 
						basePath string
 | 
				
			||||||
 | 
						app      *App
 | 
				
			||||||
 | 
						root     bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ IRouter = (*RouterGroup)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						group.Handlers = append(group.Handlers, middleware...)
 | 
				
			||||||
 | 
						return group.returnObj()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
 | 
				
			||||||
 | 
						return &RouterGroup{
 | 
				
			||||||
 | 
							Handlers: group.combineHandlers(handlers...),
 | 
				
			||||||
 | 
							basePath: group.calculateAbsolutePath(relativePath),
 | 
				
			||||||
 | 
							app:      group.app,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) BasePath() string {
 | 
				
			||||||
 | 
						return group.basePath
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						absolutePath := group.calculateAbsolutePath(relativePath)
 | 
				
			||||||
 | 
						handlers = group.combineHandlers(handlers...)
 | 
				
			||||||
 | 
						group.app.addRoute(httpMethod, absolutePath, handlers...)
 | 
				
			||||||
 | 
						return group.returnObj()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						if matched := regEnLetter.MatchString(httpMethod); !matched {
 | 
				
			||||||
 | 
							panic("http method " + httpMethod + " is not valid")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return group.handle(httpMethod, relativePath, handlers...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Post(relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						return group.handle(http.MethodPost, relativePath, handlers...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Get(relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						return group.handle(http.MethodGet, relativePath, handlers...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Delete(relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						return group.handle(http.MethodDelete, relativePath, handlers...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Patch(relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						return group.handle(http.MethodPatch, relativePath, handlers...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Put(relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						return group.handle(http.MethodPut, relativePath, handlers...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Options(relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						return group.handle(http.MethodOptions, relativePath, handlers...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Head(relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						return group.handle(http.MethodHead, relativePath, handlers...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Any registers a route that matches all the HTTP methods.
 | 
				
			||||||
 | 
					// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
 | 
				
			||||||
 | 
					func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						for _, method := range anyMethods {
 | 
				
			||||||
 | 
							group.handle(method, relativePath, handlers...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return group.returnObj()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes {
 | 
				
			||||||
 | 
						for _, method := range methods {
 | 
				
			||||||
 | 
							group.handle(method, relativePath, handlers...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return group.returnObj()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const abortIndex int8 = math.MaxInt8 >> 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) combineHandlers(handlers ...HandlerFunc) []HandlerFunc {
 | 
				
			||||||
 | 
						finalSize := len(group.Handlers) + len(handlers)
 | 
				
			||||||
 | 
						elsePanic(finalSize < int(abortIndex), "too many handlers")
 | 
				
			||||||
 | 
						mergedHandlers := make([]HandlerFunc, finalSize)
 | 
				
			||||||
 | 
						copy(mergedHandlers, group.Handlers)
 | 
				
			||||||
 | 
						copy(mergedHandlers[len(group.Handlers):], handlers)
 | 
				
			||||||
 | 
						return mergedHandlers
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
 | 
				
			||||||
 | 
						return path.Join(group.basePath, relativePath)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (group *RouterGroup) returnObj() IRoutes {
 | 
				
			||||||
 | 
						if group.root {
 | 
				
			||||||
 | 
							return group.app
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return group
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										903
									
								
								tree.go
									
									
									
									
									
								
							
							
						
						
									
										903
									
								
								tree.go
									
									
									
									
									
								
							@@ -1,76 +1,891 @@
 | 
				
			|||||||
package nf
 | 
					package nf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"unicode"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/loveuer/nf/internal/bytesconv"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type _node struct {
 | 
					var (
 | 
				
			||||||
	pattern  string
 | 
						strColon = []byte(":")
 | 
				
			||||||
	part     string
 | 
						strStar  = []byte("*")
 | 
				
			||||||
	children []*_node
 | 
						strSlash = []byte("/")
 | 
				
			||||||
	isWild   bool
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Param is a single URL parameter, consisting of a key and a value.
 | 
				
			||||||
 | 
					type Param struct {
 | 
				
			||||||
 | 
						Key   string
 | 
				
			||||||
 | 
						Value string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (n *_node) insert(pattern string, parts []string, height int) {
 | 
					// Params is a Param-slice, as returned by the router.
 | 
				
			||||||
	if len(parts) == height {
 | 
					// The slice is ordered, the first URL parameter is also the first slice value.
 | 
				
			||||||
		n.pattern = pattern
 | 
					// It is therefore safe to read values by the index.
 | 
				
			||||||
 | 
					type Params []Param
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get returns the value of the first Param which key matches the given name and a boolean true.
 | 
				
			||||||
 | 
					// If no matching Param is found, an empty string is returned and a boolean false .
 | 
				
			||||||
 | 
					func (ps Params) Get(name string) (string, bool) {
 | 
				
			||||||
 | 
						for _, entry := range ps {
 | 
				
			||||||
 | 
							if entry.Key == name {
 | 
				
			||||||
 | 
								return entry.Value, true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "", false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ByName returns the value of the first Param which key matches the given name.
 | 
				
			||||||
 | 
					// If no matching Param is found, an empty string is returned.
 | 
				
			||||||
 | 
					func (ps Params) ByName(name string) (va string) {
 | 
				
			||||||
 | 
						va, _ = ps.Get(name)
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	part := parts[height]
 | 
					type methodTree struct {
 | 
				
			||||||
	child := n.matchChild(part)
 | 
						method string
 | 
				
			||||||
	if child == nil {
 | 
						root   *node
 | 
				
			||||||
		child = &_node{part: part, isWild: part[0] == ':' || part[0] == '*'}
 | 
					 | 
				
			||||||
		n.children = append(n.children, child)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	child.insert(pattern, parts, height+1)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (n *_node) search(parts []string, height int) *_node {
 | 
					type methodTrees []methodTree
 | 
				
			||||||
	if len(parts) == height || strings.HasPrefix(n.part, "*") {
 | 
					
 | 
				
			||||||
		if n.pattern == "" {
 | 
					func (trees methodTrees) get(method string) *node {
 | 
				
			||||||
 | 
						for _, tree := range trees {
 | 
				
			||||||
 | 
							if tree.method == method {
 | 
				
			||||||
 | 
								return tree.root
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func min(a, b int) int {
 | 
				
			||||||
 | 
						if a <= b {
 | 
				
			||||||
 | 
							return a
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return b
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func longestCommonPrefix(a, b string) int {
 | 
				
			||||||
 | 
						i := 0
 | 
				
			||||||
 | 
						max := min(len(a), len(b))
 | 
				
			||||||
 | 
						for i < max && a[i] == b[i] {
 | 
				
			||||||
 | 
							i++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return i
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// addChild will add a child node, keeping wildcardChild at the end
 | 
				
			||||||
 | 
					func (n *node) addChild(child *node) {
 | 
				
			||||||
 | 
						if n.wildChild && len(n.children) > 0 {
 | 
				
			||||||
 | 
							wildcardChild := n.children[len(n.children)-1]
 | 
				
			||||||
 | 
							n.children = append(n.children[:len(n.children)-1], child, wildcardChild)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							n.children = append(n.children, child)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func countParams(path string) uint16 {
 | 
				
			||||||
 | 
						var n uint16
 | 
				
			||||||
 | 
						s := bytesconv.StringToBytes(path)
 | 
				
			||||||
 | 
						n += uint16(bytes.Count(s, strColon))
 | 
				
			||||||
 | 
						n += uint16(bytes.Count(s, strStar))
 | 
				
			||||||
	return n
 | 
						return n
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	part := parts[height]
 | 
					func countSections(path string) uint16 {
 | 
				
			||||||
	children := n.matchChildren(part)
 | 
						s := bytesconv.StringToBytes(path)
 | 
				
			||||||
 | 
						return uint16(bytes.Count(s, strSlash))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, child := range children {
 | 
					type nodeType uint8
 | 
				
			||||||
		result := child.search(parts, height+1)
 | 
					
 | 
				
			||||||
		if result != nil {
 | 
					const (
 | 
				
			||||||
			return result
 | 
						static nodeType = iota
 | 
				
			||||||
 | 
						root
 | 
				
			||||||
 | 
						param
 | 
				
			||||||
 | 
						catchAll
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type node struct {
 | 
				
			||||||
 | 
						path      string
 | 
				
			||||||
 | 
						indices   string
 | 
				
			||||||
 | 
						wildChild bool
 | 
				
			||||||
 | 
						nType     nodeType
 | 
				
			||||||
 | 
						priority  uint32
 | 
				
			||||||
 | 
						children  []*node // child nodes, at most 1 :param style node at the end of the array
 | 
				
			||||||
 | 
						handlers  []HandlerFunc
 | 
				
			||||||
 | 
						fullPath  string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Increments priority of the given child and reorders if necessary
 | 
				
			||||||
 | 
					func (n *node) incrementChildPrio(pos int) int {
 | 
				
			||||||
 | 
						cs := n.children
 | 
				
			||||||
 | 
						cs[pos].priority++
 | 
				
			||||||
 | 
						prio := cs[pos].priority
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Adjust position (move to front)
 | 
				
			||||||
 | 
						newPos := pos
 | 
				
			||||||
 | 
						for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
 | 
				
			||||||
 | 
							// Swap node positions
 | 
				
			||||||
 | 
							cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Build new index char string
 | 
				
			||||||
 | 
						if newPos != pos {
 | 
				
			||||||
 | 
							n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty
 | 
				
			||||||
 | 
								n.indices[pos:pos+1] + // The index char we move
 | 
				
			||||||
 | 
								n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return newPos
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// addRoute adds a node with the given handle to the path.
 | 
				
			||||||
 | 
					// Not concurrency-safe!
 | 
				
			||||||
 | 
					func (n *node) addRoute(path string, handlers ...HandlerFunc) {
 | 
				
			||||||
 | 
						fullPath := path
 | 
				
			||||||
 | 
						n.priority++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Empty tree
 | 
				
			||||||
 | 
						if len(n.path) == 0 && len(n.children) == 0 {
 | 
				
			||||||
 | 
							n.insertChild(path, fullPath, handlers...)
 | 
				
			||||||
 | 
							n.nType = root
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						parentFullPathIndex := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					walk:
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							// Find the longest common prefix.
 | 
				
			||||||
 | 
							// This also implies that the common prefix contains no ':' or '*'
 | 
				
			||||||
 | 
							// since the existing key can't contain those chars.
 | 
				
			||||||
 | 
							i := longestCommonPrefix(path, n.path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Split edge
 | 
				
			||||||
 | 
							if i < len(n.path) {
 | 
				
			||||||
 | 
								child := node{
 | 
				
			||||||
 | 
									path:      n.path[i:],
 | 
				
			||||||
 | 
									wildChild: n.wildChild,
 | 
				
			||||||
 | 
									nType:     static,
 | 
				
			||||||
 | 
									indices:   n.indices,
 | 
				
			||||||
 | 
									children:  n.children,
 | 
				
			||||||
 | 
									handlers:  n.handlers,
 | 
				
			||||||
 | 
									priority:  n.priority - 1,
 | 
				
			||||||
 | 
									fullPath:  n.fullPath,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								n.children = []*node{&child}
 | 
				
			||||||
 | 
								// []byte for proper unicode char conversion, see #65
 | 
				
			||||||
 | 
								n.indices = bytesconv.BytesToString([]byte{n.path[i]})
 | 
				
			||||||
 | 
								n.path = path[:i]
 | 
				
			||||||
 | 
								n.handlers = nil
 | 
				
			||||||
 | 
								n.wildChild = false
 | 
				
			||||||
 | 
								n.fullPath = fullPath[:parentFullPathIndex+i]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Make new node a child of this node
 | 
				
			||||||
 | 
							if i < len(path) {
 | 
				
			||||||
 | 
								path = path[i:]
 | 
				
			||||||
 | 
								c := path[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// '/' after param
 | 
				
			||||||
 | 
								if n.nType == param && c == '/' && len(n.children) == 1 {
 | 
				
			||||||
 | 
									parentFullPathIndex += len(n.path)
 | 
				
			||||||
 | 
									n = n.children[0]
 | 
				
			||||||
 | 
									n.priority++
 | 
				
			||||||
 | 
									continue walk
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Check if a child with the next path byte exists
 | 
				
			||||||
 | 
								for i, max := 0, len(n.indices); i < max; i++ {
 | 
				
			||||||
 | 
									if c == n.indices[i] {
 | 
				
			||||||
 | 
										parentFullPathIndex += len(n.path)
 | 
				
			||||||
 | 
										i = n.incrementChildPrio(i)
 | 
				
			||||||
 | 
										n = n.children[i]
 | 
				
			||||||
 | 
										continue walk
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Otherwise insert it
 | 
				
			||||||
 | 
								if c != ':' && c != '*' && n.nType != catchAll {
 | 
				
			||||||
 | 
									// []byte for proper unicode char conversion, see #65
 | 
				
			||||||
 | 
									n.indices += bytesconv.BytesToString([]byte{c})
 | 
				
			||||||
 | 
									child := &node{
 | 
				
			||||||
 | 
										fullPath: fullPath,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									n.addChild(child)
 | 
				
			||||||
 | 
									n.incrementChildPrio(len(n.indices) - 1)
 | 
				
			||||||
 | 
									n = child
 | 
				
			||||||
 | 
								} else if n.wildChild {
 | 
				
			||||||
 | 
									// inserting a wildcard node, need to check if it conflicts with the existing wildcard
 | 
				
			||||||
 | 
									n = n.children[len(n.children)-1]
 | 
				
			||||||
 | 
									n.priority++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Check if the wildcard matches
 | 
				
			||||||
 | 
									if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
 | 
				
			||||||
 | 
										// Adding a child to a catchAll is not possible
 | 
				
			||||||
 | 
										n.nType != catchAll &&
 | 
				
			||||||
 | 
										// Check for longer wildcard, e.g. :name and :names
 | 
				
			||||||
 | 
										(len(n.path) >= len(path) || path[len(n.path)] == '/') {
 | 
				
			||||||
 | 
										continue walk
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Wildcard conflict
 | 
				
			||||||
 | 
									pathSeg := path
 | 
				
			||||||
 | 
									if n.nType != catchAll {
 | 
				
			||||||
 | 
										pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
 | 
				
			||||||
 | 
									panic("'" + pathSeg +
 | 
				
			||||||
 | 
										"' in new path '" + fullPath +
 | 
				
			||||||
 | 
										"' conflicts with existing wildcard '" + n.path +
 | 
				
			||||||
 | 
										"' in existing prefix '" + prefix +
 | 
				
			||||||
 | 
										"'")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								n.insertChild(path, fullPath, handlers...)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Otherwise add handle to current node
 | 
				
			||||||
 | 
							if n.handlers != nil {
 | 
				
			||||||
 | 
								panic("handlers are already registered for path '" + fullPath + "'")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							n.handlers = handlers
 | 
				
			||||||
 | 
							n.fullPath = fullPath
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Search for a wildcard segment and check the name for invalid characters.
 | 
				
			||||||
 | 
					// Returns -1 as index, if no wildcard was found.
 | 
				
			||||||
 | 
					func findWildcard(path string) (wildcard string, i int, valid bool) {
 | 
				
			||||||
 | 
						// Find start
 | 
				
			||||||
 | 
						for start, c := range []byte(path) {
 | 
				
			||||||
 | 
							// A wildcard starts with ':' (param) or '*' (catch-all)
 | 
				
			||||||
 | 
							if c != ':' && c != '*' {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Find end and check for invalid characters
 | 
				
			||||||
 | 
							valid = true
 | 
				
			||||||
 | 
							for end, c := range []byte(path[start+1:]) {
 | 
				
			||||||
 | 
								switch c {
 | 
				
			||||||
 | 
								case '/':
 | 
				
			||||||
 | 
									return path[start : start+1+end], start, valid
 | 
				
			||||||
 | 
								case ':', '*':
 | 
				
			||||||
 | 
									valid = false
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return path[start:], start, valid
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "", -1, false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *node) insertChild(path string, fullPath string, handlers ...HandlerFunc) {
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							// Find prefix until first wildcard
 | 
				
			||||||
 | 
							wildcard, i, valid := findWildcard(path)
 | 
				
			||||||
 | 
							if i < 0 { // No wildcard found
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The wildcard name must only contain one ':' or '*' character
 | 
				
			||||||
 | 
							if !valid {
 | 
				
			||||||
 | 
								panic("only one wildcard per path segment is allowed, has: '" +
 | 
				
			||||||
 | 
									wildcard + "' in path '" + fullPath + "'")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// check if the wildcard has a name
 | 
				
			||||||
 | 
							if len(wildcard) < 2 {
 | 
				
			||||||
 | 
								panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if wildcard[0] == ':' { // param
 | 
				
			||||||
 | 
								if i > 0 {
 | 
				
			||||||
 | 
									// Insert prefix before the current wildcard
 | 
				
			||||||
 | 
									n.path = path[:i]
 | 
				
			||||||
 | 
									path = path[i:]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								child := &node{
 | 
				
			||||||
 | 
									nType:    param,
 | 
				
			||||||
 | 
									path:     wildcard,
 | 
				
			||||||
 | 
									fullPath: fullPath,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								n.addChild(child)
 | 
				
			||||||
 | 
								n.wildChild = true
 | 
				
			||||||
 | 
								n = child
 | 
				
			||||||
 | 
								n.priority++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// if the path doesn't end with the wildcard, then there
 | 
				
			||||||
 | 
								// will be another subpath starting with '/'
 | 
				
			||||||
 | 
								if len(wildcard) < len(path) {
 | 
				
			||||||
 | 
									path = path[len(wildcard):]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									child := &node{
 | 
				
			||||||
 | 
										priority: 1,
 | 
				
			||||||
 | 
										fullPath: fullPath,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									n.addChild(child)
 | 
				
			||||||
 | 
									n = child
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Otherwise we're done. Insert the handle in the new leaf
 | 
				
			||||||
 | 
								n.handlers = handlers
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// catchAll
 | 
				
			||||||
 | 
							if i+len(wildcard) != len(path) {
 | 
				
			||||||
 | 
								panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
 | 
				
			||||||
 | 
								pathSeg := ""
 | 
				
			||||||
 | 
								if len(n.children) != 0 {
 | 
				
			||||||
 | 
									pathSeg = strings.SplitN(n.children[0].path, "/", 2)[0]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								panic("catch-all wildcard '" + path +
 | 
				
			||||||
 | 
									"' in new path '" + fullPath +
 | 
				
			||||||
 | 
									"' conflicts with existing path segment '" + pathSeg +
 | 
				
			||||||
 | 
									"' in existing prefix '" + n.path + pathSeg +
 | 
				
			||||||
 | 
									"'")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// currently fixed width 1 for '/'
 | 
				
			||||||
 | 
							i--
 | 
				
			||||||
 | 
							if path[i] != '/' {
 | 
				
			||||||
 | 
								panic("no / before catch-all in path '" + fullPath + "'")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							n.path = path[:i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// First node: catchAll node with empty path
 | 
				
			||||||
 | 
							child := &node{
 | 
				
			||||||
 | 
								wildChild: true,
 | 
				
			||||||
 | 
								nType:     catchAll,
 | 
				
			||||||
 | 
								fullPath:  fullPath,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							n.addChild(child)
 | 
				
			||||||
 | 
							n.indices = string('/')
 | 
				
			||||||
 | 
							n = child
 | 
				
			||||||
 | 
							n.priority++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// second node: node holding the variable
 | 
				
			||||||
 | 
							child = &node{
 | 
				
			||||||
 | 
								path:     path[i:],
 | 
				
			||||||
 | 
								nType:    catchAll,
 | 
				
			||||||
 | 
								handlers: handlers,
 | 
				
			||||||
 | 
								priority: 1,
 | 
				
			||||||
 | 
								fullPath: fullPath,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							n.children = []*node{child}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If no wildcard was found, simply insert the path and handle
 | 
				
			||||||
 | 
						n.path = path
 | 
				
			||||||
 | 
						n.handlers = handlers
 | 
				
			||||||
 | 
						n.fullPath = fullPath
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// nodeValue holds return values of (*Node).getValue method
 | 
				
			||||||
 | 
					type nodeValue struct {
 | 
				
			||||||
 | 
						handlers []HandlerFunc
 | 
				
			||||||
 | 
						params   *Params
 | 
				
			||||||
 | 
						tsr      bool
 | 
				
			||||||
 | 
						fullPath string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type skippedNode struct {
 | 
				
			||||||
 | 
						path        string
 | 
				
			||||||
 | 
						node        *node
 | 
				
			||||||
 | 
						paramsCount int16
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns the handle registered with the given path (key). The values of
 | 
				
			||||||
 | 
					// wildcards are saved to a map.
 | 
				
			||||||
 | 
					// If no handle can be found, a TSR (trailing slash redirect) recommendation is
 | 
				
			||||||
 | 
					// made if a handle exists with an extra (without the) trailing slash for the
 | 
				
			||||||
 | 
					// given path.
 | 
				
			||||||
 | 
					func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
 | 
				
			||||||
 | 
						var globalParamsCount int16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					walk: // Outer loop for walking the tree
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							prefix := n.path
 | 
				
			||||||
 | 
							if len(path) > len(prefix) {
 | 
				
			||||||
 | 
								if path[:len(prefix)] == prefix {
 | 
				
			||||||
 | 
									path = path[len(prefix):]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Try all the non-wildcard children first by matching the indices
 | 
				
			||||||
 | 
									idxc := path[0]
 | 
				
			||||||
 | 
									for i, c := range []byte(n.indices) {
 | 
				
			||||||
 | 
										if c == idxc {
 | 
				
			||||||
 | 
											//  strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
 | 
				
			||||||
 | 
											if n.wildChild {
 | 
				
			||||||
 | 
												index := len(*skippedNodes)
 | 
				
			||||||
 | 
												*skippedNodes = (*skippedNodes)[:index+1]
 | 
				
			||||||
 | 
												(*skippedNodes)[index] = skippedNode{
 | 
				
			||||||
 | 
													path: prefix + path,
 | 
				
			||||||
 | 
													node: &node{
 | 
				
			||||||
 | 
														path:      n.path,
 | 
				
			||||||
 | 
														wildChild: n.wildChild,
 | 
				
			||||||
 | 
														nType:     n.nType,
 | 
				
			||||||
 | 
														priority:  n.priority,
 | 
				
			||||||
 | 
														children:  n.children,
 | 
				
			||||||
 | 
														handlers:  n.handlers,
 | 
				
			||||||
 | 
														fullPath:  n.fullPath,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													paramsCount: globalParamsCount,
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											n = n.children[i]
 | 
				
			||||||
 | 
											continue walk
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if !n.wildChild {
 | 
				
			||||||
 | 
										// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
 | 
				
			||||||
 | 
										// the current node needs to roll back to last valid skippedNode
 | 
				
			||||||
 | 
										if path != "/" {
 | 
				
			||||||
 | 
											for length := len(*skippedNodes); length > 0; length-- {
 | 
				
			||||||
 | 
												skippedNode := (*skippedNodes)[length-1]
 | 
				
			||||||
 | 
												*skippedNodes = (*skippedNodes)[:length-1]
 | 
				
			||||||
 | 
												if strings.HasSuffix(skippedNode.path, path) {
 | 
				
			||||||
 | 
													path = skippedNode.path
 | 
				
			||||||
 | 
													n = skippedNode.node
 | 
				
			||||||
 | 
													if value.params != nil {
 | 
				
			||||||
 | 
														*value.params = (*value.params)[:skippedNode.paramsCount]
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
													globalParamsCount = skippedNode.paramsCount
 | 
				
			||||||
 | 
													continue walk
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Nothing found.
 | 
				
			||||||
 | 
										// We can recommend to redirect to the same URL without a
 | 
				
			||||||
 | 
										// trailing slash if a leaf exists for that path.
 | 
				
			||||||
 | 
										value.tsr = path == "/" && n.handlers != nil
 | 
				
			||||||
 | 
										return value
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Handle wildcard child, which is always at the end of the array
 | 
				
			||||||
 | 
									n = n.children[len(n.children)-1]
 | 
				
			||||||
 | 
									globalParamsCount++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									switch n.nType {
 | 
				
			||||||
 | 
									case param:
 | 
				
			||||||
 | 
										// fix truncate the parameter
 | 
				
			||||||
 | 
										// tree_test.go  line: 204
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Find param end (either '/' or path end)
 | 
				
			||||||
 | 
										end := 0
 | 
				
			||||||
 | 
										for end < len(path) && path[end] != '/' {
 | 
				
			||||||
 | 
											end++
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Save param value
 | 
				
			||||||
 | 
										if params != nil {
 | 
				
			||||||
 | 
											// Preallocate capacity if necessary
 | 
				
			||||||
 | 
											if cap(*params) < int(globalParamsCount) {
 | 
				
			||||||
 | 
												newParams := make(Params, len(*params), globalParamsCount)
 | 
				
			||||||
 | 
												copy(newParams, *params)
 | 
				
			||||||
 | 
												*params = newParams
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if value.params == nil {
 | 
				
			||||||
 | 
												value.params = params
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											// Expand slice within preallocated capacity
 | 
				
			||||||
 | 
											i := len(*value.params)
 | 
				
			||||||
 | 
											*value.params = (*value.params)[:i+1]
 | 
				
			||||||
 | 
											val := path[:end]
 | 
				
			||||||
 | 
											if unescape {
 | 
				
			||||||
 | 
												if v, err := url.QueryUnescape(val); err == nil {
 | 
				
			||||||
 | 
													val = v
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											(*value.params)[i] = Param{
 | 
				
			||||||
 | 
												Key:   n.path[1:],
 | 
				
			||||||
 | 
												Value: val,
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// we need to go deeper!
 | 
				
			||||||
 | 
										if end < len(path) {
 | 
				
			||||||
 | 
											if len(n.children) > 0 {
 | 
				
			||||||
 | 
												path = path[end:]
 | 
				
			||||||
 | 
												n = n.children[0]
 | 
				
			||||||
 | 
												continue walk
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// ... but we can't
 | 
				
			||||||
 | 
											value.tsr = len(path) == end+1
 | 
				
			||||||
 | 
											return value
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if value.handlers = n.handlers; value.handlers != nil {
 | 
				
			||||||
 | 
											value.fullPath = n.fullPath
 | 
				
			||||||
 | 
											return value
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if len(n.children) == 1 {
 | 
				
			||||||
 | 
											// No handle found. Check if a handle for this path + a
 | 
				
			||||||
 | 
											// trailing slash exists for TSR recommendation
 | 
				
			||||||
 | 
											n = n.children[0]
 | 
				
			||||||
 | 
											value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									case catchAll:
 | 
				
			||||||
 | 
										// Save param value
 | 
				
			||||||
 | 
										if params != nil {
 | 
				
			||||||
 | 
											// Preallocate capacity if necessary
 | 
				
			||||||
 | 
											if cap(*params) < int(globalParamsCount) {
 | 
				
			||||||
 | 
												newParams := make(Params, len(*params), globalParamsCount)
 | 
				
			||||||
 | 
												copy(newParams, *params)
 | 
				
			||||||
 | 
												*params = newParams
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if value.params == nil {
 | 
				
			||||||
 | 
												value.params = params
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											// Expand slice within preallocated capacity
 | 
				
			||||||
 | 
											i := len(*value.params)
 | 
				
			||||||
 | 
											*value.params = (*value.params)[:i+1]
 | 
				
			||||||
 | 
											val := path
 | 
				
			||||||
 | 
											if unescape {
 | 
				
			||||||
 | 
												if v, err := url.QueryUnescape(path); err == nil {
 | 
				
			||||||
 | 
													val = v
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											(*value.params)[i] = Param{
 | 
				
			||||||
 | 
												Key:   n.path[2:],
 | 
				
			||||||
 | 
												Value: val,
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										value.handlers = n.handlers
 | 
				
			||||||
 | 
										value.fullPath = n.fullPath
 | 
				
			||||||
 | 
										return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
										panic("invalid node type")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if path == prefix {
 | 
				
			||||||
 | 
								// If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
 | 
				
			||||||
 | 
								// the current node needs to roll back to last valid skippedNode
 | 
				
			||||||
 | 
								if n.handlers == nil && path != "/" {
 | 
				
			||||||
 | 
									for length := len(*skippedNodes); length > 0; length-- {
 | 
				
			||||||
 | 
										skippedNode := (*skippedNodes)[length-1]
 | 
				
			||||||
 | 
										*skippedNodes = (*skippedNodes)[:length-1]
 | 
				
			||||||
 | 
										if strings.HasSuffix(skippedNode.path, path) {
 | 
				
			||||||
 | 
											path = skippedNode.path
 | 
				
			||||||
 | 
											n = skippedNode.node
 | 
				
			||||||
 | 
											if value.params != nil {
 | 
				
			||||||
 | 
												*value.params = (*value.params)[:skippedNode.paramsCount]
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											globalParamsCount = skippedNode.paramsCount
 | 
				
			||||||
 | 
											continue walk
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									//	n = latestNode.children[len(latestNode.children)-1]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// We should have reached the node containing the handle.
 | 
				
			||||||
 | 
								// Check if this node has a handle registered.
 | 
				
			||||||
 | 
								if value.handlers = n.handlers; value.handlers != nil {
 | 
				
			||||||
 | 
									value.fullPath = n.fullPath
 | 
				
			||||||
 | 
									return value
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If there is no handle for this route, but this route has a
 | 
				
			||||||
 | 
								// wildcard child, there must be a handle for this path with an
 | 
				
			||||||
 | 
								// additional trailing slash
 | 
				
			||||||
 | 
								if path == "/" && n.wildChild && n.nType != root {
 | 
				
			||||||
 | 
									value.tsr = true
 | 
				
			||||||
 | 
									return value
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if path == "/" && n.nType == static {
 | 
				
			||||||
 | 
									value.tsr = true
 | 
				
			||||||
 | 
									return value
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// No handle found. Check if a handle for this path + a
 | 
				
			||||||
 | 
								// trailing slash exists for trailing slash recommendation
 | 
				
			||||||
 | 
								for i, c := range []byte(n.indices) {
 | 
				
			||||||
 | 
									if c == '/' {
 | 
				
			||||||
 | 
										n = n.children[i]
 | 
				
			||||||
 | 
										value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
 | 
				
			||||||
 | 
											(n.nType == catchAll && n.children[0].handlers != nil)
 | 
				
			||||||
 | 
										return value
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return value
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Nothing found. We can recommend to redirect to the same URL with an
 | 
				
			||||||
 | 
							// extra trailing slash if a leaf exists for that path
 | 
				
			||||||
 | 
							value.tsr = path == "/" ||
 | 
				
			||||||
 | 
								(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
 | 
				
			||||||
 | 
									path == prefix[:len(prefix)-1] && n.handlers != nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// roll back to last valid skippedNode
 | 
				
			||||||
 | 
							if !value.tsr && path != "/" {
 | 
				
			||||||
 | 
								for length := len(*skippedNodes); length > 0; length-- {
 | 
				
			||||||
 | 
									skippedNode := (*skippedNodes)[length-1]
 | 
				
			||||||
 | 
									*skippedNodes = (*skippedNodes)[:length-1]
 | 
				
			||||||
 | 
									if strings.HasSuffix(skippedNode.path, path) {
 | 
				
			||||||
 | 
										path = skippedNode.path
 | 
				
			||||||
 | 
										n = skippedNode.node
 | 
				
			||||||
 | 
										if value.params != nil {
 | 
				
			||||||
 | 
											*value.params = (*value.params)[:skippedNode.paramsCount]
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										globalParamsCount = skippedNode.paramsCount
 | 
				
			||||||
 | 
										continue walk
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return value
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Makes a case-insensitive lookup of the given path and tries to find a handler.
 | 
				
			||||||
 | 
					// It can optionally also fix trailing slashes.
 | 
				
			||||||
 | 
					// It returns the case-corrected path and a bool indicating whether the lookup
 | 
				
			||||||
 | 
					// was successful.
 | 
				
			||||||
 | 
					func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]byte, bool) {
 | 
				
			||||||
 | 
						const stackBufSize = 128
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Use a static sized buffer on the stack in the common case.
 | 
				
			||||||
 | 
						// If the path is too long, allocate a buffer on the heap instead.
 | 
				
			||||||
 | 
						buf := make([]byte, 0, stackBufSize)
 | 
				
			||||||
 | 
						if length := len(path) + 1; length > stackBufSize {
 | 
				
			||||||
 | 
							buf = make([]byte, 0, length)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ciPath := n.findCaseInsensitivePathRec(
 | 
				
			||||||
 | 
							path,
 | 
				
			||||||
 | 
							buf,       // Preallocate enough memory for new path
 | 
				
			||||||
 | 
							[4]byte{}, // Empty rune buffer
 | 
				
			||||||
 | 
							fixTrailingSlash,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ciPath, ciPath != nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Shift bytes in array by n bytes left
 | 
				
			||||||
 | 
					func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
 | 
				
			||||||
 | 
						switch n {
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
 | 
							return rb
 | 
				
			||||||
 | 
						case 1:
 | 
				
			||||||
 | 
							return [4]byte{rb[1], rb[2], rb[3], 0}
 | 
				
			||||||
 | 
						case 2:
 | 
				
			||||||
 | 
							return [4]byte{rb[2], rb[3]}
 | 
				
			||||||
 | 
						case 3:
 | 
				
			||||||
 | 
							return [4]byte{rb[3]}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return [4]byte{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Recursive case-insensitive lookup function used by n.findCaseInsensitivePath
 | 
				
			||||||
 | 
					func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte {
 | 
				
			||||||
 | 
						npLen := len(n.path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					walk: // Outer loop for walking the tree
 | 
				
			||||||
 | 
						for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {
 | 
				
			||||||
 | 
							// Add common prefix to result
 | 
				
			||||||
 | 
							oldPath := path
 | 
				
			||||||
 | 
							path = path[npLen:]
 | 
				
			||||||
 | 
							ciPath = append(ciPath, n.path...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(path) == 0 {
 | 
				
			||||||
 | 
								// We should have reached the node containing the handle.
 | 
				
			||||||
 | 
								// Check if this node has a handle registered.
 | 
				
			||||||
 | 
								if n.handlers != nil {
 | 
				
			||||||
 | 
									return ciPath
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// No handle found.
 | 
				
			||||||
 | 
								// Try to fix the path by adding a trailing slash
 | 
				
			||||||
 | 
								if fixTrailingSlash {
 | 
				
			||||||
 | 
									for i, c := range []byte(n.indices) {
 | 
				
			||||||
 | 
										if c == '/' {
 | 
				
			||||||
 | 
											n = n.children[i]
 | 
				
			||||||
 | 
											if (len(n.path) == 1 && n.handlers != nil) ||
 | 
				
			||||||
 | 
												(n.nType == catchAll && n.children[0].handlers != nil) {
 | 
				
			||||||
 | 
												return append(ciPath, '/')
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
						return nil
 | 
											return nil
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func (n *_node) travel(list *([]*_node)) {
 | 
					 | 
				
			||||||
	if n.pattern != "" {
 | 
					 | 
				
			||||||
		*list = append(*list, n)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, child := range n.children {
 | 
					 | 
				
			||||||
		child.travel(list)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (n *_node) matchChild(part string) *_node {
 | 
					 | 
				
			||||||
	for _, child := range n.children {
 | 
					 | 
				
			||||||
		if child.part == part || child.isWild {
 | 
					 | 
				
			||||||
			return child
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (n *_node) matchChildren(part string) []*_node {
 | 
							// If this node does not have a wildcard (param or catchAll) child,
 | 
				
			||||||
	nodes := make([]*_node, 0)
 | 
							// we can just look up the next child node and continue to walk down
 | 
				
			||||||
	for _, child := range n.children {
 | 
							// the tree
 | 
				
			||||||
		if child.part == part || child.isWild {
 | 
							if !n.wildChild {
 | 
				
			||||||
			nodes = append(nodes, child)
 | 
								// Skip rune bytes already processed
 | 
				
			||||||
 | 
								rb = shiftNRuneBytes(rb, npLen)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if rb[0] != 0 {
 | 
				
			||||||
 | 
									// Old rune not finished
 | 
				
			||||||
 | 
									idxc := rb[0]
 | 
				
			||||||
 | 
									for i, c := range []byte(n.indices) {
 | 
				
			||||||
 | 
										if c == idxc {
 | 
				
			||||||
 | 
											// continue with child node
 | 
				
			||||||
 | 
											n = n.children[i]
 | 
				
			||||||
 | 
											npLen = len(n.path)
 | 
				
			||||||
 | 
											continue walk
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
	return nodes
 | 
								} else {
 | 
				
			||||||
 | 
									// Process a new rune
 | 
				
			||||||
 | 
									var rv rune
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Find rune start.
 | 
				
			||||||
 | 
									// Runes are up to 4 byte long,
 | 
				
			||||||
 | 
									// -4 would definitely be another rune.
 | 
				
			||||||
 | 
									var off int
 | 
				
			||||||
 | 
									for max := min(npLen, 3); off < max; off++ {
 | 
				
			||||||
 | 
										if i := npLen - off; utf8.RuneStart(oldPath[i]) {
 | 
				
			||||||
 | 
											// read rune from cached path
 | 
				
			||||||
 | 
											rv, _ = utf8.DecodeRuneInString(oldPath[i:])
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Calculate lowercase bytes of current rune
 | 
				
			||||||
 | 
									lo := unicode.ToLower(rv)
 | 
				
			||||||
 | 
									utf8.EncodeRune(rb[:], lo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Skip already processed bytes
 | 
				
			||||||
 | 
									rb = shiftNRuneBytes(rb, off)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									idxc := rb[0]
 | 
				
			||||||
 | 
									for i, c := range []byte(n.indices) {
 | 
				
			||||||
 | 
										// Lowercase matches
 | 
				
			||||||
 | 
										if c == idxc {
 | 
				
			||||||
 | 
											// must use a recursive approach since both the
 | 
				
			||||||
 | 
											// uppercase byte and the lowercase byte might exist
 | 
				
			||||||
 | 
											// as an index
 | 
				
			||||||
 | 
											if out := n.children[i].findCaseInsensitivePathRec(
 | 
				
			||||||
 | 
												path, ciPath, rb, fixTrailingSlash,
 | 
				
			||||||
 | 
											); out != nil {
 | 
				
			||||||
 | 
												return out
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// If we found no match, the same for the uppercase rune,
 | 
				
			||||||
 | 
									// if it differs
 | 
				
			||||||
 | 
									if up := unicode.ToUpper(rv); up != lo {
 | 
				
			||||||
 | 
										utf8.EncodeRune(rb[:], up)
 | 
				
			||||||
 | 
										rb = shiftNRuneBytes(rb, off)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										idxc := rb[0]
 | 
				
			||||||
 | 
										for i, c := range []byte(n.indices) {
 | 
				
			||||||
 | 
											// Uppercase matches
 | 
				
			||||||
 | 
											if c == idxc {
 | 
				
			||||||
 | 
												// Continue with child node
 | 
				
			||||||
 | 
												n = n.children[i]
 | 
				
			||||||
 | 
												npLen = len(n.path)
 | 
				
			||||||
 | 
												continue walk
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Nothing found. We can recommend to redirect to the same URL
 | 
				
			||||||
 | 
								// without a trailing slash if a leaf exists for that path
 | 
				
			||||||
 | 
								if fixTrailingSlash && path == "/" && n.handlers != nil {
 | 
				
			||||||
 | 
									return ciPath
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							n = n.children[0]
 | 
				
			||||||
 | 
							switch n.nType {
 | 
				
			||||||
 | 
							case param:
 | 
				
			||||||
 | 
								// Find param end (either '/' or path end)
 | 
				
			||||||
 | 
								end := 0
 | 
				
			||||||
 | 
								for end < len(path) && path[end] != '/' {
 | 
				
			||||||
 | 
									end++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Add param value to case insensitive path
 | 
				
			||||||
 | 
								ciPath = append(ciPath, path[:end]...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// We need to go deeper!
 | 
				
			||||||
 | 
								if end < len(path) {
 | 
				
			||||||
 | 
									if len(n.children) > 0 {
 | 
				
			||||||
 | 
										// Continue with child node
 | 
				
			||||||
 | 
										n = n.children[0]
 | 
				
			||||||
 | 
										npLen = len(n.path)
 | 
				
			||||||
 | 
										path = path[end:]
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// ... but we can't
 | 
				
			||||||
 | 
									if fixTrailingSlash && len(path) == end+1 {
 | 
				
			||||||
 | 
										return ciPath
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if n.handlers != nil {
 | 
				
			||||||
 | 
									return ciPath
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if fixTrailingSlash && len(n.children) == 1 {
 | 
				
			||||||
 | 
									// No handle found. Check if a handle for this path + a
 | 
				
			||||||
 | 
									// trailing slash exists
 | 
				
			||||||
 | 
									n = n.children[0]
 | 
				
			||||||
 | 
									if n.path == "/" && n.handlers != nil {
 | 
				
			||||||
 | 
										return append(ciPath, '/')
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case catchAll:
 | 
				
			||||||
 | 
								return append(ciPath, path...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								panic("invalid node type")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Nothing found.
 | 
				
			||||||
 | 
						// Try to fix the path by adding / removing a trailing slash
 | 
				
			||||||
 | 
						if fixTrailingSlash {
 | 
				
			||||||
 | 
							if path == "/" {
 | 
				
			||||||
 | 
								return ciPath
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(path)+1 == npLen && n.path[len(path)] == '/' &&
 | 
				
			||||||
 | 
								strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handlers != nil {
 | 
				
			||||||
 | 
								return append(ciPath, n.path...)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										151
									
								
								util.go
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								util.go
									
									
									
									
									
								
							@@ -13,8 +13,6 @@ const (
 | 
				
			|||||||
	MIMETextJavaScript  = "text/javascript"
 | 
						MIMETextJavaScript  = "text/javascript"
 | 
				
			||||||
	MIMEApplicationXML  = "application/xml"
 | 
						MIMEApplicationXML  = "application/xml"
 | 
				
			||||||
	MIMEApplicationJSON = "application/json"
 | 
						MIMEApplicationJSON = "application/json"
 | 
				
			||||||
	// Deprecated: use MIMETextJavaScript instead
 | 
					 | 
				
			||||||
	MIMEApplicationJavaScript = "application/javascript"
 | 
					 | 
				
			||||||
	MIMEApplicationForm = "application/x-www-form-urlencoded"
 | 
						MIMEApplicationForm = "application/x-www-form-urlencoded"
 | 
				
			||||||
	MIMEOctetStream     = "application/octet-stream"
 | 
						MIMEOctetStream     = "application/octet-stream"
 | 
				
			||||||
	MIMEMultipartForm   = "multipart/form-data"
 | 
						MIMEMultipartForm   = "multipart/form-data"
 | 
				
			||||||
@@ -29,18 +27,6 @@ const (
 | 
				
			|||||||
	MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
 | 
						MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func verifyHandlers(path string, handlers ...HandlerFunc) {
 | 
					 | 
				
			||||||
	if len(handlers) == 0 {
 | 
					 | 
				
			||||||
		panic(fmt.Sprintf("missing handler in route: %s", path))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, handler := range handlers {
 | 
					 | 
				
			||||||
		if handler == nil {
 | 
					 | 
				
			||||||
			panic(fmt.Sprintf("nil handler found in route: %s", path))
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// parseVendorSpecificContentType check if content type is vendor specific and
 | 
					// parseVendorSpecificContentType check if content type is vendor specific and
 | 
				
			||||||
// if it is parsable to any known types. If it's not vendor specific then returns
 | 
					// if it is parsable to any known types. If it's not vendor specific then returns
 | 
				
			||||||
// the original content type.
 | 
					// the original content type.
 | 
				
			||||||
@@ -79,3 +65,140 @@ func parseToStruct(aliasTag string, out interface{}, data map[string][]string) e
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func elsePanic(guard bool, text string) {
 | 
				
			||||||
 | 
						if !guard {
 | 
				
			||||||
 | 
							panic(text)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func cleanPath(p string) string {
 | 
				
			||||||
 | 
						const stackBufSize = 128
 | 
				
			||||||
 | 
						// Turn empty string into "/"
 | 
				
			||||||
 | 
						if p == "" {
 | 
				
			||||||
 | 
							return "/"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Reasonably sized buffer on stack to avoid allocations in the common case.
 | 
				
			||||||
 | 
						// If a larger buffer is required, it gets allocated dynamically.
 | 
				
			||||||
 | 
						buf := make([]byte, 0, stackBufSize)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						n := len(p)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Invariants:
 | 
				
			||||||
 | 
						//      reading from path; r is index of next byte to process.
 | 
				
			||||||
 | 
						//      writing to buf; w is index of next byte to write.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// path must start with '/'
 | 
				
			||||||
 | 
						r := 1
 | 
				
			||||||
 | 
						w := 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if p[0] != '/' {
 | 
				
			||||||
 | 
							r = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if n+1 > stackBufSize {
 | 
				
			||||||
 | 
								buf = make([]byte, n+1)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								buf = buf[:n+1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							buf[0] = '/'
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						trailing := n > 1 && p[n-1] == '/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// A bit more clunky without a 'lazybuf' like the path package, but the loop
 | 
				
			||||||
 | 
						// gets completely inlined (bufApp calls).
 | 
				
			||||||
 | 
						// loop has no expensive function calls (except 1x make)		// So in contrast to the path package this loop has no expensive function
 | 
				
			||||||
 | 
						// calls (except make, if needed).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for r < n {
 | 
				
			||||||
 | 
							switch {
 | 
				
			||||||
 | 
							case p[r] == '/':
 | 
				
			||||||
 | 
								// empty path element, trailing slash is added after the end
 | 
				
			||||||
 | 
								r++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case p[r] == '.' && r+1 == n:
 | 
				
			||||||
 | 
								trailing = true
 | 
				
			||||||
 | 
								r++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case p[r] == '.' && p[r+1] == '/':
 | 
				
			||||||
 | 
								// . element
 | 
				
			||||||
 | 
								r += 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
 | 
				
			||||||
 | 
								// .. element: remove to last /
 | 
				
			||||||
 | 
								r += 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if w > 1 {
 | 
				
			||||||
 | 
									// can backtrack
 | 
				
			||||||
 | 
									w--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if len(buf) == 0 {
 | 
				
			||||||
 | 
										for w > 1 && p[w] != '/' {
 | 
				
			||||||
 | 
											w--
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										for w > 1 && buf[w] != '/' {
 | 
				
			||||||
 | 
											w--
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								// Real path element.
 | 
				
			||||||
 | 
								// Add slash if needed
 | 
				
			||||||
 | 
								if w > 1 {
 | 
				
			||||||
 | 
									bufApp(&buf, p, w, '/')
 | 
				
			||||||
 | 
									w++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Copy element
 | 
				
			||||||
 | 
								for r < n && p[r] != '/' {
 | 
				
			||||||
 | 
									bufApp(&buf, p, w, p[r])
 | 
				
			||||||
 | 
									w++
 | 
				
			||||||
 | 
									r++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Re-append trailing slash
 | 
				
			||||||
 | 
						if trailing && w > 1 {
 | 
				
			||||||
 | 
							bufApp(&buf, p, w, '/')
 | 
				
			||||||
 | 
							w++
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If the original string was not modified (or only shortened at the end),
 | 
				
			||||||
 | 
						// return the respective substring of the original string.
 | 
				
			||||||
 | 
						// Otherwise return a new string from the buffer.
 | 
				
			||||||
 | 
						if len(buf) == 0 {
 | 
				
			||||||
 | 
							return p[:w]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return string(buf[:w])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Internal helper to lazily create a buffer if necessary.
 | 
				
			||||||
 | 
					// Calls to this function get inlined.
 | 
				
			||||||
 | 
					func bufApp(buf *[]byte, s string, w int, c byte) {
 | 
				
			||||||
 | 
						b := *buf
 | 
				
			||||||
 | 
						if len(b) == 0 {
 | 
				
			||||||
 | 
							// No modification of the original string so far.
 | 
				
			||||||
 | 
							// If the next character is the same as in the original string, we do
 | 
				
			||||||
 | 
							// not yet have to allocate a buffer.
 | 
				
			||||||
 | 
							if s[w] == c {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Otherwise use either the stack buffer, if it is large enough, or
 | 
				
			||||||
 | 
							// allocate a new buffer on the heap, and copy all previous characters.
 | 
				
			||||||
 | 
							length := len(s)
 | 
				
			||||||
 | 
							if length > cap(b) {
 | 
				
			||||||
 | 
								*buf = make([]byte, length)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								*buf = (*buf)[:length]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							b = *buf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							copy(b, s[:w])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						b[w] = c
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,2 +1,14 @@
 | 
				
			|||||||
### basic - get
 | 
					### basic - get
 | 
				
			||||||
GET http://127.0.0.1/hello/nf
 | 
					GET http://127.0.0.1/hello/nf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### test resp error
 | 
				
			||||||
 | 
					GET http://127.0.0.1/error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### test basic post
 | 
				
			||||||
 | 
					POST http://127.0.0.1/data
 | 
				
			||||||
 | 
					Content-Type: application/json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "nice"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,17 +3,15 @@ package main
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"github.com/loveuer/nf"
 | 
						"github.com/loveuer/nf"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"net"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	app := nf.New()
 | 
						app := nf.New(nf.Config{EnableNotImplementHandler: true})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	app.Get("/hello/:name", func(c *nf.Ctx) error {
 | 
						api := app.Group("/api")
 | 
				
			||||||
		name := c.Param("name")
 | 
						api.Get("/1", func(c *nf.Ctx) error {
 | 
				
			||||||
		return c.JSON(nf.Map{"status": 200, "data": "hello, " + name})
 | 
							return c.SendString("nice")
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ln, _ := net.Listen("tcp", ":80")
 | 
						log.Fatal(app.Run(":80"))
 | 
				
			||||||
	log.Fatal(app.RunListener(ln))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	app := nf.New(nf.Config{BodyLimit: -1})
 | 
						app := nf.New(nf.Config{BodyLimit: 30})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	app.Post("/data", func(c *nf.Ctx) error {
 | 
						app.Post("/data", func(c *nf.Ctx) error {
 | 
				
			||||||
		type Req struct {
 | 
							type Req struct {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								xtest/midd/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								xtest/midd/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/loveuer/nf"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						app := nf.New(nf.Config{DisableLogger: false})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.Get("/hello", func(c *nf.Ctx) error {
 | 
				
			||||||
 | 
							return c.SendString("world")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.Use(ml())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Fatal(app.Run(":80"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ml() nf.HandlerFunc {
 | 
				
			||||||
 | 
						return func(c *nf.Ctx) error {
 | 
				
			||||||
 | 
							index := []byte(`<h1>my not found</h1>`)
 | 
				
			||||||
 | 
							c.Set("Content-Type", "text/html")
 | 
				
			||||||
 | 
							c.Status(403)
 | 
				
			||||||
 | 
							_, err := c.Write(index)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										34
									
								
								xtest/midd2/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								xtest/midd2/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"github.com/loveuer/nf"
 | 
				
			||||||
 | 
						"github.com/loveuer/nf/nft/resp"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						app := nf.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						api := app.Group("/api")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						api.Get("/hello",
 | 
				
			||||||
 | 
							auth(),
 | 
				
			||||||
 | 
							func(c *nf.Ctx) error {
 | 
				
			||||||
 | 
								return resp.Resp403(c, errors.New("in hello"))
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Fatal(app.Run(":80"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func auth() nf.HandlerFunc {
 | 
				
			||||||
 | 
						return func(c *nf.Ctx) error {
 | 
				
			||||||
 | 
							token := c.Query("token")
 | 
				
			||||||
 | 
							if token != "zyp" {
 | 
				
			||||||
 | 
								return resp.Resp401(c, errors.New("no auth"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return c.Next()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								xtest/multihandler/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								xtest/multihandler/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/loveuer/nf"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						app := nf.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.Get("/nice", h1, h2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Fatal(app.Run(":80"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func h1(c *nf.Ctx) error {
 | 
				
			||||||
 | 
						you := c.Query("to")
 | 
				
			||||||
 | 
						if you == "you" {
 | 
				
			||||||
 | 
							return c.JSON(nf.Map{"status": 201, "msg": "nice to meet you"})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//return c.Next()
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func h2(c *nf.Ctx) error {
 | 
				
			||||||
 | 
						return c.JSON(nf.Map{"status": 200, "msg": "hello world"})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								xtest/multihandler/req.http
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								xtest/multihandler/req.http
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					### test multi handlers no next
 | 
				
			||||||
 | 
					GET http://127.0.0.1:3333/nice?to=you
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### test multi handlers do next
 | 
				
			||||||
 | 
					GET http://127.0.0.1:3333/nice?to=nf
 | 
				
			||||||
@@ -7,7 +7,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	app := nf.New(nf.Config{
 | 
						app := nf.New(nf.Config{
 | 
				
			||||||
		DisableRecover: true,
 | 
							DisableRecover: false,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	app.Get("/hello/:name", func(c *nf.Ctx) error {
 | 
						app.Get("/hello/:name", func(c *nf.Ctx) error {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ func main() {
 | 
				
			|||||||
		var (
 | 
							var (
 | 
				
			||||||
			err error
 | 
								err error
 | 
				
			||||||
			req = new(Req)
 | 
								req = new(Req)
 | 
				
			||||||
			rm  = make(map[string]any)
 | 
								rm  = make(map[string]interface{})
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//if err = c.QueryParser(req); err != nil {
 | 
							//if err = c.QueryParser(req); err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								xtest/quit/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								xtest/quit/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"github.com/loveuer/nf"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						app  = nf.New()
 | 
				
			||||||
 | 
						quit = make(chan bool)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.Get("/name", handleGet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							err := app.Run(":80")
 | 
				
			||||||
 | 
							log.Print("run with err=", err)
 | 
				
			||||||
 | 
							quit <- true
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<-quit
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func handleGet(c *nf.Ctx) error {
 | 
				
			||||||
 | 
						type Req struct {
 | 
				
			||||||
 | 
							Name string   `query:"name"`
 | 
				
			||||||
 | 
							Addr []string `query:"addr"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							err error
 | 
				
			||||||
 | 
							req = Req{}
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = c.QueryParser(&req); err != nil {
 | 
				
			||||||
 | 
							return nf.NewNFError(400, err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if req.Name == "quit" {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								time.Sleep(2 * time.Second)
 | 
				
			||||||
 | 
								log.Print("app quit = ", app.Shutdown(context.TODO()))
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.JSON(nf.Map{"req_map": req})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										119
									
								
								xtest/tls/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								xtest/tls/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"crypto/rsa"
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
 | 
						"crypto/x509"
 | 
				
			||||||
 | 
						"crypto/x509/pkix"
 | 
				
			||||||
 | 
						"encoding/pem"
 | 
				
			||||||
 | 
						"github.com/loveuer/nf"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"math/big"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						app := nf.New(nf.Config{
 | 
				
			||||||
 | 
							DisableHttpErrorLog: true,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.Get("/hello/:name", func(c *nf.Ctx) error {
 | 
				
			||||||
 | 
							return c.SendString("hello, " + c.Param("name"))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						st, _, _ := GenerateTlsConfig()
 | 
				
			||||||
 | 
						log.Fatal(app.RunTLS(":443", st))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GenerateTlsConfig() (serverTLSConf *tls.Config, clientTLSConf *tls.Config, err error) {
 | 
				
			||||||
 | 
						ca := &x509.Certificate{
 | 
				
			||||||
 | 
							SerialNumber: big.NewInt(2019),
 | 
				
			||||||
 | 
							Subject: pkix.Name{
 | 
				
			||||||
 | 
								Organization:  []string{"Company, INC."},
 | 
				
			||||||
 | 
								Country:       []string{"US"},
 | 
				
			||||||
 | 
								Province:      []string{""},
 | 
				
			||||||
 | 
								Locality:      []string{"San Francisco"},
 | 
				
			||||||
 | 
								StreetAddress: []string{"Golden Gate Bridge"},
 | 
				
			||||||
 | 
								PostalCode:    []string{"94016"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							NotBefore:             time.Now(),
 | 
				
			||||||
 | 
							NotAfter:              time.Now().AddDate(99, 0, 0),
 | 
				
			||||||
 | 
							IsCA:                  true,
 | 
				
			||||||
 | 
							ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
 | 
				
			||||||
 | 
							KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
 | 
				
			||||||
 | 
							BasicConstraintsValid: true,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// create our private and public key
 | 
				
			||||||
 | 
						caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// create the CA
 | 
				
			||||||
 | 
						caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// pem encode
 | 
				
			||||||
 | 
						caPEM := new(bytes.Buffer)
 | 
				
			||||||
 | 
						pem.Encode(caPEM, &pem.Block{
 | 
				
			||||||
 | 
							Type:  "CERTIFICATE",
 | 
				
			||||||
 | 
							Bytes: caBytes,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						caPrivKeyPEM := new(bytes.Buffer)
 | 
				
			||||||
 | 
						pem.Encode(caPrivKeyPEM, &pem.Block{
 | 
				
			||||||
 | 
							Type:  "RSA PRIVATE KEY",
 | 
				
			||||||
 | 
							Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						// set up our server certificate
 | 
				
			||||||
 | 
						cert := &x509.Certificate{
 | 
				
			||||||
 | 
							SerialNumber: big.NewInt(2019),
 | 
				
			||||||
 | 
							Subject: pkix.Name{
 | 
				
			||||||
 | 
								Organization:  []string{"Company, INC."},
 | 
				
			||||||
 | 
								Country:       []string{"US"},
 | 
				
			||||||
 | 
								Province:      []string{""},
 | 
				
			||||||
 | 
								Locality:      []string{"San Francisco"},
 | 
				
			||||||
 | 
								StreetAddress: []string{"Golden Gate Bridge"},
 | 
				
			||||||
 | 
								PostalCode:    []string{"94016"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							IPAddresses:  []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
 | 
				
			||||||
 | 
							NotBefore:    time.Now(),
 | 
				
			||||||
 | 
							NotAfter:     time.Now().AddDate(1, 0, 0),
 | 
				
			||||||
 | 
							SubjectKeyId: []byte{1, 2, 3, 4, 6},
 | 
				
			||||||
 | 
							ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
 | 
				
			||||||
 | 
							KeyUsage:     x509.KeyUsageDigitalSignature,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						certPEM := new(bytes.Buffer)
 | 
				
			||||||
 | 
						pem.Encode(certPEM, &pem.Block{
 | 
				
			||||||
 | 
							Type:  "CERTIFICATE",
 | 
				
			||||||
 | 
							Bytes: certBytes,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						certPrivKeyPEM := new(bytes.Buffer)
 | 
				
			||||||
 | 
						pem.Encode(certPrivKeyPEM, &pem.Block{
 | 
				
			||||||
 | 
							Type:  "RSA PRIVATE KEY",
 | 
				
			||||||
 | 
							Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						serverCert, err := tls.X509KeyPair(certPEM.Bytes(), certPrivKeyPEM.Bytes())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						serverTLSConf = &tls.Config{
 | 
				
			||||||
 | 
							Certificates: []tls.Certificate{serverCert},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						certpool := x509.NewCertPool()
 | 
				
			||||||
 | 
						certpool.AppendCertsFromPEM(caPEM.Bytes())
 | 
				
			||||||
 | 
						clientTLSConf = &tls.Config{
 | 
				
			||||||
 | 
							RootCAs: certpool,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user