Click here to Skip to main content
Click here to Skip to main content

HTTP Handler to Combine Multiple Files, Cache and Deliver Compressed Output for Faster Page Load

By , 28 Aug 2008
 
Prize winner in Competition "Best ASP.NET article of August 2008"

Introduction

It's a good practice to use many small JavaScript and CSS files instead of one large JavaScript/CSS file for better code maintainability, but bad in terms of website performance. Although you should write your JavaScript code in small files and break large CSS files into small chunks, when a browser requests those JavaScript and CSS files, it makes one HTTP request per file. Every HTTP request results in a network roundtrip from your browser to the server and the delay in reaching the server and coming back to the browser is called latency. So, if you have four JavaScripts and three CSS files loaded by a page, you are wasting time in seven network roundtrips. Within the USA, latency is average 70ms. So, you waste 7x70 = 490ms, about half a second of delay. Outside USA, average latency is around 200ms. So, that means 1400ms of waiting. The browser cannot show the page properly until CSS and JavaScripts are fully loaded. So, the more latency you have, the slower the page loads.

How Bad is Latency

Here's a graph that shows how each request latency adds up and introduces significant delay in page loading:

image_16.png

You can reduce the wait time by using a CDN. Read my previous blog post about using CDN. However, a better solution is to deliver multiple files over one request using an HttpHandler that combines several files and delivers as one output. So, instead of putting many <script> or <link> tags, you just put one <script> and one <link> tag, and point them to the HttpHandler. You tell the handler which files to combine and it delivers those files in one response. This saves browser from making many requests and eliminates the latency.

image_18.png

Here you can see how much improvement you get if you can combine multiple JavaScripts and CSS into one.

In a typical web page, you will see many JavaScripts referenced:

<script type="text/javascript" src="http://www.msmvps.com/Content/JScript/jquery.js">
</script>
<script type="text/javascript" src="http://www.msmvps.com/Content/JScript/jDate.js">
</script>
<script type="text/javascript"
    src="http://www.msmvps.com/Content/JScript/jQuery.Core.js">
</script>
<script type="text/javascript"
    src="http://www.msmvps.com/Content/JScript/jQuery.Delegate.js">
</script>
<script type="text/javascript"
    src="http://www.msmvps.com/Content/JScript/jQuery.Validation.js">
</script>

Instead of these individual <script> tags, you can use only one <script> tag to serve the whole set of scripts using an Http Handler:

<script type="text/javascript" 
    src="HttpCombiner.ashx?s=jQueryScripts&t=text/javascript&v=1" >
</script>

The HTTP Handler reads the file names defined in a configuration and combines all those files and delivers them as one response. It delivers the response as gzip compressed to save bandwidth. Moreover, it generates a proper cache header to cache the response in the browser cache, so that, the browser does not request it again on future visits.

In the query string, you specify the file set name in the "s" parameter, then the content type in the "t" parameter and a version number in "v" parameter. As the response is cached, if you make changes to any of the files in the set, you will have to increase the "v" parameter value to make browsers download the response again.

Using this HttpHandler, you can deliver CSS as well:

<link type="text/css" rel="stylesheet"
 href="HttpCombiner.ashx?s=CommonCss&t=text/css&v=1" ></link>

Here's how you define the sets in web.config:

<appSettings>
    <add key="jQueryScripts" 
        value="~/Content/JScript/jquery.js,
            ~/Content/JScript/jDate.js,
            ~/Content/JScript/jQuery.Core.js,
            ~/Content/JScript/jQuery.Delegate.js,
            ~/Content/JScript/jQuery.Validation.js"    
    />
    <add key="CommonCss"
        value="~/App_Themes/Default/Theme.css,
            ~/Css/Common.css,
            ~/Controls/Grid/grid.css"
    />
</appSettings>

Example Website that Uses HttpCombiner

I have made a simple test website to show you the use of HttpCombiner. The test website has two CSS and two JS files. The default.aspx requests both of them using only one <link> and <script> tag via the HttpCombiner.ashx.

image_4.png

Here's the content of the Default.aspx:

image_12.png

Here you see, there's one <link> tag sending a request to HttpCombiner.ashx to deliver the set named Set_Css and a <script> tag asking for a set Set_Javascript.

Files that belong to these two sets are defined in the web.config file:

image_30.png

