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 }