Skip to content

Commit 084bfae

Browse files
committed
Rewrite .NET 6 TFM to use HttpClient
1 parent d4c5b7a commit 084bfae

14 files changed

+232
-77
lines changed

src/ServiceStack.Client/AsyncServiceClient.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ public partial class AsyncServiceClient : IHasSessionId, IHasBearerToken, IHasVe
6363

6464
public CookieContainer CookieContainer { get; set; }
6565

66+
#if NET6_0_OR_GREATER
67+
public System.Net.Http.HttpClient HttpClient { get; set; }
68+
#endif
69+
6670
/// <summary>
6771
/// The request filter is called before any request.
6872
/// This request filter only works with the instance where it was set (not global).
@@ -393,11 +397,16 @@ T Complete(T response)
393397
GetAccessTokenResponse tokenResponse;
394398
try
395399
{
400+
#if NET6_0_OR_GREATER
401+
tokenResponse = (await HttpClient.SendStringToUrlAsync(uri, method:HttpMethods.Post,
402+
requestBody:refreshRequest.ToJson(), token: token).ConfigAwait()).FromJson<GetAccessTokenResponse>();
403+
#else
396404
tokenResponse = (await uri.PostJsonToUrlAsync(refreshRequest, requestFilter: req => {
397405
if (hasRefreshTokenCookie) {
398406
req.CookieContainer = CookieContainer;
399407
}
400408
}, token: token).ConfigAwait()).FromJson<GetAccessTokenResponse>();
409+
#endif
401410
}
402411
catch (WebException refreshEx)
403412
{

src/ServiceStack.Client/MetadataTypes.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ public static async Task<AppMetadata> GetAppMetadataAsync(this string baseUrl)
698698
try
699699
{
700700
ssMetadata = await baseUrl.CombineWith("/metadata")
701-
.GetStringFromUrlAsync(requestFilter:req => req.UserAgent = "ServiceStack");
701+
.GetStringFromUrlAsync(requestFilter:req => req.With(c => c.UserAgent = "ServiceStack"));
702702
}
703703
catch (Exception ssEx)
704704
{

src/ServiceStack.Client/ServerEventsClient.cs

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
using ServiceStack.Messaging;
1313
using ServiceStack.Text;
1414

15+
#if NET6_0_OR_GREATER
16+
using System.Net.Http;
17+
#endif
18+
1519
namespace ServiceStack
1620
{
1721
public class ServerEventConnect : ServerEventCommand
@@ -86,8 +90,6 @@ public partial class ServerEventsClient : IDisposable
8690
byte[] buffer;
8791
readonly Encoding encoding = Encoding.UTF8;
8892

89-
HttpWebRequest httpReq;
90-
HttpWebResponse response;
9193
CancellationTokenSource cancel;
9294
private ITimer heartbeatTimer;
9395

@@ -166,24 +168,47 @@ public string EventStreamUri
166168
public Action OnReconnect;
167169
public Action<Exception> OnException;
168170

171+
#if NET6_0_OR_GREATER
172+
public Action<HttpRequestMessage> EventStreamRequestFilter { get; set; }
173+
public Action<HttpRequestMessage> HeartbeatRequestFilter { get; set; }
174+
public Action<HttpRequestMessage> UnRegisterRequestFilter { get; set; }
175+
/// <summary>
176+
/// Apply Request Filter to all ServerEventClient Requests
177+
/// </summary>
178+
public Action<HttpRequestMessage> AllRequestFilters { get; set; }
179+
HttpClient httpClient;
180+
public Func<IServiceClient,HttpClientHandler> HttpClientHandlerFactory { get; set; } = client => new()
181+
{
182+
UseCookies = true,
183+
CookieContainer = ((IHasCookieContainer)client).CookieContainer,
184+
UseDefaultCredentials = true,
185+
AutomaticDecompression =
186+
DecompressionMethods.Brotli | DecompressionMethods.Deflate | DecompressionMethods.GZip,
187+
};
188+
#else
169189
public Action<WebRequest> EventStreamRequestFilter { get; set; }
170190
public Action<WebRequest> HeartbeatRequestFilter { get; set; }
171191
public Action<WebRequest> UnRegisterRequestFilter { get; set; }
172-
173192
/// <summary>
174193
/// Apply Request Filter to all ServerEventClient Requests
175194
/// </summary>
176195
public Action<WebRequest> AllRequestFilters { get; set; }
196+
HttpWebRequest httpReq;
197+
HttpWebResponse response;
198+
#endif
177199

178-
readonly Dictionary<string, List<Action<ServerEventMessage>>> listeners =
179-
new Dictionary<string, List<Action<ServerEventMessage>>>();
200+
readonly Dictionary<string, List<Action<ServerEventMessage>>> listeners = new();
180201

181202
public ServerEventsClient(string baseUri, params string[] channels)
182203
{
183204
this.eventStreamPath = baseUri.CombineWith("event-stream");
184205
this.Channels = channels;
185206

207+
#if NET6_0_OR_GREATER
208+
this.ServiceClient = new JsonApiClient(baseUri);
209+
#else
186210
this.ServiceClient = new JsonServiceClient(baseUri);
211+
#endif
187212

188213
this.Resolver = new NewInstanceResolver();
189214
this.ReceiverTypes = new List<Type>();
@@ -207,6 +232,25 @@ public ServerEventsClient Start()
207232
{
208233
Interlocked.Increment(ref timesStarted);
209234

235+
#if NET6_0_OR_GREATER
236+
httpClient = new HttpClient(HttpClientHandlerFactory(ServiceClient), disposeHandler:true);
237+
238+
var httpReq = new HttpRequestMessage(HttpMethod.Get, EventStreamUri)
239+
.With(c => c.Accept = "*/*");
240+
241+
EventStreamRequestFilter?.Invoke(httpReq);
242+
if (AllRequestFilters != null)
243+
{
244+
AllRequestFilters(httpReq);
245+
if (ServiceClient is JsonApiClient apiClient)
246+
apiClient.RequestFilter = AllRequestFilters;
247+
}
248+
249+
var httpRes = httpClient.Send(httpReq);
250+
httpRes.EnsureSuccessStatusCode();
251+
var stream = httpRes.Content.ReadAsStream();
252+
253+
#else
210254
httpReq = (HttpWebRequest)WebRequest.Create(EventStreamUri);
211255
//share auth cookies
212256
httpReq.CookieContainer = ((IHasCookieContainer)ServiceClient).CookieContainer;
@@ -222,6 +266,7 @@ public ServerEventsClient Start()
222266

223267
response = (HttpWebResponse)PclExport.Instance.GetResponse(httpReq);
224268
var stream = response.ResponseStream();
269+
#endif
225270

226271
buffer = new byte[BufferSize];
227272
cancel = new CancellationTokenSource();
@@ -249,10 +294,30 @@ public ServerEventsClient Start()
249294
return this;
250295
}
251296

297+
private bool HasClient() =>
298+
#if NET6_0_OR_GREATER
299+
this.httpClient != null;
300+
#else
301+
this.httpReq != null;
302+
#endif
303+
304+
private void UnsetClient()
305+
{
306+
#if NET6_0_OR_GREATER
307+
this.httpClient = null;
308+
#else
309+
using (response)
310+
{
311+
response = null;
312+
}
313+
this.httpReq = null;
314+
#endif
315+
}
316+
252317
private TaskCompletionSource<ServerEventConnect> connectTcs;
253318
public Task<ServerEventConnect> Connect()
254319
{
255-
if (httpReq == null)
320+
if (!HasClient())
256321
Start();
257322

258323
return connectTcs.Task;
@@ -328,7 +393,16 @@ protected void Heartbeat(object state)
328393

329394
EnsureSynchronizationContext();
330395

331-
ConnectionInfo.HeartbeatUrl.GetStringFromUrlAsync(requestFilter: req => {
396+
#if NET6_0_OR_GREATER
397+
var taskString = httpClient.SendStringToUrlAsync(ConnectionInfo.HeartbeatUrl, method:HttpMethods.Get, requestFilter: req => {
398+
HeartbeatRequestFilter?.Invoke(req);
399+
AllRequestFilters?.Invoke(req);
400+
401+
if (log.IsDebugEnabled)
402+
log.Debug("[SSE-CLIENT] Sending Heartbeat...");
403+
});
404+
#else
405+
var taskString = ConnectionInfo.HeartbeatUrl.GetStringFromUrlAsync(requestFilter: req => {
332406
var hold = httpReq;
333407
if (hold != null)
334408
req.CookieContainer = hold.CookieContainer;
@@ -338,8 +412,10 @@ protected void Heartbeat(object state)
338412

339413
if (log.IsDebugEnabled)
340414
log.Debug("[SSE-CLIENT] Sending Heartbeat...");
341-
})
342-
.Success(t =>
415+
});
416+
#endif
417+
418+
taskString.Success(t =>
343419
{
344420
if (cancel.IsCancellationRequested)
345421
{
@@ -529,15 +605,14 @@ public void ProcessResponse(Stream stream)
529605
t.ObserveTaskExceptions();
530606
if (cancel.IsCancellationRequested || t.IsCanceled)
531607
{
532-
httpReq = null;
533-
608+
UnsetClient();
534609
return;
535610
}
536611

537612
if (t.IsFaulted)
538613
{
539614
OnExceptionReceived(t.Exception);
540-
httpReq = null;
615+
UnsetClient();
541616
return;
542617
}
543618

@@ -780,6 +855,16 @@ public virtual Task InternalStop()
780855
{
781856
EnsureSynchronizationContext();
782857
try {
858+
#if NET6_0_OR_GREATER
859+
httpClient.SendStringToUrl(ConnectionInfo.UnRegisterUrl, method:HttpMethods.Get, requestFilter: req =>
860+
{
861+
if (log.IsDebugEnabled)
862+
log.Debug("[SSE-CLIENT] Unregistering...");
863+
864+
UnRegisterRequestFilter?.Invoke(req);
865+
AllRequestFilters?.Invoke(req);
866+
});
867+
#else
783868
ConnectionInfo.UnRegisterUrl.GetStringFromUrl(requestFilter: req =>
784869
{
785870
var hold = httpReq;
@@ -792,16 +877,12 @@ public virtual Task InternalStop()
792877
UnRegisterRequestFilter?.Invoke(req);
793878
AllRequestFilters?.Invoke(req);
794879
});
880+
#endif
795881
} catch (Exception) {}
796882
}
797883

798-
using (response)
799-
{
800-
response = null;
801-
}
802-
803884
ConnectionInfo = null;
804-
httpReq = null;
885+
UnsetClient();
805886

806887
return TypeConstants.EmptyTask;
807888
}

src/ServiceStack.Client/ServiceClientBase.cs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,24 @@ public static Action<HttpWebResponse> GlobalResponseFilter
6767
public NameValueCollection Headers { get; private set; }
6868

6969
public const string DefaultHttpMethod = HttpMethods.Post;
70-
public static string DefaultUserAgent = "ServiceStack .NET Client " + Env.VersionString;
70+
public static string DefaultUserAgent = "ServiceStackClient/" + Env.VersionString;
71+
72+
#if NET6_0_OR_GREATER
73+
public System.Net.Http.HttpClient HttpClient { get; set; }
74+
#endif
7175

7276
readonly AsyncServiceClient asyncClient;
7377

7478
protected ServiceClientBase()
7579
{
7680
this.HttpMethod = DefaultHttpMethod;
7781
this.Headers = new NameValueCollection();
82+
var cookies = new CookieContainer();
83+
#if NET6_0_OR_GREATER
84+
this.HttpClient = new System.Net.Http.HttpClient(new System.Net.Http.HttpClientHandler {
85+
CookieContainer = cookies,
86+
}, disposeHandler: true);
87+
#endif
7888

7989
asyncClient = new AsyncServiceClient
8090
{
@@ -87,8 +97,11 @@ protected ServiceClientBase()
8797
ResponseFilter = this.ResponseFilter,
8898
ResultsFilter = this.ResultsFilter,
8999
Headers = this.Headers,
100+
#if NET6_0_OR_GREATER
101+
HttpClient = this.HttpClient,
102+
#endif
90103
};
91-
this.CookieContainer = new CookieContainer();
104+
this.CookieContainer = cookies;
92105
this.StoreCookies = true; //leave
93106
this.UserAgent = DefaultUserAgent;
94107
this.EnableAutoRefreshToken = true;
@@ -1839,7 +1852,46 @@ WebRequest createWebRequest()
18391852
return response;
18401853
}
18411854
}
1855+
1856+
private static byte[] GetHeaderBytes(string fileName, string mimeType, string field, string boundary)
1857+
{
1858+
var header = "\r\n--" + boundary +
1859+
$"\r\nContent-Disposition: form-data; name=\"{field}\"; filename=\"{fileName}\"\r\nContent-Type: {mimeType}\r\n\r\n";
1860+
1861+
var headerBytes = header.ToAsciiBytes();
1862+
return headerBytes;
1863+
}
1864+
1865+
public static void UploadFile(WebRequest webRequest, Stream fileStream, string fileName, string mimeType,
1866+
string accept = null, Action<HttpWebRequest> requestFilter = null, string method = "POST",
1867+
string field = "file")
1868+
{
1869+
var httpReq = (HttpWebRequest)webRequest;
1870+
httpReq.Method = method;
1871+
1872+
if (accept != null)
1873+
httpReq.Accept = accept;
1874+
1875+
requestFilter?.Invoke(httpReq);
1876+
1877+
var boundary = Guid.NewGuid().ToString("N");
1878+
1879+
httpReq.ContentType = "multipart/form-data; boundary=\"" + boundary + "\"";
18421880

1881+
var boundaryBytes = ("\r\n--" + boundary + "--\r\n").ToAsciiBytes();
1882+
1883+
var headerBytes = GetHeaderBytes(fileName, mimeType, field, boundary);
1884+
1885+
var contentLength = fileStream.Length + headerBytes.Length + boundaryBytes.Length;
1886+
PclExport.Instance.InitHttpWebRequest(httpReq,
1887+
contentLength: contentLength, allowAutoRedirect: false, keepAlive: false);
1888+
1889+
using var outputStream = PclExport.Instance.GetRequestStream(httpReq);
1890+
outputStream.Write(headerBytes, 0, headerBytes.Length);
1891+
fileStream.CopyTo(outputStream, 4096);
1892+
outputStream.Write(boundaryBytes, 0, boundaryBytes.Length);
1893+
PclExport.Instance.CloseStream(outputStream);
1894+
}
18431895

18441896
public virtual TResponse PostFile<TResponse>(string relativeOrAbsoluteUrl, Stream fileToUpload, string fileName, string mimeType)
18451897
{
@@ -1850,7 +1902,7 @@ public virtual TResponse PostFile<TResponse>(string relativeOrAbsoluteUrl, Strea
18501902
try
18511903
{
18521904
var webRequest = createWebRequest();
1853-
webRequest.UploadFile(fileToUpload, fileName, mimeType);
1905+
UploadFile(webRequest, fileToUpload, fileName, mimeType);
18541906
var webResponse = PclExport.Instance.GetResponse(webRequest);
18551907
return HandleResponse<TResponse>(webResponse);
18561908
}
@@ -1865,7 +1917,7 @@ public virtual TResponse PostFile<TResponse>(string relativeOrAbsoluteUrl, Strea
18651917
createWebRequest,
18661918
c =>
18671919
{
1868-
c.UploadFile(fileToUpload, fileName, mimeType);
1920+
UploadFile(c, fileToUpload, fileName, mimeType);
18691921
return PclExport.Instance.GetResponse(c);
18701922
},
18711923
out TResponse response))

0 commit comments

Comments
 (0)