Here's how the handler works:

  • First it reads the file set name passed in the "s" parameter
  • Then it gets the files defined in the web.config for the set
  • It reads individual files and stores in a buffer
  • The buffer is then gzip compressed
  • The compressed buffer is sent to the browser
  • The compressed buffer is stored in ASP.NET cache so that subsequent requests for the same set can be directly served from cache without reading the individual files from file system or external URL

Benefits of the handler:

  • It saves network roundtrip. The more files you can put in one set, the more you save in network latency. This improves performance.
  • It caches the total combined response as compressed and thus saves reading file from file system and compressing it again and again. This improves scalability.

How the HttpHandler Works

First the handler reads the set, type and version to use from the query string:

image_20.png

If the set has already been cached, the it's written directly from cache. Otherwise the files in the set are loaded one by one and stored in a MemoryStream. The MemoryStream is compressed using GZipStream if browser supports compressed output.

image_22.png

After combining all the files and compressing it, the combined bytes are cached so that subsequent requests can be directly served from cache.

image_24.png

The GetFileBytes function reads a file or URL and returns the bytes. So, you can use Virtual Path to files within your website or you can use URL to external Javascript/CSS files hosted on another domain.

image_26.png

The WriteBytes function has much wisdom in it. It generates a proper header based on whether the bytes are in compressed form or not. Then it generates proper browser cache header to make browser cache the response.

image_28.png

How to use this handler::

  • Include the HttpCombiner.ashx in your project
  • Define the file sets in the <appSettings> section of your web.config
  • Change <link> and <script> tags throughout your website to point to HttpCombiner.ashx in this format:
    HttpCombiner.ashx?s=<setName>&t=<contentType>&v=<versionNo>

Conclusion

That's it! Make your website faster to load, get more users and earn more revenue.

You should also read my earlier articles about deferring and combining script loading for faster perceived speed and how to make best use of browser cache.

License

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

About the Author

Omar Al Zabir
Architect BT, UK (ex British Telecom)
United Kingdom United Kingdom
Member

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSlight improvement to the authors excellent ideamemberNorman Solomon18mins ago 
This is a very good idea which is very well implemented, and the code works fine with ASP.NET 4.0
 
One slight improvement (which I have implemented in my own project) is to add a "d" parameter which is tested in the HttpCombiner class code.
 
If the "d" parameter value == "true" I use a GUID for the "v" parameter, so that I don't have to change the version numbers in the HTML by hand during development.
 
Then for production you can set the "d" parameter to "false", and update the "v" parameter if the production website JS or CSS code has changed.
QuestionVersion updatemembersafiyullah12 Apr '13 - 0:11 
Dear Omar,
 
Excellent article... It works fine.. But whenever i changed my js or css I need to change the version number for all aspx file.. How can we tackle this.. Please let me know..
 
Thanks
Safiyullah
GeneralMy vote of 5membersmfarooq5 Nov '12 - 22:14 
You are awesome Smile | :)
QuestionAll CSS and JS are not being loadedmemberAmita Bhakkad18 Sep '12 - 21:00 
Hi Omar,
 
Great article and I feel it would definitely help to improve performance of my project. However I am facing some issues while integrating it. I am including the httpcombiner in an ascx control which is being reffered from Master page.
This does not give any error but all css and JS are not being loaded. When I debugged httpcombiner and noticed that all files are being converted to byte array.
Not sure what is happening next which leads to few JS/CSS not being load.
Can you give some pointers on what could be wrong? Could it be a size issue? I have around 14 JS and 6 css files of heavy size in MBs.
 
Regards,
Amita
AnswerRe: All CSS and JS are not being loadedmemberOmar Al Zabir19 Sep '12 - 10:59 
Please try manually combining all the files into one file in notepad and then try using that combined file. If it does not work, then the individual files have some problem which breaks the combined file.
 
If it does work, then try loading the combined file via httpcombiner. If it still does not work, then need to investigate further.
(regards) => "Omar AL Zabir"
+ "C#, ASP.NET MVP"
+ "http://omaralzabir.com";

GeneralRe: All CSS and JS are not being loadedmemberfarooq84022 Jan '13 - 2:31 
When i'm trying to combine JQuery and JQuery UI into a single file using httpcombiner its giving errors. Ref: http://alpha.360invite.com/test1.aspx
GeneralRe: All CSS and JS are not being loadedmemberfarooq84028 Jan '13 - 0:47 
Finally got the solution for the problem. Its just basic rule of java script programming having the semicolons in the end and calling the function after definition of the function has been defined.
QuestionIts really nice idea .... butmemberMuhammad Shoaib Raja29 Jun '11 - 21:14 
hi omar:
i have implemented this idea in my application and its showed great results so far.
 
