예제 #1
0
        private void AddMethodCore <TRequest, TResponse>(
            Method <TRequest, TResponse> method,
            HttpRule httpRule,
            string pattern,
            string httpVerb,
            string body,
            string responseBody,
            MethodDescriptor methodDescriptor)
            where TRequest : class
            where TResponse : class
        {
            try
            {
                if (!pattern.StartsWith('/'))
                {
                    // This validation is consistent with grpc-gateway code generation.
                    // We should match their validation to be a good member of the eco-system.
                    throw new InvalidOperationException($"Path template must start with /: {pattern}");
                }

                var(invoker, metadata) = CreateModelCore <UnaryServerMethod <TService, TRequest, TResponse> >(
                    method.Name,
                    new[] { typeof(TRequest), typeof(ServerCallContext) },
                    httpVerb,
                    httpRule,
                    methodDescriptor);

                var methodContext = global::Grpc.Shared.Server.MethodOptions.Create(new[] { _globalOptions, _serviceOptions });

                var routePattern = RoutePatternFactory.Parse(pattern);
                var routeParameterDescriptors = ServiceDescriptorHelpers.ResolveRouteParameterDescriptors(routePattern, methodDescriptor.InputType);

                ServiceDescriptorHelpers.ResolveBodyDescriptor(body, methodDescriptor, out var bodyDescriptor, out var bodyFieldDescriptors, out var bodyDescriptorRepeated);

                FieldDescriptor?responseBodyDescriptor = null;
                if (!string.IsNullOrEmpty(responseBody))
                {
                    responseBodyDescriptor = methodDescriptor.OutputType.FindFieldByName(responseBody);
                    if (responseBodyDescriptor == null)
                    {
                        throw new InvalidOperationException($"Couldn't find matching field for response body '{responseBody}' on {methodDescriptor.OutputType.Name}.");
                    }
                }

                var unaryInvoker           = new UnaryServerMethodInvoker <TService, TRequest, TResponse>(invoker, method, methodContext, _serviceActivator);
                var unaryServerCallHandler = new UnaryServerCallHandler <TService, TRequest, TResponse>(
                    unaryInvoker,
                    responseBodyDescriptor,
                    bodyDescriptor,
                    bodyDescriptorRepeated,
                    bodyFieldDescriptors,
                    routeParameterDescriptors);

                _context.AddMethod <TRequest, TResponse>(method, routePattern, metadata, unaryServerCallHandler.HandleCallAsync);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"Error binding {method.Name} on {typeof(TService).Name} to HTTP API.", ex);
            }
        }
예제 #2
0
 public override void AddMethod <TRequest, TResponse>(Method <TRequest, TResponse> method, ServerStreamingServerMethod <TRequest, TResponse> handler)
 {
     if (TryGetMethodDescriptor(method.Name, out var methodDescriptor) &&
         ServiceDescriptorHelpers.TryGetHttpRule(methodDescriptor, out _))
     {
         Log.StreamingMethodNotSupported(_logger, method.Name, typeof(TService));
     }
 }
