forked from man-group/dapr-sidekick-dotnet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
InvocationHandler.cs
165 lines (144 loc) · 6.35 KB
/
InvocationHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#if !NET35
// Original source : https://github.com/dapr/dotnet-sdk/blob/master/src/Dapr.Client/InvocationHandler.cs
// See DAPR_DOTNET_SDK_LICENSE in this directory for license information.
// IMPORTANT : This class has been modified to add the OnBeforeSendAsync() and OnAfterSendAsync() virtual methods.
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
#nullable enable
namespace Man.Dapr.Sidekick.DaprClient
{
/// <summary>
/// <para>
/// A <see cref="DelegatingHandler" /> implementation that rewrites URIs of outgoing requests
/// to use the Dapr service invocation protocol. This handle allows code using <see cref="HttpClient" />
/// to use the client as-if it were communciating with the destination application directly.
/// </para>
/// <para>
/// The handler will read the <see cref="HttpRequestMessage.RequestUri" /> property, and
/// interpret the hostname as the destination <c>app-id</c>. The <see cref="HttpRequestMessage.RequestUri" />
/// property will be replaced with a new URI with the authority section replaced by <see cref="DaprEndpoint" />
/// and the path portion of the URI rewitten to follow the format of a Dapr service invocation request.
/// </para>
/// </summary>
/// <remarks>
/// This message handler does not distinguish between requests destined for Dapr service invocation and general
/// HTTP traffic, and will attempt to forward all traffic to the Dapr endpoint. Do not attempt to set
/// <see cref="DaprEndpoint" /> to a publicly routable URI, this will result in leaking of traffic and the Dapr
/// security token.
/// </remarks>
public class InvocationHandler : DelegatingHandler
{
private Uri parsedEndpoint;
private string? apiToken;
/// <summary>
/// Initializes a new instance of <see cref="InvocationHandler" />.
/// </summary>
public InvocationHandler()
{
this.parsedEndpoint = new Uri(Http.DaprSidecarHttpClientFactory.GetDefaultDaprEndpoint(), UriKind.Absolute);
if (string.IsNullOrWhiteSpace(this.apiToken))
{
this.apiToken = Environment.GetEnvironmentVariable(DaprConstants.DaprApiTokenEnvironmentVariable);
}
}
/// <summary>
/// Gets or the sets the URI of the Dapr HTTP endpoint used for service invocation.
/// </summary>
/// <returns>The URI of the Dapr HTTP endpoint used for service invocation.</returns>
public string DaprEndpoint
{
get
{
return this.parsedEndpoint.OriginalString;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
// This will throw a reasonable exception if the URI is invalid.
var uri = new Uri(value, UriKind.Absolute);
if (uri.Scheme != "http" && uri.Scheme != "https")
{
throw new ArgumentException("The URI scheme of the Dapr endpoint must be http or https.", "value");
}
this.parsedEndpoint = uri;
}
}
// Internal for testing
internal string? DaprApiToken
{
get => this.apiToken;
set => this.apiToken = value;
}
// Internal for testing
internal HttpResponseMessage Send(HttpRequestMessage request) => SendAsync(request, CancellationToken.None).Result;
/// <inheritdoc />
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var original = request.RequestUri;
if (!this.TryRewriteUri(original, out var rewritten))
{
throw new ArgumentException($"The request URI '{original}' is not a valid Dapr service invocation destination.", nameof(request));
}
var apiTokenHeader = DaprClient.GetDaprApiTokenHeader(this.apiToken);
var isValidToken = apiTokenHeader.Key != null && apiTokenHeader.Value != null;
try
{
if (isValidToken)
{
#pragma warning disable CS8604 // Possible null reference argument.
request.Headers.Add(apiTokenHeader.Key, apiTokenHeader.Value);
#pragma warning restore CS8604 // Possible null reference argument.
}
request.RequestUri = rewritten;
OnBeforeSendAsync(request, cancellationToken);
return await base.SendAsync(request, cancellationToken);
}
finally
{
OnAfterSendAsync(request, cancellationToken);
request.RequestUri = original;
if (isValidToken)
{
request.Headers.Remove(DaprConstants.DaprApiTokenHeader);
}
}
}
protected virtual void OnBeforeSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
}
protected virtual void OnAfterSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
}
// Internal for testing
internal bool TryRewriteUri(Uri? uri, out Uri? rewritten)
{
// For now the only invalid cases are when the request URI is missing or just silly.
// We may support additional cases for validation in the future (like an allow-list of App-Ids).
if (uri is null || !uri.IsAbsoluteUri || (uri.Scheme != "http" && uri.Scheme != "https"))
{
// do nothing
rewritten = null;
return false;
}
var builder = new UriBuilder(uri)
{
Scheme = this.parsedEndpoint.Scheme,
Host = this.parsedEndpoint.Host,
Port = this.parsedEndpoint.Port,
Path = $"/v1.0/invoke/{uri.Host}/method" + uri.AbsolutePath,
};
rewritten = builder.Uri;
return true;
}
}
}
#endif