Last update: Sept. 26 2018
I recently had to set languages, using my Citrix ADC (NetScaler), for a website. My customer has several similar web-pages in different subdirectories. Naming scheme is like this:
https://example.com/en
for English
https://example.com/de
for German
…
There is currently a total of 12 languages.
There had been several requirements, and I had to create a set of responder policies to meet the requirements:
- if an user used a regional domain like example.at, example.co.uk, example.ru the user had to be forwarded to example.com/de (where de would be the German version, en the English one, …)
- If an user surfed to example.com this user had to be forwarded to the version matching his/her browser settings.
- the first condition had to take precedence over the second
I decided to create a policy label as this had to be done with both, the http and the https version of this website.
The number of languages had been huge, the number of regional domains as well. Following Citrix best practices for Citrix NetScaler ADC I kept away from many policies and instead created only three policies using string maps.
The regional domains
The regional domains differ just in the last two letters. I did not have to care about Oman (TLD om) as my customer currently only sells to Europe. Om is a problem with my policy as the TLD for Oman is equivalent to the last 2 characters of .com (see below, problems of this solution!)
Creating the Citrix ADC (NetScaler) stringmap
I created a stringmap called languages.
add policy stringmap languages
next I had to fill keys into my stringmap
bind policy stringmap languages at de
Keep in mind to put popular languages in front and rare ones to the end, just to make it a bit more efficient. You may do this by adding popular ones first.
Creating the Citrix ADC (NetScaler) policy action
add responder action res_act_send2language_hostname redirect "\"https://example.com/\" + HTTP.REQ.HOSTNAME.SUFFIX(2).MAP_STRING(\"languages\") + \"/\"" -responseStatusCode 301
Attention: Why did I use a 301 instead of a 302 (which is default)? Easy: If Google crawler sees a 301 (permanent redirect), it will follow this redirect as it know the old content is not there any more. If Google sees a 302, it will think this is just a temporary redirect and therefore not follow this redirect. As we want google to crawl our website, a 301 is better than a 302.
Creating the Citrix ADC (NetScaler) policy
add responder policy res_pol_send2language_hostname "HTTP.REQ.HOSTNAME.SUFFIX(2).IS_STRINGMAP_KEY(\"languages\")" res_act_send2language_hostname
Problems and strengths of this solutions
I don’t care about the complete host-name. I just use the last 2 characters. Therefore I can’t distinguish between example.uk and example.co.uk. This is no problem as the only valuable information in this case is UK. For historic reasons there are some more domains like example-logistics.ru. They should be treated like example.ru. My policy will do this automatically.
Unfortunately I will mix up ecample.com with example.om, as I don’t see the c in com. To avoid this I would have to slidely change my policy.
HTTP.REQ.HOSTNAME.SUFFIX(3).EQ(".com").NOT && HTTP.REQ.HOSTNAME.SUFFIX(2).IS_STRINGMAP_KEY(\"languages\")
We have no problem about uper / lower case as host-names generally are case insensitive.
Using languages defined in the browser
Browsers define languages. My browser, for example, always sends following line to a web-server: Accept-Language de/en-US;q=0.7,en;q=0.3
. This means, my browser prefers German (de) over English (en-US: American English would be fine as well).
I make things simple, I just care about the first language, in my case de.
This time I use a patern set. (GUI version is very simmilar to above: The string map)
add policy patset languages2
add values
bind policy patset languages2 de -index 5
specifying an index is optional. The index is set automatically if you don’t do it. Keep in mind to put popular languages in front and rare ones to the end, just to make it a bit more efficient. You may do this by adding popular ones first.
The Citrix NetScaler ADC responder Policy Action
add responder action res_act_send2language-Browser redirect "\"https://example.com/\" + HTTP.REQ.HEADER(\"Accept-Language\").SUBSTR(0,2) + \"/\"" -responseStatusCode 301
In this case the action is extremely simple: I’m just using the very left two characters of the language string. Reason phrase again is 301 (see above)
The Citrix NetScaler ADC responder Policy
We have to decide, when to do this. This may only be done, if there is no language set (i.e.; if the URL is empty. Keep in mind: In Citrix ADC (NetScaler) a URL does not contain the host name. So we do this only if the user surfed to http(s)://example.com/
add responder policy res_pol_send2language-browser "HTTP.REQ.HEADER(\"Accept-Language\").CONTAINS_ANY(\"languages2\")" res_act_send2language
(my screen shot, however, has a log action. I used this action for debugging reasons. It’s not needed!)
What to do with the rest?
The Citrix NetScaler ADC catch all policy action
In the end we need a policy for al the rest: Unsupported regional domains (this should not exist) and unsupported browser languages. My custumer mandated: It should point to English!
add responder action res_act_send2english redirect "\"/en/\"" -responseStatusCode 301
So all requests get send to English
The Citrix NetScaler ADC catch all policy
When will we do this? only if the user did not specify any language. The Citrix NetScaler ADC policy is similar to the policy above:
add responder policy res_pol_send2english "HTTP.REQ.URL.EQ(\"/\") " res_act_send2english
The policy label
As I need this a total of four times (for http and https, both in production and test environment), I created a policy label. Policy label are collections of policies, invoked by a single policy.
add responder policylabel res_lab_send2language
bind responder policylabel res_lab_send2language res_pol_send2language_hostname 100 END
bind responder policylabel res_lab_send2language res_pol_send2language-browser 110 END
bind responder policylabel res_lab_send2language res_pol_send2english 900 END
Priority value of binding is not very important as long as you keep the order. NetScaler usually starts binding with 100, binding in intervals of 10, so I followed this standard. And I usually put the “catch the rest” policy far behind the other to have plenty of space to create more policies, that’s why I used priurity of 900.
Invoking the policy label
Policy labels have to get invoked using policies. So I created a simple policy, doing nothing. The label gets invoked if this policy is hit.
add responder policy res_pol_send2language "(HTTP.REQ.URL.EQ(\"/\")" NOOP
bind lb vserver lb_vsrv_website_productiv_ssl -policyName res_pol_send2language -priority 100 -gotoPriorityExpression END -type REQUEST -invoke policylabel res_lab_send2language
This creates the policy res_pol_send2language with built in action NOOP. It than binds this policy to lb_vsrv_website_productiv_ssl with a priority of 100, thereby invoking the policy label.
You could say: Policies checking for a url of “/” would not need a policy condition. That’s true as they may only get invoked if this dummy policy is hit. And the dummy policy is checking for exactly this. I did the policy expressions like that as some of you foks won’t use a policy label. It’s not like that at my customer’s Citrix ADC (NetScaler).