github.com/ronaksoft/rony@v0.16.26-0.20230807065236-1743dbfe6959/edge/rest_proxy.go (about)

     1  package edge
     2  
     3  import (
     4  	"github.com/ronaksoft/rony"
     5  
     6  	"sort"
     7  	"strings"
     8  )
     9  
    10  /*
    11     Creation Time: 2021 - Jul - 02
    12     Created by:  (ehsan)
    13     Maintainers:
    14        1.  Ehsan N. Moosa (E2)
    15     Auditor: Ehsan N. Moosa (E2)
    16     Copyright Ronak Software Group 2020
    17  */
    18  
    19  type RestHandler func(conn rony.RestConn, ctx *DispatchCtx) error
    20  
    21  type RestProxy interface {
    22  	ClientMessage(conn rony.RestConn, ctx *DispatchCtx) error
    23  	ServerMessage(conn rony.RestConn, ctx *DispatchCtx) error
    24  }
    25  
    26  type restProxy struct {
    27  	cm RestHandler
    28  	sm RestHandler
    29  }
    30  
    31  func (r *restProxy) ClientMessage(conn rony.RestConn, ctx *DispatchCtx) error {
    32  	return r.cm(conn, ctx)
    33  }
    34  
    35  func (r *restProxy) ServerMessage(conn rony.RestConn, ctx *DispatchCtx) error {
    36  	return r.sm(conn, ctx)
    37  }
    38  
    39  func NewRestProxy(onClientMessage, onServerMessage RestHandler) *restProxy {
    40  	return &restProxy{
    41  		cm: onClientMessage,
    42  		sm: onServerMessage,
    43  	}
    44  }
    45  
    46  // restMux help to provide RESTFull wrappers around RPC handlers.
    47  type restMux struct {
    48  	routes map[string]*trie
    49  }
    50  
    51  func (hp *restMux) Set(method, path string, f RestProxy) {
    52  	method = strings.ToUpper(method)
    53  	if hp.routes == nil {
    54  		hp.routes = make(map[string]*trie)
    55  	}
    56  	if _, ok := hp.routes[method]; !ok {
    57  		hp.routes[method] = &trie{
    58  			root:            newTrieNode(),
    59  			hasRootWildcard: false,
    60  		}
    61  	}
    62  	hp.routes[method].Insert(path, WithTag(method), WithProxyFactory(f))
    63  }
    64  
    65  func (hp *restMux) Search(conn rony.RestConn) (string, RestProxy) {
    66  	r := hp.routes[strings.ToUpper(conn.Method())]
    67  	if r == nil {
    68  		return "", nil
    69  	}
    70  	n := r.Search(conn.Path(), conn)
    71  	if n == nil {
    72  		return "", nil
    73  	}
    74  
    75  	return n.key, n.Proxy
    76  }
    77  
    78  const (
    79  	// paramStart is the character, as a string, which a path pattern starts to define its named parameter.
    80  	paramStart = ":"
    81  	// wildcardParamStart is the character, as a string, which a path pattern starts to define
    82  	// its named parameter for wildcards. It allows everything else after that path prefix
    83  	// but the trie checks for static paths and named parameters before that in order to
    84  	// support everything that other implementations do not, and if nothing else found then it tries to
    85  	//find the closest wildcard path(super and unique).
    86  	wildcardParamStart = "*"
    87  )
    88  
    89  // trie contains the main logic for adding and searching nodes for path segments.
    90  // It supports wildcard and named path parameters.
    91  // trie supports very coblex and useful path patterns for routes.
    92  // The trie checks for static paths(path without : or *) and named parameters before that in order to
    93  // support everything that other implementations do not, and if nothing else found then it tries
    94  // to find the closest wildcard path(super and unique).
    95  type trie struct {
    96  	root *trieNode
    97  
    98  	// if true then it will handle any path if not other parent wildcard exists,
    99  	// so even 404 (on http services) is up to it, see trie#Insert.
   100  	hasRootWildcard bool
   101  
   102  	hasRootSlash bool
   103  }
   104  
   105  // InsertOption is just a function which accepts a pointer to a trieNode which can
   106  // alt its `Handler`, `Tag` and `Data`  fields.
   107  // See `WithHandler`, `WithTag` and `WithData`.
   108  type InsertOption func(*trieNode)
   109  
   110  // WithProxyFactory sets the node's `Handler` field (useful for HTTP).
   111  func WithProxyFactory(proxy RestProxy) InsertOption {
   112  	if proxy == nil {
   113  		panic("muxie/WithProxy: empty handler")
   114  	}
   115  
   116  	return func(n *trieNode) {
   117  		if n.Proxy == nil {
   118  			n.Proxy = proxy
   119  		}
   120  	}
   121  }
   122  
   123  // WithTag sets the node's `Tag` field (maybe useful for HTTP).
   124  func WithTag(tag string) InsertOption {
   125  	return func(n *trieNode) {
   126  		if n.Tag == "" {
   127  			n.Tag = tag
   128  		}
   129  	}
   130  }
   131  
   132  // Insert adds a node to the trie.
   133  func (t *trie) Insert(pattern string, options ...InsertOption) {
   134  	if pattern == "" {
   135  		panic("muxie/trie#Insert: empty pattern")
   136  	}
   137  
   138  	n := t.insert(pattern, "", nil, nil)
   139  	for _, opt := range options {
   140  		opt(n)
   141  	}
   142  }
   143  
   144  const (
   145  	pathSep  = "/"
   146  	pathSepB = '/'
   147  )
   148  
   149  func slowPathSplit(path string) []string {
   150  	if path == pathSep {
   151  		return []string{pathSep}
   152  	}
   153  
   154  	// remove last sep if any.
   155  	if path[len(path)-1] == pathSepB {
   156  		path = path[:len(path)-1]
   157  	}
   158  
   159  	return strings.Split(path, pathSep)[1:]
   160  }
   161  
   162  func resolveStaticPart(key string) string {
   163  	i := strings.Index(key, paramStart)
   164  	if i == -1 {
   165  		i = strings.Index(key, wildcardParamStart)
   166  	}
   167  	if i == -1 {
   168  		i = len(key)
   169  	}
   170  
   171  	return key[:i]
   172  }
   173  
   174  func (t *trie) insert(key, tag string, optionalData interface{}, proxy RestProxy) *trieNode {
   175  	input := slowPathSplit(key)
   176  
   177  	n := t.root
   178  	if key == pathSep {
   179  		t.hasRootSlash = true
   180  	}
   181  
   182  	var paramKeys []string
   183  
   184  	for _, s := range input {
   185  		c := s[0]
   186  
   187  		if isParam, isWildcard := c == paramStart[0], c == wildcardParamStart[0]; isParam || isWildcard {
   188  			n.hasDynamicChild = true
   189  			paramKeys = append(paramKeys, s[1:]) // without : or *.
   190  
   191  			// if node has already a wildcard, don't force a value, check for true only.
   192  			if isParam {
   193  				n.childNamedParameter = true
   194  				s = paramStart
   195  			}
   196  
   197  			if isWildcard {
   198  				n.childWildcardParameter = true
   199  				s = wildcardParamStart
   200  				if t.root == n {
   201  					t.hasRootWildcard = true
   202  				}
   203  			}
   204  		}
   205  
   206  		if !n.hasChild(s) {
   207  			child := newTrieNode()
   208  			n.addChild(s, child)
   209  		}
   210  
   211  		n = n.getChild(s)
   212  	}
   213  
   214  	n.Tag = tag
   215  	n.Proxy = proxy
   216  	n.Data = optionalData
   217  
   218  	n.paramKeys = paramKeys
   219  	n.key = key
   220  	n.staticKey = resolveStaticPart(key)
   221  	n.end = true
   222  
   223  	return n
   224  }
   225  
   226  // ParamsSetter is the interface which should be implemented by the
   227  // params writer for `search` in order to store the found named path parameters, if any.
   228  type ParamsSetter interface {
   229  	Set(string, interface{})
   230  }
   231  
   232  // Search is the most important part of the trie.
   233  // It will try to find the responsible node for a specific query (or a request path for HTTP endpoints).
   234  //
   235  // Search supports searching for static paths(path without : or *) and paths that contain
   236  // named parameters or wildcards.
   237  // Priority as:
   238  // 1. static paths
   239  // 2. named parameters with ":"
   240  // 3. wildcards
   241  // 4. closest wildcard if not found, if any
   242  // 5. root wildcard
   243  func (t *trie) Search(q string, params ParamsSetter) *trieNode {
   244  	end := len(q)
   245  
   246  	if end == 0 || (end == 1 && q[0] == pathSepB) {
   247  		// fixes only root wildcard but no / registered at.
   248  		if t.hasRootSlash {
   249  			return t.root.getChild(pathSep)
   250  		} else if t.hasRootWildcard {
   251  			// no need to going through setting parameters, this one has not but it is wildcard.
   252  			return t.root.getChild(wildcardParamStart)
   253  		}
   254  
   255  		return nil
   256  	}
   257  
   258  	n := t.root
   259  	start := 1
   260  	i := 1
   261  	var paramValues []string
   262  
   263  	for {
   264  		if i == end || q[i] == pathSepB {
   265  			if child := n.getChild(q[start:i]); child != nil {
   266  				n = child
   267  			} else if n.childNamedParameter { // && n.childWildcardParameter == false {
   268  				n = n.getChild(paramStart)
   269  				if ln := len(paramValues); cap(paramValues) > ln {
   270  					paramValues = paramValues[:ln+1]
   271  					paramValues[ln] = q[start:i]
   272  				} else {
   273  					paramValues = append(paramValues, q[start:i])
   274  				}
   275  			} else if n.childWildcardParameter {
   276  				n = n.getChild(wildcardParamStart)
   277  				if ln := len(paramValues); cap(paramValues) > ln {
   278  					paramValues = paramValues[:ln+1]
   279  					paramValues[ln] = q[start:]
   280  				} else {
   281  					paramValues = append(paramValues, q[start:])
   282  				}
   283  
   284  				break
   285  			} else {
   286  				n = n.findClosestParentWildcardNode()
   287  				if n != nil {
   288  					// means that it has :param/static and *wildcard, we go trhough the :param
   289  					// but the next path segment is not the /static, so go back to *wildcard
   290  					// instead of not found.
   291  					//
   292  					// Fixes:
   293  					// /hello/*p
   294  					// /hello/:p1/static/:p2
   295  					// req: http://localhost:8080/hello/dsadsa/static/dsadsa => found
   296  					// req: http://localhost:8080/hello/dsadsa => but not found!
   297  					// and
   298  					// /second/wild/*p
   299  					// /second/wild/static/otherstatic/
   300  					// req: /second/wild/static/otherstatic/random => but not found!
   301  					params.Set(n.paramKeys[0], q[len(n.staticKey):])
   302  
   303  					return n
   304  				}
   305  
   306  				return nil
   307  			}
   308  
   309  			if i == end {
   310  				break
   311  			}
   312  
   313  			i++
   314  			start = i
   315  
   316  			continue
   317  		}
   318  
   319  		i++
   320  	}
   321  
   322  	if n == nil || !n.end {
   323  		if n != nil { // we need it on both places, on last segment (below) or on the first unnknown (above).
   324  			if n = n.findClosestParentWildcardNode(); n != nil {
   325  				params.Set(n.paramKeys[0], q[len(n.staticKey):])
   326  
   327  				return n
   328  			}
   329  		}
   330  
   331  		if t.hasRootWildcard {
   332  			// that's the case for root wildcard, tests are passing
   333  			// even without it but stick with it for reference.
   334  			// Note ote that something like:
   335  			// Routes: /other2/*myparam and /other2/static
   336  			// Reqs: /other2/staticed will be handled
   337  			// by the /other2/*myparam and not the root wildcard (see above), which is what we want.
   338  			n = t.root.getChild(wildcardParamStart)
   339  			params.Set(n.paramKeys[0], q[1:])
   340  
   341  			return n
   342  		}
   343  
   344  		return nil
   345  	}
   346  
   347  	for i, paramValue := range paramValues {
   348  		if len(n.paramKeys) > i {
   349  			params.Set(n.paramKeys[i], paramValue)
   350  		}
   351  	}
   352  
   353  	return n
   354  }
   355  
   356  // trieNode is the trie's node which path patterns with their data like an HTTP handler are saved to.
   357  // See `trie` too.
   358  type trieNode struct {
   359  	parent *trieNode
   360  
   361  	children               map[string]*trieNode
   362  	hasDynamicChild        bool // does one of the children contains a parameter or wildcard?
   363  	childNamedParameter    bool // is the child a named parameter (single segmnet)
   364  	childWildcardParameter bool // or it is a wildcard (can be more than one path segments) ?
   365  
   366  	paramKeys []string // the param keys without : or *.
   367  	end       bool     // it is a complete node, here we stop, and we can say that the node is valid.
   368  	key       string   // if end == true then key is filled with the original value of the insertion's key.
   369  	// if key != "" && its parent has childWildcardParameter == true,
   370  	// we need it to track the static part for the closest-wildcard's parameter storage.
   371  	staticKey string
   372  
   373  	// insert main data relative to http and a tag for things like route names.
   374  	Proxy RestProxy
   375  	Tag   string
   376  
   377  	// other insert data.
   378  	Data interface{}
   379  }
   380  
   381  // newTrieNode returns a new, empty, trieNode.
   382  func newTrieNode() *trieNode {
   383  	n := new(trieNode)
   384  
   385  	return n
   386  }
   387  
   388  func (n *trieNode) addChild(s string, child *trieNode) {
   389  	if n.children == nil {
   390  		n.children = make(map[string]*trieNode)
   391  	}
   392  
   393  	if _, exists := n.children[s]; exists {
   394  		return
   395  	}
   396  
   397  	child.parent = n
   398  	n.children[s] = child
   399  }
   400  
   401  func (n *trieNode) getChild(s string) *trieNode {
   402  	if n.children == nil {
   403  		return nil
   404  	}
   405  
   406  	return n.children[s]
   407  }
   408  
   409  func (n *trieNode) hasChild(s string) bool {
   410  	return n.getChild(s) != nil
   411  }
   412  
   413  func (n *trieNode) findClosestParentWildcardNode() *trieNode {
   414  	n = n.parent
   415  	for n != nil {
   416  		if n.childWildcardParameter {
   417  			return n.getChild(wildcardParamStart)
   418  		}
   419  
   420  		n = n.parent
   421  	}
   422  
   423  	return nil
   424  }
   425  
   426  // keysSorter is the type definition for the sorting logic
   427  // that caller can pass on `GetKeys` and `Autocomplete`.
   428  type keysSorter = func(list []string) func(i, j int) bool
   429  
   430  // Keys returns this node's key (if it's a final path segment)
   431  // and its children's node's key. The "sorter" can be optionally used to sort the result.
   432  func (n *trieNode) Keys(sorter keysSorter) (list []string) {
   433  	if n == nil {
   434  		return
   435  	}
   436  
   437  	if n.end {
   438  		list = append(list, n.key)
   439  	}
   440  
   441  	if n.children != nil {
   442  		for _, child := range n.children {
   443  			list = append(list, child.Keys(sorter)...)
   444  		}
   445  	}
   446  
   447  	if sorter != nil {
   448  		sort.Slice(list, sorter(list))
   449  	}
   450  
   451  	return
   452  }
   453  
   454  // Parent returns the parent of that node, can return nil if this is the root node.
   455  func (n *trieNode) Parent() *trieNode {
   456  	return n.parent
   457  }
   458  
   459  // String returns the key, which is the path pattern for the HTTP Mux.
   460  func (n *trieNode) String() string {
   461  	return n.key
   462  }
   463  
   464  // IsEnd returns true if this trieNode is a final path, has a key.
   465  func (n *trieNode) IsEnd() bool {
   466  	return n.end
   467  }