configuration
A Simple and Robust jQuery 1.4 CDN Failover in One Line
Google, Yahoo, Microsoft and others host a variety of JavaScript libraries on their Content Delivery Networks (CDN) - the most popular of these libraries being jQuery. But it’s not until the release of jQuery 1.4 that we were able to create a robust failover solution should the CDN not be available.
First off, lets look into why you might want to use a CDN for your JavaScript libraries:
1. Caching - The chances are the person already has the resource cached from another website that linked to it.
2. Reduced Latency - For the vast majority the CDN is very likely to be much faster and closer than your own server.
3. Parallelism - There is a limit to the number of simultaneously connections you can make to the same server. By offloading resource to the CDN you free up a connection.
4. Cleanliness - Serving your static content from another domain can help ensure that you don’t get unnecessary cookies coming back with the request.
All these aspects are likely to add up to better performance for your website - something we should all be striving for.
For more discussion of this issue I highly recommend this article from Billy Hoffman : Should You Use JavaScript Library CDNs? which includes arguments for and against.
The fly in the ointment is that we are introducing another point of failure.
Major CDNs do occasionally experience outages and when that happens this means that potentially all the sites relying on that CDN go down too. So far this has happened fairly infrequently but it is always good practice to keep your points of failure to a minimum or at least provide a failover. A failover being a backup method you use if your primary method fails.
I started looking at a failover solution for loading jQuery1.3.2 from a CDN some months ago. Unfortunately jQuery 1.3.2 doesn’t recognise that the DOM is ready if it is added to a page AFTER the page has loaded. This made making a simple but robust JavaScript failover solution more difficult as the possibility existed for a race condition to occur with jQuery loading after the page is ready. Alternative methods that relied on blocking the page load and retrying an alternative source if the primary returned a 404 (page not found) error code were complicated by the fact that the Opera browser didn’t fire the "load" event in this situation, so when creating a cross-browser solution it was difficult to move on to the backup resource.
In short, writing a robust failover solution wasn’t easy and would consume significant resource. It was doubtful that the benefit would justify the expense.
The good news is that jQuery1.4 now checks for the dom-ready state and doesn’t just rely on the dom-ready event to detect when the DOM is ready, meaning that we can now use a simpler solution with more confidence.
Note that importantly the dom-ready state is now implemented in Firefox 3.6, bringing it in line with Chrome, Safari, Opera and Internet Explorer. This then gives us great browser coverage.
So now in order to provide an acceptably robust failover solution all you need do is include your link to the CDN hosted version of jQuery as usual :
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
and add some JS inline, similar to this:
<script type="text/javascript">
if (typeof jQuery === 'undefined') {
var e = document.createElement('script');
e.src = '/local/jquery-1.4.min.js';
e.type='text/javascript';
document.getElementsByTagName("head")[0].appendChild(e);
}
</script>
You are then free to load your own JavaScript as usual:
<script type="text/javascript" src="my.js"></script>
It should be well noted however that the above approach does not "defer" execution of other script loading (such as jQuery plugins) or script-execution (such as inline JavaScript) until after jQuery has fully loaded. Because the fallbacks rely on appending script tags, further script tags after it in the markup would load/execute immediately and not wait for the local jQuery to load, which could cause dependency errors.
One such solution is:
<script type="text/javascript">
if (typeof jQuery === 'undefined')
document.write('script type="text/javascript" src="/local/jquery-1.4.min.js"><\/script>');
</script>
Not quite as neat perhaps, but crucially if you use a document.write() you will block other JavaScript included lower in the page from loading and executing.
Alternatively you can use Getify’s excellent JavaScript Loading and Blocking library LABjs to create a more solid solution, something like this :
<script type="text/javascript" src="LAB.js">
<script type="text/javascript">
function loadDependencies(){
$LAB.script("jquery-ui.js").script("jquery.myplugin.js").wait(...);
}
$LAB
.script("http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js").wait(function(){
if (typeof window.jQuery === "undefined")
// first load failed, load local fallback, then dependencies
$LAB.script("/local/jquery-1.4.min.js").wait(loadDependencies);
else
// first load was a success, proceed to loading dependencies.
loadDependencies();
});
</script>
Note that since the dom-ready state is only available in Firefox 3.6 + <script> only solutions without something like LABjs are still not guaranteed to work well with versions of Firefox 3.5 and below and even with LABjs only with jQuery 1.4 (and above).
This is because LABjs contains a patch to add the missing "document.readyState" to those older Firefox browsers, so that when jQuery comes in, it sees the property with the correct value and dom-ready works as expected.
If you don’t want to use LABjs you could implement a similar patch like so:
<script type="text/javascript">
(function(d,r,c,addEvent,domLoaded,handler){
if (d[r] == null && d[addEvent]){
d[r] = "loading";
d[addEvent](domLoaded,handler = function(){
d.removeEventListener(domLoaded,handler,false);
d[r] = c;
},false);
}
})(window.document,"readyState","complete","addEventListener","DOMContentLoaded");
</script>
(Adapted from a hack suggested by Andrea Giammarchi.)
So far we have talked about CDN failure but what happens if the CDN is simply taking a long time to respond? The page-load will be delayed for that whole time before proceeding. To avoid this situation some sort of time based solution is required.
Using LABjs, you could construct a (somewhat more elaborate) solution that would also deal with the timeout issue:
<script type="text/javascript" src="LAB.js"></script>
<script type="text/javascript">
function test_jQuery() { jQuery(""); }
function success_jQuery() { alert("jQuery is loaded!");
var successfully_loaded = false;
function loadOrFallback(scripts,idx) {
function testAndFallback() {
clearTimeout(fallback_timeout);
if (successfully_loaded) return; // already loaded successfully, so just bail
try {
scripts[idx].test();
successfully_loaded = true; // won't execute if the previous "test" fails
scripts[idx].success();
} catch(err) {
if (idx < scripts.length-1) loadOrFallback(scripts,idx+1);
}
}
if (idx == null) idx = 0;
$LAB.script(scripts[idx].src).wait(testAndFallback);
var fallback_timeout = setTimeout(testAndFallback,10*1000); // only wait 10 seconds
}
loadOrFallback([
{src:"http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js", test:test_jQuery, success:success_jQuery),
{src:"/local/jquery-1.4.min.js", test:test_jQuery, success:success_jQuery}
]);
</script>
To sum up - creating a truly robust failover solution is not simple. We need to consider browser incompatabilities, document states and events, dependencies, deferred loading and time-outs! Thankfully tools like LABjs exist to help us ease the pain by giving us complete control over the loading of our JavaScript files. Having said that, you may find that in many cases the <script> only solutions may be good enough for your needs.
Either way my hope is that these methods and techniques provide you with the means to implement a robust and efficient failover mechanism in very few bytes.
A big thanks to Kyle Simpson, John Resig, Phil Dokas, Aaoran Saray and Andrea Giammarchi for the inspiration and information for this post.
Related articles / resources:
3.http://aaronsaray.com/blog/2009/11/30/auto-failover-for-cdn-based-javascript/
4.http://snipt.net/pdokas/load-jquery-even-if-the-google-cdn-is-down/
5.http://stackoverflow.com/questions/1447184/microsoft-cdn-for-jquery-or-google-cdn
6.http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
7.http://zoompf.com/blog/2010/01/should-you-use-javascript-library-cdns/
Moving a Java project between Eclipse installations
The quickest way I have found of moving a Java project from one Eclipse installation to another.
(I am using Eclipse 3.4.1)
1. Copy the directory from your source Workspace to your target Workspace.
2. Remove the .classpath and .project files from the target. (These will be regenerated by Eclipse).
3. In your target installation of Eclipse go to File->New->Java Project
4. Enter a project name and click the ‘Create project from existing source’. radio button
5. Browse to your target directory in Workspace, click ‘Finish’
6. In your target Eclipse go to Project->Properties , select Java Build Path
7. Assuming you have copied over all jars that you are using, refer to your source installation of Eclipse Project->Properties->Java Build Path, Libraries tab and add all your jars.
8. (Target Eclipse) Project->Build Project and it should build.
Optional - Defining the output directory of your class files.
9. (Target Eclipse) Project->Properties->Java Build Path, Source, Browse button
10. Click ‘Create New Folder’, specify Folder name (this can be anything), click ‘Advanced’
11. Tick ‘Link to folder in the file system’, browse to output folder (usually WEB-INF/classes)
12. Click, ok, ok, ok.
The next time you build - your classes should be compiled into your chosen output directory.
If you know of a quicker/better way - let me know!
Mark B
Configuring Apache with Tomcat on OSX Leopard
Note that to start serving Java and run Java based web applications it is not essential to use Apache. Tomcat is a perfectly good http server and is even used by many in a production environment, however it is more common to separate concerns, leaving Apache to deal with the serving of static pages and Tomcat to deal with any application logic. This set up can also more easily facilitate load-balancing. Add to this the increasing popularity of using Apache’s .htaccess files to transform querystrings to more search engine readable URLs and you might find yourself with compelling reasons to get Apache and Tomcat talking.
For those interested in the debate - Do I still need Apache Httpd? is worth a read.
You will need to have Tomcat already installed - fortunately there are already various excellent tutorials on how to do this scattered around the web.
To start with, it may be worth detailing where the various parts of the pre-installed Apache system are in OSX.
(I’m running Mac OS X Version 10.5.6 - Leopard)
| Web root (htdocs) : |
/Library/Webserver/Documents |
| Configuration file : |
/private/etc/apache2/http.conf |
| Apache Log files : |
/private/var/log/apache2/ |
| System Log file : |
/private/var/log/system.log |
| Modules : |
/usr/libexec/apache2/ |
| Apache Binary : |
/usr/sbin/apachectl |
| Root in a browser : |
http://localhost/ |
You might also find it useful to log in as root unless you are happy using sudo.
It also might be an idea to show hidden files in finder, to do this type:
defaults write com.apple.Finder AppleShowAllFiles YES
at a command line and restart Finder.
To start / restart apache:
Apple menu -> System Preferences -> Sharing -> Web Sharing (tickbox)
Activating / deactivating the tickbox effectively starts and stops Apache.
Or at the command line you can type any of the following:
apachectl start apachectl stop apachectl restart
You can verify that Apache has started by going to http://localhost/ in your browser and checking that the Apache default page appears.
So what do we have to do to get Apache and Tomcat talking to each other? First we need a connector. Currently there is a lot of discussion in the community over whether to use mod_jk or mod_proxy.
I’m going to start with mod_jk
Unfortunately the version available at the apache site (1.2.25) for Mac OSX is not compatible with Leopard.
If you want to create a version that is, you will need to build your own or download a pre-compiled version for Leopard - as kindly provided here - although you do this at your own risk.
Once you have downloaded/created a compatible version of mod_jk, rename the file mod_jk.so place in the modules directory and change the permissions to 755 with:
chmod mod_jk.so 755
(you will need to place sudo in front of this if you are not logged in as root).
Next, create a folder for the mod_jk log file to be written to. I created a directory : private/var/log/mod_jk
Open the http.conf (Note you may want to make a backup of this file before editing it). Locate the area in the file where the modules are loaded. (You should find may lines starting with the text LoadModule). Append your own LoadModule line :
LoadModule jk_module libexec/apache2/mod_jk.so
add to this the location of the log file:
JkLogFile /private/var/log/mod_jk/mod_jk.log
(This is assuming that your modules reside in libexec/apache2/ otherwise you will need to specify your own modules directory.)
Next it might be prudent to check that Apache is still working, so start it or restart it and point your browser at http://localhost/ If you are no longer seeing the apache page you should check /private/var/log/system.log and/or /private/var/log/apache2/error.log for clues.
Also, check that the mod_jk.log file has been created.
Now we need to define a workers.properties file and put this in /private/etc/apache2/mod_jk/
A workers.properties file is effectively a listener for Apache which tells it when to redirect requests to Tomcat. Mine looks like this:
workers.tomcat_home=/Library/tomcat/tomcat55 workers.java_home=/System/Library/Frameworks/JavaVM.framework/Versions/Current worker.list=ajp13 worker.ajp13.port=8009 worker.ajp13.host=localhost worker.ajp13.type=ajp13
Note that I often have different versions of Tomcat installed on my system which is why my workers.tomcat_home points at /Library/tomcat/tomcat55 but you should point at it a wherever you have installed Tomcat on your system.
Finally, tell Apache about your workers.properties file and a few other things so replace the lines you have added to httpd.conf with the following:
# mod_jk configuration LoadModule jk_module libexec/apache2/mod_jk.so # path to workers.properties file JkWorkersFile /etc/apache2/mod_jk/workers.properties # path to mod_jk log file JkLogFile /private/var/log/mod_jk/mod_jk.log # mod_jk log level [debug/error/info] JkLogLevel info # mod_jk log format JkLogStampFormat "[%a %b %d %H:%M:%S %Y] " # patterns for mounting JkMount /servlet/* ajp13 JkMount /*.jsp ajp13 # end mod_jk configuration
Restart Apache and you should be in business!
I’ll maybe come back and tackle mod_proxy but for now I’ll point you towards Michael Scepaniak’s recent article mod_jk fail, mod_proxy success.
MarkB
Configuring Leopard’s Apache to work with all Rewrite Rules
Although there are a few blog posts out there detailing how to enable .htaccess on Mac OSX 10.5. I didn’t find anything that could help me with a problem I had with a specific RewriteRule.
This post isn’t specific to Leopard but maybe useful to users of this OS due to the default values that it’s pre-installed Apache’s httpd.conf comes set with.
Say for SEO reasons you wanted to rewrite
product.php?id=17
as
product/toaster/17.html
You could use the following rewrite rule:
RewriteRule ^product/([a-zA-Z0-9_-]+)/([0-9]+)\.html$ product.php?id=$2
In other blogs it will tell you that you will need to change AllowOverride None to AllowOverride All in the <Directory “/Library/Webserver/Documents”> of /private/etc/apache2/httpd.conf and perhaps also in any conf files found in /private/etc/apache2/users. (This is not enabled by default on Leopard).
What they won’t neccesarily tell you is that to get rewrite rules like the above working you need to remove MultiViews from your Options list, leaving you with something like this :
<Directory "/Library/Webserver/Documents"> Options Indexes FollowSymLinks AllowOverride All Order allow,deny Allow from all </Directory>
I hope this saves someone the trouble I went through this morning trying to figure it out.
For a quick explanation see the short but sweet Apache Multiviews are evil
MarkB
Categories
Archives
- August 2010 (2)
- June 2010 (1)
- March 2010 (1)
- January 2010 (1)
- August 2009 (1)
- July 2009 (1)
- May 2009 (1)
- April 2009 (2)
- March 2009 (1)
- February 2009 (2)
- October 2007 (1)
- May 2006 (1)