But if one or many files being merged are going to change then we have to change its name to get the latest resource from server and then cache it again. I think it is not very handy solution we need to be little careful about this solution if we are placing our handler URL in many files (obviously we have to change that query string part in all those files etc).
 
FOR SERVER:-
I think we can use server side caching dependency for that purpose if any of list files is changed then server will create new "combined file".
 
FOR CLIENT:-
isn't it will be better to use Etag (even it is not recommended by Yahoo Best Practices ) to check the resource change before getting locally cached copy.
 
Anyway i always try to keep up to your recommendation, and experience. Thumbs Up | :thumbsup:
 
Regards, Muhammad Shoaib
AnswerRe: Its really nice idea .... butmvpOmar Al Zabir4 Jul '11 - 7:31 
ETag does not prevent the request from browser to the server. Each roundtrip is expensive. Sometimes from 70ms to 300ms per request.
 
You don't need to change the file names. You can use a version suffix to change the URL to the Handler. For ex,
http://youdomain.com/httpcombiner.ashx?files=a,b,c&version=1
 
You change the version only whenever there's an update to the file.
(regards) => "Omar AL Zabir"
+ "C#, ASP.NET MVP"
+ "http://omaralzabir.com";

Generalfyi ...memberMichael Stilson7 Apr '11 - 4:47 
the textual code example defines:
src="HttpCombiner.ashx?s=jQueryScripts&amp;t=text/javascript&amp;v=1"
 
the image code example defines:
src="HttpCombiner.ashx?s=jQueryScripts&amp;t=type/javascript&amp;v=1"
 
fyi, specifying "type/javascript", instead of "text/javascript", will generate the following warning in your browser:
 
