refactor: Use .NET HTTP client in favor of the custom client

This commit is contained in:
2025-06-21 02:22:50 +08:00
parent e0362bbb58
commit 38421b7a53
15 changed files with 8 additions and 598 deletions

View File

@@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: f8303a3eeefeacf4ca0c02b5d32e0cff
folderAsset: yes
timeCreated: 1621071543
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 1a624371d4108614b9cdc4acca1499e2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,155 +0,0 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Sockets;
using System.Text;
namespace Cryville.Common.Network.Http11 {
public class Http11Client : IDisposable {
private readonly string _directHost;
protected string DirectHost { get { return _directHost; } }
private readonly int _directPort;
protected int DirectPort { get { return _directPort; } }
readonly Uri _baseUri;
readonly int origPort;
protected const string Version = "HTTP/1.1";
protected TcpClient TcpClient { get; private set; }
protected Stream RawTcpStream { get { return TcpClient.GetStream(); } }
protected virtual Stream Stream { get { return TcpClient.GetStream(); } }
protected virtual string WindowsProxyProtocolName { get { return "http"; } }
private readonly bool _proxied = false;
public Dictionary<string, string> Headers { get; set; }
public Http11Client(Uri baseUri, int port = 80) {
_directHost = baseUri.Host;
_directPort = port;
_baseUri = baseUri;
origPort = _baseUri.Port;
Headers = new Dictionary<string, string>();
_proxied = GetProxy(ref _directHost, ref _directPort);
Shared.Logger.Log(0, "Network", "Connecting to {0}:{1}", DirectHost, DirectPort);
TcpClient = new TcpClient(DirectHost, DirectPort);
}
public virtual void Connect() {
if (_proxied) {
Request(RawTcpStream, "CONNECT", string.Format(CultureInfo.InvariantCulture, "{0}:{1}", _baseUri.Host, origPort));
}
}
public virtual void Close() {
TcpClient.Close();
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing) {
if (disposing) {
Close();
}
}
public Http11Response Request(string method, Uri uri, string body = null, Encoding encoding = null) {
string struri = GetUri(uri).PathAndQuery;
return Request(Stream, method, struri, body, encoding);
}
Http11Response Request(Stream stream, string method, string uri, string body = null, Encoding encoding = null) {
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var h in Headers)
headers.Add(h.Key, h.Value);
headers["Host"] = _baseUri.Host;
byte[] payload = null;
if (body != null) {
encoding ??= Encoding.UTF8;
payload = encoding.GetBytes(body);
headers.Add("Content-Encoding", encoding.EncodingName);
headers.Add("Content-Length", payload.Length.ToString(CultureInfo.InvariantCulture));
}
using (var writer = new StreamWriter(stream, Encoding.ASCII, 1024, true)) {
writer.Write(method);
writer.Write(' ');
writer.Write(uri);
writer.Write(' ');
writer.Write(Version);
writer.Write("\r\n");
foreach (var header in headers) {
writer.Write(header.Key);
writer.Write(':');
writer.Write(header.Value);
writer.Write("\r\n");
}
writer.Write("\r\n");
if (payload != null) writer.Write(payload);
writer.Flush();
}
var response = new Http11Response(stream);
Shared.Logger.Log(0, "Network", "{0}", response);
return response;
}
protected bool GetProxy(ref string host, ref int port) {
// TODO use winhttp.dll
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
var reg = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Internet Settings");
var proxyEnable = (int)reg.GetValue("ProxyEnable");
if (proxyEnable == 0) return false;
var proxyStr = (string)reg.GetValue("ProxyServer");
if (!string.IsNullOrEmpty(proxyStr)) {
string[] proxies = proxyStr.Split(';');
foreach (var p in proxies) {
if (!p.Contains('=')) {
string[] s = p.Split(':');
host = s[0];
port = int.Parse(s[1]);
return true;
}
else if (p.StartsWith(WindowsProxyProtocolName + "=")) {
string[] s = p.Split('=', ':');
host = s[1];
port = int.Parse(s[2]);
return true;
}
}
}
}
return false;
}
protected Uri GetUri(string path) {
Uri address;
if (_baseUri != null) {
if (!Uri.TryCreate(_baseUri, path, out address)) {
return new Uri(Path.GetFullPath(path));
}
}
else {
if (!Uri.TryCreate(path, UriKind.Absolute, out address)) {
return new Uri(Path.GetFullPath(path));
}
}
return GetUri(address);
}
protected Uri GetUri(Uri address) {
if (address == null) {
throw new ArgumentNullException("address");
}
Uri uri = address;
if (!address.IsAbsoluteUri && _baseUri != null && !Uri.TryCreate(_baseUri, address, out uri)) {
return address;
}
return uri;
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 5a795e416e54c69418de1a3c27a88932
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Cryville.Common.Network.Http11 {
public class Http11Response : IDisposable {
static readonly char[] spchar = new char[]{ ' ' };
public string HttpVersion { get; private set; }
public string StatusCode { get; private set; }
public string ReasonPhase { get; private set; }
public Dictionary<string, string> Headers { get; private set; }
public Http11ResponseStream MessageBody { get; private set; }
internal Http11Response(Stream stream) {
var reader = new BinaryReader(stream, Encoding.ASCII);
var statu_line = ReadLine(reader).Split(spchar, 3);
HttpVersion = statu_line[0];
StatusCode = statu_line[1];
ReasonPhase = statu_line[2];
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
while (ParseHeader(reader, Headers)) ;
if (Headers.ContainsKey("content-length")) {
int length = int.Parse(Headers["content-length"]);
MessageBody = new Http11ResponseBlockStream(reader, length);
}
else if (Headers.ContainsKey("transfer-encoding") && Headers["transfer-encoding"] == "chunked") {
MessageBody = new Http11ResponseChunkedStream(reader);
}
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing) {
if (disposing) {
MessageBody.Dispose();
}
}
public override string ToString() {
return string.Format("<{0} {1} {2}>", HttpVersion, StatusCode, ReasonPhase);
}
internal static bool ParseHeader(BinaryReader reader, Dictionary<string, string> headers) {
// TODO Multiline header
var header = ReadLine(reader);
if (header == "") return false;
var s = header.Split(':', 2);
string field_name = s[0].Trim().ToLower();
string field_value = s[1].Trim();
if (headers.ContainsKey(field_name)) headers[field_name] += "," + field_value;
else headers.Add(field_name, field_value);
return true;
}
internal static string ReadLine(BinaryReader reader) {
StringBuilder result = new();
char c;
while (true) {
c = reader.ReadChar();
if (c == '\r') break;
result.Append(c);
}
// TODO Unseekable
reader.ReadByte();
return result.ToString();
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 71234dd1c93d47b4893750686b2333a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,129 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
namespace Cryville.Common.Network.Http11 {
public abstract class Http11ResponseStream : Stream {
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return false; } }
public override long Length { get { throw new NotSupportedException(); } }
public override long Position {
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override void Flush() { }
public abstract byte[] ReadToEnd();
public override long Seek(long offset, SeekOrigin origin) {
throw new NotSupportedException();
}
public override void SetLength(long value) {
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count) {
throw new NotSupportedException();
}
}
internal sealed class Http11ResponseBlockStream : Http11ResponseStream {
readonly BinaryReader _reader;
readonly int _length;
int _pos = 0;
internal Http11ResponseBlockStream(BinaryReader reader, int length) {
_reader = reader;
_length = length;
}
public override int Read(byte[] buffer, int offset, int count) {
int recv = 0;
int recv_len = System.Math.Min(count, _length - _pos);
if (recv_len == 0) return 0;
while (recv < recv_len) {
recv += _reader.Read(buffer, offset + recv, count - recv);
}
_pos += recv_len;
return recv_len;
}
public override byte[] ReadToEnd() {
byte[] buffer = new byte[_length - _pos];
Read(buffer, 0, buffer.Length);
return buffer;
}
}
internal sealed class Http11ResponseChunkedStream : Http11ResponseStream {
readonly BinaryReader _reader;
byte[] _chunk = null;
int _pos = 0;
internal Http11ResponseChunkedStream(BinaryReader reader) {
_reader = reader;
ReadChunk();
}
public void ReadChunk() {
if (_chunk != null && _chunk.Length == 0) return;
string[] chunkHeader = Http11Response.ReadLine(_reader).Split(';');
if (!int.TryParse(chunkHeader[0], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int chunkSize))
throw new IOException("Corrupted chunk received");
if (chunkSize == 0) {
_chunk = new byte[0];
// TODO TE Header, now just discard
var headers = new Dictionary<string, string>();
while (Http11Response.ParseHeader(_reader, headers)) ;
return;
}
_chunk = new byte[chunkSize];
int recv = 0;
while (recv < chunkSize) {
recv += _reader.Read(_chunk, recv, chunkSize - recv);
}
_pos = 0;
if (Http11Response.ReadLine(_reader) != "")
throw new IOException("Corrupted chunk received");
}
public override int Read(byte[] buffer, int offset, int count) {
if (_chunk.Length == 0) return 0;
int recv = 0;
while (true) {
if (count - recv <= _chunk.Length - _pos) break;
Array.Copy(_chunk, _pos, buffer, recv, _chunk.Length - _pos);
recv += _chunk.Length - _pos;
ReadChunk();
if (_chunk.Length == 0) return recv;
}
Array.Copy(_chunk, _pos, buffer, recv, count - recv);
return count;
}
public override byte[] ReadToEnd() {
if (_chunk.Length == 0) return new byte[0];
List<byte[]> segs = new();
while (true) {
if (_pos != 0) {
var buffer = new byte[_chunk.Length - _pos];
Array.Copy(_chunk, _pos, buffer, 0, buffer.Length);
segs.Add(buffer);
}
else segs.Add(_chunk);
ReadChunk();
if (_chunk.Length == 0) {
var result = new byte[segs.Sum(i => i.Length)];
int p = 0;
foreach (var i in segs) {
Array.Copy(i, 0, result, p, i.Length);
p += i.Length;
}
return result;
}
}
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 49a8d5b9869e5bb42bafbe71f84fecc5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,33 +0,0 @@
using System;
using System.IO;
namespace Cryville.Common.Network.Http11 {
public class Https11Client : Http11Client {
readonly TlsClient _tlsClient;
protected override Stream Stream {
get {
return _tlsClient.Stream;
}
}
protected override string WindowsProxyProtocolName {
get {
return "https";
}
}
public Https11Client(Uri baseUri) : base(baseUri, 443) {
_tlsClient = new TlsClient(RawTcpStream, baseUri.Host);
}
public override void Connect() {
base.Connect();
_tlsClient.Connect();
}
public override void Close() {
_tlsClient.Close();
base.Close();
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c5c233e6228ce204fa1a9724c48ac8fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,96 +0,0 @@
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Tls;
using Org.BouncyCastle.Tls.Crypto;
using Org.BouncyCastle.Tls.Crypto.Impl.BC;
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text;
using BcTlsClient = Org.BouncyCastle.Tls.TlsClient;
namespace Cryville.Common.Network {
public class TlsClient : IDisposable {
readonly TlsClientProtocol _protocol;
readonly BcTlsClient _tlsClient;
public Stream Stream { get; private set; }
public TlsClient(Stream baseStream, string hostname) {
_protocol = new TlsClientProtocol(baseStream);
_tlsClient = new InternalTlsClient(hostname, new BcTlsCrypto(new SecureRandom()));
}
public void Connect() {
_protocol.Connect(_tlsClient);
Stream = _protocol.Stream;
}
public void Close() {
_protocol.Close();
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing) {
if (disposing) {
Close();
}
}
private class InternalTlsClient : DefaultTlsClient {
readonly string _host;
public InternalTlsClient(string host, TlsCrypto crypto) : base(crypto) {
_host = host;
}
protected override ProtocolVersion[] GetSupportedVersions() {
return ProtocolVersion.TLSv13.DownTo(ProtocolVersion.TLSv12);
}
protected override IList GetProtocolNames() {
IList list = new ArrayList {
ProtocolName.Http_1_1
};
return list;
}
private static readonly int[] supportedCipherSuites = {
CipherSuite.TLS_AES_128_GCM_SHA256,
CipherSuite.TLS_AES_256_GCM_SHA384,
CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
CipherSuite.TLS_AES_128_CCM_SHA256,
CipherSuite.TLS_AES_128_CCM_8_SHA256,
};
protected override int[] GetSupportedCipherSuites() {
return base.GetSupportedCipherSuites().Union(supportedCipherSuites).ToArray();
}
protected override IList GetSupportedSignatureAlgorithms() {
var result = base.GetSupportedSignatureAlgorithms();
result.Add(SignatureAndHashAlgorithm.ecdsa_brainpoolP256r1tls13_sha256);
result.Add(SignatureAndHashAlgorithm.ecdsa_brainpoolP384r1tls13_sha384);
result.Add(SignatureAndHashAlgorithm.ecdsa_brainpoolP512r1tls13_sha512);
return result;
}
protected override IList GetSniServerNames() {
return new ArrayList { new ServerName(0, Encoding.ASCII.GetBytes(_host)) };
}
public override TlsAuthentication GetAuthentication() {
return new NullTlsAuthentication();
}
}
private class NullTlsAuthentication : TlsAuthentication {
public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest) {
return null;
}
public void NotifyServerCertificate(TlsServerCertificate serverCertificate) { }
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: c9c242bb90fc1cc479a8df1407f21940
timeCreated: 1622021660
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,5 +1,4 @@
using Cryville.Common;
using Cryville.Common.Network.Http11;
using Cryville.Common.Unity;
using Cryville.Crtr.UI;
using Newtonsoft.Json;
@@ -7,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
using System.Runtime.InteropServices;
#endif
@@ -46,10 +46,10 @@ namespace Cryville.Crtr.Network {
}
}
void CheckVersion() {
using (var client = new Https11Client(BaseUri)) {
using (var client = new HttpClient()) {
client.Connect();
using var response = client.Request("GET", new Uri(BaseUri, "versions"));
var data = Encoding.UTF8.GetString(response.MessageBody.ReadToEnd());
using var response = client.GetAsync(new Uri(BaseUri, "versions")).Result;
var data = response.Content.ReadAsStringAsync().Result;
_versions = JsonConvert.DeserializeObject<List<VersionInfo>>(data, Game.GlobalJsonSerializerSettings);
}
var availableVersions = _versions.Where(v => v.platforms.ContainsKey(PlatformConfig.Name)).ToArray();
@@ -131,12 +131,12 @@ namespace Cryville.Crtr.Network {
}
void Download(VersionResourceInfo diff, string path) {
var uri = new Uri(diff.url);
using var client = new Https11Client(uri);
using var client = new HttpClient();
client.Connect();
using var response = client.Request("GET", uri);
var data = response.MessageBody.ReadToEnd();
using var response = client.GetAsync(uri).Result;
using var stream = response.Content.ReadAsStreamAsync().Result;
using var file = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
file.Write(data);
stream.CopyTo(file);
}
void ExecuteUpdate(List<string> diffPaths) {
#if UNITY_EDITOR

View File

@@ -1,33 +0,0 @@
fileFormatVersion: 2
guid: 634b689196afdab46b00f0017dc9ffc4
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant: