chore: 删除 HttpClient,使用 .Net 标准库中的 HttpClient

oneRain 2019-08-09 14:32:28 +08:00
parent f2ec04a331
commit 148f51967e
9 changed files with 93 additions and 217 deletions

View File

@ -16,7 +16,6 @@ namespace LeanCloud.Storage.Internal {
#region Server Controllers #region Server Controllers
private HttpClient httpClient;
private AppRouterController appRouterController; private AppRouterController appRouterController;
private AVCommandRunner commandRunner; private AVCommandRunner commandRunner;
private StorageController storageController; private StorageController storageController;
@ -40,7 +39,6 @@ namespace LeanCloud.Storage.Internal {
public void Reset() { public void Reset() {
lock (mutex) { lock (mutex) {
HttpClient = null;
AppRouterController = null; AppRouterController = null;
CommandRunner = null; CommandRunner = null;
StorageController = null; StorageController = null;
@ -57,20 +55,6 @@ namespace LeanCloud.Storage.Internal {
} }
} }
public HttpClient HttpClient {
get {
lock (mutex) {
httpClient = httpClient ?? new HttpClient();
return httpClient;
}
}
set {
lock (mutex) {
httpClient = value;
}
}
}
public AppRouterController AppRouterController { public AppRouterController AppRouterController {
get { get {
lock (mutex) { lock (mutex) {

View File

@ -36,18 +36,23 @@ namespace LeanCloud.Storage.Internal {
string appId = AVClient.CurrentConfiguration.ApplicationId; string appId = AVClient.CurrentConfiguration.ApplicationId;
string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId); string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId);
var request = new HttpRequest { HttpClient client = new HttpClient();
Uri = new Uri(url), HttpRequestMessage request = new HttpRequestMessage {
Method = HttpMethod.Get, RequestUri = new Uri(url),
Headers = null, Method = HttpMethod.Get
Data = null
}; };
var ret = await AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, CancellationToken.None); try {
if (ret.Item1 != HttpStatusCode.OK) { HttpResponseMessage response = await client.SendAsync(request);
throw new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", null); client.Dispose();
} request.Dispose();
return await JsonUtils.DeserializeObjectAsync<AppRouterState>(ret.Item2); string content = await response.Content.ReadAsStringAsync();
response.Dispose();
return await JsonUtils.DeserializeObjectAsync<AppRouterState>(content);
} catch (Exception e) {
throw new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", e);
}
} }
public void Clear() { public void Clear() {

View File

@ -18,10 +18,10 @@ namespace LeanCloud.Storage.Internal {
const string USE_PRODUCTION = "1"; const string USE_PRODUCTION = "1";
const string USE_DEVELOPMENT = "0"; const string USE_DEVELOPMENT = "0";
private readonly System.Net.Http.HttpClient httpClient; private readonly HttpClient httpClient;
public AVCommandRunner() { public AVCommandRunner() {
httpClient = new System.Net.Http.HttpClient(); httpClient = new HttpClient();
ProductHeaderValue product = new ProductHeaderValue(AVClient.Name, AVClient.Version); ProductHeaderValue product = new ProductHeaderValue(AVClient.Name, AVClient.Version);
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product)); httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product));
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(APPLICATION_JSON)); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(APPLICATION_JSON));
@ -48,15 +48,9 @@ namespace LeanCloud.Storage.Internal {
/// ///
/// </summary> /// </summary>
/// <param name="command"></param> /// <param name="command"></param>
/// <param name="uploadProgress"></param>
/// <param name="downloadProgress"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public async Task<Tuple<HttpStatusCode, T>> RunCommandAsync<T>(AVCommand command, public async Task<Tuple<HttpStatusCode, T>> RunCommandAsync<T>(AVCommand command,CancellationToken cancellationToken = default) {
IProgress<AVUploadProgressEventArgs> uploadProgress = null,
IProgress<AVDownloadProgressEventArgs> downloadProgress = null,
CancellationToken cancellationToken = default) {
string content = JsonConvert.SerializeObject(command.Content); string content = JsonConvert.SerializeObject(command.Content);
var request = new HttpRequestMessage { var request = new HttpRequestMessage {
RequestUri = command.Uri, RequestUri = command.Uri,
@ -115,7 +109,7 @@ namespace LeanCloud.Storage.Internal {
return new Tuple<HttpStatusCode, T>(responseCode, default); return new Tuple<HttpStatusCode, T>(responseCode, default);
} }
static void PrintRequest(System.Net.Http.HttpClient client, HttpRequestMessage request, string content) { static void PrintRequest(HttpClient client, HttpRequestMessage request, string content) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.AppendLine("=== HTTP Request Start ==="); sb.AppendLine("=== HTTP Request Start ===");
sb.AppendLine($"URL: {request.RequestUri}"); sb.AppendLine($"URL: {request.RequestUri}");

View File

@ -6,53 +6,15 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
namespace LeanCloud.Storage.Internal { namespace LeanCloud.Storage.Internal {
/// <summary> public abstract class AVFileController {
/// AVF ile controller. public abstract Task<FileState> SaveAsync(FileState state,
/// </summary>
public class AVFileController {
/// <summary>
/// Saves the async.
/// </summary>
/// <returns>The async.</returns>
/// <param name="state">State.</param>
/// <param name="dataStream">Data stream.</param>
/// <param name="sessionToken">Session token.</param>
/// <param name="progress">Progress.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public virtual async Task<FileState> SaveAsync(FileState state,
Stream dataStream, Stream dataStream,
String sessionToken, String sessionToken,
IProgress<AVUploadProgressEventArgs> progress, IProgress<AVUploadProgressEventArgs> progress,
CancellationToken cancellationToken = default(CancellationToken)) { CancellationToken cancellationToken = default(CancellationToken));
if (state.Url != null) {
// !isDirty
return state;
}
if (cancellationToken.IsCancellationRequested) {
return null;
}
var oldPosition = dataStream.Position;
var request = new HttpRequest {
Uri = new Uri("files/" + state.Name),
Method = HttpMethod.Post,
Headers = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string>("Content-Type", state.MimeType)
}
};
var ret = await AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, CancellationToken.None);
var jsonData = JsonConvert.DeserializeObject<Dictionary<string, object>>(ret.Item2, new LeanCloudJsonConverter());
return new FileState {
Name = jsonData["name"] as string,
Url = new Uri(jsonData["url"] as string, UriKind.Absolute),
MimeType = state.MimeType
};
}
public Task DeleteAsync(FileState state, string sessionToken, CancellationToken cancellationToken) { public Task DeleteAsync(FileState state, string sessionToken, CancellationToken cancellationToken) {
var command = new AVCommand { var command = new AVCommand {
Path = $"files/{state.ObjectId}", Path = $"files/{state.ObjectId}",
@ -60,6 +22,7 @@ namespace LeanCloud.Storage.Internal {
}; };
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken); return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken);
} }
internal Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetFileToken(FileState fileState, CancellationToken cancellationToken) { internal Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetFileToken(FileState fileState, CancellationToken cancellationToken) {
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> rtn; Task<Tuple<HttpStatusCode, IDictionary<string, object>>> rtn;
string currentSessionToken = AVUser.CurrentSessionToken; string currentSessionToken = AVUser.CurrentSessionToken;
@ -78,6 +41,7 @@ namespace LeanCloud.Storage.Internal {
}; };
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command); return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
} }
public Task<FileState> GetAsync(string objectId, string sessionToken, CancellationToken cancellationToken) { public Task<FileState> GetAsync(string objectId, string sessionToken, CancellationToken cancellationToken) {
var command = new AVCommand { var command = new AVCommand {
Path = $"files/{objectId}", Path = $"files/{objectId}",
@ -94,6 +58,7 @@ namespace LeanCloud.Storage.Internal {
}; };
}); });
} }
internal static string GetUniqueName(FileState fileState) { internal static string GetUniqueName(FileState fileState) {
string key = Random(12); string key = Random(12);
string extension = Path.GetExtension(fileState.Name); string extension = Path.GetExtension(fileState.Name);
@ -101,12 +66,14 @@ namespace LeanCloud.Storage.Internal {
fileState.CloudName = key; fileState.CloudName = key;
return key; return key;
} }
internal static string Random(int length) { internal static string Random(int length) {
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
var random = new Random(); var random = new Random();
return new string(Enumerable.Repeat(chars, length) return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray()); .Select(s => s[random.Next(s.Length)]).ToArray());
} }
internal static double CalcProgress(double already, double total) { internal static double CalcProgress(double already, double total) {
var pv = (1.0 * already / total); var pv = (1.0 * already / total);
return Math.Round(pv, 3); return Math.Round(pv, 3);

View File

@ -2,7 +2,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading; using System.Threading;
using System.IO; using System.IO;
using LeanCloud.Storage.Internal;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
@ -29,13 +28,19 @@ namespace LeanCloud.Storage.Internal {
internal async Task<FileState> PutFile(FileState state, string uploadUrl, Stream dataStream) { internal async Task<FileState> PutFile(FileState state, string uploadUrl, Stream dataStream) {
IList<KeyValuePair<string, string>> makeBlockHeaders = new List<KeyValuePair<string, string>>(); IList<KeyValuePair<string, string>> makeBlockHeaders = new List<KeyValuePair<string, string>>();
makeBlockHeaders.Add(new KeyValuePair<string, string>("Content-Type", state.MimeType)); makeBlockHeaders.Add(new KeyValuePair<string, string>("Content-Type", state.MimeType));
var request = new HttpRequest {
Uri = new Uri(uploadUrl), HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage {
RequestUri = new Uri(uploadUrl),
Method = HttpMethod.Put, Method = HttpMethod.Put,
Headers = makeBlockHeaders, Content = new StreamContent(dataStream)
Data = dataStream
}; };
await AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, CancellationToken.None); foreach (var header in makeBlockHeaders) {
request.Headers.Add(header.Key, header.Value);
}
await client.SendAsync(request);
client.Dispose();
request.Dispose();
return state; return state;
} }
} }

