Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Faster AJAX Web Services through multiple subdomain calls

3.94/5 (12 votes)
11 Aug 2008CPOL22 min read 1   265  
How to get faster AJAX Web Services through multiple subdomain XMLHttpRequest calls.

Introduction

AJAX is one of the most important technologies in Web 2.0. Almost every web developer nowadays uses AJAX calls in one way or another. Web Service AJAX calls are a very nice feature in ASP.NET. When you create a Web Service class on the server side, you can add the attribute [System.Web.Script.Services.ScriptService] to your class and ASP.NET will automatically generate the appropriate JavaScript file(s) to call your Web Service from the client side with very little effort.

While working with AJAX calls, I discovered several ways to enhance the usage of Web Service calls, and eventually created a client-side JavaScript class, WSAjaxCallsClass, that helps in this task. If you work with ASP.NET AJAX Web Services, you should not miss this article. Please note that the techniques described here are not limited for ASP.NET itself; however, the implementation given in this article is for ASP.NET.

Before deciding on the title of this article, I had several titles in mind:

  1. Calling AJAX Web Services through multiple sub domains to increase throughput.
  2. An AJAX Web Service calls manager, a must for every ASP.NET AJAX developer.
  3. AJAX Web Service throttling.

But I decided to stick with the current one.

The WSAjaxCallsClass class gives you several benefits, but the most important one is in the title of this article. I will start by presenting the class, and then talk about the benefits you get, in details.

The Web Service calls manager class