! Resource interpreted as Script but transferred with MIME type type/javascript.
GeneralMy vote of 5memberJennOpp8 Feb '11 - 11:52 
Very clear, great code, and it works!
QuestionHow can i achieve this in sharepoint site?memberJim Mathew27 Sep '10 - 5:31 
Nice article Omair.
How can i implement this in sharepoint site? i have about 10 javascript files stored in style library tried to use your`handler but it didnt work. do i need to store the files in 12 Hive folder?
Could yo plz help me to make it work?
GeneralThere is Error in returns by the this handler [modified]memberRavenet31 Jul '10 - 3:11 
Hi Omar,
 
This is great work , but there is error return from the handler sometimes
 
Error Message
 
:Specified argument was out of the range of valid values. Parameter name: offset Source :System.Web Target Site :Void Write(Byte[], Int32, Int32) Stack Track : at System.Web.HttpResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count) at HttpCombiner.WriteBytes(Byte[] bytes, HttpContext context, Boolean isCompressed, String contentType) at HttpCombiner.ProcessRequest(HttpContext context) at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
 
I'm expecting your solutions to this.
thank you
 
Any solutions to this problem omar?
RRave
MCTS,MCPD
All experts are welcome to http://codegain.com
 

 

modified on Sunday, August 1, 2010 9:23 PM

GeneralParser Errormemberrashadkk12 Jul '10 - 20:34 
what could be the reason for such error:
Could not create type 'WebApplication.HttpCombiner'.
GeneralA better way to serve dynamically created css/jsmembertaliesins19 Apr '10 - 3:34 
Serving requests with an http handler that dynamically creates a crushed js or css file per a page is not a good idea. It is better to crush your entire sites js and css files into one file and rather serve that. This way the browser loads it once and its cached. All further requests just load from cache. Dynamically crushed js and css files create a file per page. So you might be re-serving the same css and js files for every page.
 
You can then let the web server serve the crushed css/js file. IIS implements kernel mode caching, which is the fastest way to serve any static file.
 
So if you want a performant, scalable solution that works in web farms check out:
 
http://code.google.com/p/talifun-web/w/edit/CrusherModule
GeneralRe: A better way to serve dynamically created css/jsmvpOmar Al Zabir19 Apr '10 - 7:28 
The handler combine multiple files into one "in-memory" and caches it on both server as well as on browser. It emits the proper response cache headers to ensure the files are all cached on the browser and browser does not fetch them again. It does not generate one file per page. Nor does it process the same files twice. Once the files are read and combined, subsequent requests are served directly from ASP.NET cache. Thus no additional Disk I/O.
 
Just combining the files using a crusher does not give the caching benefit unless you setup IIS to emit the right cache headers for the static files. This module overcomes the issue with IIS when you have a shared hosting and cannot run on Content Expiration on IIS.
(regards) => "Omar AL Zabir"
+ "C#, ASP.NET MVP"
+ "http://omaralzabir.com";

GeneralRe: A better way to serve dynamically created css/jsmembervardars12 Aug '10 - 21:44 
I think Taliensins is right. 'cause if different pages use different and also same js files, then they will be seperately served and cached. For example 2 pages uses same jQuery framework but different jQuery plugins. Then if you want them to seperately get js files through httphandler, 2 different gzipped and served file will be. (both have jQuery framework)
 
It would be a problem, but gzipping and caching can benefit these problems rather than using the old way.
GeneralNot working with Url RewritingmemberRavenet25 Dec '09 - 0:09 
Hi Expert,
 
This is nice idea, but when we are use the url re writing.its not working.
when we are in home its working, when i move to sub menu using url rewriting its not load css and javascript.
 
Please give the solutions
 
thank you
 
RRave
MCTS,MCPD
All experts are welcome to http://codegain.com
 

 

GeneralRe: Not working with Url RewritingmvpOmar Al Zabir25 Dec '09 - 0:13 
Why does it not work?
 
Regards,
Omar AL Zabir
Visual C# MVP

GeneralRe: Not working with Url RewritingmemberRavenet25 Dec '09 - 0:28 
Thank you for your prompt response,
 
Say i have added httpcombiner in master page, when I'm in home page its ok.now i'm navigating fully virtual url, when i was in that page my jquery function not working and menu styles alos gone.
 
i don't know y. now i'm try to use the firefug and checking path
 
thank you
 
RRave
MCTS,MCPD
All experts are welcome to http://codegain.com
 

 

GeneralRe: Not working with Url RewritingmemberRavenet25 Dec '09 - 0:43 
Hi
 
This message i got from firefug.
 
The resource from this URL is not text: ...x?s=Set_Javascript&t=type/javascript&v=1
 
u is undefined
anonymous(Object name=u, Object base_uri=Object)HttpComb...cript&v=1 (line 91)
anonymous(Object name=u, Object name=nh)HttpComb...cript&v=1 (line 91)
anonymous()HttpComb...cript&v=1 (line 91)
anonymous()HttpComb...cript&v=1 (line 91)
HttpCombiner.ashx?s=Set_Javascript&t=type/javascript&v=1()
 
thank you
 
RRave
MCTS,MCPD
All experts are welcome to http://codegain.com
 

 

GeneralMinify and obfuscate with YUI CompressormemberKellros5 Oct '09 - 1:59 
Hello,
 
It wouldn't be too hard to implement css/js minification with this HTTP Handler to minify and obfuscate the javascript or just minify the css for better compression (supposedly this can reduce the combined file size by 40% ish before gzip/deflate compression).
 
Great article Omar, been a fan of yours for just over a year now.
 
Links:
Single file minify/obfuscate code via YUI Compressor in C#
YUI Compressor
QuestionCannot update file after cachingmemberMember 53116015 Sep '09 - 4:17 
Hi,
Initially it worked fine.
Then I made changes in my .js file, changed version and uploaded, but cannot see changes and get error.
I addeed one function in .js file and when I click on web page, it gives me error.
 
Let me know how to fix.
Thanks in advance,
best,
ubuser
AnswerRe: Cannot update file after cachingmvpOmar Al Zabir15 Sep '09 - 5:42 
your old js is cached in browser. that's why it's not downloading anymore.
 
You need to add some version suffix to the url.
 
Regards,
Omar AL Zabir
Visual C# MVP

GeneralRe: Cannot update file after cachingmemberMember 53116015 Sep '09 - 6:17 
Hi Omar,
I did try to change version. Initially I was using version number like 2009.09.14.05 and did not work. Then I changed version number like 22 (two digit number) and still same problem. Basically my intention was that if I change file in future then I can change version number to 2009.09.15.01 (today's date 2009.09.15.xx) so I don't get caching problem. You think using version number like 2009.09.14.05 causing problem? And if so, how can I remove from cache and refresh with updated file manually?
 
I appreciate for your quick response.
Let me know.
 
Best,
ubuser

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 28 Aug 2008
Article Copyright 2008 by Omar Al Zabir
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid