Caspar Wiegel

Localization Redirects with cookies via Variables Support!

Blog Post created by Caspar Wiegel Employee on Sep 19, 2016

Recently we've released the support for Variables inside the Property Manager UI and as this blog post already shows, it puts a lot more power into your hands!

 

I started contemplating what you as our customers could do with this new found power. A discussion with one of my customers led me to explore if their full localization redirection logic could be moved into the property configuration and still maintain it as a self-serviceable property. It turns out this is possible!

 

Key requirements

Key requirements to be able to move this logic into the edge server and allow the customer to remove it from the backend are:

  1. The list of supported countries and languages needs to be maintainable by the customer without requiring Professional Services support for each new country or language release
  2. It needs to take the language from the Accept-Language header into account as the preferred language for the user
  3. It needs to support a per-country language default that is also maintainable by the customer without PS support
  4. It needs an overal default to fall back to in case there was no match for a supported country
  5. It needs to also redirect specific base URLs to the localized variant

 

Localization in this example is done by prefixing all URLs with a /<language_code>_<country_code>/. Examples are /en_us/, /en_gb/, /nl_nl/.

 

The redirect logic

The redirect logic to be implemented is as follows:

  1. If the request path is "/":
    1. Check if a LocalizationUrl cookie exists, if so use this to redirect to the value of the cookie
    2. If the cookie is not set:
      1. Determine the country of the user based on the IP address
      2. Determine the preferred language of the browser based on the Accept-Language header
      3. Check if the combination of these two exists in a set of defined combinations
      4. If a match is found, redirect to that combination
    3. If we did not find a match:
      1. Check if we do support this country and choose its default language
      2. Redirect to the country's default language page
  2. If the request path is not "/" but does match a specific base product path (e.g. "/foo"):
    1. Determine the localization as per the above sequence
    2. Redirect to the same base URL but then prefixed with the localization string (e.g. "/de_de/foo")
  3. Carry over any query strings that might have been sent

 

What you need on your contract

As we are going to need some kind of geo IP database to determine the country, the following product is needed on the contract to which the property will belong:

  • Content Targeting / EdgeScape

 

You can work with your account team to get this added to your contract, if it is not on there just yet!

 

How to build it

Here's what you need to do to build such a solution on the edge and offload all of these hits from your origin, to improve the user experience (faster redirects) and remove the maintenance of this from your backend!

 

Defining the Variables

As in many programming languages, in Luna Property Manager we also need to declare the variables before we can start using them in behaviors, assign values to them, modify their values or use their values. Start by expanding the "Property Variables" section just beneath the "Property Hostnames" and above the "Property Configuration Settings". Once expanded, insert the following list of variables:

Variable NameInitial ValueDescription
PMUSER_DOIREDIRECTFalseStores a boolean to track if we need to redirect in this property
PMUSER_EXTRACTEDCOUNTRYStores the extracted country based on IP address
PMUSER_EXTRACTEDLANGUAGEStores the extracted language based on the request header Accept-Language
PMUSER_EXTRACTEDPRODUCTPATHStores the first path element of the request URI
PMUSER_LOCALURLPREFIX/en_gbStores the final localization URI prefix
PMUSER_TMPLOCALURLPREFIXStores a temporary localization URI prefix
PMUSER_DEFAULTLANGUAGESnl=nl&be=nl&de=de&it=it&gb=en&us=en&ca=en&fr=fr Stores the default languages for each supported country
PMUSER_DEFAULTLANGUAGEStores the default language as matched by the country
PMUSER_PREDEFINEDMATCHFalseStores whether we found a match between the extracted country AND language in the available set of permutations

 

Once done, it should look somewhat like this:

Property Variables

 

Building the rule structure

Now we can start defining the business logic. I've put all the business logic in a rule called "Handle localization, product and root redirects". The rule itself is empty, but inside this rule, I created seven child rules that will contain the meat of the solution:

  • Check for LocalizationUrl cookie
  • Determine country and language
  • Check if country and language match
  • Check if country can still be matched with default language
  • Handle product root folders
  • Handle empty root folder
  • Conditionally execute redirect

 

The rules should now look as follows:

 

As said, it is just the empty set of rules now, so let's start filling them. Do note that the parent rule "Handle localization, product and root redirects" will remain empty!

 

Check for LocalizationUrl

As you can see, this rule starts with testing if the cookie exists using a simple match criteria. Once the edge has established this to be the case, the edge will:

  • Set the variable PMUSER_LOCALURLPREFIX to the value of the Request Cookie "LocalizationUrl" without any operations applied

This way we can use the value of the cookie later on, modify it or whatever we want.

 

Determine country and language

 

Initially we just want to make sure we only execute this logic when the LocalizationUrl cookie is not set of course! So a simple match criteria establishes that. Then we do the following:

  • Assign the country code from the Edgescape Data to the variable PMUSER_EXTRACTEDCOUNTRY, making sure it is forced to lower case (as I'm not sure how the casing is returned and whether it is 100% consistent, we'll benefit from this later on)
  • Assign the first two characters (using the substring operation) of the value of the Accept-Language header to the variable PMUSER_EXTRACTEDLANGUAGE
  • As we already executed an operation (substring) on the PMUSER_EXTRACTEDLANGUAGE, we need another behavior for the second operation: force it to lowercase
  • Then we store the combination of the found language and country in the PMUSER_TMPLOCALURLPREFIX.

Now if one would approach the edge server with a German IP address and the Accept-Language header with a value of "DE-DE; en q0.8;" the PMUSER_TMPLOCALURLPREFIX variable will contain the value "de_de"

 

Check if country and language match

