using System; using System.IO; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Net.Http; using System.Linq; using NetHttpClient = System.Net.Http.HttpClient; using System.Net.Http.Headers; using System.Collections.Generic; namespace LeanCloud.Storage.Internal { public class HttpClient : IHttpClient { private static HashSet HttpContentHeaders = new HashSet { { "Allow" }, { "Content-Disposition" }, { "Content-Encoding" }, { "Content-Language" }, { "Content-Length" }, { "Content-Location" }, { "Content-MD5" }, { "Content-Range" }, { "Content-Type" }, { "Expires" }, { "Last-Modified" } }; public HttpClient() { client = new NetHttpClient(); client.DefaultRequestHeaders.Add("User-Agent", "LeanCloud-dotNet-SDK/" + "2.0.0"); } public HttpClient(NetHttpClient client) { this.client = client; } private NetHttpClient client; public Task> ExecuteAsync(HttpRequest httpRequest, IProgress uploadProgress, IProgress downloadProgress, CancellationToken cancellationToken) { uploadProgress = uploadProgress ?? new Progress(); downloadProgress = downloadProgress ?? new Progress(); HttpMethod httpMethod = new HttpMethod(httpRequest.Method); HttpRequestMessage message = new HttpRequestMessage(httpMethod, httpRequest.Uri); // Fill in zero-length data if method is post. Stream data = httpRequest.Data; if (httpRequest.Data == null && httpRequest.Method.ToLower().Equals("post")) { data = new MemoryStream(new byte[0]); } if (data != null) { message.Content = new StreamContent(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; // TODO: (richardross) investigate progress here, maybe there's something we're missing in order to support this. uploadProgress.Report(new AVUploadProgressEventArgs { Progress = 0 }); return client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ContinueWith(httpMessageTask => { var response = httpMessageTask.Result; uploadProgress.Report(new AVUploadProgressEventArgs { Progress = 1 }); return response.Content.ReadAsStreamAsync().ContinueWith(streamTask => { var resultStream = new MemoryStream(); var responseStream = streamTask.Result; int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesRead = 0; long totalLength = -1; long readSoFar = 0; try { totalLength = responseStream.Length; } catch (NotSupportedException) { } return InternalExtensions.WhileAsync(() => { return responseStream.ReadAsync(buffer, 0, bufferSize, cancellationToken).OnSuccess(readTask => { bytesRead = readTask.Result; return bytesRead > 0; }); }, () => { cancellationToken.ThrowIfCancellationRequested(); return resultStream.WriteAsync(buffer, 0, bytesRead, cancellationToken).OnSuccess(_ => { cancellationToken.ThrowIfCancellationRequested(); readSoFar += bytesRead; if (totalLength > -1) { downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = 1.0 * readSoFar / totalLength }); } }); }).ContinueWith(_ => { responseStream.Dispose(); return _; }).Unwrap().OnSuccess(_ => { // If getting stream size is not supported, then report download only once. if (totalLength == -1) { downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = 1.0 }); } // Assume UTF-8 encoding. var resultAsArray = resultStream.ToArray(); var resultString = Encoding.UTF8.GetString(resultAsArray, 0, resultAsArray.Length); resultStream.Dispose(); return new Tuple(response.StatusCode, resultString); }); }); }).Unwrap().Unwrap(); } } }