/// <summary> /// Rewrites the path to point to the cached image. /// </summary> /// <param name="context"> /// The <see cref="HttpContext"/> encapsulating all information about the request. /// </param> public override void RewritePath(HttpContext context) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.cachedRewritePath); if (this.streamCachedImage) { // Write the blob storage directly to the stream request.Method = "GET"; using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { Stream cachedStream = response.GetResponseStream(); if (cachedStream != null) { HttpResponse contextResponse = context.Response; cachedStream.CopyTo(contextResponse.OutputStream); ImageProcessingModule.SetHeaders(context, this.mimeType, null, this.MaxDays, response.StatusCode); } } } else { // Redirect the request to the blob URL request.Method = "HEAD"; using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { HttpStatusCode responseCode = response.StatusCode; ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(responseCode == HttpStatusCode.NotFound ? this.CachedPath : this.cachedRewritePath, false); } } }
/// <summary> /// Rewrites the path to point to the cached image. /// </summary> /// <param name="context"> /// The <see cref="HttpContext"/> encapsulating all information about the request. /// </param> public override void RewritePath(HttpContext context) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.cachedRewritePath); if (this.streamCachedImage) { // Write the blob storage directly to the stream request.Method = "GET"; TryFiveTimes( () => { using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { Stream cachedStream = response.GetResponseStream(); if (cachedStream != null) { HttpResponse contextResponse = context.Response; cachedStream.CopyTo(contextResponse.OutputStream); ImageProcessingModule.SetHeaders(context, response.ContentType, null, this.BrowserMaxDays, response.StatusCode); } } }, () => { ImageProcessorBootstrapper.Instance.Logger.Log <AzureBlobCache>("Unable to stream cached path: " + this.cachedRewritePath); }); } else { // Redirect the request to the blob URL request.Method = "HEAD"; TryFiveTimes( () => { using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { HttpStatusCode responseCode = response.StatusCode; ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(responseCode == HttpStatusCode.NotFound ? this.CachedPath : this.cachedRewritePath, false); } }, () => { ImageProcessorBootstrapper.Instance.Logger.Log <AzureBlobCache>("Unable to rewrite cached path to: " + this.cachedRewritePath); }); } }
/// <summary> /// Rewrites the path to point to the cached image. /// </summary> /// <param name="context"> /// The <see cref="HttpContext"/> encapsulating all information about the request. /// </param> public override void RewritePath(HttpContext context) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.cachedRewritePath); if (this.streamCachedImage) { // Write the blob storage directly to the stream request.Method = "GET"; TryFiveTimes(() => { using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { Stream cachedStream = response.GetResponseStream(); if (cachedStream != null) { HttpResponse contextResponse = context.Response; cachedStream.CopyTo(contextResponse.OutputStream); // Mimetype can be null when returning from the cache. ImageProcessingModule.SetHeaders( context, string.IsNullOrWhiteSpace(this.mimeType) ? contextResponse.ContentType : this.mimeType, null, this.MaxDays, response.StatusCode); } } }); } else { // Redirect the request to the blob URL request.Method = "HEAD"; TryFiveTimes(() => { using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { HttpStatusCode responseCode = response.StatusCode; ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(responseCode == HttpStatusCode.NotFound ? this.CachedPath : this.cachedRewritePath, false); } }); } }
/// <summary> /// Rewrites the path to point to the cached image. /// </summary> /// <param name="context"> /// The <see cref="HttpContext"/> encapsulating all information about the request. /// </param> public override void RewritePath(HttpContext context) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.cachedRewritePath); if (this.streamCachedImage) { // Map headers to enable 304s to pass through if (context.Request.Headers["If-Modified-Since"] != null) { request.IfModifiedSince = DateTime.Parse(context.Request.Headers["If-Modified-Since"]); } string[] mapRequestHeaders = { "Cache-Control", "If-None-Match" }; foreach (string h in mapRequestHeaders) { if (context.Request.Headers[h] != null) { request.Headers.Add(h, context.Request.Headers[h]); } } // Write the blob storage directly to the stream request.Method = "GET"; TryFiveTimes( () => { HttpWebResponse response; try { response = (HttpWebResponse)request.GetResponse(); } catch (WebException ex) { // A 304 is not an error if (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotModified) { response = (HttpWebResponse)ex.Response; } else { throw; } } Stream cachedStream = response.GetResponseStream(); if (cachedStream != null) { HttpResponse contextResponse = context.Response; contextResponse.Headers.Add("ETag", response.Headers["ETag"]); contextResponse.Headers.Add("Last-Modified", response.Headers["Last-Modified"]); cachedStream.CopyTo(contextResponse.OutputStream); // Will be empty on 304s ImageProcessingModule.SetHeaders(context, response.StatusCode == HttpStatusCode.NotModified ? null : response.ContentType, null, this.BrowserMaxDays, response.StatusCode); } response.Dispose(); }, () => { ImageProcessorBootstrapper.Instance.Logger.Log <AzureBlobCache>("Unable to stream cached path: " + this.cachedRewritePath); }); } else { // Redirect the request to the blob URL request.Method = "HEAD"; request.Timeout = this.timeout; TryFiveTimes( () => { using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { HttpStatusCode responseCode = response.StatusCode; ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(responseCode == HttpStatusCode.NotFound ? this.CachedPath : this.cachedRewritePath, false); } }, () => { ImageProcessorBootstrapper.Instance.Logger.Log <AzureBlobCache>("Unable to rewrite cached path to: " + this.cachedRewritePath); }); } }
/// <summary> /// Rewrites the path to point to the cached image. /// </summary> /// <param name="context"> /// The <see cref="HttpContext"/> encapsulating all information about the request. /// </param> public override void RewritePath(HttpContext context) { if (streamCachedImage) { string blobPath = this.CachedPath.Substring(cloudCachedBlobContainer.Uri.ToString().Length + 1); CloudBlockBlob blockBlob = cloudCachedBlobContainer.GetBlockBlobReference(blobPath); if (blockBlob.Exists()) { using (MemoryStream cachedStream = MemoryStreamPool.Shared.GetStream()) { // Map headers to enable 304s to pass through TrySetIfModifiedSinceDate(context, out AccessCondition accessCondition); const string IfNoneMatch = "If-None-Match"; // TODO: Cache-Control? if (context.Request.Headers[IfNoneMatch] != null) { accessCondition.IfNoneMatchETag = context.Request.Headers[IfNoneMatch]; } bool is304 = false; try { blockBlob.DownloadToStream(cachedStream, accessCondition); cachedStream.Position = 0; } catch (StorageException ex) { // A 304 is not a true error, we still need to feed back. var webException = ex.InnerException as WebException; if (!(webException is null)) { if (webException.Response != null && (((HttpWebResponse)webException.Response).StatusCode == HttpStatusCode.NotModified)) { is304 = true; } else { ImageProcessorBootstrapper.Instance.Logger.Log <AzureBlobCache>("Unable to stream cached path: " + this.cachedRewritePath); return; } } } BlobProperties properties = blockBlob.Properties; HttpResponse contextResponse = context.Response; if (!string.IsNullOrWhiteSpace(properties.ETag)) { contextResponse.Headers.Add("ETag", properties.ETag); } if (properties.LastModified.HasValue) { contextResponse.Headers.Add("Last-Modified", properties.LastModified.Value.UtcDateTime.ToString("R")); } cachedStream.CopyTo(contextResponse.OutputStream); // Will be empty on 304s if (contextResponse.OutputStream.CanSeek) { contextResponse.OutputStream.Position = 0; } ImageProcessingModule.SetHeaders( context, !is304 ? properties.ContentType : null, null, this.BrowserMaxDays, !is304 ? HttpStatusCode.OK : HttpStatusCode.NotModified); } } } else { // Prevent redundant metadata request if paths match. if (this.CachedPath == this.cachedRewritePath) { ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(this.CachedPath, false); return; } var request = (HttpWebRequest)WebRequest.Create(this.cachedRewritePath); // Redirect the request to the blob URL request.Method = "HEAD"; request.Timeout = timeout; HttpWebResponse response; try { response = (HttpWebResponse)request.GetResponse(); response.Dispose(); ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(this.cachedRewritePath, false); } catch (WebException ex) { response = (HttpWebResponse)ex.Response; if (response != null) { HttpStatusCode responseCode = response.StatusCode; // A 304 is not an error // It appears that some CDN's on Azure (Akamai) do not work properly when making head requests. // They will return a response url and other headers but a 500 status code. if (responseCode == HttpStatusCode.NotModified || response.ResponseUri.AbsoluteUri.Equals(this.cachedRewritePath, StringComparison.OrdinalIgnoreCase)) { response.Dispose(); ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(this.cachedRewritePath, false); } else { response.Dispose(); ImageProcessorBootstrapper.Instance.Logger.Log <AzureBlobCache>("Unable to rewrite cached path to: " + this.cachedRewritePath); } } else { // It's a 404, we should redirect to the cached path we have just saved to. ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(this.CachedPath, false); } } } }
/// <summary> /// Rewrites the path to point to the cached image. /// </summary> /// <param name="context"> /// The <see cref="HttpContext"/> encapsulating all information about the request. /// </param> public override void RewritePath(HttpContext context) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.cachedRewritePath); if (this.streamCachedImage) { // Map headers to enable 304s to pass through if (context.Request.Headers["If-Modified-Since"] != null) { TrySetIfModifiedSinceDate(context, request); } string[] mapRequestHeaders = { "Cache-Control", "If-None-Match" }; foreach (string h in mapRequestHeaders) { if (context.Request.Headers[h] != null) { request.Headers.Add(h, context.Request.Headers[h]); } } // Write the blob storage directly to the stream request.Method = "GET"; request.Timeout = this.timeout; HttpWebResponse response = null; try { response = (HttpWebResponse)request.GetResponse(); } catch (WebException ex) { // A 304 is not an error if (ex.Response != null && ((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotModified) { response = (HttpWebResponse)ex.Response; } else { response?.Dispose(); ImageProcessorBootstrapper.Instance.Logger.Log <AzureBlobCache>("Unable to stream cached path: " + this.cachedRewritePath); return; } } Stream cachedStream = response.GetResponseStream(); if (cachedStream != null) { HttpResponse contextResponse = context.Response; // If streaming but not using a CDN the headers will be null. // See https://github.com/JimBobSquarePants/ImageProcessor/pull/466 string etagHeader = response.Headers["ETag"]; if (!string.IsNullOrWhiteSpace(etagHeader)) { contextResponse.Headers.Add("ETag", etagHeader); } string lastModifiedHeader = response.Headers["Last-Modified"]; if (!string.IsNullOrWhiteSpace(lastModifiedHeader)) { contextResponse.Headers.Add("Last-Modified", lastModifiedHeader); } cachedStream.CopyTo(contextResponse.OutputStream); // Will be empty on 304s ImageProcessingModule.SetHeaders( context, response.StatusCode == HttpStatusCode.NotModified ? null : response.ContentType, null, this.BrowserMaxDays, response.StatusCode); } cachedStream?.Dispose(); response.Dispose(); } else { // Redirect the request to the blob URL request.Method = "HEAD"; request.Timeout = this.timeout; HttpWebResponse response; try { response = (HttpWebResponse)request.GetResponse(); response.Dispose(); ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(this.cachedRewritePath, false); } catch (WebException ex) { response = (HttpWebResponse)ex.Response; if (response != null) { HttpStatusCode responseCode = response.StatusCode; // A 304 is not an error if (responseCode == HttpStatusCode.NotModified) { response.Dispose(); ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(this.cachedRewritePath, false); } else { response.Dispose(); ImageProcessorBootstrapper.Instance.Logger.Log <AzureBlobCache>("Unable to rewrite cached path to: " + this.cachedRewritePath); } } else { // It's a 404, we should redirect to the cached path we have just saved to. ImageProcessingModule.AddCorsRequestHeaders(context); context.Response.Redirect(this.CachedPath, false); } } } }
/// <summary> /// Rewrites the path to point to the cached image. /// </summary> /// <param name="context"> /// The <see cref="HttpContext"/> encapsulating all information about the request. /// </param> public override void RewritePath(HttpContext context) { var request = (HttpWebRequest)WebRequest.Create(this.cachedRewritePath); if (streamCachedImage) { // Map headers to enable 304s to pass through if (context.Request.Headers["If-Modified-Since"] != null) { TrySetIfModifiedSinceDate(context, request); } string[] mapRequestHeaders = { "Cache-Control", "If-None-Match" }; foreach (string h in mapRequestHeaders) { if (context.Request.Headers[h] != null) { request.Headers.Add(h, context.Request.Headers[h]); } } // Write the blob storage directly to the stream request.Method = "GET"; request.Timeout = timeout; HttpWebResponse response = null; try { response = (HttpWebResponse)request.GetResponse(); } catch (WebException ex) { // A 304 is not an error // It appears that some CDN's on Azure (Akamai) do not work properly when making head requests. // They will return a response url and other headers but a 500 status code. if (ex.Response != null && (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotModified || ex.Response.ResponseUri.AbsoluteUri.Equals(this.cachedRewritePath, StringComparison.OrdinalIgnoreCase))) { response = (HttpWebResponse)ex.Response; } else { response?.Dispose(); ImageProcessorBootstrapper.Instance.Logger.Log <AmazonS3Cache>("Unable to stream cached path: " + this.cachedRewritePath); return; } } Stream cachedStream = response.GetResponseStream(); if (cachedStream != null) { HttpResponse contextResponse = context.Response; // If streaming but not using a CDN the headers will be null. // See https://github.com/JimBobSquarePants/ImageProcessor/pull/466 string etagHeader = response.Headers["ETag"]; if (!string.IsNullOrWhiteSpace(etagHeader)) { contextResponse.Headers.Add("ETag", etagHeader); } string lastModifiedHeader = response.Headers["Last-Modified"]; if (!string.IsNullOrWhiteSpace(lastModifiedHeader)) { contextResponse.Headers.Add("Last-Modified", lastModifiedHeader); } cachedStream.CopyTo(contextResponse.OutputStream); // Will be empty on 304s ImageProcessingModule.SetHeaders( context, response.StatusCode == HttpStatusCode.NotModified ? null : response.ContentType, null, this.BrowserMaxDays, response.StatusCode); } cachedStream?.Dispose(); response.Dispose(); } else { // Prevent redundant metadata request if paths match. if (this.CachedPath == this.cachedRewritePath) { ImageProcessingModule.SetHeaders(context, null, null, this.BrowserMaxDays); context.Response.Redirect(this.CachedPath, false); return; } // Redirect the request to the blob URL request.Method = "HEAD"; request.Timeout = timeout; HttpWebResponse response = null; try { response = (HttpWebResponse)request.GetResponse(); response.Dispose(); ImageProcessingModule.SetHeaders(context, null, null, this.BrowserMaxDays); context.Response.Redirect(this.cachedRewritePath, false); } catch (WebException ex) { response = (HttpWebResponse)ex.Response; if (response != null) { HttpStatusCode responseCode = response.StatusCode; // A 304 is not an error // It appears that some CDN's on Azure (Akamai) do not work properly when making head requests. // They will return a response url and other headers but a 500 status code. if (responseCode == HttpStatusCode.NotModified || response.ResponseUri.AbsoluteUri.Equals(this.cachedRewritePath, StringComparison.OrdinalIgnoreCase)) { response.Dispose(); ImageProcessingModule.SetHeaders(context, null, null, this.BrowserMaxDays); context.Response.Redirect(this.cachedRewritePath, false); } else { response.Dispose(); ImageProcessorBootstrapper.Instance.Logger.Log <AmazonS3Cache>("Unable to rewrite cached path to: " + this.cachedRewritePath); } } else { // It's a 404, we should redirect to the cached path we have just saved to. ImageProcessingModule.SetHeaders(context, null, null, this.BrowserMaxDays); context.Response.Redirect(this.CachedPath, false); } } } }
/// <summary> /// Rewrites the path to point to the cached image. /// </summary> /// <param name="context"> /// The <see cref="HttpContext"/> encapsulating all information about the request. /// </param> public override void RewritePath(HttpContext context) { if (streamCachedImage) { string blobPath = this.CachedPath.Substring(cloudCachedBlobContainer.Uri.ToString().Length + 1); CloudBlockBlob blockBlob = cloudCachedBlobContainer.GetBlockBlobReference(blobPath); if (blockBlob.Exists()) { using (MemoryStream cachedStream = MemoryStreamPool.Shared.GetStream()) { // Map headers to enable 304s to pass through TrySetIfModifiedSinceDate(context, out AccessCondition accessCondition); const string IfNoneMatch = "If-None-Match"; // TODO: Cache-Control? if (context.Request.Headers[IfNoneMatch] != null) { accessCondition.IfNoneMatchETag = context.Request.Headers[IfNoneMatch]; } bool is304 = false; try { blockBlob.DownloadToStream(cachedStream, accessCondition); cachedStream.Position = 0; } catch (StorageException ex) { // A 304 is not a true error, we still need to feed back. if (ex.RequestInformation?.HttpStatusCode == (int)HttpStatusCode.NotModified) { is304 = true; } else if (ex.InnerException is WebException webException && webException.Response is HttpWebResponse httpWebResponse && httpWebResponse.StatusCode == HttpStatusCode.NotModified) { is304 = true; } else { var sb = new StringBuilder(); sb.AppendFormat("Unable to stream cached path: {0}", this.cachedRewritePath); sb.AppendLine(); sb.AppendFormat("Exception: {0}", ex.ToString()); ImageProcessorBootstrapper.Instance.Logger.Log <AzureBlobCache>(sb.ToString()); return; } } BlobProperties properties = blockBlob.Properties; HttpResponse contextResponse = context.Response; if (!string.IsNullOrWhiteSpace(properties.ETag)) { contextResponse.Headers.Add("ETag", properties.ETag); } if (properties.LastModified.HasValue) { contextResponse.Headers.Add("Last-Modified", properties.LastModified.Value.UtcDateTime.ToString("R")); } cachedStream.CopyTo(contextResponse.OutputStream); // Will be empty on 304s if (contextResponse.OutputStream.CanSeek) { contextResponse.OutputStream.Position = 0; } ImageProcessingModule.SetHeaders( context, !is304 ? properties.ContentType : null, null, this.BrowserMaxDays, !is304 ? HttpStatusCode.OK : HttpStatusCode.NotModified); }