예제 #3
0
        private async Task <IMessage> CreateMessage(HttpRequest request)
        {
            IMessage?requestMessage;

            if (_bodyDescriptor != null)
            {
                if (request.ContentType == null ||
                    !request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
                {
                    throw new InvalidOperationException("Request content-type of application/json is required.");
                }

                if (!request.Body.CanSeek)
                {
                    // JsonParser does synchronous reads. In order to avoid blocking on the stream, we asynchronously
                    // read everything into a buffer, and then seek back to the beginning.
                    request.EnableBuffering();
                    Debug.Assert(request.Body.CanSeek);

                    await request.Body.DrainAsync(CancellationToken.None);

                    request.Body.Seek(0L, SeekOrigin.Begin);
                }

                var encoding = RequestEncoding.SelectCharacterEncoding(request);
                // TODO: Handle unsupported encoding

                using (var requestReader = new HttpRequestStreamReader(request.Body, encoding))
                {
                    if (_bodyDescriptorRepeated)
                    {
                        var containingMessage = ParseRepeatedContent(requestReader);

                        if (_resolvedBodyFieldDescriptors !.Count > 0)
                        {
                            requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                            ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, _resolvedBodyFieldDescriptors, containingMessage);
                        }
                        else
                        {
                            requestMessage = containingMessage;
                        }
                    }
                    else
                    {
                        var bodyContent = JsonParser.Default.Parse(requestReader, _bodyDescriptor);

                        if (_bodyFieldDescriptors != null)
                        {
                            requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                            ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, _bodyFieldDescriptors, bodyContent);
                        }
                        else
                        {
                            requestMessage = bodyContent;
                        }
                    }
                }
예제 #4
0
        private void ProcessHttpRule <TRequest, TResponse>(Method <TRequest, TResponse> method, MethodDescriptor methodDescriptor, HttpRule httpRule)
            where TRequest : class
            where TResponse : class
        {
            if (ServiceDescriptorHelpers.TryResolvePattern(httpRule, out var pattern, out var httpVerb))
            {
                AddMethodCore(method, httpRule, pattern, httpVerb, httpRule.Body, httpRule.ResponseBody, methodDescriptor);
            }

            foreach (var additionalRule in httpRule.AdditionalBindings)
            {
                ProcessHttpRule(method, methodDescriptor, additionalRule);
            }
        }
예제 #5
0
        private static Dictionary <string, List <FieldDescriptor> > ResolveRouteParameterDescriptors(string pattern, MessageDescriptor messageDescriptor)
        {
            var routePattern = RoutePatternFactory.Parse(pattern);

            var routeParameterDescriptors = new Dictionary <string, List <FieldDescriptor> >(StringComparer.Ordinal);

            foreach (var routeParameter in routePattern.Parameters)
            {
                if (!ServiceDescriptorHelpers.TryResolveDescriptors(messageDescriptor, routeParameter.Name, out var fieldDescriptors))
                {
                    throw new InvalidOperationException($"Couldn't find matching field for route parameter '{routeParameter.Name}' on {messageDescriptor.Name}.");
                }

                routeParameterDescriptors.Add(routeParameter.Name, fieldDescriptors);
            }

            return(routeParameterDescriptors);
        }
예제 #6
0
 public override void AddMethod <TRequest, TResponse>(Method <TRequest, TResponse> method, UnaryServerMethod <TRequest, TResponse> handler)
 {
     if (TryGetMethodDescriptor(method.Name, out var methodDescriptor))
     {
         if (ServiceDescriptorHelpers.TryGetHttpRule(methodDescriptor, out var httpRule))
         {
             ProcessHttpRule(method, methodDescriptor, httpRule);
         }
         else
         {
             // Consider setting to enable mapping to methods without HttpRule
             // AddMethodCore(method, method.FullName, "GET", string.Empty, string.Empty, methodDescriptor);
         }
     }
     else
     {
         Log.MethodDescriptorNotFound(_logger, method.Name, typeof(TService));
     }
 }
예제 #7
0
        private ApiDescriptionGroupCollection GetCollection()
        {
            var descriptions = new List <ApiDescription>();

            var endpoints = _endpointDataSource.Endpoints;

            foreach (var endpoint in endpoints)
            {
                if (endpoint is RouteEndpoint routeEndpoint)
                {
                    var grpcMetadata = endpoint.Metadata.GetMetadata <GrpcHttpMetadata>();

                    if (grpcMetadata != null)
                    {
                        var httpRule         = grpcMetadata.HttpRule;
                        var methodDescriptor = grpcMetadata.MethodDescriptor;

                        if (ServiceDescriptorHelpers.TryResolvePattern(grpcMetadata.HttpRule, out var pattern, out var verb))
                        {
                            var apiDescription = new ApiDescription();
                            apiDescription.HttpMethod       = verb;
                            apiDescription.ActionDescriptor = new ActionDescriptor
                            {
                                RouteValues = new Dictionary <string, string>
                                {
                                    // Swagger uses this to group endpoints together.
                                    // Group methods together using the service name.
                                    ["controller"] = methodDescriptor.Service.FullName
                                }
                            };
                            apiDescription.RelativePath = pattern.TrimStart('/');
                            apiDescription.SupportedRequestFormats.Add(new ApiRequestFormat {
                                MediaType = "application/json"
                            });
                            apiDescription.SupportedResponseTypes.Add(new ApiResponseType
                            {
                                ApiResponseFormats = { new ApiResponseFormat {
                                                           MediaType = "application/json"
                                                       } },
                                ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(methodDescriptor.OutputType.ClrType)),
                                StatusCode    = 200
                            });

                            var routeParameters = ServiceDescriptorHelpers.ResolveRouteParameterDescriptors(routeEndpoint.RoutePattern, methodDescriptor.InputType);

                            foreach (var routeParameter in routeParameters)
                            {
                                var field = routeParameter.Value.Last();

                                apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
                                {
                                    Name          = routeParameter.Key,
                                    ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(MessageDescriptorHelpers.ResolveFieldType(field))),
                                    Source        = BindingSource.Path,
                                    DefaultValue  = string.Empty
                                });
                            }

                            ServiceDescriptorHelpers.ResolveBodyDescriptor(httpRule.Body, methodDescriptor, out var bodyDescriptor, out _, out _);
                            if (bodyDescriptor != null)
                            {
                                apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
                                {
                                    Name          = "Input",
                                    ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(bodyDescriptor.ClrType)),
                                    Source        = BindingSource.Body
                                });
                            }

                            descriptions.Add(apiDescription);
                        }
                    }
                }
            }

            var groups = new List <ApiDescriptionGroup>();

            groups.Add(new ApiDescriptionGroup("Grpc", descriptions));

            return(new ApiDescriptionGroupCollection(groups, 1));
        }
