Showing posts with label processor. Show all posts
Showing posts with label processor. Show all posts

IRequiresSessionState slowing down Sitecore Media Requests


For last few weeks we faced a performance issue on our Sitecore production servers. Page response time was substantially increased by 50% and getting slowness in full page loading. While investigation we found that this was just because of our customized MediaRequestHandler.

Our requirement was to display some Disclaimer Page when a user request for some Sitecore media files (not for any image), after accepting disclaimer we can continue to download requested media. All other media items should be served directly.

We used session for managing this disclaimer. For using session in the MediaRequestHandler, we implemented iRequiresSessionState interface in it. See below code snippet
    class CustomRequestHandler : Sitecore.Resources.Media.MediaRequestHandler, IRequiresSessionState
    {
        protected override bool DoProcessRequest(HttpContext context)
        {
            if (context.Request.Url.ToString().ToLower().Contains("/~/media/files/"))
            {
                // Check session, if session does not available, then send it to disclaimer
                // Once user accepts Disclaimer, generate a session with user details.
            }
            else
            {
                // Proceed all image requests
                base.DoProcessRequest(...);
            }
        }
    }

So, what the reason for the slower page rendering? Yes, that's IRequiresSessionState which was a halts in performance.

Can IRequiresSessionState really make my page slower?

Below image shows how ASP.NET Session state works:


Now we can definitely say YES, it can slow down. State access requires that the aspx pages or handler, run sequentially. Each page/handler may read from and write to the session state. To handle this without creating any errors in the session state, .NET will only run one at a time using locking mechanism, so no handlers will run when the aspx page itself is running.

Tactics:
  • Can we customize handler not to use session state?
    - No, this will disable session so we cannot achieve disclaimer functionality
  • We can decide the request needs session or not and use session dynamically.
    - Is it possible? Yes, it is, using HttpModules

Solved by Dynamically deciding Session State Behavior programmatically

Changing ASP.NET session state behavior dynamically is possible only using HttpModules or Global.asax. It is not possible in HttpHandlers or other code behind. This solution is possible only in .NET 4.0 and later. Yes, we can define it at Page Directives but that's static.

As per our requirement, we have to show Dislaimer for selected Sitecore Media Files only (Not for any Media Images), we thought to create a HttpModule, which will check whether the request is for media files or images. If it is a file, then enable session state and if it is an image, disable session state.

Step-1:

We removed IRequiresSessionState from our CustomMediaRequestHandler, so it will not use Session State directly.

Step-2:

Created HttpModule, which will decide whether to use Session State or not.
    public class CustomMediaRequestSessionModule : IHttpModule
    {
        public void Init(HttpApplication application)
        {
            application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
        }

        private void Application_BeginRequest(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            var CurrentContext = HttpContext.Current;

            if (CurrentContext.Request.Url.ToString().ToLower().Contains("/~/media/files/"))
            {
                // Files need Disclaimer functionality, so session needed
                CurrentContext.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
            }
            else if (CurrentContext.Request.Url.ToString().ToLower().Contains("/~/media/images/"))
            {
                // Images do not need Disclaimer, so no session needed
                CurrentContext.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Disabled);
            }
        }
        
        public void Dispose()
        {
        }
    }

Step-3:

Now, in web.config, register this module under system.webServer/modules section:
 <add type="SitecoreTactics.Resources.Media.CustomMediaRequestSessionModule, SitecoreTactics" name="CustomMediaRequestSessionModule" /> 

And now we checked Page Response time, now feeling heavenly. All problem gone, Page Response Time now dropped down to earlier stage.

Good reads for improving Sitecore Media Item Performance

Render Sitecore Content Item with name of language

One issue reported in Sitecore SDN forum regarding rendering an content item under Home which has name of a language. I mean, there is an item /sitecore/content/Home/uk. Now, Sitecore consider /uk/ as an language, so it will render the Home item with uk (Ukranian) language (even if uk language is not added to Sitecore Languages). Its expected behavior is to render /Home/uk item in default language itself.

See what's the issue

We have an item named uk under Home.




Now, see what happens when we access http://patelyogesh.in/uk

Sitecore content item with language name

