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

gRPC-Web client and server #695

Merged
merged 8 commits into from Jan 9, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 20 additions & 6 deletions Grpc.DotNet.sln
Expand Up @@ -108,7 +108,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GrpcAspNetCoreServer", "per
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GrpcCoreServer", "perf\benchmarkapps\GrpcCoreServer\GrpcCoreServer.csproj", "{781111FC-8F3C-433E-BC96-D88ADAEE3064}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.AspNetCore.HealthChecks", "src\Grpc.AspNetCore.HealthChecks\Grpc.AspNetCore.HealthChecks.csproj", "{E46446FE-2B93-4F42-84C8-A4D09B48B173}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Net.Client.Web", "src\Grpc.Net.Client.Web\Grpc.Net.Client.Web.csproj", "{429EB088-94FF-4F06-8E54-72157089C8C3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.AspNetCore.Web", "src\Grpc.AspNetCore.Web\Grpc.AspNetCore.Web.csproj", "{778DB6EE-E5B2-4875-A209-40010B5A3E21}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.AspNetCore.HealthChecks", "src\Grpc.AspNetCore.HealthChecks\Grpc.AspNetCore.HealthChecks.csproj", "{39A9F2B5-2541-423E-83C9-46C7BFF53F41}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -208,10 +212,18 @@ Global
{781111FC-8F3C-433E-BC96-D88ADAEE3064}.Debug|Any CPU.Build.0 = Debug|Any CPU
{781111FC-8F3C-433E-BC96-D88ADAEE3064}.Release|Any CPU.ActiveCfg = Release|Any CPU
{781111FC-8F3C-433E-BC96-D88ADAEE3064}.Release|Any CPU.Build.0 = Release|Any CPU
{E46446FE-2B93-4F42-84C8-A4D09B48B173}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E46446FE-2B93-4F42-84C8-A4D09B48B173}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E46446FE-2B93-4F42-84C8-A4D09B48B173}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E46446FE-2B93-4F42-84C8-A4D09B48B173}.Release|Any CPU.Build.0 = Release|Any CPU
{429EB088-94FF-4F06-8E54-72157089C8C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{429EB088-94FF-4F06-8E54-72157089C8C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{429EB088-94FF-4F06-8E54-72157089C8C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{429EB088-94FF-4F06-8E54-72157089C8C3}.Release|Any CPU.Build.0 = Release|Any CPU
{778DB6EE-E5B2-4875-A209-40010B5A3E21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{778DB6EE-E5B2-4875-A209-40010B5A3E21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{778DB6EE-E5B2-4875-A209-40010B5A3E21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{778DB6EE-E5B2-4875-A209-40010B5A3E21}.Release|Any CPU.Build.0 = Release|Any CPU
{39A9F2B5-2541-423E-83C9-46C7BFF53F41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39A9F2B5-2541-423E-83C9-46C7BFF53F41}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39A9F2B5-2541-423E-83C9-46C7BFF53F41}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39A9F2B5-2541-423E-83C9-46C7BFF53F41}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -249,7 +261,9 @@ Global
{69C50655-71EE-4E69-BC2C-ABCA568F6E76} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
{1D9AB69D-244C-4871-867E-DCEC52B552A4} = {1B8B6117-CE39-4580-BAFA-D8026102767A}
{781111FC-8F3C-433E-BC96-D88ADAEE3064} = {1B8B6117-CE39-4580-BAFA-D8026102767A}
{E46446FE-2B93-4F42-84C8-A4D09B48B173} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
{429EB088-94FF-4F06-8E54-72157089C8C3} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
{778DB6EE-E5B2-4875-A209-40010B5A3E21} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
{39A9F2B5-2541-423E-83C9-46C7BFF53F41} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CD5C2B19-49B4-480A-990C-36D98A719B07}
Expand Down
10 changes: 10 additions & 0 deletions kokoro/build_nuget.sh
Expand Up @@ -29,6 +29,9 @@ mkdir -p artifacts

build/expand_dev_version.sh

# "-" in the version number means prerelease
grep -o '<GrpcDotnetVersion>.*-.*</GrpcDotnetVersion>' build/version.props && BUILDING_PREVIEW_VERSION=true

(cd src/Grpc.Net.Common && dotnet pack --configuration Release --output ../../artifacts)
(cd src/Grpc.Net.Client && dotnet pack --configuration Release --output ../../artifacts)
(cd src/Grpc.Net.ClientFactory && dotnet pack --configuration Release --output ../../artifacts)
Expand All @@ -37,3 +40,10 @@ build/expand_dev_version.sh
(cd src/Grpc.AspNetCore.Server.Reflection && dotnet pack --configuration Release --output ../../artifacts)
(cd src/Grpc.AspNetCore && dotnet pack --configuration Release --output ../../artifacts)
(cd src/dotnet-grpc && dotnet pack --configuration Release --output ../../artifacts)

if [ "${BUILDING_PREVIEW_VERSION}" == "true" ]
then
# these packages are only built as pre-release
(cd src/Grpc.Net.Client.Web && dotnet pack --configuration Release --output ../../artifacts)
(cd src/Grpc.AspNetCore.Web && dotnet pack --configuration Release --output ../../artifacts)
fi
1 change: 1 addition & 0 deletions perf/benchmarkapps/BenchmarkClient/BenchmarkClient.csproj
Expand Up @@ -9,6 +9,7 @@
<ItemGroup>
<Protobuf Include="..\Shared\benchmark_service.proto" GrpcServices="Client" Link="Protos\benchmark_service.proto" />
<Protobuf Include="..\Shared\messages.proto" GrpcServices="Client" Link="Protos\messages.proto" />
<ProjectReference Include="..\..\..\src\Grpc.Net.Client.Web\Grpc.Net.Client.Web.csproj" />

<ProjectReference Include="..\..\..\src\Grpc.Net.Client\Grpc.Net.Client.csproj" />

Expand Down
Expand Up @@ -17,11 +17,13 @@
#endregion

using System.IO;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;

namespace BenchmarkClient.ChannelFactory
{
Expand All @@ -30,12 +32,14 @@ public class GrpcNetClientChannelFactory : IChannelFactory
private readonly string _target;
private readonly bool _useTls;
private readonly bool _useClientCertificate;
private readonly GrpcWebMode? _useGrpcWeb;

public GrpcNetClientChannelFactory(string target, bool useTls, bool useClientCertificate)
public GrpcNetClientChannelFactory(string target, bool useTls, bool useClientCertificate, GrpcWebMode? useGrpcWeb)
{
_target = target;
_useTls = useTls;
_useClientCertificate = useClientCertificate;
_useGrpcWeb = useGrpcWeb;
}

public Task<ChannelBase> CreateAsync()
Expand All @@ -54,9 +58,15 @@ public Task<ChannelBase> CreateAsync()
httpClientHandler.ClientCertificates.Add(clientCertificate);
}

HttpMessageHandler httpMessageHandler = httpClientHandler;
if (_useGrpcWeb != null)
{
httpMessageHandler = new GrpcWebHandler(_useGrpcWeb.Value, HttpVersion.Version11, httpMessageHandler);
}

var channel = GrpcChannel.ForAddress(url, new GrpcChannelOptions
{
HttpClient = new HttpClient(httpClientHandler)
HttpClient = new HttpClient(httpMessageHandler)
});

return Task.FromResult<ChannelBase>(channel);
Expand Down
15 changes: 12 additions & 3 deletions perf/benchmarkapps/BenchmarkClient/Program.cs
Expand Up @@ -23,6 +23,7 @@
using System.Threading.Tasks;
using BenchmarkClient.ChannelFactory;
using BenchmarkClient.Worker;
using Grpc.Net.Client.Web;

namespace BenchmarkClient
{
Expand All @@ -32,20 +33,21 @@ class Program
private const int DurationSeconds = 5;
private const bool UseTls = false;
private const bool UseClientCertificate = false;
private static readonly GrpcWebMode? UseGrpcWeb = null;
// The host name is tied to some certificates
private const string Target = "localhost:5000";
private readonly static bool StopOnError = false;
private static readonly bool StopOnError = false;

static async Task Main(string[] args)
{
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

var benchmarkResults = new List<BenchmarkResult>();

var grpcNetClientChannelFactory = new GrpcNetClientChannelFactory(Target, UseTls, UseClientCertificate);
var grpcNetClientChannelFactory = new GrpcNetClientChannelFactory(Target, UseTls, UseClientCertificate, UseGrpcWeb);
var grpcCoreChannelFactory = new GrpcCoreChannelFactory(Target);

benchmarkResults.Add(await ExecuteBenchmark("GrpcRaw-UnaryWorker", id => new GrpcRawUnaryWorker(id, Target, UseTls)));
benchmarkResults.Add(await ExecuteBenchmark("GrpcRaw-UnaryWorker", id => new GrpcRawUnaryWorker(id, Target, UseTls, UseGrpcWeb)));
benchmarkResults.Add(await ExecuteBenchmark("GrpcNetClient-UnaryWorker", id => new GrpcUnaryWorker(id, grpcNetClientChannelFactory)));
benchmarkResults.Add(await ExecuteBenchmark("GrpcNetClient-PingPongStreamingWorker", id => new GrpcPingPongStreamingWorker(id, grpcNetClientChannelFactory)));
benchmarkResults.Add(await ExecuteBenchmark("GrpcNetClient-ServerStreamingWorker", id => new GrpcServerStreamingWorker(id, grpcNetClientChannelFactory)));
Expand Down Expand Up @@ -75,6 +77,13 @@ private static async Task<BenchmarkResult> ExecuteBenchmark(string name, Func<in
Log($"Setting up benchmark '{name}'");

await CreateWorkers(workers, workerFactory, workerRequests);
foreach (var worker in workers)
{
// Warm up
runTasks.Add(Task.Run(() => worker.CallAsync()));
}
await Task.WhenAll(runTasks);
runTasks.Clear();

Log($"Starting benchmark '{name}'");

Expand Down
22 changes: 18 additions & 4 deletions perf/benchmarkapps/BenchmarkClient/Worker/GrpcRawUnaryWorker.cs
Expand Up @@ -20,25 +20,29 @@
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Net.Client.Web;
using Grpc.Testing;

namespace BenchmarkClient.Worker
{
public class GrpcRawUnaryWorker : IWorker
{
private readonly bool _useClientCertificate;
private readonly GrpcWebMode? _useGrpcWeb;
private readonly DateTime? _deadline;
private HttpClient? _client;

public GrpcRawUnaryWorker(int id, string target, bool useClientCertificate, DateTime? deadline = null)
public GrpcRawUnaryWorker(int id, string target, bool useClientCertificate, GrpcWebMode? useGrpcWeb, DateTime? deadline = null)
{
Id = id;
Target = target;
_useClientCertificate = useClientCertificate;
_useGrpcWeb = useGrpcWeb;
_deadline = deadline;
}

Expand Down Expand Up @@ -74,10 +78,14 @@ public async Task CallAsync()
request.Headers.Add("grpc-timeout", "1S");
}

var response = await _client!.SendAsync(request);
var response = await _client!.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();

await response.Content.ReadAsByteArrayAsync();
var stream = await response.Content.ReadAsStreamAsync();
while (await stream.ReadAsync(data) > 0)
{

}

var grpcStatus = response.TrailingHeaders.GetValues("grpc-status").SingleOrDefault();
if (grpcStatus != "0")
Expand All @@ -91,7 +99,13 @@ public Task ConnectAsync()
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

_client = new HttpClient(handler);
HttpMessageHandler httpMessageHandler = handler;
if (_useGrpcWeb != null)
{
httpMessageHandler = new GrpcWebHandler(_useGrpcWeb.Value, HttpVersion.Version11, httpMessageHandler);
}

_client = new HttpClient(httpMessageHandler);
return Task.CompletedTask;
}

Expand Down
Expand Up @@ -4,6 +4,8 @@
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework Condition="'$(BenchmarksTargetFramework)' != ''">$(BenchmarksTargetFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<!-- Uncomment line below to enable gRPC-Web on the server -->
<!--<DefineConstants>$(DefineConstants);GRPC_WEB</DefineConstants>-->

<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<!-- Uncomment line below to automatically compile .proto files in the project directory -->
Expand All @@ -26,7 +28,9 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

<ProjectReference Include="../../../src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj" />
<ProjectReference Include="..\..\..\src\Grpc.AspNetCore.Server\Grpc.AspNetCore.Server.csproj" />

<ProjectReference Include="..\..\..\src\Grpc.AspNetCore.Web\Grpc.AspNetCore.Web.csproj" />

<PackageReference Include="Google.Protobuf" Version="$(GoogleProtobufPackageVersion)" />
<PackageReference Include="Grpc.Tools" Version="$(GrpcPackageVersion)" PrivateAssets="All" />
Expand Down
11 changes: 9 additions & 2 deletions perf/benchmarkapps/GrpcAspNetCoreServer/Program.cs
Expand Up @@ -62,7 +62,10 @@ public static IHostBuilder CreateHostBuilder(string[] args)
{
var endPoint = config.CreateIPEndPoint();

options.Listen(endPoint, listenOptions =>
// ListenAnyIP will work with IPv4 and IPv6.
// Chosen over Listen+IPAddress.Loopback, which would have a 2 second delay when
// creating a connection on a local Windows machine.
options.ListenAnyIP(endPoint.Port, listenOptions =>
{
var protocol = config["protocol"] ?? "";

Expand All @@ -85,6 +88,10 @@ public static IHostBuilder CreateHostBuilder(string[] args)
{
listenOptions.Protocols = HttpProtocols.Http2;
}
else if (protocol.Equals("http1", StringComparison.OrdinalIgnoreCase))
{
listenOptions.Protocols = HttpProtocols.Http1;
}
else
{
throw new InvalidOperationException($"Unexpected protocol: {protocol}");
Expand All @@ -99,7 +106,7 @@ public static IHostBuilder CreateHostBuilder(string[] args)
if (Enum.TryParse<LogLevel>(config["LogLevel"], out var logLevel))
{
Console.WriteLine($"Console Logging enabled with level '{logLevel}'");
loggerFactory.AddConsole().SetMinimumLevel(logLevel);
loggerFactory.AddConsole(o => o.TimestampFormat = "ss.ffff ").SetMinimumLevel(logLevel);
}
})
.UseDefaultServiceProvider((context, options) =>
Expand Down
8 changes: 8 additions & 0 deletions perf/benchmarkapps/GrpcAspNetCoreServer/Startup.cs
Expand Up @@ -24,6 +24,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
#if CLIENT_CERTIFICATE_AUTHENTICATION
using System.Security.Cryptography.X509Certificates;
Expand All @@ -48,6 +49,9 @@ public void ConfigureServices(IServiceCollection services)
options.RevocationMode = X509RevocationMode.NoCheck;
options.AllowedCertificateTypes = CertificateTypes.All;
});
#endif
#if GRPC_WEB
services.AddGrpcWeb(o => o.GrpcWebEnabled = true);
#endif
}

Expand All @@ -63,6 +67,10 @@ public void Configure(IApplicationBuilder app, IHostApplicationLifetime applicat
app.UseAuthorization();
#endif

#if GRPC_WEB
app.UseGrpcWeb();
#endif

app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<BenchmarkServiceImpl>();
Expand Down
1 change: 1 addition & 0 deletions src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj
Expand Up @@ -14,6 +14,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="..\Shared\CommonGrpcProtocolHelpers.cs" Link="Internal\CommonGrpcProtocolHelpers.cs" />
<Compile Include="..\Shared\DefaultDeserializationContext.cs" Link="Internal\DefaultDeserializationContext.cs" />
<Compile Include="..\Shared\Server\BindMethodFinder.cs" Link="Model\Internal\BindMethodFinder.cs" />
<Compile Include="..\Shared\Server\ClientStreamingServerMethodInvoker.cs" Link="Model\Internal\ClientStreamingServerMethodInvoker.cs" />
Expand Down
Expand Up @@ -51,10 +51,22 @@ public Task HandleCallAsync(HttpContext httpContext)
{
if (GrpcProtocolHelpers.IsInvalidContentType(httpContext, out var error))
{
GrpcServerLog.UnsupportedRequestContentType(Logger, httpContext.Request.ContentType);
// This might be a CORS preflight request and CORS middleware hasn't been configured
if (HttpMethods.IsOptions(httpContext.Request.Method))
{
GrpcServerLog.UnhandledCorsPreflightRequest(Logger);

GrpcProtocolHelpers.BuildHttpErrorResponse(httpContext.Response, StatusCodes.Status415UnsupportedMediaType, StatusCode.Internal, error);
return Task.CompletedTask;
GrpcProtocolHelpers.BuildHttpErrorResponse(httpContext.Response, StatusCodes.Status405MethodNotAllowed, StatusCode.Internal, "Unhandled CORS preflight request received. CORS may not be configured correctly in the application.");
httpContext.Response.Headers[HeaderNames.Allow] = HttpMethods.Post;
return Task.CompletedTask;
}
else
{
GrpcServerLog.UnsupportedRequestContentType(Logger, httpContext.Request.ContentType);

GrpcProtocolHelpers.BuildHttpErrorResponse(httpContext.Response, StatusCodes.Status415UnsupportedMediaType, StatusCode.Internal, error);
return Task.CompletedTask;
}
}
if (httpContext.Request.Protocol != GrpcProtocolConstants.Http2Protocol &&
httpContext.Request.Protocol != GrpcProtocolConstants.Http20Protocol)
Expand Down
3 changes: 3 additions & 0 deletions src/Grpc.AspNetCore.Server/Internal/GrpcProtocolConstants.cs
Expand Up @@ -25,6 +25,9 @@ namespace Grpc.AspNetCore.Server.Internal
internal static class GrpcProtocolConstants
{
internal const string GrpcContentType = "application/grpc";
internal const string GrpcWebContentType = "application/grpc-web";
internal const string GrpcWebTextContentType = "application/grpc-web-text";

internal const string Http2Protocol = "HTTP/2"; // This is what Kestrel sets
internal const string Http20Protocol = "HTTP/2.0"; // This is what IIS sets

Expand Down