...

Source file src/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor/services.go

Documentation: github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor

     1  package descriptor
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/golang/glog"
     8  	"github.com/golang/protobuf/proto"
     9  	descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
    10  	"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/httprule"
    11  	options "google.golang.org/genproto/googleapis/api/annotations"
    12  )
    13  
    14  // loadServices registers services and their methods from "targetFile" to "r".
    15  // It must be called after loadFile is called for all files so that loadServices
    16  // can resolve names of message types and their fields.
    17  func (r *Registry) loadServices(file *File) error {
    18  	glog.V(1).Infof("Loading services from %s", file.GetName())
    19  	var svcs []*Service
    20  	for _, sd := range file.GetService() {
    21  		glog.V(2).Infof("Registering %s", sd.GetName())
    22  		svc := &Service{
    23  			File:                   file,
    24  			ServiceDescriptorProto: sd,
    25  		}
    26  		for _, md := range sd.GetMethod() {
    27  			glog.V(2).Infof("Processing %s.%s", sd.GetName(), md.GetName())
    28  			opts, err := extractAPIOptions(md)
    29  			if err != nil {
    30  				glog.Errorf("Failed to extract HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
    31  				return err
    32  			}
    33  			optsList := r.LookupExternalHTTPRules((&Method{Service: svc, MethodDescriptorProto: md}).FQMN())
    34  			if opts != nil {
    35  				optsList = append(optsList, opts)
    36  			}
    37  			if len(optsList) == 0 {
    38  				if r.generateUnboundMethods {
    39  					defaultOpts, err := defaultAPIOptions(svc, md)
    40  					if err != nil {
    41  						glog.Errorf("Failed to generate default HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
    42  						return err
    43  					}
    44  					optsList = append(optsList, defaultOpts)
    45  				} else {
    46  					logFn := glog.V(1).Infof
    47  					if r.warnOnUnboundMethods {
    48  						logFn = glog.Warningf
    49  					}
    50  					logFn("No HttpRule found for method: %s.%s", svc.GetName(), md.GetName())
    51  				}
    52  			}
    53  			meth, err := r.newMethod(svc, md, optsList)
    54  			if err != nil {
    55  				return err
    56  			}
    57  			svc.Methods = append(svc.Methods, meth)
    58  		}
    59  		if len(svc.Methods) == 0 {
    60  			continue
    61  		}
    62  		glog.V(2).Infof("Registered %s with %d method(s)", svc.GetName(), len(svc.Methods))
    63  		svcs = append(svcs, svc)
    64  	}
    65  	file.Services = svcs
    66  	return nil
    67  }
    68  
    69  func (r *Registry) newMethod(svc *Service, md *descriptor.MethodDescriptorProto, optsList []*options.HttpRule) (*Method, error) {
    70  	requestType, err := r.LookupMsg(svc.File.GetPackage(), md.GetInputType())
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	responseType, err := r.LookupMsg(svc.File.GetPackage(), md.GetOutputType())
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	meth := &Method{
    79  		Service:               svc,
    80  		MethodDescriptorProto: md,
    81  		RequestType:           requestType,
    82  		ResponseType:          responseType,
    83  	}
    84  
    85  	newBinding := func(opts *options.HttpRule, idx int) (*Binding, error) {
    86  		var (
    87  			httpMethod   string
    88  			pathTemplate string
    89  		)
    90  		switch {
    91  		case opts.GetGet() != "":
    92  			httpMethod = "GET"
    93  			pathTemplate = opts.GetGet()
    94  			if opts.Body != "" {
    95  				return nil, fmt.Errorf("must not set request body when http method is GET: %s", md.GetName())
    96  			}
    97  
    98  		case opts.GetPut() != "":
    99  			httpMethod = "PUT"
   100  			pathTemplate = opts.GetPut()
   101  
   102  		case opts.GetPost() != "":
   103  			httpMethod = "POST"
   104  			pathTemplate = opts.GetPost()
   105  
   106  		case opts.GetDelete() != "":
   107  			httpMethod = "DELETE"
   108  			pathTemplate = opts.GetDelete()
   109  			if opts.Body != "" && !r.allowDeleteBody {
   110  				return nil, fmt.Errorf("must not set request body when http method is DELETE except allow_delete_body option is true: %s", md.GetName())
   111  			}
   112  
   113  		case opts.GetPatch() != "":
   114  			httpMethod = "PATCH"
   115  			pathTemplate = opts.GetPatch()
   116  
   117  		case opts.GetCustom() != nil:
   118  			custom := opts.GetCustom()
   119  			httpMethod = custom.Kind
   120  			pathTemplate = custom.Path
   121  
   122  		default:
   123  			glog.V(1).Infof("No pattern specified in google.api.HttpRule: %s", md.GetName())
   124  			return nil, nil
   125  		}
   126  
   127  		parsed, err := httprule.Parse(pathTemplate)
   128  		if err != nil {
   129  			return nil, err
   130  		}
   131  		tmpl := parsed.Compile()
   132  
   133  		if md.GetClientStreaming() && len(tmpl.Fields) > 0 {
   134  			return nil, fmt.Errorf("cannot use path parameter in client streaming")
   135  		}
   136  
   137  		b := &Binding{
   138  			Method:     meth,
   139  			Index:      idx,
   140  			PathTmpl:   tmpl,
   141  			HTTPMethod: httpMethod,
   142  		}
   143  
   144  		for _, f := range tmpl.Fields {
   145  			param, err := r.newParam(meth, f)
   146  			if err != nil {
   147  				return nil, err
   148  			}
   149  			b.PathParams = append(b.PathParams, param)
   150  		}
   151  
   152  		// TODO(yugui) Handle query params
   153  
   154  		b.Body, err = r.newBody(meth, opts.Body)
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  
   159  		b.ResponseBody, err = r.newResponse(meth, opts.ResponseBody)
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  
   164  		return b, nil
   165  	}
   166  
   167  	applyOpts := func(opts *options.HttpRule) error {
   168  		b, err := newBinding(opts, len(meth.Bindings))
   169  		if err != nil {
   170  			return err
   171  		}
   172  
   173  		if b != nil {
   174  			meth.Bindings = append(meth.Bindings, b)
   175  		}
   176  		for _, additional := range opts.GetAdditionalBindings() {
   177  			if len(additional.AdditionalBindings) > 0 {
   178  				return fmt.Errorf("additional_binding in additional_binding not allowed: %s.%s", svc.GetName(), meth.GetName())
   179  			}
   180  			b, err := newBinding(additional, len(meth.Bindings))
   181  			if err != nil {
   182  				return err
   183  			}
   184  			meth.Bindings = append(meth.Bindings, b)
   185  		}
   186  
   187  		return nil
   188  	}
   189  
   190  	for _, opts := range optsList {
   191  		if err := applyOpts(opts); err != nil {
   192  			return nil, err
   193  		}
   194  	}
   195  
   196  	return meth, nil
   197  }
   198  
   199  func extractAPIOptions(meth *descriptor.MethodDescriptorProto) (*options.HttpRule, error) {
   200  	if meth.Options == nil {
   201  		return nil, nil
   202  	}
   203  	if !proto.HasExtension(meth.Options, options.E_Http) {
   204  		return nil, nil
   205  	}
   206  	ext, err := proto.GetExtension(meth.Options, options.E_Http)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	opts, ok := ext.(*options.HttpRule)
   211  	if !ok {
   212  		return nil, fmt.Errorf("extension is %T; want an HttpRule", ext)
   213  	}
   214  	return opts, nil
   215  }
   216  
   217  func defaultAPIOptions(svc *Service, md *descriptor.MethodDescriptorProto) (*options.HttpRule, error) {
   218  	// FQSN prefixes the service's full name with a '.', e.g.: '.example.ExampleService'
   219  	fqsn := strings.TrimPrefix(svc.FQSN(), ".")
   220  
   221  	// This generates an HttpRule that matches the gRPC mapping to HTTP/2 described in
   222  	// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
   223  	// i.e.:
   224  	//   * method is POST
   225  	//   * path is "/<service name>/<method name>"
   226  	//   * body should contain the serialized request message
   227  	rule := &options.HttpRule{
   228  		Pattern: &options.HttpRule_Post{
   229  			Post: fmt.Sprintf("/%s/%s", fqsn, md.GetName()),
   230  		},
   231  		Body: "*",
   232  	}
   233  	return rule, nil
   234  }
   235  
   236  func (r *Registry) newParam(meth *Method, path string) (Parameter, error) {
   237  	msg := meth.RequestType
   238  	fields, err := r.resolveFieldPath(msg, path, true)
   239  	if err != nil {
   240  		return Parameter{}, err
   241  	}
   242  	l := len(fields)
   243  	if l == 0 {
   244  		return Parameter{}, fmt.Errorf("invalid field access list for %s", path)
   245  	}
   246  	target := fields[l-1].Target
   247  	switch target.GetType() {
   248  	case descriptor.FieldDescriptorProto_TYPE_MESSAGE, descriptor.FieldDescriptorProto_TYPE_GROUP:
   249  		glog.V(2).Infoln("found aggregate type:", target, target.TypeName)
   250  		if IsWellKnownType(*target.TypeName) {
   251  			glog.V(2).Infoln("found well known aggregate type:", target)
   252  		} else {
   253  			return Parameter{}, fmt.Errorf("aggregate type %s in parameter of %s.%s: %s", target.Type, meth.Service.GetName(), meth.GetName(), path)
   254  		}
   255  	}
   256  	return Parameter{
   257  		FieldPath: FieldPath(fields),
   258  		Method:    meth,
   259  		Target:    fields[l-1].Target,
   260  	}, nil
   261  }
   262  
   263  func (r *Registry) newBody(meth *Method, path string) (*Body, error) {
   264  	msg := meth.RequestType
   265  	switch path {
   266  	case "":
   267  		return nil, nil
   268  	case "*":
   269  		return &Body{FieldPath: nil}, nil
   270  	}
   271  	fields, err := r.resolveFieldPath(msg, path, false)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	return &Body{FieldPath: FieldPath(fields)}, nil
   276  }
   277  
   278  func (r *Registry) newResponse(meth *Method, path string) (*Body, error) {
   279  	msg := meth.ResponseType
   280  	switch path {
   281  	case "", "*":
   282  		return nil, nil
   283  	}
   284  	fields, err := r.resolveFieldPath(msg, path, false)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  	return &Body{FieldPath: FieldPath(fields)}, nil
   289  }
   290  
   291  // lookupField looks up a field named "name" within "msg".
   292  // It returns nil if no such field found.
   293  func lookupField(msg *Message, name string) *Field {
   294  	for _, f := range msg.Fields {
   295  		if f.GetName() == name {
   296  			return f
   297  		}
   298  	}
   299  	return nil
   300  }
   301  
   302  // resolveFieldPath resolves "path" into a list of fieldDescriptor, starting from "msg".
   303  func (r *Registry) resolveFieldPath(msg *Message, path string, isPathParam bool) ([]FieldPathComponent, error) {
   304  	if path == "" {
   305  		return nil, nil
   306  	}
   307  
   308  	root := msg
   309  	var result []FieldPathComponent
   310  	for i, c := range strings.Split(path, ".") {
   311  		if i > 0 {
   312  			f := result[i-1].Target
   313  			switch f.GetType() {
   314  			case descriptor.FieldDescriptorProto_TYPE_MESSAGE, descriptor.FieldDescriptorProto_TYPE_GROUP:
   315  				var err error
   316  				msg, err = r.LookupMsg(msg.FQMN(), f.GetTypeName())
   317  				if err != nil {
   318  					return nil, err
   319  				}
   320  			default:
   321  				return nil, fmt.Errorf("not an aggregate type: %s in %s", f.GetName(), path)
   322  			}
   323  		}
   324  
   325  		glog.V(2).Infof("Lookup %s in %s", c, msg.FQMN())
   326  		f := lookupField(msg, c)
   327  		if f == nil {
   328  			return nil, fmt.Errorf("no field %q found in %s", path, root.GetName())
   329  		}
   330  		if !(isPathParam || r.allowRepeatedFieldsInBody) && f.GetLabel() == descriptor.FieldDescriptorProto_LABEL_REPEATED {
   331  			return nil, fmt.Errorf("repeated field not allowed in field path: %s in %s", f.GetName(), path)
   332  		}
   333  		result = append(result, FieldPathComponent{Name: c, Target: f})
   334  	}
   335  	return result, nil
   336  }
   337  

View as plain text