Why this happens

StripLanguage processor in preprocessRequest pipeline checks the FilePath and extracts expected language name from it. If the FilePath contains a valid language, it will assign it to Context.

So, in our URL: http://patelyogesh.in/uk, it will get FilePath as uk. So, assumes the uk item as uk language and set it to Context Language and Home is set to Context item.

How to solve this

One solution came in my mind to write a custom processor in httpRequestBegin, which will over-write Context Language and Context Item both, using it it is possible to solve. But, as John West suggested in forum, this might be possible using StripLanguage. So, thought to implement this and yes, it solves and is easy too. We can solve this issue overriding StripLanguage processor in preprocessRequest.
<!--<processor type="Sitecore.Pipelines.PreprocessRequest.StripLanguage, Sitecore.Kernel"/>-->
<processor type="SitecoreTactics.StripLanguage, SitecoreTactics">
  <ignoreLanguages hint=”list”>
    <language>uk</language>
    <language>en-gb</language>
    …
  </ignoreLanguages>
</processor>
To override the StripLanguage, we need to modify only one function named ExtractLanguage, which extracts the language. Now, as per our requirement, as need to ignore uk and en-gb languages. So, we will not consider language when it is uk or en-gb.

Below code should be implemented for this, but it does not contain code to read above ignoreLanguages setting. Here I have hard-coded these languages for better understanding:
public class StripLanguage : PreprocessRequestProcessor
{

    // Other methods
    ......

    private static Language ExtractLanguage(HttpRequest request)
    {
        Language language;
        Assert.ArgumentNotNull(request, "request");
        string str = WebUtil.ExtractLanguageName(request.FilePath);

        // Our code starts here
        // If the found language is uk, then set it to Empty, 
        // so sitecore will consider this as no language
        if (str == "uk" || str == "en-gb")
            str = string.Empty;
        // Our code ends here

        if (string.IsNullOrEmpty(str))
        {
            return null;
        }
        if (!Language.TryParse(str, out language))
        {
            return null;
        }
        return language;
    }

    ....
    // Other methods
}
Now, see below screen, referring /Home/uk page now refers to uk page itself with en language instead of Home page with uk language.



Finally, StripLanguage worked, we might need some extra code when implementing Multisite environment!!

Sitecore Custom HTTP Handlers

We recently had a requirement that we need thumbnail for PDFs stored in Sitecore, so whenever we need to find PDFs or other media files, we can find out them by its thumbnail. In starting we thought to create a thumbnail for each media file and then store it to another location in Sitecore tree itself while uploading media. It was a good idea, but the problem in this approach would come when a user updates the media? We need to create thumbnail again... Same thing when user deletes/moves/publishes, etc. it needed to generate thumbnail all the time.

Finally we came to a perfect solution of using Custom HTTP Handler, using which we can fulfill this requirement easily.

Overview of Sitecore Custom Handlers?

Search for in your web.config. These are the custom handlers called by Sitecore to serve requests of media, xaml, icon, feed, etc. See below block in web.config:
    
      <handler handler="sitecore_media.ashx" trigger="~/media/" />
      <handler handler="sitecore_api.ashx" trigger="~/api/" />
      <handler handler="sitecore_xaml.ashx" trigger="~/xaml/" />
      <handler handler="sitecore_icon.ashx" trigger="~/icon/" />
      <handler handler="sitecore_feed.ashx" trigger="~/feed/" />
    
Sitecore.Pipelines.HttpRequest.CustomHandlers processor from httpBeginRequest dispatches requests to specialized handlers that are required. These handlers are defined in web.config under configuration/sitecore/customHandlers.

If the current request ends with the a handler’s file (e.g., sitecore_media.ashx), then Sitecore aborts the current pipeline, since this request is specifically targeting a handler directly, which are defined in system.webServer/handlers section as below, which shows that the media request will be served through Sitecore.Resources.Media.MediaRequestHandler. See below block of web.config:
   <handlers>
      <add name="Sitecore.MediaRequestHandler" path="sitecore_media.ashx"  type="Sitecore.Resources.Media.MediaRequestHandler, Sitecore.Kernel" verb="*" />
   </handlers>

