/// <summary> /// Handles the request for a cached tile. /// </summary> /// <param name="options"></param> /// <param name="pathParts">The path parts from the request.</param> /// <param name="fileName">The filename from the request.</param> /// <param name="clientEndpoint"></param> /// <param name="headers">The request headers.</param> /// <returns></returns> public WebRequestOutcome ProcessRequest(Options options, IEnumerable <string> pathParts, string fileName, IPAddress clientEndpoint, RequestHeadersDictionary headers) { var result = new WebRequestOutcome(); var displayOutcome = new RequestOutcome() { ID = Interlocked.Increment(ref _NextID), ReceivedUtc = DateTime.UtcNow, }; RecentRequestOutcomes.Add(displayOutcome); try { var urlValues = _TileServerUrlTranslator.ExtractEncodedValuesFromUrlParts(pathParts, fileName); displayOutcome.CopyUrlValues(urlValues); if (urlValues != null) { var cachedContent = TileCache.GetCachedTile(urlValues); if (cachedContent != null) { result.ImageBytes = cachedContent.Content; displayOutcome.ServedFromCache = true; Interlocked.Increment(ref _CountServedFromCache); Plugin.Singleton?.RefreshStatusDescription(); } else { if (options.IsOfflineModeEnabled) { displayOutcome.MissingFromCache = true; result.StatusCode = HttpStatusCode.NotFound; } else { FetchTileServerImage(options, urlValues, clientEndpoint, headers, result, displayOutcome); if (result.ImageBytes?.Length > 0) { TileCache.SaveTile(urlValues, result.ImageBytes); } } } if (result.ImageBytes != null) { result.StatusCode = HttpStatusCode.OK; result.ImageExtension = urlValues.TileImageExtension; } } } catch (ThreadAbortException) { displayOutcome.Abandoned = true; ; // .NET will rethrow this automatically } catch { displayOutcome.ExceptionEncountered = true; throw; } finally { try { displayOutcome.CompletedUtc = DateTime.UtcNow; } catch { ; // Don't want to confuse the issue with exceptions when trying to update the outcome display } } return(result); }
/// <summary> /// Downloads the tile image using the URL values passed across. /// </summary> /// <param name="options"></param> /// <param name="urlValues"></param> /// <param name="clientEndpoint"></param> /// <param name="headers"></param> /// <param name="outcome"></param> /// <param name="displayOutcome"></param> /// <returns></returns> private void FetchTileServerImage(Options options, FakeUrlEncodedValues urlValues, IPAddress clientEndpoint, HeadersDictionary headers, WebRequestOutcome outcome, RequestOutcome displayOutcome) { var tileServerSettings = Plugin.TileServerSettingsManagerWrapper.GetRealTileServerSettings( urlValues.MapProvider, urlValues.Name ); if (tileServerSettings != null) { var tileImageUrl = _TileServerUrlTranslator.ExpandUrlParameters( tileServerSettings.Url, urlValues, tileServerSettings.Subdomains ); var request = (HttpWebRequest)HttpWebRequest.Create(tileImageUrl); request.Method = "GET"; request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; foreach (var headerKey in headers.Keys) { var value = headers[headerKey]; switch (headerKey.ToLower()) { case "accept": request.Accept = value; break; case "accept-encoding": break; case "connection": break; case "content-length": break; case "content-type": request.ContentType = value; break; case "date": break; case "expect": request.Expect = value; break; case "host": break; case "if-modified-since": break; case "proxy-connection": break; case "range": break; case "referer": request.Referer = value; break; case "transfer-encoding": request.TransferEncoding = value; break; case "user-agent": request.UserAgent = value; break; default: request.Headers[headerKey] = value; break; } } request.AuthenticationLevel = AuthenticationLevel.None; request.Credentials = null; request.UseDefaultCredentials = false; request.KeepAlive = true; request.Timeout = 1000 * options.TileServerTimeoutSeconds; lock (_SyncLock) { if (!_TileServerNameToCookieCollection.TryGetValue(urlValues.Name, out var cookies)) { cookies = new CookieCollection(); _TileServerNameToCookieCollection.Add(urlValues.Name, cookies); } foreach (Cookie cookie in cookies) { request.CookieContainer.Add(cookie); } } var forwarded = request.Headers["Forwarded"] ?? ""; if (forwarded.Length > 0) { forwarded = $"{forwarded}, "; } forwarded = $"{forwarded}for={clientEndpoint}"; request.Headers["Forwarded"] = forwarded; try { using (var response = (HttpWebResponse)request.GetResponse()) { using (var memoryStream = new MemoryStream()) { using (var responseStream = response.GetResponseStream()) { var buffer = new byte[1024]; var bytesRead = 0; do { bytesRead = responseStream.Read(buffer, 0, buffer.Length); if (bytesRead > 0) { memoryStream.Write(buffer, 0, bytesRead); } } while(bytesRead > 0); } outcome.ImageBytes = memoryStream.ToArray(); displayOutcome.FetchedFromTileServer = true; displayOutcome.TileServerResponseStatusCode = (int)response.StatusCode; } var cookies = new CookieCollection(); foreach (Cookie cookie in response.Cookies) { cookies.Add(cookie); } lock (_SyncLock) { _TileServerNameToCookieCollection[urlValues.Name] = cookies; } } } catch (WebException ex) { if (ex.Status == WebExceptionStatus.Timeout) { displayOutcome.TimedOut = true; } else { displayOutcome.WebExceptionErrorMessage = ex.Message; outcome.StatusCode = HttpStatusCode.InternalServerError; if (ex.Response is HttpWebResponse httpResponse) { if (httpResponse.StatusCode != HttpStatusCode.OK) { outcome.StatusCode = httpResponse.StatusCode; displayOutcome.TileServerResponseStatusCode = (int)httpResponse.StatusCode; } } } } } }