Sunday, July 8, 2018

Sitecore and Akamai Hidden Gems, Part 2: Device Detection

     At the first part of Akamai article series I wrote how to utilize GeoIp data on Sitecore. Another useful information that could be provided by Akamai is device characteristics:
  1. Akamai maintains database of useragent strings
  2. When visitor requests your website under Akamai, it is able to parse useragent header on the fly and add parsed information about device. 
  3. You get X-Akamai-Device-Characteristics request header on your server that contains information that could be used. E.g.: X-Akamai-Device-Characteristics: brand_name=Google; is_tablet=false; device_os=Android
  4.  You can use this information to display different content depending on information that you have got.
    Sitecore code that respond for getting device characteristics is not easy to override comparing to GeoIp detection. But it is possible to build Sitecore rules that will parse Akamai headers. Here is example of "Device is Mobile" rule (real code looks differently, it is just example to have everything in one place for better understanding):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Sitecore.Diagnostics;
using Sitecore.Rules;
using Sitecore.Rules.Conditions;

namespace Foundation.Akamai.DeviceDetection.Rules.Conditions
{
    public class DeviceIsMobile<T> : OperatorCondition<T> where T : RuleContext
    {
        private HeaderParser HeaderParser
        {
            get
            {
                return new HeaderParser();
            }
        }

        protected override bool Execute(T ruleContext)
        {
            Assert.ArgumentNotNull(ruleContext, "ruleContext");
            Assert.IsNotNullOrEmpty(HttpContext.Current.Request.Headers["X-Akamai-Device-Characteristics"], "Akamai header X-Akamai-Device-Characteristics is null or empty");
            var headerValue = HttpContext.Current.Request.Headers["X-Akamai-Device-Characteristics"];
            var dictionary = headerValue.ParseAkamaiHeader(";");
            bool.TryParse(dictionary["is_mobile"], out bool value);
            return value;
        }
    }

    public static class Extensions
    {
        public static Dictionary<string, string> ParseAkamaiHeader(this string headerValue, string delimiter = ",")
        {
            var pairs = headerValue.Split(new string[] { delimiter }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
            var dictionary = new Dictionary<string, string>();
            foreach (var pair in pairs)
            {
                var parts = pair.Split('=');
                if (parts.Length > 1)
                {
                    var key = parts[0];
                    var value = parts[1];
                    dictionary.Add(key, value);
                }
            }

            return dictionary;
        }
    }
}

    It is possible to create a lot of rules based on Akamai information about visitor location or his device without overriding anything in Sitecore.


   All code and Sitecore items serialization are available in GitHub repository, also it is possible to download Sitecore update package and use it.

Sitecore and Akamai Hidden Gems, Part 1: GeoIp Detection

    Akamai is well known CDN(content delivery network) provider. It provides content delivery features that are very widely used  across popular websites. Besides CDN, Akamai provides additional features, but not everyone is aware of them. One of them is providing GeoIp data. In brief, how it works:
  1. Akamai maintains database of IP addresses
  2. When user requests your website under Akamai, Akamai is able to parse IP address of request on the fly and add information about user’s geographic location, network, connection speed, etc. to request header. 
  3. You get X-Akamai-Edgescape request header on your server that contains a lot of information that could be used. E.g.: X-Akamai-Edgescape: georegion=263,country_code=US,region_code=MA,city=CAMBRIDGE,dma=50 6,pmsa=1120,areacode=617,county=MIDDLESEX,fips=25017,lat=42.3933,l ong=-71.1333,timezone=EST,zip=02138-02142+02238-02239,continent=NA ,throughput=vhigh,asnum=21399
  4. You are able parse this header and get more information about your visitor to show him relevant information on website
      Out of the box Sitecore also provides provides GeoIp personalization. Both Akamai and Sitecore GeoIp detection have it’s benefits. Akamai GeoIp is free(if you are already customer of Akamai) and could be quicker, because you don’t need to spent server time to get information about IP address. Sitecore GeoIp is ready to use out of the box and is better integrated with Sitecore Experience Platform. Depending on project you can prefer different GeoIp provider.
    If you choose Akamai then you are able to find article that describes how to do it. That solution works properly for Sitecore 6.6 - 7.2, but doesn’t work on Sitecore 8+. Since some time Sitecore moved parsing GeoIp headers to separate thread:

if (orCreate.GeoIpResolveState == GeoIpResolveState.Unresolved)
{
 GeoIpManager.StartResolvingThread(orCreate);
}

That thread doesn’t know nothing about your context, that is why call of HttpContext.Current.Request.Headers["X-Akamai-Edgescape"] causes “Object reference not set to an instance of an object” exception. It means that only overriding of LookupManager will not work. I have managed to transfer data from Akamai header to Sitecore analytics context in next way:
  1. Disable Sitecore.Analytics.Pipelines.CommitSession.UpdateGeoIpData, Sitecore.Analytics.Pipelines.CreateVisits.UpdateGeoIpData and Sitecore.Analytics.Pipelines.EnsureClassification.UpdateGeoIpData processors
  2. Override Sitecore.Analytics.Pipelines.StartTracking.UpdateGeoIpData processor

using Foundation.Akamai.GeoIp;

namespace Foundation.Akamai.Pipelines
{
    public class UpdateGeoIpData 
    {
        public void Process(object args)
        {
            var whois = new LookupProvider().GetInformationByIp("");
            if(whois!=null)
            {
                Sitecore.Analytics.Tracker.Current.Interaction.SetGeoData(whois);
            }
        }
    }
}

    This approach works, because parsing header is quick operation that doesn’t need running separate thread. As I understand four UpdateGeoIpData processors in different pipelines were required to sync thread that get GeoIp information with main HttpRequest thread.
    To get more details you can review my repository on GitHub or download package and try it by yourself.