As my customer only has a limited set (about 60 permutations) of supported languages and countries, we need to make sure the extracted value from the previous child rule exists in their list of known combos. So first, we make sure the cookie is not present and we check the value of the PMUSER_TMPLOCALURLPREFIX variable to see if it exists in the list of known combos using the match criteria. This makes the list of supported combos self-serviceable, and as they only would release new languages once every year or at most half a year, this is manageable. If the frequency is higher, I would recommend to write a small script that uses our PAPI. For the purpose of this demo, I only inserted a small set of supported combinations.

 

Once we established we have a valid combination, we:

  • Assign the value of PMUSER_TMPLOCALURLPREFIX to the variable PMUSER_LOCALURLPREFIX prefixed with a slash forward. No operation applied.
  • Set the LocalizationUrl response cookie (as the origin would have done this otherwise) now that we established we have created a valid language and country code combo.
  • Set PMUSER_PREDEFINEDMATCH to True inside the "Check if country and language match" rule. This allows us to prevent execution of the next rule that we are going to add.

 

Check if country still be matched with default language

Now we are going to define the logic for the default language fallback for a country that is supported, but an Accept-Language header value that we don't support. We define the following match criteria:

  • Request Cookie "LocalizationUrl" does not exist, as we always prefer the cookie over anything else
  • Variable PMUSER_PREDEFINEDMATCH is not True, as it would imply a combination was already found
  • Variable PMUSER_EXTRACTEDCOUNTRY is one of "nl, be, de, it, gb, us, ca ,fr" which is where we define the list of supported countries

 

First we are going to extract the default language for the supported country from the PMUSER_DEFAULTLANGUAGES (note the use of the plural variable name). This variable contains a list of key / value pairs in the form of a simple query string. The query string format can be used to do a lookup with the Set Variable behavior and the "Extract Parameter" operation. So by telling the Set Variable behavior that the source is {{PMUSER_DEFAULTLANGUAGES}} and running the "Extract Parameter" operation, we can now just ask it for the matching language that is defined for the PMUSER_EXTRACTEDCOUNTRY. We store the found default language in the PMUSER_DEFAULTLANGUAGE variable.

 

Then we construct the PMUSER_LOCALURLPREFIX from the found default language (PMUSER_DEFAULTLANGUAGE) and the already extract and match PMUSER_EXTRACTEDCOUNTRY. Also, making sure again we are setting the cookie now.

 

IMPORTANT NOTE: Now... You might have to do the above in the legacy property manager as the new one has a bug on the "Extract Parameter" option on the Set Variable behavior (highlighted in the screenshot in red) which prevents you from saving / activating the config without errors... This should be sorted soon! You can switch to / from the legacy property manager at the top of the Luna portal screen.

 

Handle product root folders

 

We don't need to test for the cookie, as this logic needs to be executed regardless how we determine the localization prefix.

 

Part of the requirement is to also redirect if the path contains a known string, for a given product the URL could for instance be /foo or /bar, but maybe also /foo/ or /bar/. Now from a maintainability this sounds not so good to do this in a property, but the use case only applied to an odd 20 root folders or so. Marketing URLs can still be managed outside of the property using our Edge Redirector cloudlet for instance!

 

So, we start with a path match criteria specifying all the supported root folder redirects. Then:

  • We store the root path in a variable PMUSER_EXTRACTEDPRODUCTPATH by taking the data from the "Path Component Offset" with an offset of 1, which effectively takes whatever exists in the URL between the first two slash forwards, or after the first slash forward if there is no second slash forward. We apply no operation.
  • Then we reassign the value of PMUSER_EXTRACTEDPRODUCTPATH to itself, but whilst doing so we strip all slashes from the variable (by doing a global substitution against the slash forward symbol, escaped by a back slash, with an empty string). Yes, this one and the previous one could be merged :-).
  • Then, we reassign the same variable to itself once more, but this time adding the trailing slash forward.
  • And since we established now that we need to redirect (as the path matched in the match criteria) we set the PMUSER_DOIREDIRECT boolean to True (of course it is a fake boolean, it is really a string).

 

Handle empty root folder

In this case the end user is requesting the root path / and we match against that. We set two variables:

  • PMUSER_EXTRACTEDPRODUCTPATH is forced to be empty. This variable should really still be empty by this point, but let's make sure it is.
  • PMUSER_DOIREDIRECT is set to True, as no user should ever land on /, they always need to go to their localized version or the default /en_gb/

 

Conditionally execute redirect

So all we need to do now is to test if we need to redirect by using the variable PMUSER_DOIREDIRECT in a match criteria and check if its value matches "True". If this matches, we issue the 302 redirect using the two variables we prepared:

{{user.PMUSER_LOCALURLPREFIX}}/{{user.PMUSER_EXTRACTEDPRODUCTPATH}}

 

This covers all the use cases from the my specific customer. I suspect many others follow the same or a very similar concept. As such, this can be of great help by:

  • Creating a huge offload of redirects from your origin
  • Remove the need to maintain a geo IP database at the origin
  • Improve end user performance by redirecting at the edge instead of at the origin
  • Have more fun using Property Manager!

 

Perhaps your requirements aren't exactly the same, in which case I hope this post has shown you enough to modify this so it suits your use cases!

 

I hope you enjoyed this tutorial and that it helps you in bringing more Akamai value to your company and end-users!

 

Caspar Wiegel

Senior Solutions Engineer @Akamai BeNeLux

 

PS: Please find some helpful links below:

Variable Support help page https://control.akamai.com/dl/rd/propmgr/Content/VS-SetVariableTransforms.htm

Set Variable Behavior Operations explained  

Testing Configurations with Variables (a.k.a. why can I control the "Visibility" of a variable!?) 

Outcomes