-
-
Notifications
You must be signed in to change notification settings - Fork 389
/
HttpProxy.cs
256 lines (235 loc) · 8.61 KB
/
HttpProxy.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
//-----------------------------------------------------------------------
// <copyright file="HttpProxy.cs" company="Marimer LLC">
// Copyright (c) Marimer LLC. All rights reserved.
// Website: https://cslanet.com
// </copyright>
// <summary>Implements a data portal proxy to relay data portal</summary>
//-----------------------------------------------------------------------
using Csla.Configuration;
using Csla.DataPortalClient;
using Csla.Properties;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
namespace Csla.Channels.Http
{
/// <summary>
/// Implements a data portal proxy to relay data portal
/// calls to a remote application server by using http.
/// </summary>
public class HttpProxy : DataPortalProxy
{
private HttpClient _httpClient;
/// <summary>
/// Creates an instance of the type, initializing
/// it to use the supplied HttpClient object and options.
/// </summary>
/// <param name="applicationContext"></param>
/// <param name="httpClient">HttpClient instance</param>
/// <param name="options">Options for HttpProxy</param>
/// <param name="dataPortalOptions">Data portal options</param>
public HttpProxy(ApplicationContext applicationContext, HttpClient httpClient, HttpProxyOptions options, DataPortalOptions dataPortalOptions)
: base(applicationContext)
{
_httpClient = httpClient;
Options = options;
DataPortalUrl = options.DataPortalUrl;
VersionRoutingTag = dataPortalOptions.VersionRoutingTag;
}
/// <summary>
/// Current options for the proxy.
/// </summary>
protected HttpProxyOptions Options { get; set; }
private string VersionRoutingTag { get; set; }
#nullable enable
/// <summary>
/// Gets an HttpClientHandler for use
/// in initializing the HttpClient instance.
/// </summary>
protected virtual HttpClientHandler? GetHttpClientHandler()
{
return null;
}
#nullable disable
/// <summary>
/// Gets an HttpClient object for use in
/// asynchronous communication with the server.
/// </summary>
protected virtual HttpClient GetHttpClient()
{
if (_httpClient == null)
{
var handler = GetHttpClientHandler() ?? CreateDefaultHandler();
_httpClient = new HttpClient(handler);
if (Timeout > 0)
{
_httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout);
}
}
return _httpClient;
HttpClientHandler CreateDefaultHandler()
{
var handler = new HttpClientHandler();
if (!Options.UseTextSerialization)
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
return handler;
}
}
/// <summary>
/// Gets an WebClient object for use in
/// synchronous communication with the server.
/// </summary>
protected virtual WebClient GetWebClient()
{
return new DefaultWebClient(Timeout);
}
/// <summary>
/// Select client to make request based on isSync parameter and return response from server
/// </summary>
/// <param name="serialized">Serialized request</param>
/// <param name="operation">DataPortal operation</param>
/// <param name="routingToken">Routing Tag for server</param>
/// <param name="isSync">True if the client-side proxy should synchronously invoke the server.</param>
/// <returns>Serialized response from server</returns>
protected override async Task<byte[]> CallDataPortalServer(byte[] serialized, string operation, string routingToken, bool isSync)
{
return isSync
? CallViaWebClient(serialized, operation, routingToken)
: await CallViaHttpClient(serialized, operation, routingToken);
}
/// <summary>
/// Override to set headers or other properties of the
/// HttpRequestMessage before it is sent to the server
/// (asynchronous only).
/// </summary>
/// <param name="request">HttpRequestMessage instance</param>
protected virtual void SetHttpRequestHeaders(HttpRequestMessage request)
{ }
/// <summary>
/// Override to set headers or other properties of the
/// WebClient before it is sent to the server
/// (synchronous only).
/// </summary>
/// <param name="client">WebClient instance</param>
protected virtual void SetWebClientHeaders(WebClient client)
{ }
private async Task<byte[]> CallViaHttpClient(byte[] serialized, string operation, string routingToken)
{
var client = GetHttpClient();
using var httpRequest = new HttpRequestMessage(
HttpMethod.Post,
$"{DataPortalUrl}?operation={CreateOperationTag(operation, VersionRoutingTag, routingToken)}");
SetHttpRequestHeaders(httpRequest);
#if NET8_0_OR_GREATER
if (Options.UseTextSerialization)
{
httpRequest.Content = new StringContent(
Convert.ToBase64String(serialized),
mediaType: new MediaTypeHeaderValue("application/base64,text/plain"));
}
else
{
httpRequest.Content = new ByteArrayContent(serialized);
httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
}
#else
if (Options.UseTextSerialization)
{
httpRequest.Content = new StringContent(Convert.ToBase64String(serialized));
httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
}
else
{
httpRequest.Content = new ByteArrayContent(serialized);
httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
}
#endif
using var httpResponse = await client.SendAsync(httpRequest);
await VerifyResponseSuccess(httpResponse);
if (Options.UseTextSerialization)
serialized = Convert.FromBase64String(await httpResponse.Content.ReadAsStringAsync());
else
serialized = await httpResponse.Content.ReadAsByteArrayAsync();
return serialized;
}
private byte[] CallViaWebClient(byte[] serialized, string operation, string routingToken)
{
if (!WebCallCapabilities.AreSyncWebClientMethodsSupported())
{
throw new NotSupportedException(Resources.SyncDataAccessNotSupportedException);
}
WebClient client = GetWebClient();
var url = $"{DataPortalUrl}?operation={CreateOperationTag(operation, VersionRoutingTag, routingToken)}";
client.Headers["Content-Type"] = Options.UseTextSerialization ? "application/base64,text/plain" : "application/octet-stream";
SetWebClientHeaders(client);
try
{
if (Options.UseTextSerialization)
{
var result = client.UploadString(url, Convert.ToBase64String(serialized));
serialized = Convert.FromBase64String(result);
}
else
{
var result = client.UploadData(url, serialized);
serialized = result;
}
return serialized;
}
catch (WebException ex)
{
string message;
if (ex.Response != null)
{
using var reader = new StreamReader(ex.Response.GetResponseStream());
message = reader.ReadToEnd();
}
else
{
message = ex.Message;
}
throw new DataPortalException(message, ex);
}
}
private static async Task VerifyResponseSuccess(HttpResponseMessage httpResponse)
{
if (!httpResponse.IsSuccessStatusCode)
{
var message = new StringBuilder();
message.Append((int)httpResponse.StatusCode);
message.Append(": ");
message.Append(httpResponse.ReasonPhrase);
var content = await httpResponse.Content.ReadAsStringAsync();
if (!string.IsNullOrWhiteSpace(content))
{
message.AppendLine();
message.Append(content);
}
throw new HttpRequestException(message.ToString());
}
}
private string CreateOperationTag(string operation, string versionToken, string routingToken)
{
if (!string.IsNullOrWhiteSpace(versionToken) || !string.IsNullOrWhiteSpace(routingToken))
return $"{operation}/{routingToken}-{versionToken}";
return operation;
}
#pragma warning disable SYSLIB0014
private class DefaultWebClient : WebClient
{
private int Timeout { get; set; }
public DefaultWebClient(int timeout) => Timeout = timeout;
protected override WebRequest GetWebRequest(Uri address)
{
var req = base.GetWebRequest(address) as HttpWebRequest;
if (Timeout > 0)
req.Timeout = Timeout;
return req;
}
}
#pragma warning restore SYSLIB0014
}
}