The code presented in this article gives you the following benefits; I will state the benefits and explain each one in details.

  • Queuing manager:

    According to an article by Omar Al Zabir, if you ask your browser to execute more than two AJAX calls at once, it will hang, and this is worst in IE. I have tested this myself, but I could not reproduce this problem, maybe the current versions of IE have fixed this problem. However, this is not the main reason why I decided to do a queuing manager. Sometimes, in your code, without the developer paying attention to cases like this, you initiate AJAX calls by reacting to some events or actions that the end user does. I was developing an application that sends SMS (Short Messaging Service), where a part of the code was to calculate the cost of the current SMS to be sent based on the message’s text length, Unicode or not, the number of recipients chosen, and the destination of each recipient. Sometimes, if the user’s input is fast enough, multiple calls are generated to the Calculate function without the first one being done yet. Sometimes, I end up with up to 5 or 6 unnecessary calls where only one is needed. By queuing the calls, we can detect such cases where we call the same AJAX function more than once before the first call has returned, and we can decide to ignore the multiples, or we can decide to always keep the last call in the queue, which is the more logical choice for most cases.

  • Common success, failure methods:

    When you call the Initialize method of the class WSAjaxCallsClass, it allows you to provide common success, failure methods for all the AJAX calls before control is passed to the original success/failure method of the AJAX call. Let me explain why this is important. When I use AJAX calls in a web application, I tend to return the same object from all the Web Service calls, and I do not allow my Web Service methods on the server to throw exceptions (I will tell you why a little later). To make my idea clearer and explain why this is important, let me show you the class that I use: GenericReturnObject.

    The first member of the class is an enum called errorLevel. errorLevel has one of the following values:

    • None: If there are no errors.
    • Warning: If the exception or error is not a fatal one, but just a warning, something like “You do not have enough units to send this SMS”.
    • Fatal: If a fatal error was thrown, something like a database connection error.
    • SessionExpired: This is a special error indicating that the session on the server does not exist (has expired). The script on the client side reacts to this error by redirecting the end user to the login page and asking him to log in again.

    The next member ret is a boolean value that indicates the success or failure of the AJAX call. If it is false, the errorLevel value is checked for more details. The member message has a description of the error. The member payload is an extra object that you want to pass to the client side. Thanks to JSON, this object will be serialized and sent properly to the client side. The member payloadTypeName contains the name of the type of the object that is filled in the payload member, just in case you need it on the client side. Along with these members, you will find some static methods that help you create this object in a compact manner. Let us look at a typical Web Service method that I define:

    C#
    [WebMethod(true)]
    public GenericReturnObject GetContactNameFromID(int contactid)
    {            
       try
       {
          if (!IsLoggedIn)
             return GenericReturnObject.CreateSessionExpired();
          
          string contactName = Application.IBusinessLayer().IContacts.
                               GetContactNameFromID(AccountID, contactid);
          return GenericReturnObject.CreateSuccess("", 
                 new { ContactName = contactName });
       }
       catch (Exception ex)
       {
          return GenericReturnObject.CreateFromException(ex);
       }
    }

    A typical Web Service method would first check if the user is logged in or not (session expired or not), and if the session has expired, it will create the appropriate GenericReturnObject with this errorLevel value and return it to the client side for proper handling of this case. Next, we continue to the core of our function and pass it to the business layer, and then we create a success GenericReturnObject and return an anonymous object that has the return values that we want (ContactName). Notice how I catch all exceptions and return a GenericReturnObject with a fatal error level and the exception in the message member of the object. If I let exceptions to be thrown from Web Service methods, then on the client side, the failure function is going to be called. So, you will have to process two types of errors in your failure function on the client side: errors that come from your Web Service calls as exceptions (they could be business logic errors or fatal errors), and errors that come from calling AJAX Web Service methods themselves and communication errors between the browser and the server; this is going to complicate things for us.

    To make our lives easier, we do not allow exceptions from our Web Service calls. This way, your failure function on the client side can concentrate only on AJAX communication problems, and you can have one common failure function with one common action to do. This means that exceptions are now returned to the success function on the client side, and you will have to handle the cases for non-successful business calls to your Web Service (when the GenericReturnObject has an errorLevel). Well, this is where the common success function shines. The common success function can now handle the cases of GenericReturnObject that are returned that have generic errors like session expired, warning (react by popup message), or a fatal error by showing a fatal error popup window or similar. Finally, the success function of your specific Web Service call (not the common success function) can concentrate on the business logic of the method that you just called and not worry about the more common errors that happen.

  • Speeding up your AJAX calls:

    Last but not least, the main benefit is the topic of this article. How does the class WSAjaxCallsClass speed up my AJAX calls? There is a technique that developers sometimes use to speed up the loading of a website, which is to load different resources from different domains or sub domains of the website. For example, if our website is under example.com, then we will add the images under the images.example.com domain and not the main domain. The browser will open a limited maximum number of connections per domain (usually two), so if you have 10 resources, then these 10 resources will load with a maximum of two connections at a time. Putting the images under a different domain will make the browser open two connections for images and two connections for the rest of the site. You can do this for several sub domains if you want, and not just two. However, adding too many sub domains will eventually bring the performance down. A good, balanced method would be to use a different domain for images, scripts, and CSS files.

    XMLHttpRequest (XHR) is also limited by the number of connections of the domain the page belongs to (usually two per domain). Now, if we want to follow the same technique and divide our Web Service calls into several sub domains, we will soon sadly discover that this cannot be done because the browser will stop you and will not allow your script to access AJAX calls to domains other than the page’s domain that you are running the script in. So, why is XHR different from the rest, why do we have this restriction on it? Well, this is for security reasons, so that if you have an XSS vulnerability in your website, someone will not be able to inject code that will communicate via AJAX to a location other than the page’s domain (say the attacker's own domain), making XSS much more dangerous than it already is. Fortunately, there is a way around this limitation, and we can make AJAX calls faster by allowing more parallel connections at the same time. We will break out of the two connections per domain limitation.

    So, how do we break out of this constraint? We will use hidden IFrames, where each IFrame will connect to a separate sub domain of the main site and our WSAjaxCallsClass class will handle the task of dividing the AJAX requests properly on the IFrames depending on which IFrame is free and can accept calls.

    Say, we want four parallel connections to go at the same time (as opposed to just the default two of the browser), then we create four hidden IFrames and have each IFrame connect to a separate sub domain. For example, if the main site is example.com, then the sub domains would be 1.example.com, 2.example.com, 3.example.com, and 4.example.com. Now, to setup this, we need two things. First, we need to setup our DNS server to resolve these sub domains. We can easily add one record to our DNS server, with a wildcard to resolve all these sub domains (*.example.com). If adding a wildcard to your top domain like this leaves a bad taste in your mouth, then you can pick any sub domain and add the wildcard to it (*.ajax.example.com). Next, we need to tell IIS to map these extra domains to the same website. IIS does not allow you to use wildcards, so you need to add your sub domains manually. You do that by adding the extra domains as extra “host headers” for the website. On IIS 6, you do that by pressing the button Advanced on the ‘Web Site’ tab of the website’s Properties dialog.

    Now, some developers at this point are wondering about the communication with the hidden IFrames. The parent will not be able to communicate (via JavaScript) to the hidden IFrame (and vice versa) if the IFrame is on a different domain than the parent (example.com and *.example.com), so how do we overcome this obstacle? The answer is document.domain. If both your IFrame and your parent set the document.domain variable in their script to a common upper level domain, then they will be able to communicate. In this case, we will set them both to example.com.

    Now that we have a proper communication between the parent document and the hidden IFrames, it is time to delegate the proper AJAX calls to the IFrames to keep things transparent for our ASP.NET AJAX calls. How can we achieve that? The answer is deep inside the ASP.NET AJAX library. If you do some debugging when you do your AJAX calls, you will reach a function named Sys$Net$WebServiceProxy$invoke and then eventually reach a function named Sys$Net$XMLHttpExecutor$executeRequest. These two functions are intercepted and replaced with different versions by our class.

    The first function Sys$Net$WebServiceProxy$invoke is called when a new Web Service call is requested. The new overridden version of it will stop the Web Service call and just add it to the internal queue of the class WSAjaxCallsClass for further processing. The second function Sys$Net$XMLHttpExecutor$executeRequest is a beautiful function in that it is responsible for making the actual AJAX call. It is the function that creates the XMLHttpRequest object and starts the actual call to the server, and luckily for us, it is a very coherent function. Aall it needs is the “this” object that it runs in its context. The new overridden version of this function will intercept AJAX calls just before they happen, and will send each of the intercepted calls to one of our IFrames depending on which IFrame is free, so that the IFrame can run its own copy of Sys$Net$XMLHttpExecutor$executeRequest that it loaded, but with the “this” context that we give it.

    The next section will present a proof of concept web application that shows you the speed benefits that we are gaining, but don’t take my word for it, you can download it and run it yourself to see the results, or you can check a running version on here.

    The proof of concept application is attached with this article, and check my blog for the latest versions. Please read the next section for some instructions on how to run the project locally on your machine.

Proof of concept and results

I will present here screen captures with details under the image.

Screen 1

screen1.gif

This first screen introduces the application. The proof of concept project shows you a table with the columns: name, start, end, and time line.

The column ‘name’ has the name of the method called, 'start' has the start time, the ‘end’ column has the end time, and the ‘time line’ column has a simple graphical representation of the time line. Times are given by minutes, seconds, and milliseconds. There are 20 rows in the table for 20 separate AJAX calls. In the table’s header, you have the button that initiates the calls (here you can see it disabled as it has been already pressed and run), and you have the magical checkbox “Use multiple sub domains”. When you choose the multi option, the magic of IFrames happens. The drop down list beside the checkbox shows the number of parallel connections you want the queue manager to call at the same time. This first screen is running with one connection at a time without the multi option. See how the calls are done one after the other (ignore the last three boxes, they seem to be running at the same time, but this is because the HTML table was not wide enough). These calls wait 1000 milliseconds on the server side before returning, the extra milliseconds that you see (for example, the first call: 1028 ms.) are the network communication overhead time. Notice the total time, around 20 seconds, 1 second for each call, more or less. Note that I have taken these screen caps from the application, working locally on my machine, to make the results clearer.

Screen 2

screen2.gif

This screen shows you running with two parallel connections and without the multi option. Notice how each two calls are running at the same time. Also notice that the total time has dropped from 20 seconds to 10 seconds, half the time, since now we are running two at a time. At this point, we are still not using IFrames, we are using the normal browser behavior.

Screen 3

screen3.gif

Screen 3 shows that we are requesting three parallel connections at the same time, and we are still not using the multi option. Notice the time line column; notice how even though we are sending three calls to the browser at a time, the browser is only running two calls at a time and queuing the third call internally. The total time is still 10 seconds.

Screen 4

screen4.gif

Screen 4 shows that we are requesting four parallel connections at the same time, and we are still not using the multi option. Again, notice the time line column; notice how even though we are sending four calls to the browser at a time, the browser is only running two calls at a time and queuing the third and fourth calls internally. The total time is still 10 seconds.

Screen 5

screen5.gif

Screen 5 is showing that we are requesting one connection at a time, but this time we are using the multi feature (the checkbox is checked). This page now internally is using one hidden IFrame to do its AJAX calls. Notice each call, notice how these calls are slightly longer than the Screen 1 calls. This is the extra overhead of delegating calls to the IFrame and having the IFrame return the result to us. Ignore the last calls being under each other, this is just because the HTML table was not wide enough. The total time is around 20 seconds.

Screen 6

screen6.gif

Screen 6 is showing that we are requesting two connections at a time, but this time, we are using the multi feature (the checkbox is checked). This page now internally is using two hidden IFrames to do its AJAX calls. Notice each call, notice how these calls are slightly longer than the Screen 2 calls. Again, this is the extra overhead of delegating calls to the IFrames and having the IFrames return the results to us. The total time is around 10 seconds. The next screen is the interesting one.

Screen 7

screen7.gif

Screen 7 is the first screen that starts to show the speed benefits. Now we are requesting three parallel connections at the same time with the multi option. Notice now how we are able to break out of the two parallel connections per domain limitation of the browser. Our AJAX calls are now being served in three parallel calls. Notice how the total time now drops below 10 seconds, it is around 7.5 seconds.

Screen 8

screen8.gif

Screen 8 continues with the speed benefits. Now we are able to run four parallel connections at the same time using four hidden IFrames. Notice how the total time has dropped to a little over 5 seconds, that’s 50% increase in speed as compared to doing AJAX calls through one domain.

Screen 9

screen9.gif

Screen 9 uses five parallel connections using five hidden IFrames. Notice how the total time has dropped even more.

Screen 10

screen10.gif

Screen 10 is using six parallel connections using six hidden IFrames. Notice how the total time has dropped even more.

Screen 11

screen11.gif

Screen 11 is using seven parallel connections using seven hidden IFrames. Notice how the total time has dropped to 3.3 seconds as compared to 10 seconds with the standard way of calling AJAX methods, that’s around 70% increase in speed. Increasing connections further will not help much anymore; depending on the application, a decision has to be made on the optimum number of parallel connections to be used.

During my tests, I found an interesting find. I used Fiddler to take a closer look on how each of the browsers supported (IE, FF, Opera, Safari) behaved with respect to the order of the connections it opened to the server. First, I will present you with four screen caps. Each test was made with a different browser. Fiddler was used, and 20 parallel connections were made using 20 hidden IFrames in each of the screens. Fiddler is an HTTP debugging application, it actd as a proxy and gives you valuable information about your web application’s behavior.

Screen: IE with Fiddler with 20 parallel connections using 20 hidden IFrames

screenIEFiddler.gif

screenFiddlerIE.gif

Screen: FF with Fiddler with 20 parallel connections using 20 hidden IFrames

screenFF.gif

screenFiddlerFF.gif

Screen: Opera with Fiddler with 20 parallel connections using 20 hidden IFrames

screenOperaFiddler.gif

screenFiddlerOpera.gif

Screen: Safari with Fiddler with 20 parallel connections using 20 hidden IFrames

screenSafariFiddler.gif

screenFiddlerSafari.gif

It is an interesting find to see that IE seems to send the requests to the server (for the IFrames) in a stack order (last added, first served) which does not seem logical to me and which will naturally mess up the order of your calls! Firefox does the normal order (queue) first come, first served. Safari does things differently; it seems, it does not have a specific order, it might have some sort of internal queuing system that decides depending on other factors which connection goes first. Opera seems to do things in order too, much like Firefox, but the order is not perfect, it seems that it too decides based on other factors as well.

If you are going to download the project and test things yourself locally, you will have to add some virtual domains to your hosts file (C:\Windows\System32\drivers\etc\hosts). Edit this file and add some entries to it like this:

127.0.0.1    localhost.com
127.0.0.1    1.localhost.com
127.0.0.1    2.localhost.com

All the way to 20.localhost.com, then when you run your project in Visual Studio, you have to do two more things:

  1. Open up the file default.aspx and replace the text "http://?.localt.com:54835" with the top level domain of the entries you have added to the hosts file. Following the above example, that would be “?.localhost.com:54835”. I have configured the application’s project file to run with the above port always, 54835. If, for some reason, you have a different port when you run your application, then you have to update the port as well. There is a helper function called GetRandomizingSubdomainFromDocument that will get you the appropriate randomizing string needed from the current page’s URL, so you won’t need to hardcode things.
  2. The second thing you need to do is when you run the application from Visual Studio, it is going to open your browser with the domain “localhost”. If you try to run your examples like this, you will get access denied in your JavaScript, and you should replace the localhost in your browser and point it to the new top level virtual domain you have created in your hosts file. Following the above example, that would be localhost.com. I hope I have made this clear enough.

Using the code in your applications

In this section, I will write the steps needed to add the code in this article to your own ASP.NET Web applications. The code works with IE7, Firefox 2.0, Opera 9.5, and Safari 3.0. First of all, you need to add three files to your project. You can add them anywhere, but I advise you to add them under the scripts subfolder. If you decide to add these files under a different folder, you need to update the variable WSAjaxCallsClass_ProxyScriptLocation found in the WebServiceAjaxCallsHelper.js file with the appropriate value.

  1. vnetLinkedlist.js: A double linked list implementation. You can use this file in any project you want if you need a double linked list. The linked list is needed by the WSAjaxCallsClass class to keep track of the queued calls.
  2. WebServiceAjaxCallsHelper.js: This is the main JavaScript file that has most of the code in it. It has the class WSAjaxCallsClass. At the time of this writing, its version was 1.0.9.
  3. WebServiceAjaxCallsHelperIframe.aspx: This form is loaded into each of the hidden IFrames. It contains code that executes the delegated ASP.NET AJAX calls.

Next, you need to add a ScriptManager (or a ScriptManagerProxy) to the pages that you wish do call ASP.NET AJAX Web Service methods. Make sure you add to your ScriptManager, two extra files in the scripts section, vnetLinkedlist.js and WebServiceAjaxCallsHelper.js. You can take a look at the file default.aspx in the project file attached with this article. Next, you need to call the Initialize function of the class WSAjaxCallsClass.

C#
WSAjaxCallsClass.Initialize(GenericSuccess, GenericFailure, 
    "http://?.localt.com:54835", channels, true, true);

The Initialize method takes six parameters. The first parameter is the common success method. The second parameter is the common failure method. The third parameter is the sub domain URL, it should have a question mark where the engine will replace it with the appropriate number depending on the number of invisible IFrames created. Note that if you pass null to the third parameter, you disable the multi domain AJAX calls feature. The fourth parameter takes the number of parallel connections you wish to have. The fifth parameter takes a boolean whether or not to ignore repetitive calls to the same method before the method has returned. The sixth parameter takes a boolean that decides how to ignore the repetitive calls; if you pass true, it will always keep the last queued call (this is the logical choice and it works in most cases); if you pass false, then repetitive calls will be ignored and completely discarded. Note that the public JavaScript code is properly commented with XML comments for JavaScript intellisense in Visual Studio 2008.

Now, every time you want to make an AJAX call, calls are transparently routed to the class WSAjaxCallsClass. And that’s about it. Do not forget the steps you need to do on the IIS and DNS levels, described above in this article.

You can also use the file vnetLinkedlist.js in your own projects if you need a client-side JavaScript double linked list implementation. I will post on my blog later to describe how to use this file.

What's next?

The technique described here using hidden IFrames to enable multiple AJAX calls through multiple sub domains can be used on languages other than ASP.NET; however, the implementation provided is for ASP.NET.

The next article/code that I will write will implement this technique on ASP.NET UpdatePanels to make things faster for UpdatePanels much in the same way, doing parallel AJAX calls through multiple sub domains. If the reader has any comments about this idea, I would like to know about them.

Someone suggested that I add automatic retries to timed-out AJAX calls. In my work, I never had many problems with timeouts, and I did not face a situation where one or more retries would fix things. If you, the reader, think that I should add such a feature, please let me hear more about it, your comments are very much welcomed. And also, if the reader has any other comments or features that he or she would like to see added to this class, please let me know on my blog.

I hope that this article will benefit a lot of developers and web applications out there. If this code helped you speed up your particular site, I would like to hear your success story.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)