Analytics JavaScript Injection using an F5 BIG-IP iRule Part 1
Scenario
A line of business web application (we shall refer to it here as ExpensiveApp1) is currently delivered via the F5 BIG-IP and there is a business need to evaluate the performance of the application in the company offices across the world. This would normally be a simple task of adding the JavaScript used by the analytics software but the suppliers of ExpensiveApp1 are highly reluctant to allow any changes to be made to the servers on which the application runs.
Solution
Given that the analytics JavaScript, in this case WebTuna, runs client side, it could be injected into the server response as it was returned through the BIG-IP. A stream profile and a simple iRule provided the solution for the replacement of the HTML text. This would require no changes of any sort to be made on the servers hosting ExpensiveApp1 and could also be easily disabled for diagnostic purposes. A blank stream profile was therefore created and added to BIG-IP virtual server – it already had the prerequisite TCP and HTTP profiles - for ExpensiveApp1 and an iRule created to do the actual injection. The BIG-IP then rechunks the data after insertion and delivers it onwards to the client computer.
IRule
when HTTP_REQUEST {
# Disable the stream filter for client requests as we are only interested in the server response
STREAM::disable
}
when HTTP_RESPONSE {
# Disable the stream filter for all server responses
STREAM::disable
# except for valid text responses only then enable the stream filter
if {([HTTP::header value Content-Type] contains "text") && ([HTTP::status] == 200) }{
# Replace old text with javascript
STREAM::expression {@</title>@</title><script type="text/javascript" src="http://goodapp1.mycompany.local/js/webtuna.js"></script>@}
# Enable the stream filter for this response only
STREAM::enable }
}
Notes
- The script was tested on v11.4.0 and v11.4.1 firmware.
- It was noted during that testing that, although the JavaScript could be added and served directly from the F5 BIGIP, better performance was obtained by delivering it from the Virtual IP of another application already optimised by the BIGIP. This is goodapp1 referred to in the iRule.
- Using stream profiles is incompatible with the use of compression. In this case, this was irrelevant as all the servers were in a data centre and all the users were in sites connected using Riverbed Steelheads. By default, these devices remove the Accept-Encoding header that activates compression between client and server in favour of their own data reduction technologies. As the JavaScript injection takes place before the Steelhead, its processing will not affect the end result. Inside the data centre, server side compression is not needed and support for it can either be disabled on the actual servers or, as in this case, by removing all URI / Content types from the URI / Content List fields in the HTTP compression profile and thus the BIG-IP will not compress any HTTP content. Alternatively, and this will be necessary in older versions, prior to v11.2, of the F5 firmware, any server side compression profile can be removed.
- There is an impact to performance when using the iRule but it proved to be minimal. With a normal user load (about 1400 concurrent HTTP connections) for ExpensiveApp1, the addition of the iRule had only a minimal effect. Testing to 8000 connections, far in excess of any production load, produced a rise of 2.2% in CPU utilisation.
- The injection point for the JavaScript was determined by monitoring ExpensiveApp1 using HttpWatch but Fiddler would do the job just as well. Note that the point of injection may vary. In the case of ExpensiveApp1, The application was based on SharePoint so all pages were based on a master page and that all pages had a <title></title> tag pair even if there was no actual title text. Other applications behave differently so careful analysis is needed. MS Dynamics CRM, for example, loads a lot of its own scripts and therefore it proved appropriate to inject the JavaScript after all the scripts had been loaded, just at the </head> tag.