예제 #8
0
        private async Task <IMessage> CreateMessage(HttpRequest request)
        {
            IMessage?requestMessage;

            if (_bodyDescriptor != null)
            {
                if (request.ContentType == null ||
                    !request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
                {
                    throw new InvalidOperationException("Request content-type of application/json is required.");
                }

                if (!request.Body.CanSeek)
                {
                    // JsonParser does synchronous reads. In order to avoid blocking on the stream, we asynchronously
                    // read everything into a buffer, and then seek back to the beginning.
                    request.EnableBuffering();
                    Debug.Assert(request.Body.CanSeek);

                    await request.Body.DrainAsync(CancellationToken.None);

                    request.Body.Seek(0L, SeekOrigin.Begin);
                }

                var encoding = RequestEncoding.SelectCharacterEncoding(request);
                // TODO: Handle unsupported encoding

                IMessage bodyContent;
                using (var requestReader = new HttpRequestStreamReader(request.Body, encoding))
                {
                    bodyContent = JsonParser.Default.Parse(requestReader, _bodyDescriptor);
                }

                if (_bodyFieldDescriptor != null)
                {
                    requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                    _bodyFieldDescriptor.Accessor.SetValue(requestMessage, bodyContent);
                }
                else
                {
                    requestMessage = bodyContent;
                }
            }
            else
            {
                requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
            }

            foreach (var parameterDescriptor in _routeParameterDescriptors)
            {
                var routeValue = request.RouteValues[parameterDescriptor.Key];
                if (routeValue != null)
                {
                    ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, parameterDescriptor.Value, routeValue);
                }
            }

            foreach (var item in request.Query)
            {
                if (CanBindQueryStringVariable(item.Key))
                {
                    if (!_queryParameterDescriptors.TryGetValue(item.Key, out var pathDescriptors))
                    {
                        if (ServiceDescriptorHelpers.TryResolveDescriptors(requestMessage.Descriptor, item.Key, out pathDescriptors))
                        {
                            _queryParameterDescriptors[item.Key] = pathDescriptors;
                        }
                    }

                    if (pathDescriptors != null)
                    {
                        object value = item.Value.Count == 1 ? (object)item.Value[0] : item.Value;
                        ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, pathDescriptors, value);
                    }
                }
            }

            return(requestMessage);
        }
