// Define other methods and classes here private void ResponseCallback(IAsyncResult result) { // Get and fill the RequestState RequestState state = (RequestState)result.AsyncState; try { bool downloadFile = false; HttpWebRequest request = state.Request; // End the Asynchronous response and get the actual response object state.Response = (HttpWebResponse)request.EndGetResponse(result); state.StatusCode = state.Response.StatusCode; switch (state.StatusCode) { case HttpStatusCode.OK: case HttpStatusCode.Created: case HttpStatusCode.Accepted: state.Downloaded = DateTime.UtcNow; break; case HttpStatusCode.NoContent: Device.Log.Info("Empty payload returned in CacheFetcher: Result {0} for {1}", state.StatusCode, request.RequestUri); state.Expiration = DateTime.UtcNow; state.AttemptToRefresh = DateTime.UtcNow; state.Downloaded = DateTime.UtcNow; OnDownloadComplete(state); return; default: state.ErrorMessage = String.Format("Get failed. Received HTTP {0} for {1}", state.StatusCode, request.RequestUri); Device.Log.Error(state.ErrorMessage); state.Expiration = DateTime.UtcNow; state.AttemptToRefresh = DateTime.UtcNow; state.Downloaded = DateTime.UtcNow; OnDownloadComplete(state); return; } #region Determine whether new version of file needs to be downloaded. // create storage directory if it doesn't exist. Device.File.EnsureDirectoryExistsForFile(state.CacheFileName); string tempCacheFile = state.CacheFileName + "_" + DateTime.Now.Ticks.ToString() + ".tmp"; bool itemInCache = Device.File.Exists(state.CacheFileName); Device.Log.Debug("CacheFetcher.ResponseCallback Uri: {0} IsExpired: {1} ", state.AbsoluteUri, state.CacheIndexItem.IsExpired); Device.Log.Debug("CacheFetcher.ResponseCallback Uri: {0} IsStale: {1} ", state.AbsoluteUri, state.CacheIndexItem.IsStale); Device.Log.Debug("CacheFetcher.ResponseCallback Uri: {0} Downloaded: {1} ", state.AbsoluteUri, state.CacheIndexItem.Downloaded); Device.Log.Debug("CacheFetcher.ResponseCallback Uri: {0} Header Last-Modified: {1} ", state.AbsoluteUri, state.Response.Headers["Last-Modified"].TryParseDateTimeUtc()); Device.Log.Debug("CacheFetcher.ResponseCallback Uri: {0} Header Etag: {1} NRL: Etag: {2}", state.AbsoluteUri, state.Response.Headers["Etag"], state.CacheIndexItem.ETag); if (!itemInCache) { downloadFile = true; } else if (state.CacheIndexItem.IsExpired || state.CacheIndexItem.IsStale) { // At this point, since the cached item is "old", assume the item needs to be downloaded... downloadFile = true; // ... but check headers for actual content expiration. If the file in cache hasn't changed on // the web server, we won't waste any time downloading the same thing we've already got. if (state.Response.Headers["Last-Modified"].TryParseDateTimeUtc() < state.CacheIndexItem.Downloaded && state.CacheIndexItem.Downloaded > DateTime.MinValue.ToUniversalTime()) { Device.Log.Debug("CacheFetcher.ResponseCallback.Download Check: Unchanged since Last-Downloaded value"); downloadFile = false; } else if (state.Response.Headers["ETag"] != null && state.CacheIndexItem.ETag != null && state.CacheIndexItem.ETag == state.Response.Headers["ETag"]) { Device.Log.Debug("CacheFetcher.ResponseCallback.Download Check: Etag matched"); downloadFile = false; } } #endregion #region Download file if necessary DateTime dtMetric; if (downloadFile) { //To-Do: refactor streamreaders/writers conversion to strings to a more efficient streaming mechanism Stream httpStream = null; try { httpStream = state.Response.GetResponseStream(); dtMetric = DateTime.UtcNow; using (var ms = new MemoryStream()) { httpStream.CopyTo(ms); state.ResponseBytes = ms.ToArray(); state.ResponseString = Encoding.UTF8.GetString(state.ResponseBytes, 0, state.ResponseBytes.Length); } // pre download work. // if the item is in cache and expired then delete it if (itemInCache) { Device.File.Delete(state.CacheFileName); itemInCache = false; } Device.File.Save(tempCacheFile, state.ResponseBytes); Device.Log.Metric(string.Format("CacheFetcher save stream to temp file: Name: {0} Time: {1:F0} milliseconds ", tempCacheFile, DateTime.UtcNow.Subtract(dtMetric).TotalMilliseconds)); // Set the CacheIndexItem properties upon successful download. state.CacheIndexItem.Downloaded = state.Downloaded; state.CacheIndexItem.Expiration = SetExpirationTime(state); state.CacheIndexItem.AttemptToRefresh = state.Response.Headers["iFactr-Attempt-Refresh"].TryParseDateTimeUtc(); state.CacheIndexItem.ETag = state.Response.Headers["ETag"]; state.CacheIndexItem.ContentType = state.Response.Headers["Content-Type"]; state.AttemptToRefresh = state.CacheIndexItem.AttemptToRefresh; state.Expiration = state.CacheIndexItem.Expiration; } finally { if (httpStream != null) { httpStream.Close(); httpStream.Dispose(); } if (state.Response != null) { state.Response.Dispose(); } state.Response.Close(); } // move downloaded tmp file to cache file location. if (Device.File.Length(tempCacheFile) == 0 && !(state.Response != null && state.Response.StatusCode == HttpStatusCode.OK)) { Device.File.Delete(tempCacheFile); string error = "File Download returned an empty file. Validate the URI is correct. uri=" + state.Request.RequestUri; //throw new NetworkResourceLibraryException( error ); Device.Log.Error(error); } else { dtMetric = DateTime.UtcNow; if (Device.File.Exists(state.CacheFileName)) { Device.File.Delete(state.CacheFileName); } Device.File.Move(tempCacheFile, state.CacheFileName); Device.Log.Metric(string.Format("CacheFetcher move temp file to cache file: Name: {0} Length: {1} Time: {2:0} milliseconds ", state.CacheFileName, Device.File.Length(state.CacheFileName), DateTime.UtcNow.Subtract(dtMetric).TotalMilliseconds)); } } else // downloadFile == false { Device.Log.Debug("CacheFetcher.ResponseCallback.Download Check: No need to download, file is current"); // update cache index item with current values state.CacheIndexItem.Downloaded = state.Response.Headers["Date"].TryParseDateTimeUtc(); state.CacheIndexItem.Expiration = SetExpirationTime(state); state.CacheIndexItem.AttemptToRefresh = state.Response.Headers["iFactr-Attempt-Refresh"].TryParseDateTimeUtc(); state.CacheIndexItem.ETag = state.Response.Headers["ETag"]; state.CacheIndexItem.ContentType = state.Response.Headers["Content-Type"]; } #endregion OnDownloadComplete(state); } catch (WebException ex) { string StatusDescription = string.Empty; ex.Data.Add("Uri", state.Request.RequestUri); ex.Data.Add("Verb", state.Request.Method); if (ex.Response != null) { state.StatusCode = ((HttpWebResponse)ex.Response).StatusCode; StatusDescription = ((HttpWebResponse)ex.Response).StatusDescription; } else if (ex.Message.ToLower().Contains("request was aborted")) { state.StatusCode = HttpStatusCode.RequestTimeout; StatusDescription = "Request cancelled by client because the server did not respond within timeout"; } else { state.StatusCode = (HttpStatusCode)(-2); } state.WebExceptionStatusCode = ex.Status; ex.Data.Add("StatusCode", state.StatusCode); ex.Data.Add("WebException.Status", ex.Status); ex.Data.Add("StatusDescription", StatusDescription); state.ErrorMessage = string.Format("Call to {0} had a WebException. {1} Status: {2} Desc: {3}", state.Request.RequestUri, ex.Message, ex.Status, StatusDescription); state.Exception = ex; state.Expiration = DateTime.UtcNow; state.AttemptToRefresh = DateTime.UtcNow; state.Downloaded = DateTime.UtcNow; OnError(state); } catch (Exception ex) { ex.Data.Add("Uri", state.Request.RequestUri); ex.Data.Add("Verb", state.Request.Method); state.ErrorMessage = string.Format("Call to {0} had an Exception. {1}", state.Request.RequestUri, ex.Message); state.Exception = ex; state.StatusCode = (HttpStatusCode)(-1); state.Expiration = DateTime.UtcNow; state.AttemptToRefresh = DateTime.UtcNow; state.Downloaded = DateTime.UtcNow; OnError(state); } finally { if (state.Response != null) { state.Response.Dispose(); state.Response.Close(); } state.Request = null; _allDone.Set(); } }
/// <summary> /// Performs an asynchronous fetch from the cache index. /// </summary> /// <param name="parameters">The parameters.</param> /// <param name="timeoutMilliseconds">The timeout milliseconds.</param> public void FetchAsynch(Object parameters, int timeoutMilliseconds) { var fetchParameters = (FetchParameters)parameters; var request = (HttpWebRequest)WebRequest.Create(fetchParameters.CacheIndex.GetAbsouteUri(fetchParameters.CacheIndexItem)); request.Method = "GET"; //request.Proxy = null; request.AutomaticDecompression = DecompressionMethods.GZip; if (fetchParameters.Headers != null && fetchParameters.Headers.Any()) { foreach (string key in fetchParameters.Headers.Keys) { if (key.ToLower() == "accept") { request.Accept = fetchParameters.Headers[key]; } else if (key.ToLower() == "content-type") { request.ContentType = fetchParameters.Headers[key]; } else if (key.ToLower() == "host") { //TODO: add the URL explaining PCL incompatibility Exception ex = new ArgumentException("Host header value cannot be set in PCL libraries."); Device.Log.Error(ex); throw ex; } else { request.Headers[key] = fetchParameters.Headers[key]; } } } RequestState state = new RequestState() { Request = request, CacheFileName = fetchParameters.CacheIndex.GetCachePath(fetchParameters.CacheIndexItem), CacheIndex = fetchParameters.CacheIndex, CacheIndexItem = fetchParameters.CacheIndexItem, RelativeUri = fetchParameters.CacheIndexItem.RelativeUri, BaseUri = fetchParameters.CacheIndex.BaseUri, Expiration = DateTime.UtcNow.Add(fetchParameters.DefaultExpiration), }; try { // Start the asynchronous request. IAsyncResult result = request.BeginGetResponse(ResponseCallback, state); if (!_allDone.WaitOne(timeoutMilliseconds)) { try { request.Abort(); } catch (Exception) { } // .Abort() always throws exception return; } } catch (Exception exc) { Device.Log.Error("CacheFetcher.FetchAsynch encountered exception", exc); _autoEvent.Set(); } }