Create own Sitecore Custom Handlers to generate thumbnails of media items

Now, we created a new custom handler for creating thumbnail and registered in handlers as below:
    
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>

    <!-- Define Custom Handler -->
    <customHandlers>
      <handler trigger="~/mediathumb/" handler="sitecore_media_thumb.ashx"  />
    </customHandlers>

    <!-- Define Media Prefix -->
    <mediaLibrary>
      <mediaPrefixes>
        <prefix value="~/mediathumb" />
      </mediaPrefixes>
    </mediaLibrary>
  </sitecore>


  <!-- Define Web Handler -->
  <system.webServer>
 <handlers>
     <add verb="*" path="sitecore_media_thumb.ashx" type="SitecoreTactics.ThumbnailManager.PDFThumbnailRequestHandler, SitecoreTactics.ThumbnailManager" name="SitecoreTactics.PDFThumbnailRequestHandler"/>
 </handlers>
  </system.webServer>
</configuration>    
This class will create a thumbnail of the request PDF on-the-fly using GhostScript. We will save this thumbnail as Media Cache, so on the next request, the thumbnail will not be created, but will be served from Media Cache itself, same as Sitecore serves media items.

In this case suppose, the PDF we uploaded has URL:
http://mydomain.com/~/media/files/Docs/mypdf.pdf.
Now, we can get its thumbnail as JPG file using URL, which will be served by our new handler because it contains /~/mediathumb trigger.
http://mydomain.com/~/mediathumb/files/Docs/mypdf.jpg.

Finally, we do not need to take care if media item is moved, deleted, published or updated. I will attach its source code very soon.... :)

What else we can do using Custom Handlers?

- In above approach, we can also generate thumbnail of different size, dimensions passed through querystring, same way Sitecore provides it for images. Read how we can Resize Sitecore Media Images on the fly.
- Even Custom Handlers can be useful to serve different kind of resources like creating sitemap (/~/sitemap/), RSS for page or its children (/~/rss/), stylesheets (/~/css/), javascripts (/~/js/), etc.

Related Posts:
- PDF Thumbnail Handler
- Show PDF Thumbnail Icons in Content Editor

Sitecore Item alias - Alternate URL

An Alias is an alternate path for an item when browsing the web site. In other terms we can have URL alias in Sitecore.

Suppose, you have an item's URL as:
http://mysite.com/news/2013/cricket/

Now, you want the same page to be opened using some alternate URLs like:
http://mysite.com/cricket-news/
OR
http://mysite.com/news-cricket/

This is quite possible using AliasResolver in Sitecore.

How to configure

We will create two different aliases cricket-news and news-cricket under /sitecore/system/aliases/. Both items will have same configuration, means both will point to content item /sitecore/content/Home/News/2013/Cricket/ So, AliasResolver read these settings and work accordingly.

See below image how alias can be set for this item.

How can we know URL of alternate URL or Alias?

To see if an alias exists, use the database.Aliases.Exists() method. This will check if both the alias exists and the target item exists sd below:
    // Code from Sitecore.Pipelines.HttpRequest.AliasResolver class
    Database database = Context.Database;
    if (database == null)
    {
        Tracer.Warning("There is no context database in AliasResover.");
    }
    else
    {
        if (database.Aliases.Exists(args.LocalPath) && !this.ProcessItem(args))
        {
            this.ProcessExternalUrl(args);
        }
        Profiler.EndOperation();
    }

    private void ProcessExternalUrl(HttpRequestArgs args)
    {
        string targetUrl = Context.Database.Aliases.GetTargetUrl(args.LocalPath);
        if (targetUrl.Length > 0)
        {
            this.ProcessExternalUrl(targetUrl);
        }
    }

    private void ProcessExternalUrl(string path)
    {
        if (Context.Page.FilePath.Length <= 0)
        {
            Context.Page.FilePath = path;
        }
    }


What to do in multisite environment?

