private async Task <Metadata> GetResponseHeadersCoreAsync() { Debug.Assert(_httpResponseTask != null); try { var httpResponse = await _httpResponseTask.ConfigureAwait(false); // Check if the headers have a status. If they do then wait for the overall call task // to complete before returning headers. This means that if the call failed with a // a status then it is possible to await response headers and then call GetStatus(). var grpcStatus = GrpcProtocolHelpers.GetHeaderValue(httpResponse.Headers, GrpcProtocolConstants.StatusTrailer); if (grpcStatus != null) { await CallTask.ConfigureAwait(false); } return(GrpcProtocolHelpers.BuildMetadata(httpResponse.Headers)); } catch (Exception ex) { ResolveException(ex, out _, out var resolvedException); throw resolvedException; } }
private async Task <Metadata> GetResponseHeadersCoreAsync() { CompatibilityExtensions.Assert(_httpResponseTask != null); try { var httpResponse = await _httpResponseTask.ConfigureAwait(false); // Check if the headers have a status. If they do then wait for the overall call task // to complete before returning headers. This means that if the call failed with a // a status then it is possible to await response headers and then call GetStatus(). var grpcStatus = GrpcProtocolHelpers.GetHeaderValue(httpResponse.Headers, GrpcProtocolConstants.StatusTrailer); if (grpcStatus != null) { await CallTask.ConfigureAwait(false); } var metadata = GrpcProtocolHelpers.BuildMetadata(httpResponse.Headers); // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#exposed-retry-metadata if (_attemptCount > 1) { metadata.Add(GrpcProtocolConstants.RetryPreviousAttemptsHeader, (_attemptCount - 1).ToString(CultureInfo.InvariantCulture)); } return(metadata); } catch (Exception ex) when(ResolveException(ErrorStartingCallMessage, ex, out _, out var resolvedException)) { throw resolvedException; } }
private async ValueTask RunCall(HttpRequestMessage request, TimeSpan?timeout) { using (StartScope()) { var(diagnosticSourceEnabled, activity) = InitializeCall(request, timeout); if (Options.Credentials != null || Channel.CallCredentials?.Count > 0) { await ReadCredentials(request).ConfigureAwait(false); } // Unset variable to check that FinishCall is called in every code path bool finished; Status?status = null; try { // Fail early if deadline has already been exceeded _callCts.Token.ThrowIfCancellationRequested(); try { _httpResponseTask = Channel.HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _callCts.Token); HttpResponse = await _httpResponseTask.ConfigureAwait(false); } catch (Exception ex) { GrpcCallLog.ErrorStartingCall(Logger, ex); throw; } status = ValidateHeaders(HttpResponse); // A status means either the call has failed or grpc-status was returned in the response header if (status != null) { if (_responseTcs != null) { // gRPC status in the header if (status.Value.StatusCode != StatusCode.OK) { finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); SetFailedResult(status.Value); } else { // The server should never return StatusCode.OK in the header for a unary call. // If it does then throw an error that no message was returned from the server. GrpcCallLog.MessageNotReturned(Logger); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); _responseTcs.TrySetException(new InvalidOperationException("Call did not return a response message.")); } FinishResponseAndCleanUp(status.Value); } else { finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); FinishResponseAndCleanUp(status.Value); } } else { if (_responseTcs != null) { // Read entire response body immediately and read status from trailers // Trailers are only available once the response body had been read var responseStream = await HttpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); var message = await responseStream.ReadMessageAsync( Logger, Method.ResponseMarshaller.ContextualDeserializer, GrpcProtocolHelpers.GetGrpcEncoding(HttpResponse), Channel.ReceiveMaxMessageSize, Channel.CompressionProviders, singleMessage : true, _callCts.Token).ConfigureAwait(false); status = GrpcProtocolHelpers.GetResponseStatus(HttpResponse); FinishResponseAndCleanUp(status.Value); if (message == null) { GrpcCallLog.MessageNotReturned(Logger); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); SetFailedResult(status.Value); } else { GrpcEventSource.Log.MessageReceived(); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); if (status.Value.StatusCode == StatusCode.OK) { _responseTcs.TrySetResult(message); } else { SetFailedResult(status.Value); } } } else { // Duplex or server streaming call Debug.Assert(ClientStreamReader != null); ClientStreamReader.HttpResponseTcs.TrySetResult((HttpResponse, status)); // Wait until the response has been read and status read from trailers. // TCS will also be set in Dispose. status = await CallTask.ConfigureAwait(false); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); FinishResponseAndCleanUp(status.Value); } } } catch (Exception ex) { Exception resolvedException; ResolveException(ex, out status, out resolvedException); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); _responseTcs?.TrySetException(resolvedException); Cleanup(status.Value); } // Verify that FinishCall is called in every code path of this method. // Should create an "Unassigned variable" compiler error if not set. Debug.Assert(finished); } }
private async Task RunCall(HttpRequestMessage request, TimeSpan?timeout) { using (StartScope()) { var(diagnosticSourceEnabled, activity) = InitializeCall(request, timeout); if (Options.Credentials != null || Channel.CallCredentials?.Count > 0) { await ReadCredentials(request).ConfigureAwait(false); } // Unset variable to check that FinishCall is called in every code path bool finished; Status?status = null; try { // Fail early if deadline has already been exceeded _callCts.Token.ThrowIfCancellationRequested(); try { // If a HttpClient has been specified then we need to call it with ResponseHeadersRead // so that the response message is available for streaming _httpResponseTask = (Channel.HttpInvoker is HttpClient httpClient) ? httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _callCts.Token) : Channel.HttpInvoker.SendAsync(request, _callCts.Token); HttpResponse = await _httpResponseTask.ConfigureAwait(false); } catch (Exception ex) { // Don't log OperationCanceledException if deadline has exceeded. if (ex is OperationCanceledException && _callTcs.Task.IsCompletedSuccessfully && _callTcs.Task.Result.StatusCode == StatusCode.DeadlineExceeded) { throw; } else { GrpcCallLog.ErrorStartingCall(Logger, ex); throw; } } status = ValidateHeaders(HttpResponse); // A status means either the call has failed or grpc-status was returned in the response header if (status != null) { if (_responseTcs != null) { // gRPC status in the header if (status.Value.StatusCode != StatusCode.OK) { finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); } else { // The server should never return StatusCode.OK in the header for a unary call. // If it does then throw an error that no message was returned from the server. GrpcCallLog.MessageNotReturned(Logger); // Change the status code to a more accurate status. // This is consistent with Grpc.Core client behavior. status = new Status(StatusCode.Internal, "Failed to deserialize response message."); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); } FinishResponseAndCleanUp(status.Value); // Set failed result makes the response task thrown an error. Must be called after // the response is finished. Reasons: // - Finishing the response sets the status. Required for GetStatus to be successful. // - We want GetStatus to always work when called after the response task is done. SetFailedResult(status.Value); } else { finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); FinishResponseAndCleanUp(status.Value); } } else { if (_responseTcs != null) { // Read entire response body immediately and read status from trailers // Trailers are only available once the response body had been read var responseStream = await HttpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); var message = await ReadMessageAsync( responseStream, GrpcProtocolHelpers.GetGrpcEncoding(HttpResponse), singleMessage : true, _callCts.Token).ConfigureAwait(false); status = GrpcProtocolHelpers.GetResponseStatus(HttpResponse, Channel.OperatingSystem.IsBrowser); if (message == null) { GrpcCallLog.MessageNotReturned(Logger); if (status.Value.StatusCode == StatusCode.OK) { // Change the status code if OK is returned to a more accurate status. // This is consistent with Grpc.Core client behavior. status = new Status(StatusCode.Internal, "Failed to deserialize response message."); } FinishResponseAndCleanUp(status.Value); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); // Set failed result makes the response task thrown an error. Must be called after // the response is finished. Reasons: // - Finishing the response sets the status. Required for GetStatus to be successful. // - We want GetStatus to always work when called after the response task is done. SetFailedResult(status.Value); } else { GrpcEventSource.Log.MessageReceived(); FinishResponseAndCleanUp(status.Value); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); if (status.Value.StatusCode == StatusCode.OK) { _responseTcs.TrySetResult(message); } else { // Set failed result makes the response task thrown an error. Must be called after // the response is finished. Reasons: // - Finishing the response sets the status. Required for GetStatus to be successful. // - We want GetStatus to always work when called after the response task is done. SetFailedResult(status.Value); } } } else { // Duplex or server streaming call Debug.Assert(ClientStreamReader != null); ClientStreamReader.HttpResponseTcs.TrySetResult((HttpResponse, status)); // Wait until the response has been read and status read from trailers. // TCS will also be set in Dispose. status = await CallTask.ConfigureAwait(false); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); Cleanup(status.Value); } } } catch (Exception ex) { Exception resolvedException; ResolveException(ErrorStartingCallMessage, ex, out status, out resolvedException); finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value); _responseTcs?.TrySetException(resolvedException); Cleanup(status.Value); } // Verify that FinishCall is called in every code path of this method. // Should create an "Unassigned variable" compiler error if not set. Debug.Assert(finished); } }
private async ValueTask RunCall(HttpRequestMessage request) { using (StartScope()) { var(diagnosticSourceEnabled, activity) = InitializeCall(request); if (Options.Credentials != null || Channel.CallCredentials?.Count > 0) { await ReadCredentials(request).ConfigureAwait(false); } Status?status = null; try { try { HttpResponse = await Channel.HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _callCts.Token).ConfigureAwait(false); } catch (Exception ex) { GrpcCallLog.ErrorStartingCall(Logger, ex); throw; } BuildMetadata(HttpResponse); status = ValidateHeaders(HttpResponse); // A status means either the call has failed or grpc-status was returned in the response header if (status != null) { if (_responseTcs != null) { // gRPC status in the header if (status.Value.StatusCode != StatusCode.OK) { SetFailedResult(status.Value); } else { // The server should never return StatusCode.OK in the header for a unary call. // If it does then throw an error that no message was returned from the server. GrpcCallLog.MessageNotReturned(Logger); _responseTcs.TrySetException(new InvalidOperationException("Call did not return a response message.")); } } FinishResponse(status.Value); } else { if (_responseTcs != null) { // Read entire response body immediately and read status from trailers // Trailers are only available once the response body had been read var responseStream = await HttpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); var message = await responseStream.ReadMessageAsync( Logger, Method.ResponseMarshaller.ContextualDeserializer, GrpcProtocolHelpers.GetGrpcEncoding(HttpResponse), Channel.ReceiveMaxMessageSize, Channel.CompressionProviders, singleMessage : true, _callCts.Token).ConfigureAwait(false); status = GrpcProtocolHelpers.GetResponseStatus(HttpResponse); FinishResponse(status.Value); if (message == null) { GrpcCallLog.MessageNotReturned(Logger); SetFailedResult(status.Value); } else { GrpcEventSource.Log.MessageReceived(); if (status.Value.StatusCode == StatusCode.OK) { _responseTcs.TrySetResult(message); } else { SetFailedResult(status.Value); } } } else { // Duplex or server streaming call Debug.Assert(ClientStreamReader != null); ClientStreamReader.HttpResponseTcs.TrySetResult((HttpResponse, status)); // Wait until the response has been read and status read from trailers. // TCS will also be set in Dispose. status = await CallTask.ConfigureAwait(false); } } } catch (Exception ex) { 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 { status = new Status(StatusCode.Internal, "Error starting gRPC call: " + ex.Message); resolvedException = CreateRpcException(status.Value); } _metadataTcs.TrySetException(resolvedException); _responseTcs?.TrySetException(resolvedException); Cleanup(status !.Value); } FinishCall(request, diagnosticSourceEnabled, activity, status); } }