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); } }
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)); } }
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; } } }
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); } }
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); }
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)); } }
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)); }
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); }
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); }