In multisite environment, we can still use this functionality. We can customize the AliasResolver processor in HttpRequestBegin pipeline, where the alias items will be stored in Site specific folder, so, the Custom AliasResolver will search aliases in site folder only. We can create a alias folder structure site-wise, something like,
/Sitecore/System/Aliases/Site-A/Alias1
/Sitecore/System/Aliases/Site-A/Alias2
/Sitecore/System/Aliases/Site-B/Alias1
/Sitecore/System/Aliases/Site-B/Alias2

See below snap:

This structure might change depending on what folder structure you follow. See how code will go for Custom AliasResolver:
namespace SitecoreTactics.Pipelines.HttpRequestBegin
{
class CustomAliasResolver : AliasResolver
{
    public new void Process(HttpRequestArgs args)
    {
        Assert.ArgumentNotNull(args, "args");

        if (!Settings.AliasesActive)
        {
            Tracer.Warning("Aliases are not active.");
        } 
        else 
        {
            Sitecore.Data.Database database = Sitecore.Context.Database;
            if (database == null)
            {
                Tracer.Warning("There is no context database in AliasResover.");
            }

            Item aliasItem = getAliasItem(args);
            if (aliasItem != null)
            {
                LinkField linkField = aliasItem.Fields["Linked item"];
                if (linkField != null)
                {
                    Item AliasLinkedTo = Sitecore.Context.Database.GetItem(linkField.TargetID);

                    if (AliasLinkedTo != null)
                    {
                        Sitecore.Context.Item = AliasLinkedTo;
                    }
                }
                else
                {
                    base.Process(args);
                }
            }
        }            
    }

    /// 
    /// Gets the alias item.
    /// 
    /// The args.
    /// 
    private Item getAliasItem(HttpRequestArgs args) 
    {
        string siteName = Sitecore.Context.Site.RootPath.ToLower();

        if (args.LocalPath.Length > 1)
        {
            Item aliasItem = Sitecore.Context.Database.GetItem("/sitecore/system/Aliases/" + siteName + "/" + args.LocalPath);
            if (aliasItem != null)
            {
                return aliasItem;
            }                
        }

        return null;
    }
}
}

Read more about Sitecore alias:

- Sitecore language based Alias
- Sitecore Alias for multisite solution - Marketplace Module

Sitecore Pipelines and Processors in web request

Sitecore is such a flexible CMS, you can do any customizations so quickly. Sitecore has customized ASP.NET's framework to provide more flexibility and power for itself and Sitecore developers.

Pipelines are nothing but to perform a sequential opterations/process, which is defined in web.config. Which gives better flexibility by adding new or overriding existing processors.

Below are the pipelines covered while any page is requested. These pipelines would be different on Staging and Live environment, also it depends on different modules installed on Sitecore environment.

initialize


This pipeline initiate Sitecore application

preProcessRequest


It is invoked for each HTTP request managed by ASP.NET. It is used to prevent Sitecore from processing requests with specific extensions other than .aspx. And yet, it is not used for request processing logic.

Below are processors in this pipeline:

NormalizeRawUrl

If Sitecore not getting proper raw URL, then it normalize (rewrite)  it and set into current context.

IIS404Handler

It handles 404 error page and rewrite to proper URL.

FilterUrlExtensions

It decides which extensions to allow or reject. i.e., aspx, html, asmx, ics, etc. are allowed (Allowed/Blocked extensions are specified within this processor configurations in web.config file)

StripLanguage

It rewrite the URL by embedding language.

httpRequestBegin


This pipeline contains processors which are used for request processing logic. It sequential finds Site, Device, Language, Item, Layout, etc. details for the requested URL and then allow Sitecore to continue rendering it.

CheckIgnoreFlag

This is where Sitecore realises the html file doesn’t exists and redirects it to it’s 404 page.

StartMeasurements

It starts timer to record performance counters/measurements. The timer is stopped and performance counters/measurements are retrieved in the httpRequestEnd pipeline’s StopMeasurements processor. If any counter exceeds the defined threashold, it's logged in log file.

IgnoreList

Decides whether handle the current request or not depending on the ignoreUrlPrefix setting value. It checks if the requested page is in the ignore URL list in the web.config.

Mostly the URLs which Sitecore does not render itself are set in IgnoreList. Like, RichTextEditor, Telerik Dialogs, axd files.
Set IgnoreUrlPrefixes to a '|' separated list of url prefixes that should not be regarded and processed as friendly urls (ie. forms etc.)
If such page is defined, the pipeline terminates