예제 #9
0
        private void AddMethodCore <TRequest, TResponse>(
            Method <TRequest, TResponse> method,
            string pattern,
            string httpVerb,
            string body,
            string responseBody,
            MethodDescriptor methodDescriptor)
            where TRequest : class
            where TResponse : class
        {
            try
            {
                if (!pattern.StartsWith('/'))
                {
                    // This validation is consistent with grpc-gateway code generation.
                    // We should match their validation to be a good member of the eco-system.
                    throw new InvalidOperationException($"Path template must start with /: {pattern}");
                }

                var(invoker, metadata) = CreateModelCore <UnaryServerMethod <TService, TRequest, TResponse> >(
                    method.Name,
                    new[] { typeof(TRequest), typeof(ServerCallContext) },
                    httpVerb);

                var methodContext = global::Grpc.Shared.Server.MethodOptions.Create(new[] { _globalOptions, _serviceOptions });

                var routeParameterDescriptors = ResolveRouteParameterDescriptors(pattern, methodDescriptor.InputType);

                MessageDescriptor?     bodyDescriptor       = null;
                List <FieldDescriptor>?bodyFieldDescriptors = null;
                var bodyDescriptorRepeated = false;
                if (!string.IsNullOrEmpty(body))
                {
                    if (!string.Equals(body, "*", StringComparison.Ordinal))
                    {
                        if (!ServiceDescriptorHelpers.TryResolveDescriptors(methodDescriptor.InputType, body, out bodyFieldDescriptors))
                        {
                            throw new InvalidOperationException($"Couldn't find matching field for body '{body}' on {methodDescriptor.InputType.Name}.");
                        }
                        var leafDescriptor = bodyFieldDescriptors.Last();
                        if (leafDescriptor.IsRepeated)
                        {
                            // A repeating field isn't a message type. The JSON parser will parse using the containing
                            // type to get the repeating collection.
                            bodyDescriptor         = leafDescriptor.ContainingType;
                            bodyDescriptorRepeated = true;
                        }
                        else
                        {
                            bodyDescriptor = leafDescriptor.MessageType;
                        }
                    }
                    else
                    {
                        bodyDescriptor = methodDescriptor.InputType;
                    }
                }

                FieldDescriptor?responseBodyDescriptor = null;
                if (!string.IsNullOrEmpty(responseBody))
                {
                    responseBodyDescriptor = methodDescriptor.OutputType.FindFieldByName(responseBody);
                    if (responseBodyDescriptor == null)
                    {
                        throw new InvalidOperationException($"Couldn't find matching field for response body '{responseBody}' on {methodDescriptor.OutputType.Name}.");
                    }
                }

                var unaryInvoker           = new UnaryServerMethodInvoker <TService, TRequest, TResponse>(invoker, method, methodContext, _serviceActivator);
                var unaryServerCallHandler = new UnaryServerCallHandler <TService, TRequest, TResponse>(
                    unaryInvoker,
                    responseBodyDescriptor,
                    bodyDescriptor,
                    bodyDescriptorRepeated,
                    bodyFieldDescriptors,
                    routeParameterDescriptors);

                _context.AddMethod <TRequest, TResponse>(method, RoutePatternFactory.Parse(pattern), metadata, unaryServerCallHandler.HandleCallAsync);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"Error binding {method.Name} on {typeof(TService).Name} to HTTP API.", ex);
            }
        }
        private async Task <IMessage> CreateMessage(HttpContext httpContext)
        {
            IMessage?requestMessage;

            if (_bodyDescriptor != null)
            {
                if (string.Equals(httpContext.Request.ContentType, "application/json", StringComparison.OrdinalIgnoreCase))
                {
                    throw new InvalidOperationException("Request content-type of application/json is required.");
                }

                var ms = new MemoryStream();
                await httpContext.Request.Body.CopyToAsync(ms);

                ms.Seek(0, SeekOrigin.Begin);

                var bodyContent = JsonParser.Default.Parse(new StreamReader(ms), _bodyDescriptor);
                if (_bodyFieldDescriptor != null)
                {
                    requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                    _bodyFieldDescriptor.Accessor.SetValue(requestMessage, bodyContent);
                }
                else
                {
                    requestMessage = bodyContent;
                }
            }
            else
            {
                requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
            }

            foreach (var parameterDescriptor in _routeParameterDescriptors)
            {
                var routeValue = httpContext.Request.RouteValues[parameterDescriptor.Key];
                if (routeValue != null)
                {
                    ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, parameterDescriptor.Value, routeValue);
                }
            }

            foreach (var item in httpContext.Request.Query)
            {
                if (CanBindQueryStringVariable(item.Key))
                {
                    if (!_queryParameterDescriptors.TryGetValue(item.Key, out var pathDescriptors))
                    {
                        if (ServiceDescriptorHelpers.TryResolveDescriptors(requestMessage.Descriptor, item.Key, out pathDescriptors))
                        {
                            _queryParameterDescriptors[item.Key] = pathDescriptors;
                        }
                    }

                    if (pathDescriptors != null)
                    {
                        object value = item.Value.Count == 1 ? (object)item.Value[0] : item.Value;
                        ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, pathDescriptors, value);
                    }
                }
            }

            return(requestMessage);
        }