github.com/gogf/gf/v2@v2.7.4/net/ghttp/ghttp_server_router_serve.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  package ghttp
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"net/http"
    13  	"strings"
    14  
    15  	"github.com/gogf/gf/v2/container/glist"
    16  	"github.com/gogf/gf/v2/encoding/gurl"
    17  	"github.com/gogf/gf/v2/errors/gerror"
    18  	"github.com/gogf/gf/v2/internal/intlog"
    19  	"github.com/gogf/gf/v2/internal/json"
    20  	"github.com/gogf/gf/v2/text/gregex"
    21  )
    22  
    23  // handlerCacheItem is an item just for internal router searching cache.
    24  type handlerCacheItem struct {
    25  	parsedItems []*HandlerItemParsed
    26  	serveItem   *HandlerItemParsed
    27  	hasHook     bool
    28  	hasServe    bool
    29  }
    30  
    31  // serveHandlerKey creates and returns a handler key for router.
    32  func (s *Server) serveHandlerKey(method, path, domain string) string {
    33  	if len(domain) > 0 {
    34  		domain = "@" + domain
    35  	}
    36  	if method == "" {
    37  		return path + strings.ToLower(domain)
    38  	}
    39  	return strings.ToUpper(method) + ":" + path + strings.ToLower(domain)
    40  }
    41  
    42  // getHandlersWithCache searches the router item with cache feature for a given request.
    43  func (s *Server) getHandlersWithCache(r *Request) (parsedItems []*HandlerItemParsed, serveItem *HandlerItemParsed, hasHook, hasServe bool) {
    44  	var (
    45  		ctx    = r.Context()
    46  		method = r.Method
    47  		path   = r.URL.Path
    48  		host   = r.GetHost()
    49  	)
    50  	// In case of, eg:
    51  	// Case 1:
    52  	// 		GET /net/http
    53  	// 		r.URL.Path    : /net/http
    54  	// 		r.URL.RawPath : (empty string)
    55  	// Case 2:
    56  	// 		GET /net%2Fhttp
    57  	// 		r.URL.Path    : /net/http
    58  	// 		r.URL.RawPath : /net%2Fhttp
    59  	if r.URL.RawPath != "" {
    60  		path = r.URL.RawPath
    61  	}
    62  	// Special http method OPTIONS handling.
    63  	// It searches the handler with the request method instead of OPTIONS method.
    64  	if method == http.MethodOptions {
    65  		if v := r.Request.Header.Get("Access-Control-Request-Method"); v != "" {
    66  			method = v
    67  		}
    68  	}
    69  	// Search and cache the router handlers.
    70  	if xUrlPath := r.Header.Get(HeaderXUrlPath); xUrlPath != "" {
    71  		path = xUrlPath
    72  	}
    73  	var handlerCacheKey = s.serveHandlerKey(method, path, host)
    74  	value, err := s.serveCache.GetOrSetFunc(ctx, handlerCacheKey, func(ctx context.Context) (interface{}, error) {
    75  		parsedItems, serveItem, hasHook, hasServe = s.searchHandlers(method, path, host)
    76  		if parsedItems != nil {
    77  			return &handlerCacheItem{parsedItems, serveItem, hasHook, hasServe}, nil
    78  		}
    79  		return nil, nil
    80  	}, routeCacheDuration)
    81  	if err != nil {
    82  		intlog.Errorf(ctx, `%+v`, err)
    83  	}
    84  	if value != nil {
    85  		item := value.Val().(*handlerCacheItem)
    86  		return item.parsedItems, item.serveItem, item.hasHook, item.hasServe
    87  	}
    88  	return
    89  }
    90  
    91  // searchHandlers retrieve and returns the routers with given parameters.
    92  // Note that the returned routers contain serving handler, middleware handlers and hook handlers.
    93  func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*HandlerItemParsed, serveItem *HandlerItemParsed, hasHook, hasServe bool) {
    94  	if len(path) == 0 {
    95  		return nil, nil, false, false
    96  	}
    97  	// In case of double '/' URI, for example:
    98  	// /user//index, //user/index, //user//index//
    99  	var previousIsSep = false
   100  	for i := 0; i < len(path); {
   101  		if path[i] == '/' {
   102  			if previousIsSep {
   103  				path = path[:i] + path[i+1:]
   104  				continue
   105  			} else {
   106  				previousIsSep = true
   107  			}
   108  		} else {
   109  			previousIsSep = false
   110  		}
   111  		i++
   112  	}
   113  	// Split the URL.path to separate parts.
   114  	var array []string
   115  	if strings.EqualFold("/", path) {
   116  		array = []string{"/"}
   117  	} else {
   118  		array = strings.Split(path[1:], "/")
   119  	}
   120  	var (
   121  		lastMiddlewareElem    *glist.Element
   122  		parsedItemList        = glist.New()
   123  		repeatHandlerCheckMap = make(map[int]struct{}, 16)
   124  	)
   125  
   126  	// The default domain has the most priority when iteration.
   127  	// Please see doSetHandler if you want to get known about the structure of serveTree.
   128  	for _, domainItem := range []string{DefaultDomainName, domain} {
   129  		p, ok := s.serveTree[domainItem]
   130  		if !ok {
   131  			continue
   132  		}
   133  		// Make a list array with a capacity of 16.
   134  		lists := make([]*glist.List, 0, 16)
   135  		for i, part := range array {
   136  			// Add all lists of each node to the list array.
   137  			if v, ok := p.(map[string]interface{})["*list"]; ok {
   138  				lists = append(lists, v.(*glist.List))
   139  			}
   140  			if v, ok := p.(map[string]interface{})[part]; ok {
   141  				// Loop to the next node by certain key name.
   142  				p = v
   143  				if i == len(array)-1 {
   144  					if v, ok := p.(map[string]interface{})["*list"]; ok {
   145  						lists = append(lists, v.(*glist.List))
   146  						break
   147  					}
   148  				}
   149  			} else if v, ok := p.(map[string]interface{})["*fuzz"]; ok {
   150  				// Loop to the next node by fuzzy node item.
   151  				p = v
   152  			}
   153  			if i == len(array)-1 {
   154  				// It here also checks the fuzzy item,
   155  				// for rule case like: "/user/*action" matches to "/user".
   156  				if v, ok := p.(map[string]interface{})["*fuzz"]; ok {
   157  					p = v
   158  				}
   159  				// The leaf must have a list item. It adds the list to the list array.
   160  				if v, ok := p.(map[string]interface{})["*list"]; ok {
   161  					lists = append(lists, v.(*glist.List))
   162  				}
   163  			}
   164  		}
   165  
   166  		// OK, let's loop the result list array, adding the handler item to the result handler result array.
   167  		// As the tail of the list array has the most priority, it iterates the list array from its tail to head.
   168  		for i := len(lists) - 1; i >= 0; i-- {
   169  			for e := lists[i].Front(); e != nil; e = e.Next() {
   170  				item := e.Value.(*HandlerItem)
   171  				// Filter repeated handler items, especially the middleware and hook handlers.
   172  				// It is necessary, do not remove this checks logic unless you really know how it is necessary.
   173  				//
   174  				// The `repeatHandlerCheckMap` is used for repeat handler filtering during handler searching.
   175  				// As there are fuzzy nodes, and the fuzzy nodes have both sub-nodes and sub-list nodes, there
   176  				// may be repeated handler items in both sub-nodes and sub-list nodes. It here uses handler item id to
   177  				// identify the same handler item that registered.
   178  				//
   179  				// The same handler item is the one that is registered in the same function doSetHandler.
   180  				// Note that, one handler function(middleware or hook function) may be registered multiple times as
   181  				// different handler items using function doSetHandler, and they have different handler item id.
   182  				//
   183  				// Note that twice, the handler function may be registered multiple times as different handler items.
   184  				if _, isRepeatedHandler := repeatHandlerCheckMap[item.Id]; isRepeatedHandler {
   185  					continue
   186  				} else {
   187  					repeatHandlerCheckMap[item.Id] = struct{}{}
   188  				}
   189  				// Serving handler can only be added to the handler array just once.
   190  				// The first route item in the list has the most priority than the rest.
   191  				// This ignoring can implement route overwritten feature.
   192  				if hasServe {
   193  					switch item.Type {
   194  					case HandlerTypeHandler, HandlerTypeObject:
   195  						continue
   196  					}
   197  				}
   198  				if item.Router.Method == defaultMethod || item.Router.Method == method {
   199  					// Note the rule having no fuzzy rules: len(match) == 1
   200  					if match, err := gregex.MatchString(item.Router.RegRule, path); err == nil && len(match) > 0 {
   201  						parsedItem := &HandlerItemParsed{item, nil}
   202  						// If the rule contains fuzzy names,
   203  						// it needs paring the URL to retrieve the values for the names.
   204  						if len(item.Router.RegNames) > 0 {
   205  							if len(match) > len(item.Router.RegNames) {
   206  								parsedItem.Values = make(map[string]string)
   207  								// It there repeated names, it just overwrites the same one.
   208  								for i, name := range item.Router.RegNames {
   209  									parsedItem.Values[name], _ = gurl.Decode(match[i+1])
   210  								}
   211  							}
   212  						}
   213  						switch item.Type {
   214  						// The serving handler can be added just once.
   215  						case HandlerTypeHandler, HandlerTypeObject:
   216  							hasServe = true
   217  							serveItem = parsedItem
   218  							parsedItemList.PushBack(parsedItem)
   219  
   220  						// The middleware is inserted before the serving handler.
   221  						// If there are multiple middleware, they're inserted into the result list by their registering order.
   222  						// The middleware is also executed by their registered order.
   223  						case HandlerTypeMiddleware:
   224  							if lastMiddlewareElem == nil {
   225  								lastMiddlewareElem = parsedItemList.PushFront(parsedItem)
   226  							} else {
   227  								lastMiddlewareElem = parsedItemList.InsertAfter(lastMiddlewareElem, parsedItem)
   228  							}
   229  
   230  						// HOOK handler, just push it back to the list.
   231  						case HandlerTypeHook:
   232  							hasHook = true
   233  							parsedItemList.PushBack(parsedItem)
   234  
   235  						default:
   236  							panic(gerror.Newf(`invalid handler type %s`, item.Type))
   237  						}
   238  					}
   239  				}
   240  			}
   241  		}
   242  	}
   243  	if parsedItemList.Len() > 0 {
   244  		var index = 0
   245  		parsedItems = make([]*HandlerItemParsed, parsedItemList.Len())
   246  		for e := parsedItemList.Front(); e != nil; e = e.Next() {
   247  			parsedItems[index] = e.Value.(*HandlerItemParsed)
   248  			index++
   249  		}
   250  	}
   251  	return
   252  }
   253  
   254  // MarshalJSON implements the interface MarshalJSON for json.Marshal.
   255  func (item HandlerItem) MarshalJSON() ([]byte, error) {
   256  	switch item.Type {
   257  	case HandlerTypeHook:
   258  		return json.Marshal(
   259  			fmt.Sprintf(
   260  				`%s %s:%s (%s)`,
   261  				item.Router.Uri,
   262  				item.Router.Domain,
   263  				item.Router.Method,
   264  				item.HookName,
   265  			),
   266  		)
   267  	case HandlerTypeMiddleware:
   268  		return json.Marshal(
   269  			fmt.Sprintf(
   270  				`%s %s:%s (MIDDLEWARE)`,
   271  				item.Router.Uri,
   272  				item.Router.Domain,
   273  				item.Router.Method,
   274  			),
   275  		)
   276  	default:
   277  		return json.Marshal(
   278  			fmt.Sprintf(
   279  				`%s %s:%s`,
   280  				item.Router.Uri,
   281  				item.Router.Domain,
   282  				item.Router.Method,
   283  			),
   284  		)
   285  	}
   286  }
   287  
   288  // MarshalJSON implements the interface MarshalJSON for json.Marshal.
   289  func (item HandlerItemParsed) MarshalJSON() ([]byte, error) {
   290  	return json.Marshal(item.Handler)
   291  }