public HttpResponseInfo Copy() { HttpResponseInfo copy = (HttpResponseInfo)this.MemberwiseClone(); return(copy); }
/// <summary> /// A powerful function that performs, via HttpClient, an HTTP GET request, /// but while adding the following helpful functions: /// 1) If <paramref name="settings"/> (<see cref="HttpNotModifiedProperties"/>) is not null, /// it allows a conditional GET request to be made. /// 2) Timeout allows a true timeout to be set that doesn't have to use the deeply flawed /// HttpClient timeout value. This is a great design flaw since their design requires the HttpClient /// to be reused globally. /// 3) Useful information is returned about the request, the HttpStatus, the basic result (Success or NotModified /// or Failed...), and the HttpHeaders, as well as NotModified related information that can be used on the next /// request to make a 304 (NotModified) request (<see cref="HttpResponseInfo"/> inherits <see cref="HttpNotModifiedProperties"/>). /// 4) Timing diagnostics on the response times, for both headers and content. /// 5) Exceptions are caught, but set on the <see cref="HttpResponseInfo.Ex"/> property. /// </summary> /// <param name="url">Url to GET.</param> /// <param name="settings">Not modified properties in order to make a not-modified request.</param> /// <param name="timeout">Timeout if any.</param> /// <param name="client">Client if any.</param> /// <param name="cancellationToken">Cancellation token.</param> public static async Task <HttpResponseInfo> GetAsync( string url, HttpNotModifiedProperties settings = null, TimeSpan?timeout = null, HttpClient client = null, CancellationToken?cancellationToken = null) { var swTotal = Stopwatch.StartNew(); if (url.IsNulle()) { throw new ArgumentNullException(nameof(url)); } var s = settings ?? new HttpNotModifiedProperties(); HttpResponseInfo h = new HttpResponseInfo() { Date = DateTimeOffset.UtcNow }; h.CopyValuesToThis(s); if (client == null) { client = _getHttpClient(); } CancellationToken cancelToken = cancellationToken ?? new CancellationToken(); bool condGet = s.ConditionalGet; TimeSpan _timeout = timeout ?? TimeSpan.Zero; if (_timeout <= TimeSpan.Zero) { _timeout = DefaultTimeout; } //if(timeoutSeconds > 0) // problem, HttpClient is supposed to be used globally!! // client.Timeout = TimeSpan.FromSeconds((int)timeoutSeconds); bool closeClient = client == null; try { // --- PREP REQUEST --- var request = new HttpRequestMessage(HttpMethod.Get, url) .SetDefaultHeaders(); // set request IfModifiedSince header if available if (condGet) { request.Headers.IfModifiedSince = s.LastModified; } // set request ETag if available if (condGet && s.ETag.NotNulle()) { //request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue("")) request.Headers.Add("If-None-Match", FixETag(s.ETag)); } // -- GET RESPONSE -- try { var sw = Stopwatch.StartNew(); var response = await client .SendAsync(request, cancelToken) //.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancelToken) .TimeoutAfterAsync(_timeout); h.HeaderResponseTime = sw.ElapsedAndReset(); TimeSpan remainingTime = _timeout - h.HeaderResponseTime; if (remainingTime <= TimeSpan.Zero) { remainingTime = TimeSpan.FromMilliseconds(1); // shouldn't happen, but if TimeoutAfterAsync didn't throw, make this not // a negative number at least, which is invalid for the next TimeoutAfterAsync call } h.StatusCode = response.StatusCode; h.ResultHeadersDict = response.GetHeadersDictionary(); h.ResultHeaders = h.ResultHeadersDict.__kvHeadersToString(); // --- Status code checks --- if (response.StatusCode == HttpStatusCode.NotModified) { h.Result = HttpResponseResult.NotModified; return(h); } bool isSuccess = response.IsSuccessStatusCode; h.Result = isSuccess ? HttpResponseResult.Success : HttpResponseResult.Fail; // OLD! now we DO allow content to be read even if is fail, // only NotMod stops the content from being read //if (!response.IsSuccessStatusCode) { // h.Result = HttpResponseResult.Fail; // return h; //} if (isSuccess) { // --- ETag --- string responseEtag = FixETag(h.ResultHeadersDict.V("ETag")); // response.Headers.ETag; // IS NULL!! STUPID!! Come on guys! if (condGet && s.ETag.NotNulle() && responseEtag == s.ETag) { h.Result = HttpResponseResult.NotModified; return(h); } h.ETag = responseEtag; // --- ContentLength check #1) If Content-Length header is set --- int?contentLengthHeader = h.ResultHeadersDict.V("Content-Length").TrimIfNeeded().ToIntN(); if (condGet && s.ContentLengthNotModified(contentLengthHeader)) { // contentLengthNotModified(eqCntLenNotMod, contentLengthHeader, s.ContentLength)) { h.Result = HttpResponseResult.NotModified; return(h); } } // --- READ CONTENT --- sw.Start(); byte[] data = await response.Content .ReadAsByteArrayAsync() .TimeoutAfterAsync(remainingTime); h.ContentResponseTime = sw.ElapsedAndStop(); if (data == null) { data = new byte[0]; // this seems right } h.ContentData = data; int dataLen = data.LengthN(); // --- ContentLength check #2) If actual data length is equal --- if (isSuccess && condGet && s.ContentLengthNotModified(dataLen)) { h.Result = HttpResponseResult.NotModified; return(h); } h.ContentLength = dataLen; // methods above should have set, but no hurt return(h); } catch (Exception ex) { h.Result = HttpResponseResult.Fail; if (ex is TimeoutException) { h.Result = HttpResponseResult.TimeOut; } else { h.Ex = ex; // don't save if just a timeout } return(h); } } finally { if (closeClient && client != null) { client.Dispose(); } if (h != null && swTotal != null && swTotal.IsRunning) { h.TotalResponseTime = swTotal.Elapsed; } } }