From 06b06abb939c264b76c1403c7bc1a56f6f5696b4 Mon Sep 17 00:00:00 2001 From: Mattia Tommasone Date: Wed, 6 Mar 2024 15:42:32 +0100 Subject: [PATCH 1/3] Allow adding a custom interceptor and add a code example to demonstrate how to do so Change-Id: I9f508f1c0dd982de82ef95d6a2994df36b764e3b --- .../Interceptors/StreamingRpcInterceptor.cs | 4 +- .../src/Interceptors/UnaryRpcInterceptor.cs | 4 +- .../src/Lib/GoogleAdsClient.cs | 17 +- .../src/Lib/GoogleAdsServiceClientFactory.cs | 16 ++ .../AddCustomGrpcInterceptor.cs | 173 ++++++++++++++++++ 5 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 Google.Ads.GoogleAds/examples/AdvancedOperations/AddCustomGrpcInterceptor.cs diff --git a/Google.Ads.Gax/src/Interceptors/StreamingRpcInterceptor.cs b/Google.Ads.Gax/src/Interceptors/StreamingRpcInterceptor.cs index 1fcf99939..324cf6fb8 100644 --- a/Google.Ads.Gax/src/Interceptors/StreamingRpcInterceptor.cs +++ b/Google.Ads.Gax/src/Interceptors/StreamingRpcInterceptor.cs @@ -26,7 +26,7 @@ namespace Google.Ads.Gax.Interceptors /// /// The type of the response. /// - internal class StreamingRpcInterceptor : IAsyncStreamReader + public class StreamingRpcInterceptor : IAsyncStreamReader where TResponse : class { /// @@ -45,7 +45,7 @@ internal class StreamingRpcInterceptor : IAsyncStreamReaderThe stream reader to wrap over.. /// The callback to be invoked when MoveNext() is /// called. - internal StreamingRpcInterceptor(IAsyncStreamReader streamReader, + public StreamingRpcInterceptor(IAsyncStreamReader streamReader, Action moveNextCallback) { this.innerStreamReader = streamReader; diff --git a/Google.Ads.Gax/src/Interceptors/UnaryRpcInterceptor.cs b/Google.Ads.Gax/src/Interceptors/UnaryRpcInterceptor.cs index f91b35948..1eeb4aa62 100644 --- a/Google.Ads.Gax/src/Interceptors/UnaryRpcInterceptor.cs +++ b/Google.Ads.Gax/src/Interceptors/UnaryRpcInterceptor.cs @@ -24,7 +24,7 @@ namespace Google.Ads.Gax.Interceptors /// This class provides functionality to intercept tasks and callbacks, and performs custom /// exception handling. /// - internal class UnaryRpcInterceptor + public class UnaryRpcInterceptor { /// /// Intercepts an async unary call adds a custom exception handler. @@ -34,7 +34,7 @@ internal class UnaryRpcInterceptor /// Response callback /// The async unary call with custom exception handling for /// . - internal static AsyncUnaryCall Intercept( + public static AsyncUnaryCall Intercept( AsyncUnaryCall call, Action> responseCallback) { return new AsyncUnaryCall( diff --git a/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsClient.cs b/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsClient.cs index 65ff1f6eb..5ef63ae2c 100644 --- a/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsClient.cs +++ b/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsClient.cs @@ -16,7 +16,9 @@ using Google.Ads.Gax.Lib; using Google.Ads.GoogleAds.Config; using Google.Api.Gax.Grpc; +using Grpc.Core.Interceptors; using System; +using System.Collections.Generic; namespace Google.Ads.GoogleAds.Lib { @@ -25,6 +27,9 @@ namespace Google.Ads.GoogleAds.Lib /// public class GoogleAdsClient : AdsClient { + + private List userInterceptors = new List(); + /// /// Initializes a new instance of the class. /// @@ -39,6 +44,15 @@ public GoogleAdsClient() : this(new GoogleAdsConfig()) /// The client configuration. public GoogleAdsClient(GoogleAdsConfig config) : base(config) { } + /// + /// Adds a custom gRPC . + /// + /// The custom interceptor. + public void AddInterceptor(Interceptor interceptor) + { + userInterceptors.Add(interceptor); + } + /// /// Gets an instance of the specified service. Use this method with a predefined /// list of templates available for each supported version. E.g. @@ -59,7 +73,8 @@ public TService GetService( where TServiceSetting : ServiceSettingsBase, new() where TService : GoogleAdsServiceClientBase { - GoogleAdsServiceClientFactory factory = new GoogleAdsServiceClientFactory(); + GoogleAdsServiceClientFactory factory = + new GoogleAdsServiceClientFactory(customInterceptors); TService service = factory.GetService(serviceTemplate, Config); service.ServiceContext.Client = this; return service; diff --git a/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsServiceClientFactory.cs b/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsServiceClientFactory.cs index 87322a18b..0a090be4c 100644 --- a/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsServiceClientFactory.cs +++ b/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsServiceClientFactory.cs @@ -22,6 +22,7 @@ using Grpc.Core; using Grpc.Core.Interceptors; using System; +using System.Collections.Generic; namespace Google.Ads.GoogleAds.Lib { @@ -30,11 +31,21 @@ namespace Google.Ads.GoogleAds.Lib /// internal class GoogleAdsServiceClientFactory : AdsServiceClientFactory { + /// + /// The custom interceptors. + /// + private List userInterceptors = new List(); + /// /// The channel factory. /// private CachedChannelFactory channelFactory = new CachedChannelFactory(); + internal GoogleAdsServiceClientFactory(List userInterceptors) + { + this.userInterceptors = userInterceptors; + } + /// /// Gets an instance of the specified service. /// @@ -51,6 +62,11 @@ internal TService GetService( CallInvoker interceptedInvoker = channel .Intercept(new GoogleAdsGrpcInterceptor()); + foreach (Interceptor customInterceptor in userInterceptors) + { + interceptedInvoker = interceptedInvoker.Intercept(customInterceptor); + } + CallInvoker callInvoker = config.EnableProfiling ? new ProfilingCallInvoker(interceptedInvoker, config) : interceptedInvoker; diff --git a/Google.Ads.GoogleAds/examples/AdvancedOperations/AddCustomGrpcInterceptor.cs b/Google.Ads.GoogleAds/examples/AdvancedOperations/AddCustomGrpcInterceptor.cs new file mode 100644 index 000000000..2b7575cdd --- /dev/null +++ b/Google.Ads.GoogleAds/examples/AdvancedOperations/AddCustomGrpcInterceptor.cs @@ -0,0 +1,173 @@ +// Copyright 2024 Google LLC +// +// 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. +using CommandLine; +using Google.Api.Gax; +using Google.Ads.Gax.Examples; +using Google.Ads.Gax.Interceptors; +using Google.Ads.GoogleAds.Lib; +using Google.Ads.GoogleAds.V16.Errors; +using Google.Ads.GoogleAds.V16.Services; +using Grpc.Core; +using Grpc.Core.Interceptors; +using System; + +using System.Threading.Tasks; + +namespace Google.Ads.GoogleAds.Examples.V16 +{ + /// + /// This code example shows how to add a custom gRPC interceptor. + /// + public class AddCustomGrpcInterceptor : ExampleBase + { + /// + /// Command line options for running the example. + /// + public class Options : OptionsBase + { + /// + /// The Google Ads customer ID for which the call is made. + /// + [Option("customerId", Required = true, HelpText = + "The Google Ads customer ID for which the call is made.")] + public long CustomerId { get; set; } + } + + /// + /// Main method, to run this code example as a standalone application. + /// + /// The command line arguments. + public static void Main(string[] args) + { + Options options = ExampleUtilities.ParseCommandLine(args); + + AddCustomGrpcInterceptor codeExample = new AddCustomGrpcInterceptor(); + Console.WriteLine(codeExample.Description); + GoogleAdsClient client = new GoogleAdsClient(); + // Add a custom interceptor. + client.AddInterceptor(new CustomInterceptor()); + codeExample.Run(client, + options.CustomerId); + } + + /// + /// Returns a description about the code example. + /// + public override string Description => + "This code example shows how to add a custom gRPC interceptor."; + + /// + /// Runs the code example. + /// + /// The Google Ads client. + /// The Google Ads customer ID for which the call is made. + public void Run(GoogleAdsClient client, long customerId) + { + // Get the GoogleAdsService. + GoogleAdsServiceClient googleAdsService = client.GetService( + Services.V16.GoogleAdsService); + + // Create a query that will retrieve all campaigns, just to demonstrate usage of the + // custom interceptor. + string query = @"SELECT + campaign.id, + FROM campaign + ORDER BY campaign.id"; + + try + { + // Issue a streaming search request; we don't need to do anything with the response + // here, we just want to demonstrate usage of the interceptor. + googleAdsService.SearchStream(customerId.ToString(), query, + delegate (SearchGoogleAdsStreamResponse resp){} + ); + } + catch (GoogleAdsException e) + { + Console.WriteLine("Failure:"); + Console.WriteLine($"Message: {e.Message}"); + Console.WriteLine($"Failure: {e.Failure}"); + Console.WriteLine($"Request ID: {e.RequestId}"); + throw; + } + + try + { + // Issue a non-streaming call. + PagedEnumerable response = + googleAdsService.Search(customerId.ToString(), query); + foreach (GoogleAdsRow googleAdsRow in response) + { + // The response for Search is lazy, meaning that the actual gRPC call will be + // sent only when the response is actually accessed; to demonstrate usage of + // the interceptor, then, we need to ensure the call is sent by looping + // through the response results. + Console.WriteLine("Campaign with ID {0} was found.", + googleAdsRow.Campaign.Id, googleAdsRow.Campaign.Name); + } + } + catch (GoogleAdsException e) + { + Console.WriteLine("Failure:"); + Console.WriteLine($"Message: {e.Message}"); + Console.WriteLine($"Failure: {e.Failure}"); + Console.WriteLine($"Request ID: {e.RequestId}"); + throw; + } + } + } + + /// + /// A custom interceptor for both streaming and non-streaming gRPC calls. + /// + internal class CustomInterceptor : Interceptor + { + + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, ClientInterceptorContext context, + AsyncUnaryCallContinuation continuationCallback) + { + AsyncUnaryCall call = continuationCallback(request, context); + + Action> callback = delegate (Task oldTask) + { + Console.WriteLine($"Intercepted a non-streaming call to {context.Method.Name}"); + }; + + return UnaryRpcInterceptor.Intercept(call, callback); + } + + public override AsyncServerStreamingCall AsyncServerStreamingCall( + TRequest request, ClientInterceptorContext context, + AsyncServerStreamingCallContinuation continuation) + { + AsyncServerStreamingCall call = continuation(request, context); + StreamingRpcInterceptor responseStream = null; + + responseStream = new StreamingRpcInterceptor(call.ResponseStream, + delegate (TResponse response, AggregateException rpcException) + { + Console.WriteLine($"Intercepted a streaming call to {context.Method.Name}"); + }); + + return new AsyncServerStreamingCall( + responseStream, + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose + ); + } + } +} From fe228e2e79d6c13e20fdc884be5a6124ad068743 Mon Sep 17 00:00:00 2001 From: Mattia Tommasone Date: Wed, 6 Mar 2024 17:01:45 +0100 Subject: [PATCH 2/3] Fix compilation errors Change-Id: Ic1a764045ea4e0f75fce50c90657db4e7292b568 --- Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsClient.cs | 2 +- .../examples/AdvancedOperations/AddCustomGrpcInterceptor.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsClient.cs b/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsClient.cs index 5ef63ae2c..9f133c6fe 100644 --- a/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsClient.cs +++ b/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsClient.cs @@ -74,7 +74,7 @@ public TService GetService( where TService : GoogleAdsServiceClientBase { GoogleAdsServiceClientFactory factory = - new GoogleAdsServiceClientFactory(customInterceptors); + new GoogleAdsServiceClientFactory(userInterceptors); TService service = factory.GetService(serviceTemplate, Config); service.ServiceContext.Client = this; return service; diff --git a/Google.Ads.GoogleAds/examples/AdvancedOperations/AddCustomGrpcInterceptor.cs b/Google.Ads.GoogleAds/examples/AdvancedOperations/AddCustomGrpcInterceptor.cs index 2b7575cdd..546919cd2 100644 --- a/Google.Ads.GoogleAds/examples/AdvancedOperations/AddCustomGrpcInterceptor.cs +++ b/Google.Ads.GoogleAds/examples/AdvancedOperations/AddCustomGrpcInterceptor.cs @@ -101,7 +101,6 @@ FROM campaign Console.WriteLine($"Request ID: {e.RequestId}"); throw; } - try { // Issue a non-streaming call. From 278bb1416db9e20a2624a5f0f8c4789073006f0e Mon Sep 17 00:00:00 2001 From: Mattia Tommasone Date: Thu, 7 Mar 2024 15:35:55 +0100 Subject: [PATCH 3/3] Fix compilation errors Change-Id: I272bdf3bbf7cda9973fbda409fab91e19fda6e1f --- .../src/Lib/GoogleAdsServiceClientFactory.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsServiceClientFactory.cs b/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsServiceClientFactory.cs index 0a090be4c..d5f1f1d2e 100644 --- a/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsServiceClientFactory.cs +++ b/Google.Ads.GoogleAds.Core/src/Lib/GoogleAdsServiceClientFactory.cs @@ -41,6 +41,9 @@ internal class GoogleAdsServiceClientFactory : AdsServiceClientFactory /// private CachedChannelFactory channelFactory = new CachedChannelFactory(); + internal GoogleAdsServiceClientFactory(): this(new List()) { + } + internal GoogleAdsServiceClientFactory(List userInterceptors) { this.userInterceptors = userInterceptors;