SiteResolver

It parses the incoming URL and Determines the current site defined in /configuration/sitecore/sites. It identifies Site either by hostname of the current request or by passed querystring parameter: sc_site.It resolves Sitecore Site object. This object is now added to the current Context (Sitecore.Context.Site)

UserResolver

Identifies current user and add it to current request context (Sitecore.Context.User). In backend, Sitecore uses standard ASP.NET membership.

DatabaseResolver

Identifies current database and add it to current request context.(Sitecore.Context.Database. It also resovles database by passing querystring parameter:sc_content.

BeginDiagnostics

If debugging is on, it starts diagnosis of sitecore request.

DeviceResolver

Resolves the current Sitecore device either by querystring parameter sc_device or then knowing BrowserAgent and add it to current context (Sitecore.Context.Site.Device)

LanguageResolver

Resolves the Sitecore context language and add it to current context (Sitecore.Context.Language). Language can be identified either by querystring parameter sc_lang or by cookie (SiteName#Lang) value set to the browser.

CustomHandlers

It triggers custom handlers defiend in customHandlers/handler in web.config. This fulfills use of .ashx requests. Like, for url starts with ~/media/, the hander is set to sitecore_media.ashx. So, it will proceed for all requests of media. Similarly we have different handlers for api, xaml, icon, feed, etc.

FilterUrlExtensions

This processor is actually deprecated, same processor is taking care in preprocessRequest pipeline.

QueryStringResolver

It analyzes such query string as “sc_itemid”. It checks this item in context database with context language. If this item is valid, then it sets it to current context.

DynamicLinkResolver

It parses dynamic links (Links served using ~link.aspx) and gives itemid, language and database details by using a media prefix syntax (specified in the configuration/sitecore/mediaLibrary/mediaPrefixes section of web.config).
Depending on it, it sets this item as Context Site item.

AliasResolver

One Sitecore item can have multiple aliases, means one item can be accessed using different URLs. It checks the requested URL to see if it matches an alias that exists on the system.
An Alias is a an alternate path for an item when browsing the web site.
For this "AliasesActive" setting should be enabled in web.config

DefaultResolver

Resolves the start path item for the context site. Sitecore instantiates this item using the “RootPath” and “StartItem” attributes of the context site by simply concatenating these values when
  1. Context.Item hasn’t been set yet
  2. Context.Database has been set (usually from Context.Site.Database)
  3. Context.Site has been set and Context.Site.StartPath is non-empty (i.e., positive length)
  4. The LocalPath is empty or equals /default

FileResolver

It checks whether this request is a physical file or not. If it is physical file, then will be served as is, otherwise Sitecore will use its filepath as default.aspx (Default Page) and and continue request with Sitecore rendering process. If it finds same Item Name and a physical file, then physical file will be getting more priority.

ItemResolver

Resolves the Sitecore context item from the incoming URL.
The item will be null when a request is requested, which does not find any Sitecore item.

LayoutResolver

It determins layout for the request from querystring parameter: sc_layout or by getting layout assigned to the Sitecore.Context.Item (Set from the presentation details). Sitecore assigns Layout to a request by updating Sitecore.Context.Page.FilePath

ExecuteRequest

Rewrites the context path and handles the “item not found” or ”layout not found” errors

renderLayout


PageHandlers

Custom page handlers are executed here defined in pageHandlers/handler in web.config. We are not having any custom page handlers.

SecurityCheck

Checks security of all items in request for current user. It also checks security depending on Current Site requested and Preview Mode.

InsertRenderings

Adds renderings to the current page, which are assigned.

PageExtenders

We can add page extenders here defined in pageextenders/pageextender. For example, PreviewPageExtender, WebEditPageExtender, DebuggerPageExtender

BuildTree

Builds full page, and expands all controls and placeholders.

InsertSystemControls

All controls are inserted to the page as System Controls.

InsertUnusedControls

All unused controls are inserted to the page as System Controls.

BrowserCaching

Sets browser caching headers. Also add last modified header (Item updated DateTime)