go.uber.org/yarpc@v1.72.1/router.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package yarpc
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"sort"
    27  	"strconv"
    28  	"strings"
    29  
    30  	"go.uber.org/yarpc/api/transport"
    31  	"go.uber.org/yarpc/internal/humanize"
    32  	"go.uber.org/yarpc/yarpcerrors"
    33  )
    34  
    35  var (
    36  	_ transport.Router = (*MapRouter)(nil)
    37  )
    38  
    39  type serviceProcedure struct {
    40  	service   string
    41  	procedure string
    42  }
    43  
    44  type serviceProcedureEncoding struct {
    45  	service   string
    46  	procedure string
    47  	encoding  transport.Encoding
    48  }
    49  
    50  // MapRouter is a Router that maintains a map of the registered
    51  // procedures.
    52  type MapRouter struct {
    53  	defaultService            string
    54  	serviceProcedures         map[serviceProcedure]transport.Procedure
    55  	serviceProcedureEncodings map[serviceProcedureEncoding]transport.Procedure
    56  	supportedEncodings        map[serviceProcedure][]string
    57  	serviceNames              map[string]struct{}
    58  }
    59  
    60  // NewMapRouter builds a new MapRouter that uses the given name as the
    61  // default service name.
    62  func NewMapRouter(defaultService string) MapRouter {
    63  	return MapRouter{
    64  		defaultService:            defaultService,
    65  		serviceProcedures:         make(map[serviceProcedure]transport.Procedure),
    66  		serviceProcedureEncodings: make(map[serviceProcedureEncoding]transport.Procedure),
    67  		supportedEncodings:        make(map[serviceProcedure][]string),
    68  		serviceNames:              map[string]struct{}{defaultService: {}},
    69  	}
    70  }
    71  
    72  // Register registers the procedure with the MapRouter.
    73  // If the procedure does not specify its service name, the procedure will
    74  // inherit the default service name of the router.
    75  // Procedures should specify their encoding, and multiple procedures with the
    76  // same name and service name can exist if they handle different encodings.
    77  // If a procedure does not specify an encoding, it can only support one handler.
    78  // The router will select that handler regardless of the encoding.
    79  func (m MapRouter) Register(rs []transport.Procedure) {
    80  	for _, r := range rs {
    81  		if r.Service == "" {
    82  			r.Service = m.defaultService
    83  		}
    84  
    85  		if r.Name == "" {
    86  			panic("Expected procedure name not to be empty string in registration")
    87  		}
    88  
    89  		m.serviceNames[r.Service] = struct{}{}
    90  
    91  		sp := serviceProcedure{
    92  			service:   r.Service,
    93  			procedure: r.Name,
    94  		}
    95  
    96  		if r.Encoding == "" {
    97  			// Protect against masking encoding-specific routes.
    98  			if _, ok := m.serviceProcedures[sp]; ok {
    99  				panic(fmt.Sprintf("Cannot register multiple handlers for every encoding for service %q and procedure  %q", sp.service, sp.procedure))
   100  			}
   101  			if se, ok := m.supportedEncodings[sp]; ok {
   102  				panic(fmt.Sprintf("Cannot register a handler for every encoding for service %q and procedure %q when there are already handlers for %s", sp.service, sp.procedure, humanize.QuotedJoin(se, "and", "no encodings")))
   103  			}
   104  			// This supports wild card encodings (for backward compatibility,
   105  			// since type models like Thrift were not previously required to
   106  			// specify the encoding of every procedure).
   107  			m.serviceProcedures[sp] = r
   108  			continue
   109  		}
   110  
   111  		spe := serviceProcedureEncoding{
   112  			service:   r.Service,
   113  			procedure: r.Name,
   114  			encoding:  r.Encoding,
   115  		}
   116  
   117  		// Protect against overriding wildcards
   118  		if _, ok := m.serviceProcedures[sp]; ok {
   119  			panic(fmt.Sprintf("Cannot register a handler for both (service, procedure) on any * encoding and (service, procedure, encoding), specifically (%q, %q, %q)", r.Service, r.Name, r.Encoding))
   120  		}
   121  		// Route to individual handlers for unique combinations of service,
   122  		// procedure, and encoding. This shall henceforth be the
   123  		// recommended way for models to register procedures.
   124  		m.serviceProcedureEncodings[spe] = r
   125  		// Record supported encodings.
   126  		m.supportedEncodings[sp] = append(m.supportedEncodings[sp], string(r.Encoding))
   127  	}
   128  }
   129  
   130  // Procedures returns a list procedures that
   131  // have been registered so far.
   132  func (m MapRouter) Procedures() []transport.Procedure {
   133  	procs := make([]transport.Procedure, 0, len(m.serviceProcedures)+len(m.serviceProcedureEncodings))
   134  	for _, v := range m.serviceProcedures {
   135  		procs = append(procs, v)
   136  	}
   137  	for _, v := range m.serviceProcedureEncodings {
   138  		procs = append(procs, v)
   139  	}
   140  	sort.Sort(sortableProcedures(procs))
   141  	return procs
   142  }
   143  
   144  type sortableProcedures []transport.Procedure
   145  
   146  func (ps sortableProcedures) Len() int {
   147  	return len(ps)
   148  }
   149  
   150  func (ps sortableProcedures) Less(i int, j int) bool {
   151  	return ps[i].Less(ps[j])
   152  }
   153  
   154  func (ps sortableProcedures) Swap(i int, j int) {
   155  	ps[i], ps[j] = ps[j], ps[i]
   156  }
   157  
   158  // Choose retrives the HandlerSpec for the service, procedure, and encoding
   159  // noted on the transport request, or returns an unrecognized procedure error
   160  // (testable with transport.IsUnrecognizedProcedureError(err)).
   161  func (m MapRouter) Choose(ctx context.Context, req *transport.Request) (transport.HandlerSpec, error) {
   162  	service, procedure, encoding := req.Service, req.Procedure, req.Encoding
   163  	if service == "" {
   164  		service = m.defaultService
   165  	}
   166  
   167  	if _, ok := m.serviceNames[service]; !ok {
   168  		return transport.HandlerSpec{},
   169  			yarpcerrors.Newf(yarpcerrors.CodeUnimplemented, "unrecognized service name %q, "+
   170  				"available services: %s", req.Service, getAvailableServiceNames(m.serviceNames))
   171  	}
   172  
   173  	// Fully specified combinations of service, procedure, and encoding.
   174  	spe := serviceProcedureEncoding{
   175  		service:   service,
   176  		procedure: procedure,
   177  		encoding:  encoding,
   178  	}
   179  	if procedure, ok := m.serviceProcedureEncodings[spe]; ok {
   180  		return procedure.HandlerSpec, nil
   181  	}
   182  
   183  	// Alternately use the original behavior: route all encodings to the same
   184  	// handler.
   185  	sp := serviceProcedure{
   186  		service:   service,
   187  		procedure: procedure,
   188  	}
   189  	if procedure, ok := m.serviceProcedures[sp]; ok {
   190  		return procedure.HandlerSpec, nil
   191  	}
   192  
   193  	// Supported procedure, unrecognized encoding.
   194  	if wantEncodings := m.supportedEncodings[sp]; len(wantEncodings) == 1 {
   195  		// To maintain backward compatibility with the error messages provided
   196  		// on the wire (as verified by Crossdock across all language
   197  		// implementations), this routes an invalid encoding to the sole
   198  		// implementation of a procedure.
   199  		// The handler is then responsible for detecting the invalid encoding
   200  		// and providing an error including "failed to decode".
   201  		spe.encoding = transport.Encoding(wantEncodings[0])
   202  		return m.serviceProcedureEncodings[spe].HandlerSpec, nil
   203  	}
   204  
   205  	return transport.HandlerSpec{}, yarpcerrors.Newf(yarpcerrors.CodeUnimplemented, "unrecognized procedure %q for service %q", req.Procedure, req.Service)
   206  }
   207  
   208  // Extract keys from service names map and return a formatted string
   209  func getAvailableServiceNames(svcMap map[string]struct{}) string {
   210  	var serviceNames []string
   211  	for key := range svcMap {
   212  		serviceNames = append(serviceNames, strconv.Quote(key))
   213  	}
   214  	// Sort the string array to generate consistent result
   215  	sort.Strings(serviceNames)
   216  	return strings.Join(serviceNames, ", ")
   217  }