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