Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error message when CORS is not configured correctly #1171

Merged
merged 1 commit into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Grpc.Net.Client/GrpcChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ public sealed class GrpcChannel : ChannelBase, IDisposable
internal Dictionary<string, ICompressionProvider> CompressionProviders { get; }
internal string MessageAcceptEncoding { get; }
internal bool Disposed { get; private set; }
// Timing related options that are set in unit tests

// Options that are set in unit tests
internal ISystemClock Clock = SystemClock.Instance;
internal IOperatingSystem OperatingSystem = Internal.OperatingSystem.Instance;
internal bool DisableClientDeadline;
internal long MaxTimerDueTime = uint.MaxValue - 1; // Max System.Threading.Timer due time

Expand Down
2 changes: 1 addition & 1 deletion src/Grpc.Net.Client/Internal/GrpcCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ private async Task RunCall(HttpRequestMessage request, TimeSpan? timeout)
GrpcProtocolHelpers.GetGrpcEncoding(HttpResponse),
singleMessage: true,
_callCts.Token).ConfigureAwait(false);
status = GrpcProtocolHelpers.GetResponseStatus(HttpResponse);
status = GrpcProtocolHelpers.GetResponseStatus(HttpResponse, Channel.OperatingSystem.IsBrowser);

if (message == null)
{
Expand Down
10 changes: 8 additions & 2 deletions src/Grpc.Net.Client/Internal/GrpcProtocolHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,14 +327,20 @@ public static void AddHeader(HttpRequestHeaders headers, Metadata.Entry entry)
}
}

public static Status GetResponseStatus(HttpResponseMessage httpResponse)
public static Status GetResponseStatus(HttpResponseMessage httpResponse, bool isBrowser)
{
Status? status;
try
{
if (!TryGetStatusCore(httpResponse.TrailingHeaders, out status))
{
status = new Status(StatusCode.Cancelled, "No grpc-status found on response.");
var detail = "No grpc-status found on response.";
if (isBrowser)
{
detail += " If the gRPC call is cross domain then CORS must be correctly configured. Access-Control-Expose-Headers needs to include 'grpc-status' and 'grpc-message'.";
}

status = new Status(StatusCode.Cancelled, detail);
}
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ private async Task<bool> MoveNextCore(CancellationToken cancellationToken)
{
// No more content in response so report status to call.
// The call will handle finishing the response.
var status = GrpcProtocolHelpers.GetResponseStatus(_httpResponse);
var status = GrpcProtocolHelpers.GetResponseStatus(_httpResponse, _call.Channel.OperatingSystem.IsBrowser);
_call.ResponseStreamEnded(status);
if (status.StatusCode != StatusCode.OK)
{
Expand Down
39 changes: 39 additions & 0 deletions src/Grpc.Net.Client/Internal/OperatingSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using System.Runtime.InteropServices;

namespace Grpc.Net.Client.Internal
{
internal interface IOperatingSystem
{
bool IsBrowser { get; }
}

internal class OperatingSystem : IOperatingSystem
{
public static readonly OperatingSystem Instance = new OperatingSystem();

public bool IsBrowser { get; }

private OperatingSystem()
{
IsBrowser = RuntimeInformation.IsOSPlatform(OSPlatform.Create("browser"));
}
}
}
30 changes: 30 additions & 0 deletions test/Grpc.Net.Client.Tests/GetStatusTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,36 @@ public async Task AsyncUnaryCall_MissingStatus_ThrowError()
Assert.AreEqual(StatusCode.Cancelled, call.GetStatus().StatusCode);
}

[Test]
public async Task AsyncUnaryCall_MissingStatusBrowser_ThrowError()
{
// Arrange
var httpClient = ClientTestHelpers.CreateTestClient(async request =>
{
var streamContent = await ClientTestHelpers.CreateResponseContent(new HelloReply()).DefaultTimeout();
var response = ResponseUtils.CreateResponse(HttpStatusCode.OK, streamContent, grpcStatusCode: null);
return response;
});

var os = new TestOperatingSystem { IsBrowser = true };
var invoker = HttpClientCallInvokerFactory.Create(httpClient, operatingSystem: os);

// Act
var call = invoker.AsyncUnaryCall<HelloRequest, HelloReply>(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest());

// Assert
var ex = await ExceptionAssert.ThrowsAsync<RpcException>(() => call.ResponseAsync).DefaultTimeout();

Assert.AreEqual("No grpc-status found on response. If the gRPC call is cross domain then CORS must be correctly configured. Access-Control-Expose-Headers needs to include 'grpc-status' and 'grpc-message'.", ex.Status.Detail);
Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode);
Assert.AreEqual(StatusCode.Cancelled, call.GetStatus().StatusCode);
}

private class TestOperatingSystem : IOperatingSystem
{
public bool IsBrowser { get; set; }
}

[Test]
public async Task AsyncUnaryCall_InvalidStatus_ThrowError()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public static HttpClientCallInvoker Create(
ISystemClock? systemClock = null,
Action<GrpcChannelOptions>? configure = null,
bool? disableClientDeadline = null,
long? maxTimerPeriod = null)
long? maxTimerPeriod = null,
IOperatingSystem? operatingSystem = null)
{
var channelOptions = new GrpcChannelOptions
{
Expand All @@ -50,6 +51,10 @@ public static HttpClientCallInvoker Create(
{
channel.MaxTimerDueTime = maxTimerPeriod.Value;
}
if (operatingSystem != null)
{
channel.OperatingSystem = operatingSystem;
}

return new HttpClientCallInvoker(channel);
}
Expand Down