Dianoga is an automatic image optimizer for the Sitecore media library. It reduces the size of your images served from Sitecore by 8-70% automatically. Dianoga is great for situations where content editors may not be image editing experts.
How Dianoga works?
By default, Dianoga runs asynchronously after the image is saved into the media cache. This means it has practically no effect on the site's frontend performance (though it does use some CPU time in the background).
Dianoga updates the cache after the first image request. This means the first hit to images will serve the unoptimized version of image. Subsequent requests will receive the optimized version from cache.
Where is the problem?
Dianoga's behavior of serving unoptimized images on first request creates problem while using CDNs. CDNs cache the responses of media requests. Since first hit to images serve unoptimized image, CDN will cache the unoptimized version. CDN will serve this unoptimized version of image in its cache to all subsequent requests to image.
How to mitigate this?
You can enable Dianoga.Strategy.GetMediaStreamSync.config.disabled and disable Dianoga.Strategy.MediaCacheAsync.config. This will cause optimization to occur synchronously as the media is first requested, which is appropriate if the media is being sent to a CDN. However, this will cause a delay for the first hit user before they start seeing images and can impact the page metrics.
What we did?
We decided to keep using async strategy but set the max-age header to 2 minutes in case an unoptimized version of image is being returned in response. This ensures that on first request for an image, user doesn't experience a delay and also, that once the optimized version of image is available in media cache folder, optimized version of image is served via CDN.
How To Do It?
To achieve this, you have to override DoProcessRequest method of MediaRequstHandler as in code snippet below -
public class MediaRequestHandler : Sitecore.Resources.Media.MediaRequestHandler
{
private readonly string[] disabledRangeRetrievalMimeTypes;
public MediaRequestHandler()
{
this.disabledRangeRetrievalMimeTypes = ((IEnumerable<string>)Settings.GetSetting("Media.DisableRangeRetrievalRequestMimeTypes").Split(',')).Select<string, string>((Func<string, string>)(x => x.Trim())).Where<string>((Func<string, bool>)(x => !string.IsNullOrEmpty(x))).ToArray<string>();
}
protected override bool DoProcessRequest(
HttpContext context,
MediaRequest request,
Sitecore.Resources.Media.Media media)
{
Sitecore.Diagnostics.Assert.ArgumentNotNull((object)context, nameof(context));
Sitecore.Diagnostics.Assert.ArgumentNotNull((object)request, nameof(request));
Sitecore.Diagnostics.Assert.ArgumentNotNull((object)media, nameof(media));
if (this.Modified(context, media, request.Options) == Tristate.False)
{
Event.RaiseEvent("media:request", (object)request);
this.SendMediaHeaders(media, context);
context.Response.StatusCode = 304;
return true;
}
this.ProcessImageDimensions(request, media);
MediaStream stream = media.GetStream(request.Options);
if (stream == null)
return false;
Event.RaiseEvent("media:request", (object)request);
bool flag = this.IsRangeRetrievalAllowedType(media);
if (Settings.Media.EnableRangeRetrievalRequest && Settings.Media.CachingEnabled && flag)
{
using (stream)
{
this.SendTempOrCorrectMediaHeaders(stream, media, context);
RangeRetrievalRequest retrievalRequest = RangeRetrievalRequest.BuildRequest(context, media);
if (retrievalRequest.Precondition is Sitecore.Resources.Media.Streaming.Preconditions.IfUnmodifiedSincePrecondition)
{
Sitecore.Support.Resources.Media.Streaming.Preconditions.IfUnmodifiedSincePrecondition sincePrecondition = Sitecore.Support.Resources.Media.Streaming.Preconditions.IfUnmodifiedSincePrecondition.TryBuild(retrievalRequest, context);
if (sincePrecondition != null)
retrievalRequest.Precondition = (IPrecondition)sincePrecondition;
}
new RangeRetrievalResponse(retrievalRequest, stream).ExecuteRequest(context);
return true;
}
}
else
{
this.SendTempOrCorrectMediaHeaders(stream, media, context);
this.SendStreamHeaders(stream, context);
using (stream)
{
context.Response.AddHeader("Content-Length", stream.Stream.Length.ToString());
WebUtil.TransmitStream(stream.Stream, context.Response, Settings.Media.StreamBufferSize);
}
return true;
}
}
private bool IsRangeRetrievalAllowedType(Sitecore.Resources.Media.Media media)
{
return !((IEnumerable<string>)this.disabledRangeRetrievalMimeTypes).Contains<string>(media.MediaData.MediaItem.InnerItem["Mime Type"]);
}
private void ProcessImageDimensions(MediaRequest request, Sitecore.Resources.Media.Media media)
{
Sitecore.Diagnostics.Assert.ArgumentNotNull((object)request, nameof(request));
Sitecore.Diagnostics.Assert.ArgumentNotNull((object)media, nameof(media));
Item innerItem = media.MediaData.MediaItem.InnerItem;
int result1;
int.TryParse(innerItem["Height"], out result1);
int result2;
int.TryParse(innerItem["Width"], out result2);
bool flag = false;
int maxHeight = Settings.Media.Resizing.MaxHeight;
if (maxHeight != 0 && request.Options.Height > Math.Max(maxHeight, result1))
{
flag = true;
request.Options.Height = Math.Max(maxHeight, result1);
}
int maxWidth = Settings.Media.Resizing.MaxWidth;
if (maxWidth != 0 && request.Options.Width > Math.Max(maxWidth, result2))
{
flag = true;
request.Options.Width = Math.Max(maxWidth, result2);
}
if (!flag)
return;
Log.Warn(string.Format("Requested image exceeds allowed size limits. Requested URL:{0}", (object)request.InnerRequest.RawUrl), (object)this);
}
protected virtual void SendTempOrCorrectMediaHeaders(MediaStream mediaStream, Sitecore.Resources.Media.Media media, HttpContext context)
{
// if it's memory stream, that means the Dianoga has not compressed image yet
if (mediaStream.Stream is MemoryStream)
{
this.SendTemporaryMediaHeaders(media, (HttpContextBase)new HttpContextWrapper(context));
}
else
{
this.SendMediaHeaders(media, context);
}
}
protected virtual void SendTemporaryMediaHeaders(Sitecore.Resources.Media.Media media, HttpContextBase context)
{
TimeSpan delta = TimeSpan.FromMinutes(2);
DateTime date = media.MediaData.Updated;
if (date > DateTime.UtcNow)
date = DateTime.UtcNow;
HttpCachePolicyBase cache = context.Response.Cache;
cache.SetLastModified(date - delta);
cache.SetETag(media.MediaData.MediaId + "_temp");
cache.SetCacheability(Settings.MediaResponse.Cacheability);
if (delta > TimeSpan.Zero)
{
if (delta > TimeSpan.FromDays(365.0))
delta = TimeSpan.FromDays(365.0);
cache.SetMaxAge(delta);
cache.SetExpires(DateTime.UtcNow + delta);
}
Tristate slidingExpiration = Settings.MediaResponse.SlidingExpiration;
if (slidingExpiration != Tristate.Undefined)
cache.SetSlidingExpiration(slidingExpiration == Tristate.True);
string cacheExtensions = Settings.MediaResponse.CacheExtensions;
if (cacheExtensions.Length > 0)
cache.AppendCacheExtension(cacheExtensions);
string varyHeader = this.GetVaryHeader(media, context.ApplicationInstance.Context);
if (string.IsNullOrEmpty(varyHeader))
return;
context.Response.AppendHeader("vary", varyHeader);
}
}
Like always, like and share :) Thanks for your time!
Comments
Post a Comment