Replace WebClient with HttpClient
This commit is contained in:
@@ -1,96 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class Download
|
||||
{
|
||||
readonly object syncObject = new object();
|
||||
WebClient wc;
|
||||
|
||||
public static string FormatErrorMessage(Exception e)
|
||||
{
|
||||
var ex = e as WebException;
|
||||
if (ex == null)
|
||||
return e.Message;
|
||||
|
||||
switch (ex.Status)
|
||||
{
|
||||
case WebExceptionStatus.RequestCanceled:
|
||||
return "Cancelled";
|
||||
case WebExceptionStatus.NameResolutionFailure:
|
||||
return "DNS lookup failed";
|
||||
case WebExceptionStatus.Timeout:
|
||||
return "Connection timeout";
|
||||
case WebExceptionStatus.ConnectFailure:
|
||||
return "Cannot connect to remote server";
|
||||
case WebExceptionStatus.ProtocolError:
|
||||
return "File not found on remote server";
|
||||
default:
|
||||
return ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
void EnableTLS12OnWindows()
|
||||
{
|
||||
// Enable TLS 1.2 on Windows: .NET 4.7 on Windows 10 only supports obsolete protocols by default
|
||||
// SecurityProtocolType.Tls12 is not defined in the .NET 4.5 reference dlls used by mono,
|
||||
// so we must use the enum's constant value directly
|
||||
if (Platform.CurrentPlatform == PlatformType.Windows)
|
||||
ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;
|
||||
}
|
||||
|
||||
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
|
||||
{
|
||||
EnableTLS12OnWindows();
|
||||
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadFileCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
|
||||
wc.DownloadFileAsync(new Uri(url), path);
|
||||
}
|
||||
}
|
||||
|
||||
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs> onComplete)
|
||||
{
|
||||
EnableTLS12OnWindows();
|
||||
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadDataCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
|
||||
wc.DownloadDataAsync(new Uri(url));
|
||||
}
|
||||
}
|
||||
|
||||
void DisposeWebClient()
|
||||
{
|
||||
lock (syncObject)
|
||||
{
|
||||
wc.Dispose();
|
||||
wc = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelAsync()
|
||||
{
|
||||
lock (syncObject)
|
||||
wc?.CancelAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
63
OpenRA.Game/HttpExtension.cs
Normal file
63
OpenRA.Game/HttpExtension.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public delegate void OnProgress(long total, long totalRead, int progressPercentage);
|
||||
|
||||
public static class HttpExtension
|
||||
{
|
||||
public static async Task ReadAsStreamWithProgress(this HttpResponseMessage response, Stream outputStream, OnProgress onProgress, CancellationToken token)
|
||||
{
|
||||
var total = response.Content.Headers.ContentLength ?? -1;
|
||||
var canReportProgress = total > 0;
|
||||
|
||||
#if !MONO
|
||||
using (var contentStream = await response.Content.ReadAsStreamAsync(token))
|
||||
#else
|
||||
using (var contentStream = await response.Content.ReadAsStreamAsync())
|
||||
#endif
|
||||
{
|
||||
var totalRead = 0L;
|
||||
var buffer = new byte[8192];
|
||||
var hasMoreToRead = true;
|
||||
|
||||
do
|
||||
{
|
||||
var read = await contentStream.ReadAsync(buffer.AsMemory(0, buffer.Length), token);
|
||||
if (read == 0)
|
||||
hasMoreToRead = false;
|
||||
else
|
||||
{
|
||||
await outputStream.WriteAsync(buffer.AsMemory(0, read), token);
|
||||
|
||||
totalRead += read;
|
||||
|
||||
if (canReportProgress)
|
||||
{
|
||||
var progressPercentage = (int)((double)totalRead / total * 100);
|
||||
onProgress?.Invoke(total, totalRead, progressPercentage);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (hasMoreToRead && !token.IsCancellationRequested);
|
||||
|
||||
onProgress?.Invoke(total, totalRead, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -76,17 +76,16 @@ namespace OpenRA
|
||||
if (State != LinkState.Unlinked && State != LinkState.Linked && State != LinkState.ConnectionFailed)
|
||||
return;
|
||||
|
||||
Action<DownloadDataCompletedEventArgs> onQueryComplete = i =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (i.Error != null)
|
||||
{
|
||||
innerState = LinkState.ConnectionFailed;
|
||||
return;
|
||||
}
|
||||
var client = HttpClientFactory.Create();
|
||||
|
||||
var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First();
|
||||
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
|
||||
var result = await httpResponseMessage.Content.ReadAsStringAsync();
|
||||
|
||||
var yaml = MiniYaml.FromString(result).First();
|
||||
if (yaml.Key == "Player")
|
||||
{
|
||||
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
||||
@@ -110,10 +109,9 @@ namespace OpenRA
|
||||
{
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
innerState = LinkState.CheckingLink;
|
||||
new Download(playerDatabase.Profile + Fingerprint, _ => { }, onQueryComplete);
|
||||
}
|
||||
|
||||
public void GenerateKeypair()
|
||||
|
||||
@@ -14,9 +14,8 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
@@ -179,24 +178,16 @@ namespace OpenRA
|
||||
|
||||
var url = repositoryUrl + "hash/" + string.Join(",", maps.Keys) + "/yaml";
|
||||
|
||||
Action<DownloadDataCompletedEventArgs> onInfoComplete = i =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if (i.Error != null)
|
||||
{
|
||||
Log.Write("debug", "Remote map query failed with error: {0}", Download.FormatErrorMessage(i.Error));
|
||||
Log.Write("debug", "URL was: {0}", url);
|
||||
foreach (var p in maps.Values)
|
||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
||||
|
||||
queryFailed?.Invoke();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var data = Encoding.UTF8.GetString(i.Result);
|
||||
try
|
||||
{
|
||||
var yaml = MiniYaml.FromString(data);
|
||||
var client = HttpClientFactory.Create();
|
||||
|
||||
var httpResponseMessage = await client.GetAsync(url);
|
||||
var result = await httpResponseMessage.Content.ReadAsStringAsync();
|
||||
|
||||
var yaml = MiniYaml.FromString(result);
|
||||
foreach (var kv in yaml)
|
||||
maps[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived);
|
||||
|
||||
@@ -206,13 +197,15 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Can't parse remote map search data:\n{0}", data);
|
||||
Log.Write("debug", "Exception: {0}", e);
|
||||
Log.Write("debug", "Remote map query failed with error: {0}", e);
|
||||
Log.Write("debug", "URL was: {0}", url);
|
||||
|
||||
foreach (var p in maps.Values)
|
||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
||||
|
||||
queryFailed?.Invoke();
|
||||
}
|
||||
};
|
||||
|
||||
new Download(url, _ => { }, onInfoComplete);
|
||||
});
|
||||
}
|
||||
|
||||
void LoadAsyncInternal()
|
||||
|
||||
@@ -14,13 +14,15 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -166,7 +168,6 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
Download download;
|
||||
public long DownloadBytes { get; private set; }
|
||||
public int DownloadPercentage { get; private set; }
|
||||
|
||||
@@ -435,74 +436,57 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
var mapInstallPackage = installLocation.Key as IReadWritePackage;
|
||||
new Thread(() =>
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// Request the filename from the server
|
||||
// Run in a worker thread to avoid network delays
|
||||
var mapUrl = mapRepositoryUrl + Uid;
|
||||
var mapFilename = string.Empty;
|
||||
try
|
||||
{
|
||||
var request = WebRequest.Create(mapUrl);
|
||||
request.Method = "HEAD";
|
||||
using (var res = request.GetResponse())
|
||||
void OnDownloadProgress(long total, long received, int percentage)
|
||||
{
|
||||
// Map not found
|
||||
if (res.Headers["Content-Disposition"] == null)
|
||||
{
|
||||
innerData.Status = MapStatus.DownloadError;
|
||||
return;
|
||||
}
|
||||
|
||||
mapFilename = res.Headers["Content-Disposition"].Replace("attachment; filename = ", "");
|
||||
DownloadBytes = total;
|
||||
DownloadPercentage = percentage;
|
||||
}
|
||||
|
||||
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i => { DownloadBytes = i.BytesReceived; DownloadPercentage = i.ProgressPercentage; };
|
||||
Action<DownloadDataCompletedEventArgs> onDownloadComplete = i =>
|
||||
var client = HttpClientFactory.Create();
|
||||
|
||||
var response = await client.GetAsync(mapUrl, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
download = null;
|
||||
innerData.Status = MapStatus.DownloadError;
|
||||
return;
|
||||
}
|
||||
|
||||
if (i.Error != null)
|
||||
{
|
||||
Log.Write("debug", "Remote map download failed with error: {0}", Download.FormatErrorMessage(i.Error));
|
||||
Log.Write("debug", "URL was: {0}", mapUrl);
|
||||
response.Headers.TryGetValues("Content-Disposition", out var values);
|
||||
var mapFilename = values.First().Replace("attachment; filename = ", "");
|
||||
|
||||
var fileStream = new MemoryStream();
|
||||
|
||||
await response.ReadAsStreamWithProgress(fileStream, OnDownloadProgress, CancellationToken.None);
|
||||
|
||||
mapInstallPackage.Update(mapFilename, fileStream.ToArray());
|
||||
Log.Write("debug", "Downloaded map to '{0}'", mapFilename);
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
|
||||
if (package == null)
|
||||
innerData.Status = MapStatus.DownloadError;
|
||||
return;
|
||||
}
|
||||
|
||||
mapInstallPackage.Update(mapFilename, i.Result);
|
||||
Log.Write("debug", "Downloaded map to '{0}'", mapFilename);
|
||||
Game.RunAfterTick(() =>
|
||||
else
|
||||
{
|
||||
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
|
||||
if (package == null)
|
||||
innerData.Status = MapStatus.DownloadError;
|
||||
else
|
||||
{
|
||||
UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType);
|
||||
onSuccess();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
download = new Download(mapUrl, onDownloadProgress, onDownloadComplete);
|
||||
UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType);
|
||||
onSuccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
innerData.Status = MapStatus.DownloadError;
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
|
||||
public void CancelInstall()
|
||||
{
|
||||
if (download == null)
|
||||
return;
|
||||
|
||||
download.CancelAsync();
|
||||
download = null;
|
||||
});
|
||||
}
|
||||
|
||||
public void Invalidate()
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<AdditionalFiles Include="../stylecop.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Mono)' == ''">
|
||||
|
||||
@@ -9,13 +9,12 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -39,14 +38,16 @@ namespace OpenRA
|
||||
var spriteSize = IconSize * density;
|
||||
var sprite = sheetBuilder.Allocate(new Size(spriteSize, spriteSize), 1f / density);
|
||||
|
||||
Action<DownloadDataCompletedEventArgs> onComplete = i =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if (i.Error != null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var icon = new Png(new MemoryStream(i.Result));
|
||||
var client = HttpClientFactory.Create();
|
||||
|
||||
var httpResponseMessage = await client.GetAsync(url);
|
||||
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
||||
|
||||
var icon = new Png(result);
|
||||
if (icon.Width == spriteSize && icon.Height == spriteSize)
|
||||
{
|
||||
Game.RunAfterTick(() =>
|
||||
@@ -57,9 +58,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
};
|
||||
|
||||
new Download(url, _ => { }, onComplete);
|
||||
});
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
@@ -560,53 +561,50 @@ namespace OpenRA.Server
|
||||
{
|
||||
waitingForAuthenticationCallback++;
|
||||
|
||||
Action<DownloadDataCompletedEventArgs> onQueryComplete = i =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var httpClient = HttpClientFactory.Create();
|
||||
var httpResponseMessage = await httpClient.GetAsync(playerDatabase.Profile + handshake.Fingerprint);
|
||||
var result = await httpResponseMessage.Content.ReadAsStringAsync();
|
||||
PlayerProfile profile = null;
|
||||
|
||||
if (i.Error == null)
|
||||
try
|
||||
{
|
||||
try
|
||||
var yaml = MiniYaml.FromString(result).First();
|
||||
if (yaml.Key == "Player")
|
||||
{
|
||||
var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First();
|
||||
if (yaml.Key == "Player")
|
||||
{
|
||||
profile = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
||||
profile = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
||||
|
||||
var publicKey = Encoding.ASCII.GetString(Convert.FromBase64String(profile.PublicKey));
|
||||
var parameters = CryptoUtil.DecodePEMPublicKey(publicKey);
|
||||
if (!profile.KeyRevoked && CryptoUtil.VerifySignature(parameters, newConn.AuthToken, handshake.AuthSignature))
|
||||
{
|
||||
client.Fingerprint = handshake.Fingerprint;
|
||||
Log.Write("server", "{0} authenticated as {1} (UID {2})", newConn.Socket.RemoteEndPoint,
|
||||
profile.ProfileName, profile.ProfileID);
|
||||
}
|
||||
else if (profile.KeyRevoked)
|
||||
{
|
||||
profile = null;
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (key revoked)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
|
||||
}
|
||||
else
|
||||
{
|
||||
profile = null;
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (signature verification failed)",
|
||||
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
|
||||
}
|
||||
var publicKey = Encoding.ASCII.GetString(Convert.FromBase64String(profile.PublicKey));
|
||||
var parameters = CryptoUtil.DecodePEMPublicKey(publicKey);
|
||||
if (!profile.KeyRevoked && CryptoUtil.VerifySignature(parameters, newConn.AuthToken, handshake.AuthSignature))
|
||||
{
|
||||
client.Fingerprint = handshake.Fingerprint;
|
||||
Log.Write("server", "{0} authenticated as {1} (UID {2})", newConn.Socket.RemoteEndPoint,
|
||||
profile.ProfileName, profile.ProfileID);
|
||||
}
|
||||
else if (profile.KeyRevoked)
|
||||
{
|
||||
profile = null;
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (key revoked)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
|
||||
}
|
||||
else
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (invalid server response: `{2}` is not `Player`)",
|
||||
newConn.Socket.RemoteEndPoint, handshake.Fingerprint, yaml.Key);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (exception occurred)",
|
||||
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
|
||||
Log.Write("server", ex.ToString());
|
||||
{
|
||||
profile = null;
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (signature verification failed)",
|
||||
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
|
||||
}
|
||||
}
|
||||
else
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (invalid server response: `{2}` is not `Player`)",
|
||||
newConn.Socket.RemoteEndPoint, handshake.Fingerprint, yaml.Key);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (exception occurred)",
|
||||
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
|
||||
Log.Write("server", ex.ToString());
|
||||
}
|
||||
else
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (server error: `{2}`)",
|
||||
newConn.Socket.RemoteEndPoint, handshake.Fingerprint, i.Error);
|
||||
|
||||
delayedActions.Add(() =>
|
||||
{
|
||||
@@ -636,9 +634,7 @@ namespace OpenRA.Server
|
||||
|
||||
waitingForAuthenticationCallback--;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
new Download(playerDatabase.Profile + handshake.Fingerprint, _ => { }, onQueryComplete);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
48
OpenRA.Game/Support/HttpClientFactory.cs
Normal file
48
OpenRA.Game/Support/HttpClientFactory.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace OpenRA.Support
|
||||
{
|
||||
public class HttpClientFactory
|
||||
{
|
||||
#if !MONO
|
||||
const int MaxConnectionPerServer = 20;
|
||||
static readonly TimeSpan ConnectionLifeTime = TimeSpan.FromMinutes(1);
|
||||
#endif
|
||||
|
||||
static readonly Lazy<HttpMessageHandler> Handler = new Lazy<HttpMessageHandler>(GetHandler);
|
||||
|
||||
public static HttpClient Create()
|
||||
{
|
||||
return new HttpClient(Handler.Value, false);
|
||||
}
|
||||
|
||||
static HttpMessageHandler GetHandler()
|
||||
{
|
||||
#if !MONO
|
||||
return new SocketsHttpHandler
|
||||
{
|
||||
// https://github.com/dotnet/corefx/issues/26895
|
||||
// https://github.com/dotnet/corefx/issues/26331
|
||||
// https://github.com/dotnet/corefx/pull/26839
|
||||
PooledConnectionLifetime = ConnectionLifeTime,
|
||||
PooledConnectionIdleTimeout = ConnectionLifeTime,
|
||||
MaxConnectionsPerServer = MaxConnectionPerServer
|
||||
};
|
||||
#else
|
||||
return new HttpClientHandler();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
61
OpenRA.Game/Support/HttpQueryBuilder.cs
Normal file
61
OpenRA.Game/Support/HttpQueryBuilder.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.Support
|
||||
{
|
||||
public class HttpQueryBuilder : IEnumerable
|
||||
{
|
||||
readonly string url;
|
||||
readonly List<Parameter> parameters = new List<Parameter>();
|
||||
|
||||
public HttpQueryBuilder(string url)
|
||||
{
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public void Add(string name, object value)
|
||||
{
|
||||
parameters.Add(new Parameter
|
||||
{
|
||||
Name = name,
|
||||
Value = Uri.EscapeUriString(value.ToString())
|
||||
});
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder(url);
|
||||
|
||||
builder.Append("?");
|
||||
|
||||
foreach (var parameter in parameters)
|
||||
builder.Append($"{parameter.Name}={parameter.Value}&");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
class Parameter
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user