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);
            }
        }
Exemplo n.º 2
0
        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);
        }
Exemplo n.º 4
0
        /// <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));
        }
Exemplo n.º 5
0
        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);
        }
Exemplo n.º 6
0
        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);
        }
Exemplo n.º 7
0
            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));
            }
Exemplo n.º 8
0
        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);
        }
Exemplo n.º 9
0
        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);
            }
        }
Exemplo n.º 11
0
        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;
            }
        }
Exemplo n.º 12
0
        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);
            }
        }
Exemplo n.º 13
0
        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);
        }
Exemplo n.º 14
0
        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);
        }
Exemplo n.º 15
0
        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);
            }
        }
Exemplo n.º 16
0
        /// <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);
        }
Exemplo n.º 17
0
        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);
                }
            }
        }
Exemplo n.º 18
0
            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));
            }