View File

@ -20,7 +20,7 @@ namespace LeanCloud.Storage.Internal {
bool done; bool done;
private long sliceSize = (long)CommonSize.KB512; private long sliceSize = (long)CommonSize.KB512;
public Task<FileState> SaveAsync(FileState state, public override Task<FileState> SaveAsync(FileState state,
Stream dataStream, Stream dataStream,
string sessionToken, string sessionToken,
IProgress<AVUploadProgressEventArgs> progress, IProgress<AVUploadProgressEventArgs> progress,
@ -132,6 +132,7 @@ namespace LeanCloud.Storage.Internal {
return HexStringFromBytes(hashBytes); return HexStringFromBytes(hashBytes);
} }
async Task<Tuple<HttpStatusCode, IDictionary<string, object>>> PostToQCloud( async Task<Tuple<HttpStatusCode, IDictionary<string, object>>> PostToQCloud(
Dictionary<string, object> body, Dictionary<string, object> body,
byte[] sliceFile, byte[] sliceFile,
@ -146,16 +147,22 @@ namespace LeanCloud.Storage.Internal {
sliceHeaders.Add(new KeyValuePair<string, string>("Content-Type", contentType)); sliceHeaders.Add(new KeyValuePair<string, string>("Content-Type", contentType));
var request = new HttpRequest { var client = new HttpClient();
Uri = new Uri(this.uploadUrl), var request = new HttpRequestMessage {
RequestUri = new Uri(uploadUrl),
Method = HttpMethod.Post, Method = HttpMethod.Post,
Headers = sliceHeaders, Content = new StreamContent(tempStream)
Data = tempStream
}; };
var ret = await AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, CancellationToken.None); foreach (var header in sliceHeaders) {
var result = new Tuple<HttpStatusCode, IDictionary<string, object>>(ret.Item1, request.Headers.Add(header.Key, header.Value);
JsonConvert.DeserializeObject<Dictionary<string, object>>(ret.Item2, new LeanCloudJsonConverter())); }
return result; var response = await client.SendAsync(request);
client.Dispose();
request.Dispose();
var content = await response.Content.ReadAsStringAsync();
response.Dispose();
// TODO 修改反序列化返回
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, IDictionary<string, object>>>(content);
} }
public static Stream HttpUploadFile(byte[] file, string fileName, out string contentType, out long contentLength, IDictionary<string, object> nvc) { public static Stream HttpUploadFile(byte[] file, string fileName, out string contentType, out long contentLength, IDictionary<string, object> nvc) {
string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");

View File

@ -175,25 +175,41 @@ namespace LeanCloud.Storage.Internal
MemoryStream firstChunkData = new MemoryStream(firstChunkBinary, 0, firstChunkBinary.Length); MemoryStream firstChunkData = new MemoryStream(firstChunkBinary, 0, firstChunkBinary.Length);
var headers = GetQiniuRequestHeaders(state); var headers = GetQiniuRequestHeaders(state);
headers.Add(new KeyValuePair<string, string>("Content-Type", "application/octet-stream")); headers.Add(new KeyValuePair<string, string>("Content-Type", "application/octet-stream"));
var request = new HttpRequest { var client = new HttpClient();
Uri = new Uri(new Uri(UP_HOST) + string.Format("mkblk/{0}", blcokSize)), var request = new HttpRequestMessage {
RequestUri = new Uri($"{UP_HOST}/mkblk/{blcokSize}"),
Method = HttpMethod.Post, Method = HttpMethod.Post,
Headers = headers, Content = new StreamContent(firstChunkData)
Data = firstChunkData
}; };
return await AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, CancellationToken.None); foreach (var header in headers) {
request.Headers.Add(header.Key, header.Value);
}
var response = await client.SendAsync(request);
client.Dispose();
request.Dispose();
var content = await response.Content.ReadAsStringAsync();
response.Dispose();
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, string>>(content);
} }
async Task<Tuple<HttpStatusCode, string>> PutChunk(FileState state, byte[] chunkBinary, string LastChunkctx, long currentChunkOffsetInBlock) { async Task<Tuple<HttpStatusCode, string>> PutChunk(FileState state, byte[] chunkBinary, string LastChunkctx, long currentChunkOffsetInBlock) {
MemoryStream chunkData = new MemoryStream(chunkBinary, 0, chunkBinary.Length); MemoryStream chunkData = new MemoryStream(chunkBinary, 0, chunkBinary.Length);
var request = new HttpRequest { var client = new HttpClient();
Uri = new Uri(new Uri(UP_HOST) + string.Format("bput/{0}/{1}", LastChunkctx, currentChunkOffsetInBlock)), var request = new HttpRequestMessage {
RequestUri = new Uri($"{UP_HOST}/bput/{LastChunkctx}/{currentChunkOffsetInBlock}"),
Method = HttpMethod.Post, Method = HttpMethod.Post,
Headers = GetQiniuRequestHeaders(state), Content = new StreamContent(chunkData)
Data = chunkData
}; };
var ret = await AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, CancellationToken.None); var headers = GetQiniuRequestHeaders(state);
return ret; foreach (var header in headers) {
request.Headers.Add(header.Key, header.Value);
}
var response = await client.SendAsync(request);
client.Dispose();
request.Dispose();
var content = await response.Content.ReadAsStringAsync();
response.Dispose();
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, string>>(content);
} }
internal async Task<Tuple<HttpStatusCode, string>> QiniuMakeFile(FileState state, Stream dataStream, string upToken, string key, long fsize, string[] ctxes, CancellationToken cancellationToken) internal async Task<Tuple<HttpStatusCode, string>> QiniuMakeFile(FileState state, Stream dataStream, string upToken, string key, long fsize, string[] ctxes, CancellationToken cancellationToken)
@ -232,15 +248,21 @@ namespace LeanCloud.Storage.Internal
} }
} }
body.Seek(0, SeekOrigin.Begin); body.Seek(0, SeekOrigin.Begin);
var request = new HttpRequest {
Uri = new Uri(urlBuilder.ToString()), var client = new HttpClient();
var request = new HttpRequestMessage {
RequestUri = new Uri(urlBuilder.ToString()),
Method = HttpMethod.Post, Method = HttpMethod.Post,
Headers = headers, Content = new StreamContent(body)
Data = body
}; };
var ret = await AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, CancellationToken.None); var response = await client.SendAsync(request);
return ret; client.Dispose();
request.Dispose();
var content = await response.Content.ReadAsStringAsync();
response.Dispose();
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, string>>(content);
} }
internal void MergeFromJSON(FileState state, IDictionary<string, object> jsonData) internal void MergeFromJSON(FileState state, IDictionary<string, object> jsonData)
{ {
lock (this.mutex) lock (this.mutex)

View File

@ -1,86 +0,0 @@
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.IO;
using NetHttpClient = System.Net.Http.HttpClient;
namespace LeanCloud.Storage.Internal {
public class HttpClient {
static readonly HashSet<string> HttpContentHeaders = new HashSet<string> {
{ "Allow" },
{ "Content-Disposition" },
{ "Content-Encoding" },
{ "Content-Language" },
{ "Content-Length" },
{ "Content-Location" },
{ "Content-MD5" },
{ "Content-Range" },
{ "Content-Type" },
{ "Expires" },
{ "Last-Modified" }
};
readonly NetHttpClient client;
public HttpClient() {
client = new NetHttpClient();
// 设置版本号
client.DefaultRequestHeaders.Add("User-Agent", $"LeanCloud-csharp-sdk-{AVClient.Version}");
}
public HttpClient(NetHttpClient client) {
this.client = client;
}
public async Task<Tuple<HttpStatusCode, string>> ExecuteAsync(HttpRequest httpRequest,
IProgress<AVUploadProgressEventArgs> uploadProgress,
IProgress<AVDownloadProgressEventArgs> downloadProgress,
CancellationToken cancellationToken) {
HttpMethod httpMethod = httpRequest.Method;
HttpRequestMessage message = new HttpRequestMessage(httpMethod, httpRequest.Uri);
// Fill in zero-length data if method is post.
if (httpRequest.Data == null && httpRequest.Method == HttpMethod.Post) {
message.Content = new StreamContent(new MemoryStream(new byte[0]));
}
if (httpRequest.Data != null) {
message.Content = new StreamContent(httpRequest.Data);
}
if (httpRequest.Headers != null) {
foreach (var header in httpRequest.Headers) {
if (!string.IsNullOrEmpty(header.Value)) {
if (HttpContentHeaders.Contains(header.Key)) {
message.Content.Headers.Add(header.Key, header.Value);
} else {
message.Headers.Add(header.Key, header.Value);
}
}
}
}
// Avoid aggressive caching on Windows Phone 8.1.
message.Headers.Add("Cache-Control", "no-cache");
message.Headers.IfModifiedSince = DateTimeOffset.UtcNow;
uploadProgress?.Report(new AVUploadProgressEventArgs { Progress = 0 });
var response = await client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
uploadProgress?.Report(new AVUploadProgressEventArgs { Progress = 1 });
message.Dispose();
var resultString = await response.Content.ReadAsStringAsync();
response.Dispose();
downloadProgress?.Report(new AVDownloadProgressEventArgs { Progress = 1 });
return new Tuple<HttpStatusCode, string>(response.StatusCode, resultString);
}
}
}

View File

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
namespace LeanCloud.Storage.Internal
{
/// <summary>
/// <code>IHttpRequest</code> is an interface that provides an API to execute HTTP request data.
/// </summary>
public class HttpRequest
{
public Uri Uri { get; set; }
public IList<KeyValuePair<string, string>> Headers { get; set; }
// HttpMethod
public HttpMethod Method { get; set; }
public virtual Stream Data { get; set; }
}
}