public ServerCallDeadlineManager(HttpContextServerCallContext serverCallContext, ISystemClock clock, TimeSpan timeout, long maxTimerDueTime = DefaultMaxTimerDueTime) { // Set fields that need to exist before setting up deadline CTS // Ensures callback can run successfully before CTS timer starts _serverCallContext = serverCallContext; Deadline = clock.UtcNow.Add(timeout); _systemClock = clock; var timerMilliseconds = CommonGrpcProtocolHelpers.GetTimerDueTime(timeout, maxTimerDueTime); if (timerMilliseconds == maxTimerDueTime) { // Create timer and set to field before setting time. // Ensures there is no weird situation where the timer triggers // before the field is set. Shouldn't happen because only long deadlines // will take this path but better to be safe than sorry. _longDeadlineTimer = new Timer(DeadlineExceededLongDelegate, (this, maxTimerDueTime), Timeout.Infinite, Timeout.Infinite); _longDeadlineTimer.Change(timerMilliseconds, Timeout.Infinite); } else { _longDeadlineTimer = new Timer(DeadlineExceededDelegate, this, timerMilliseconds, Timeout.Infinite); } }
private void DeadlineExceededCallback(object?state) { // Deadline is only exceeded if the timeout has passed and // the response has not been finished or canceled if (!_callCts.IsCancellationRequested && !ResponseFinished) { TimeSpan remaining; lock (this) { // If _deadline is MaxValue then the DEADLINE_EXCEEDED status has // already been received by the client and the timer can stop. if (_deadline == DateTime.MaxValue) { return; } remaining = _deadline - Channel.Clock.UtcNow; if (remaining <= TimeSpan.Zero) { DeadlineExceeded(); return; } if (_deadlineTimer != null) { // Deadline has not been reached because timer maximum due time was smaller than deadline. // Reschedule DeadlineExceeded again until deadline has been exceeded. GrpcCallLog.DeadlineTimerRescheduled(Logger, remaining); var dueTime = CommonGrpcProtocolHelpers.GetTimerDueTime(remaining, Channel.MaxTimerDueTime); _deadlineTimer.Change(dueTime, Timeout.Infinite); } } } }
internal static string BuildErrorMessage(string message, Exception exception, bool?includeExceptionDetails) { if (includeExceptionDetails ?? false) { return(message + " " + CommonGrpcProtocolHelpers.ConvertToRpcExceptionMessage(exception)); } return(message); }
/// <summary> /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation. /// </summary> /// <param name="request">The HTTP request message to send to the server.</param> /// <param name="cancellationToken">A cancellation token to cancel operation.</param> /// <returns>The task object representing the asynchronous operation.</returns> protected override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (CommonGrpcProtocolHelpers.IsContentType(GrpcWebProtocolConstants.GrpcContentType, request.Content?.Headers.ContentType?.MediaType)) { return(SendAsyncCore(request, cancellationToken)); } return(base.SendAsync(request, cancellationToken)); }
private (bool diagnosticSourceEnabled, Activity?activity) InitializeCall(HttpRequestMessage request, TimeSpan?timeout) { GrpcCallLog.StartingCall(Logger, Method.Type, request.RequestUri !); GrpcEventSource.Log.CallStart(Method.FullName); // Deadline will cancel the call CTS. // Only exceed deadline/start timer after reader/writer have been created, otherwise deadline will cancel // the call CTS before they are created and leave them in a non-canceled state. if (timeout != null && !Channel.DisableClientDeadline) { if (timeout.Value <= TimeSpan.Zero) { // Call was started with a deadline in the past so immediately trigger deadline exceeded. DeadlineExceeded(); } else { GrpcCallLog.StartingDeadlineTimeout(Logger, timeout.Value); var dueTime = CommonGrpcProtocolHelpers.GetTimerDueTime(timeout.Value, Channel.MaxTimerDueTime); _deadlineTimer = new Timer(DeadlineExceededCallback, null, dueTime, Timeout.Infinite); } } var diagnosticSourceEnabled = GrpcDiagnostics.DiagnosticListener.IsEnabled() && GrpcDiagnostics.DiagnosticListener.IsEnabled(GrpcDiagnostics.ActivityName, request); Activity?activity = null; // Set activity if: // 1. Diagnostic source is enabled // 2. Logging is enabled // 3. There is an existing activity (to enable activity propagation) if (diagnosticSourceEnabled || Logger.IsEnabled(LogLevel.Critical) || Activity.Current != null) { activity = new Activity(GrpcDiagnostics.ActivityName); activity.AddTag(GrpcDiagnostics.GrpcMethodTagName, Method.FullName); activity.Start(); if (diagnosticSourceEnabled) { GrpcDiagnostics.DiagnosticListener.Write(GrpcDiagnostics.ActivityStartKey, new { Request = request }); } } if (Options.CancellationToken.CanBeCanceled) { // The cancellation token will cancel the call CTS. // This must be registered after the client writer has been created // so that cancellation will always complete the writer. _ctsRegistration = Options.CancellationToken.Register(CancelCallFromCancellationToken); } return(diagnosticSourceEnabled, activity); }
private static bool IsMatchingResponseContentType(GrpcWebMode mode, string?contentType) { if (mode == Web.GrpcWebMode.GrpcWeb) { return(CommonGrpcProtocolHelpers.IsContentType(GrpcWebProtocolConstants.GrpcWebContentType, contentType)); } if (mode == Web.GrpcWebMode.GrpcWebText) { return(CommonGrpcProtocolHelpers.IsContentType(GrpcWebProtocolConstants.GrpcWebTextContentType, contentType)); } return(false); }
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { if (httpContext == null) { return(false); } if (!HttpMethods.IsPost(httpContext.Request.Method)) { return(false); } return(CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcContentType, httpContext.Request.ContentType) || CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcWebContentType, httpContext.Request.ContentType) || CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcWebTextContentType, httpContext.Request.ContentType)); }
internal static ServerGrpcWebMode GetGrpcWebMode(HttpContext httpContext) { if (HttpMethods.IsPost(httpContext.Request.Method)) { if (CommonGrpcProtocolHelpers.IsContentType(GrpcWebProtocolConstants.GrpcWebContentType, httpContext.Request.ContentType)) { return(ServerGrpcWebMode.GrpcWeb); } else if (CommonGrpcProtocolHelpers.IsContentType(GrpcWebProtocolConstants.GrpcWebTextContentType, httpContext.Request.ContentType)) { return(ServerGrpcWebMode.GrpcWebText); } } return(ServerGrpcWebMode.None); }
public static bool IsInvalidContentType(HttpContext httpContext, [NotNullWhen(true)] out string?error) { if (httpContext.Request.ContentType == null) { error = "Content-Type is missing from the request."; return(true); } else if (!CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcContentType, httpContext.Request.ContentType)) { error = $"Content-Type '{httpContext.Request.ContentType}' is not supported."; return(true); } error = null; return(false); }
private static void DeadlineExceededLongCallback(object?state) { var(manager, maxTimerDueTime) = (ValueTuple <ServerCallDeadlineManager, long>)state !; var remaining = manager.Deadline - manager._systemClock.UtcNow; if (remaining <= TimeSpan.Zero) { _ = manager.DeadlineExceededAsync(); } else { // Deadline has not been reached because timer maximum due time was smaller than deadline. // Reschedule DeadlineExceeded again until deadline has been exceeded. GrpcServerLog.DeadlineTimerRescheduled(manager._serverCallContext.Logger, remaining); manager._longDeadlineTimer.Change(CommonGrpcProtocolHelpers.GetTimerDueTime(remaining, maxTimerDueTime), Timeout.Infinite); } }
protected RetryCallBase(GrpcChannel channel, Method <TRequest, TResponse> method, CallOptions options, string loggerName, int retryAttempts) { Logger = channel.LoggerFactory.CreateLogger(loggerName); Channel = channel; Method = method; Options = options; _commitedCallTcs = new TaskCompletionSource <IGrpcCall <TRequest, TResponse> >(TaskCreationOptions.RunContinuationsAsynchronously); BufferedMessages = new List <ReadOnlyMemory <byte> >(); // Raise OnCancellation event for cancellation related clean up. CancellationTokenSource = new CancellationTokenSource(); CancellationTokenSource.Token.Register(state => ((RetryCallBase <TRequest, TResponse>)state !).OnCancellation(), this); // If the passed in token is canceled then we want to cancel the retry cancellation token. // Note that if the token is already canceled then callback is run inline. if (options.CancellationToken.CanBeCanceled) { _ctsRegistration = options.CancellationToken.Register(state => ((RetryCallBase <TRequest, TResponse>)state !).CancellationTokenSource.Cancel(), this); } var deadline = Options.Deadline.GetValueOrDefault(DateTime.MaxValue); if (deadline != DateTime.MaxValue) { var timeout = CommonGrpcProtocolHelpers.GetTimerDueTime(deadline - Channel.Clock.UtcNow, Channel.MaxTimerDueTime); CancellationTokenSource.CancelAfter(TimeSpan.FromMilliseconds(timeout)); } if (HasClientStream()) { // Run continuation synchronously so awaiters execute inside the lock NewActiveCallTcs = new TaskCompletionSource <IGrpcCall <TRequest, TResponse>?>(TaskCreationOptions.None); } if (retryAttempts > Channel.MaxRetryAttempts) { Log.MaxAttemptsLimited(Logger, retryAttempts, Channel.MaxRetryAttempts.GetValueOrDefault()); MaxRetryAttempts = Channel.MaxRetryAttempts.GetValueOrDefault(); } else { MaxRetryAttempts = retryAttempts; } }
private async Task HandleGrpcWebRequest(HttpContext httpContext, ServerGrpcWebMode mode) { var feature = new GrpcWebFeature(mode, httpContext); var initialProtocol = httpContext.Request.Protocol; // Modifying the request is required to stop Grpc.AspNetCore.Server from rejecting it httpContext.Request.Protocol = GrpcWebProtocolConstants.Http2Protocol; httpContext.Request.ContentType = ResolveContentType(GrpcWebProtocolConstants.GrpcContentType, httpContext.Request.ContentType); // Update response content type back to gRPC-Web httpContext.Response.OnStarting(() => { // Reset request protocol back to its original value. Not doing this causes a 2 second // delay when making HTTP/1.1 calls. httpContext.Request.Protocol = initialProtocol; if (CommonGrpcProtocolHelpers.IsContentType(GrpcWebProtocolConstants.GrpcContentType, httpContext.Response.ContentType)) { var contentType = mode == ServerGrpcWebMode.GrpcWeb ? GrpcWebProtocolConstants.GrpcWebContentType : GrpcWebProtocolConstants.GrpcWebTextContentType; var responseContentType = ResolveContentType(contentType, httpContext.Response.ContentType); httpContext.Response.ContentType = responseContentType; Log.SendingGrpcWebResponse(_logger, responseContentType); } return(Task.CompletedTask); }); try { await _next(httpContext); // If trailers have already been written in CompleteAsync then this will no-op await feature.WriteTrailersAsync(); } finally { feature.DetachFromContext(httpContext); } }
internal static Status?ValidateHeaders(HttpResponseMessage httpResponse, out Metadata?trailers) { // gRPC status can be returned in the header when there is no message (e.g. unimplemented status) // An explicitly specified status header has priority over other failing statuses if (GrpcProtocolHelpers.TryGetStatusCore(httpResponse.Headers, out var status)) { // Trailers are in the header because there is no message. // Note that some default headers will end up in the trailers (e.g. Date, Server). trailers = GrpcProtocolHelpers.BuildMetadata(httpResponse.Headers); return(status); } trailers = null; // ALPN negotiation is sending HTTP/1.1 and HTTP/2. // Check that the response wasn't downgraded to HTTP/1.1. if (httpResponse.Version < GrpcProtocolConstants.Http2Version) { return(new Status(StatusCode.Internal, $"Bad gRPC response. Response protocol downgraded to HTTP/{httpResponse.Version.ToString(2)}.")); } if (httpResponse.StatusCode != HttpStatusCode.OK) { var statusCode = MapHttpStatusToGrpcCode(httpResponse.StatusCode); return(new Status(statusCode, "Bad gRPC response. HTTP status code: " + (int)httpResponse.StatusCode)); } // Don't access Headers.ContentType property because it is not threadsafe. var contentType = GrpcProtocolHelpers.GetHeaderValue(httpResponse.Content?.Headers, "Content-Type"); if (contentType == null) { return(new Status(StatusCode.Cancelled, "Bad gRPC response. Response did not have a content-type header.")); } if (!CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcContentType, contentType)) { return(new Status(StatusCode.Cancelled, "Bad gRPC response. Invalid content-type value: " + contentType)); } // Call is still in progress return(null); }
private Status?ValidateHeaders(HttpResponseMessage httpResponse) { GrpcCallLog.ResponseHeadersReceived(Logger); // gRPC status can be returned in the header when there is no message (e.g. unimplemented status) // An explicitly specified status header has priority over other failing statuses if (GrpcProtocolHelpers.TryGetStatusCore(httpResponse.Headers, out var status)) { // Trailers are in the header because there is no message. // Note that some default headers will end up in the trailers (e.g. Date, Server). _trailers = GrpcProtocolHelpers.BuildMetadata(httpResponse.Headers); return(status); } // ALPN negotiation is sending HTTP/1.1 and HTTP/2. // Check that the response wasn't downgraded to HTTP/1.1. if (httpResponse.Version < HttpVersion.Version20) { return(new Status(StatusCode.Internal, $"Bad gRPC response. Response protocol downgraded to HTTP/{httpResponse.Version.ToString(2)}.")); } if (httpResponse.StatusCode != HttpStatusCode.OK) { var statusCode = MapHttpStatusToGrpcCode(httpResponse.StatusCode); return(new Status(statusCode, "Bad gRPC response. HTTP status code: " + (int)httpResponse.StatusCode)); } if (httpResponse.Content?.Headers.ContentType == null) { return(new Status(StatusCode.Cancelled, "Bad gRPC response. Response did not have a content-type header.")); } var grpcEncoding = httpResponse.Content.Headers.ContentType; if (!CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcContentType, grpcEncoding?.MediaType)) { return(new Status(StatusCode.Cancelled, "Bad gRPC response. Invalid content-type value: " + grpcEncoding)); } // Call is still in progress return(null); }
private void ResolveException(Exception ex, [NotNull] out Status?status, out Exception resolvedException) { if (ex is OperationCanceledException) { status = (CallTask.IsCompletedSuccessfully) ? CallTask.Result : new Status(StatusCode.Cancelled, string.Empty); resolvedException = Channel.ThrowOperationCanceledOnCancellation ? ex : CreateRpcException(status.Value); } else if (ex is RpcException rpcException) { status = rpcException.Status; resolvedException = CreateRpcException(status.Value); } else { var exceptionMessage = CommonGrpcProtocolHelpers.ConvertToRpcExceptionMessage(ex); status = new Status(StatusCode.Internal, "Error starting gRPC call. " + exceptionMessage); resolvedException = CreateRpcException(status.Value); } }
/// <summary> /// Resolve the specified exception to an end-user exception that will be thrown from the client. /// The resolved exception is normally a RpcException. Returns true when the resolved exception is changed. /// </summary> internal bool ResolveException(string summary, Exception ex, [NotNull] out Status?status, out Exception resolvedException) { if (ex is OperationCanceledException) { status = (CallTask.IsCompletedSuccessfully) ? CallTask.Result : new Status(StatusCode.Cancelled, string.Empty); if (!Channel.ThrowOperationCanceledOnCancellation) { resolvedException = CreateRpcException(status.Value); return(true); } } else if (ex is RpcException rpcException) { status = rpcException.Status; // If trailers have been set, and the RpcException isn't using them, then // create new RpcException with trailers. Want to try and avoid this as // the exact stack location will be lost. // // Trailers could be set in another thread so copy to local variable. var trailers = Trailers; if (trailers != null && rpcException.Trailers != trailers) { resolvedException = CreateRpcException(status.Value); return(true); } } else { var exceptionMessage = CommonGrpcProtocolHelpers.ConvertToRpcExceptionMessage(ex); var statusCode = GrpcProtocolHelpers.ResolveRpcExceptionStatusCode(ex); status = new Status(statusCode, summary + " " + exceptionMessage, ex); resolvedException = CreateRpcException(status.Value); return(true); } resolvedException = ex; return(false); }
private void DeadlineExceededCallback(object?state) { // Deadline is only exceeded if the timeout has passed and // the response has not been finished or canceled if (!_callCts.IsCancellationRequested && !ResponseFinished) { var remaining = _deadline - Channel.Clock.UtcNow; if (remaining <= TimeSpan.Zero) { DeadlineExceeded(); } else { // Deadline has not been reached because timer maximum due time was smaller than deadline. // Reschedule DeadlineExceeded again until deadline has been exceeded. GrpcCallLog.DeadlineTimerRescheduled(Logger, remaining); var dueTime = CommonGrpcProtocolHelpers.GetTimerDueTime(remaining, Channel.MaxTimerDueTime); _deadlineTimer !.Change(dueTime, Timeout.Infinite); } } }
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { if (httpContext == null) { return(false); } // Constraint needs to be valid when a CORS preflight request is received so that CORS middleware will run if (GrpcProtocolHelpers.IsCorsPreflightRequest(httpContext)) { return(true); } if (!HttpMethods.IsPost(httpContext.Request.Method)) { return(false); } return(CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcContentType, httpContext.Request.ContentType) || CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcWebContentType, httpContext.Request.ContentType) || CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcWebTextContentType, httpContext.Request.ContentType)); }