Skip to content

Commit

Permalink
Adds an option to set a timeout for service invocation (#1252)
Browse files Browse the repository at this point in the history
* Adds http timeout

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Adds a timeout for the grpc client

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Small updates

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Updates test

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Adds a timeout example in docs

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Adds e2e test for http service invocation

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Adds tests for grpc service invocation

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Removes grpc timeout, because it’s not needed. It can be passed directly to the call as shown in the updated tests and docs

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Update src/Dapr.Client/DaprClientBuilder.cs

Signed-off-by: Elena Kolevska <elena-kolevska@users.noreply.github.com>

---------

Signed-off-by: Elena Kolevska <elena@kolevska.com>
Signed-off-by: Elena Kolevska <elena-kolevska@users.noreply.github.com>
Co-authored-by: Phillip Hoff <phillip@orst.edu>
  • Loading branch information
elena-kolevska and philliphoff committed Apr 8, 2024
1 parent 31af35b commit bdca3b3
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 3 deletions.
25 changes: 23 additions & 2 deletions daprdocs/content/en/dotnet-sdk-docs/dotnet-client/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ The .NET SDK allows you to interface with all of the [Dapr building blocks]({{<

### Invoke a service

#### HTTP
You can either use the `DaprClient` or `System.Net.Http.HttpClient` to invoke your services.

{{< tabs SDK HTTP>}}

{{% codetab %}}
```csharp
using var client = new DaprClientBuilder().Build();
using var client = new DaprClientBuilder().
UseTimeout(TimeSpan.FromSeconds(2)). // Optionally, set a timeout
Build();

// Invokes a POST method named "deposit" that takes input of type "Transaction"
var data = new { id = "17", amount = 99m };
Expand All @@ -40,15 +43,33 @@ Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance)
```csharp
var client = DaprClient.CreateInvokeHttpClient(appId: "routing");

// To set a timeout on the HTTP client:
client.Timeout = TimeSpan.FromSeconds(2);

var deposit = new Transaction { Id = "17", Amount = 99m };
var response = await client.PostAsJsonAsync("/deposit", deposit, cancellationToken);
var account = await response.Content.ReadFromJsonAsync<Account>(cancellationToken: cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
```
{{% /codetab %}}

{{< /tabs >}}

#### gRPC
You can use the `DaprClient` to invoke your services over gRPC.
{{% codetab %}}
```csharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
var invoker = DaprClient.CreateInvocationInvoker(appId: myAppId, daprEndpoint: serviceEndpoint);
var client = new MyService.MyServiceClient(invoker);

var options = new CallOptions(cancellationToken: cts.Token, deadline: DateTime.UtcNow.AddSeconds(1));
await client.MyMethodAsync(new Empty(), options);

Assert.Equal(StatusCode.DeadlineExceeded, ex.StatusCode);
```
{{% /codetab %}}


- For a full guide on service invocation visit [How-To: Invoke a service]({{< ref howto-invoke-discover-services.md >}}).

### Save & get application state
Expand Down
21 changes: 20 additions & 1 deletion src/Dapr.Client/DaprClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public DaprClientBuilder()
// property exposed for testing purposes
internal GrpcChannelOptions GrpcChannelOptions { get; private set; }
internal string DaprApiToken { get; private set; }
internal TimeSpan Timeout { get; private set; }

/// <summary>
/// Overrides the HTTP endpoint used by <see cref="DaprClient" /> for communicating with the Dapr runtime.
Expand Down Expand Up @@ -136,6 +137,17 @@ public DaprClientBuilder UseDaprApiToken(string apiToken)
return this;
}

/// <summary>
/// Sets the timeout for the HTTP client used by the <see cref="DaprClient" />.
/// </summary>
/// <param name="timeout"></param>
/// <returns></returns>
public DaprClientBuilder UseTimeout(TimeSpan timeout)
{
this.Timeout = timeout;
return this;
}

/// <summary>
/// Builds a <see cref="DaprClient" /> instance from the properties of the builder.
/// </summary>
Expand All @@ -162,9 +174,16 @@ public DaprClient Build()

var channel = GrpcChannel.ForAddress(this.GrpcEndpoint, this.GrpcChannelOptions);
var client = new Autogenerated.Dapr.DaprClient(channel);



var apiTokenHeader = DaprClient.GetDaprApiTokenHeader(this.DaprApiToken);
var httpClient = HttpClientFactory is object ? HttpClientFactory() : new HttpClient();

if (this.Timeout > TimeSpan.Zero)
{
httpClient.Timeout = this.Timeout;
}

return new DaprClientGrpc(channel, client, httpClient, httpEndpoint, this.JsonSerializerOptions, apiTokenHeader);
}
}
Expand Down
11 changes: 11 additions & 0 deletions test/Dapr.AspNetCore.Test/DaprClientBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System;
using System.Text.Json;
using Dapr.Client;
using Grpc.Core;
using Grpc.Net.Client;
using Xunit;

Expand Down Expand Up @@ -110,5 +111,15 @@ public void DaprClientBuilder_ApiTokenNotSet_EmptyApiTokenHeader()
var entry = DaprClient.GetDaprApiTokenHeader(builder.DaprApiToken);
Assert.Equal(default, entry);
}

[Fact]
public void DaprClientBuilder_SetsTimeout()
{
var builder = new DaprClientBuilder();
builder.UseTimeout(TimeSpan.FromSeconds(2));
builder.Build();
Assert.Equal(2, builder.Timeout.Seconds);
}
}

}
1 change: 1 addition & 0 deletions test/Dapr.E2E.Test.App.Grpc/Proto/message.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ service Messager {
rpc GetMessage(GetMessageRequest) returns (MessageResponse);
// Send a series of broadcast messages.
rpc StreamBroadcast(stream Broadcast) returns (stream MessageResponse);
rpc DelayedResponse(google.protobuf.Empty) returns (google.protobuf.Empty);
}

message SendMessageRequest {
Expand Down
7 changes: 7 additions & 0 deletions test/Dapr.E2E.Test.App.Grpc/Services/MessagerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------

using System;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
Expand Down Expand Up @@ -44,5 +45,11 @@ public override async Task StreamBroadcast(IAsyncStreamReader<Broadcast> request
await responseStream.WriteAsync(new MessageResponse { Message = request.Message });
}
}

public override async Task<Empty> DelayedResponse(Empty request, ServerCallContext context)
{
await Task.Delay(TimeSpan.FromSeconds(2));
return new Empty();
}
}
}
9 changes: 9 additions & 0 deletions test/Dapr.E2E.Test.App/Controllers/TestController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,14 @@ public ActionResult<Account> AccountDetailsRequiresApiToken(Transaction transact
};
return account;
}

[Authorize("Dapr")]
[HttpGet("DelayedResponse")]
public async Task<IActionResult> DelayedResponse()
{
await Task.Delay(TimeSpan.FromSeconds(2));
return Ok();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -77,5 +78,21 @@ public async Task TestGrpcProxyStreamingBroadcast()
await responseTask;
}
}

[Fact]
public async Task TestGrpcServiceInvocationWithTimeout()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
var invoker = DaprClient.CreateInvocationInvoker(appId: this.AppId, daprEndpoint: this.GrpcEndpoint);
var client = new Messager.MessagerClient(invoker);

var options = new CallOptions(cancellationToken: cts.Token, deadline: DateTime.UtcNow.AddSeconds(1));
var ex = await Assert.ThrowsAsync<RpcException>(async () =>
{
await client.DelayedResponseAsync(new Empty(), options);
});

Assert.Equal(StatusCode.DeadlineExceeded, ex.StatusCode);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@
namespace Dapr.E2E.Test
{
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Xunit;

public partial class E2ETests
Expand Down Expand Up @@ -58,6 +63,25 @@ public async Task TestServiceInvocationRequiresApiToken()
Assert.Equal("1", account.Id);
Assert.Equal(150, account.Balance);
}

[Fact]
public async Task TestHttpServiceInvocationWithTimeout()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
using var client = new DaprClientBuilder()
.UseHttpEndpoint(this.HttpEndpoint)
.UseTimeout(TimeSpan.FromSeconds(1))
.Build();

await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
await client.InvokeMethodAsync<HttpResponseMessage>(
appId: this.AppId,
methodName: "DelayedResponse",
httpMethod: new HttpMethod("GET"),
cancellationToken: cts.Token);
});
}
}

internal class Transaction
Expand Down

0 comments on commit bdca3b3

Please sign in to comment.