[OPEN] [#129] Can Ajax requests send If-Modified-Since headers

  1. #1

    [OPEN] [#129] Can Ajax requests send If-Modified-Since headers

    Hi,

    I've been struggling on this one for a bit. I have a store with an HttpProxy pointing to an ashx.

    In the ASHX I am actually able to cache quite aggressively:

    context.Response.Cache.SetExpires(DateTime.Now.AddHours(10d));
    context.Response.Cache.SetCacheability(HttpCacheability.Public);
    context.Response.Cache.SetValidUntilExpires(true);
    context.Response.Cache.SetLastModified(lastModified); // <!-- I am able to get this value from my system
    context.Response.Cache.VaryByHeaders["Accept-Language"] = true;
    context.Response.Cache.VaryByParams["id"] = true;
    context.Response.Cache.VaryByParams["dir"] = true;
    context.Response.Cache.VaryByParams["limit"] = true;
    context.Response.Cache.VaryByParams["sort"] = true;
    context.Response.Cache.VaryByParams["start"] = true;
    
    context.Response.Write(JSON.Serialize(pagingData));
    You will see that I use a 2-pronged approach in my caching:

    1. I can tell the browser to cache the response (e.g. 10 hours in the above)
    2. Before 10 hours, if the exact same request is made (same parameters and same language) within that time, the browser won't bother making an http request (it just works under the hoods by the browser which returns the cached response from earlier)
    3. After 10 hours the browser will request the server. The request should ideally include the If-Modified-Since header (because it was set in the above code). This gives the server a chance to say there have been no modifications since the last modified time (10 hours ago, or even before) and this means the server doesn't have to send back any data (or request it from the back end), so it becomes a really short http response and the browser just fetches the previously cached request from its own internal cache and gives it back to the underlying XHR object - in other words it should be transparent.


    To explain further how scenario 3) would normally work, I would write something like this:

    if (IsClientCached(lastModified, context))
    {
        context.Response.StatusCode = 304;
        context.Response.SuppressContent = true;
        context.Response.StatusDescription = "Not Modified";
        context.Response.AddHeader("Content-Length", "0");
        return;
    }
    Where IsClientCached is determined like this:

    private bool IsClientCached(DateTime contentModified, HttpContext context)
    {
        string header = context.Request.Headers["If-Modified-Since"];
    
        if (header != null)
        {
            DateTime isModifiedSince;
            if (DateTime.TryParse(header, out isModifiedSince))
            {
                return isModifiedSince > contentModified;
            }
        }
    
        return false;
    }
    However, for scenario 3) when I look in firebug, I don't see the If-Modified-Since header being sent as part of the Store/Proxy's http request and so the server ends up getting and returning all the data again which it may not have needed to do.

    There is one way I can think of it to manually send this header:

    <ext:Store ID="ImagesStore" runat="server" AutoLoad="false" RemoteSort="true">
        <Proxy>
            <ext:HttpProxy Method="GET" Url="GetImages.ashx" DisableCaching="false">
                <Listeners>
                    <BeforeLoad Handler="if (#{ImagesStore}.reader.jsonData) {
                        this.conn.headers = Ext.apply({}, { 'If-Modified-Since' : /* get the value somehow */ }, this.conn.headers);
                    }" />
                </Listeners>
            </ext:HttpProxy>
        </Proxy>
        
        <!-- etc -->
    </ext:Store>
    However, there are two problems with the above:

    1) Knowing what the If-Modified-Since value was is difficult to know, unless I send it as part of the response body (e.g. by subclassing Paging<T> to have that property in it).

    2) Even with the subclass approach it then that means in JavaScript I need to cache the responses for each variation of the request (because I can vary the cache by numerous Store parameters such as paging, pagin size, sort, etc). I'd be trying to re-implement what browsers already do transparently...

    (Also, when I do try this, I finally get the server to return a 304, but I then see the Request Failure window popup showing status 304 and Not modified. So even if I do get this to work, is the AJAX response handler only looking for statuses of 200?)

    Am I missing a trick on how to do this?

    Let me know if you need a reproducible example. This is in Ext.NET v1 btw; I've not had the time to try it in v2 yet...
    Last edited by Daniil; Jan 18, 2013 at 4:27 AM. Reason: [OPEN] [#129]
  2. #2
    Hi,

    Very interesting scenario. Thanks for the detailed explanation as always.

    For now, I am not sure it is possible to implement easily. But we will look into it how it might be implemented without caching responses manually on client.

    However, for scenario 3) when I look in firebug, I don't see the If-Modified-Since header being sent as part of the Store/Proxy's http request
    I am not sure why you are expecting this header should be applied for a request on client automatically. As far as I can understand there is no such functionality. So, your approach to customize
    this.conn.headers
    looks to be a single one to add some additional header to a request.

    (Also, when I do try this, I finally get the server to return a 304, but I then see the Request Failure window popup showing status 304 and Not modified. So even if I do get this to work, is the AJAX response handler only looking for statuses of 200?)
    There is the following code within the handleTransactionResponse function of Ext.lib.Ajax.
    if ((httpStatus >= 200 && httpStatus < 300) || (Ext.isIE && httpStatus == 1223)) {
        responseObject = createResponseObject(o, callback.argument);
        if (callback.success) {
            if (!callback.scope) {
                callback.success(responseObject);
            }
            else {
                callback.success.apply(callback.scope, [responseObject]);
            }
        }
    }
    The rest statuses are considered as failure ones.

    Again, to say more concretely we need some time to investigate the problem deeper.
  3. #3
    Thanks for the response. Not sure why I expected If-Modified-Header to automatically be sent as part of the ajax request - I know browsers do it automatically for regular GETs so maybe I just assumed it would be the same here. I've not looked deeply enough into the Ext AJAX library to see if headers get stripped out or not.

    Given that for the moment it is not being sent, I could try to cache the information using a subclass of Paging<T> so for each request I cache the last modified time that is returned by the Pageing<T> subclass and I can try to cache based on the parameters (limit, sort, etc). Only limitation with this is if I want to vary caching by other headers such as the Accept-Language header; I can maybe get server to send me some kind of request identifier and I can use that as my cache key.

    For the Ext implementation of treating stuff outside of 200 - 300 as an error I don't think that is right. I was reading the W3C specs on the XHR status and it doesn't have such restrictions. 304 can be a great strategy to include in performance/caching techniques (not the only one, for sure).

    I'll try to play with some examples later this week if I can get some spare time and if it seems okay will raise an issue with Sencha. I am guessing that they (and others, like jQuery and the rest) may have the same issue in that if a 304 comes from the server there isn't any code you can write to get the original data for that request; the browser has it and usually deals with it seamlessly. If the browser doesn't do it for the XHR behind the scenes then 304s could be difficult to do with XHR unless you have your own mechanism to cache data on the client (like I may have in my current example). In that case Sencha shouldn't be treating as 304 as error - maybe by default it should but at least have a way to handle this.
  4. #4
    I am not sure as well why 304 is considered as failure. Maybe, due to the fact that there is no implementation for caching.

    At the moment, we suspect there is no ready solution in ExtJS/Ext.NET to cache HttpProxy responses.

    Though we will try to play with it more.

    By the way, here is the interesting article related to the problem.
    http://blog.httpwatch.com/2009/08/07...portant-facts/
  5. #5
    Quote Originally Posted by Daniil View Post
    I am not sure as well why 304 is considered as failure. Maybe, due to the fact that there is no implementation for caching.

    At the moment, we suspect there is no ready solution in ExtJS/Ext.NET to cache HttpProxy responses.
    I imagine that to be the case too.

    Quote Originally Posted by Daniil View Post
    Though we will try to play with it more.

    By the way, here is the interesting article related to the problem.
    http://blog.httpwatch.com/2009/08/07...portant-facts/
    Yeah, those are the kinds of techniques I am basically trying to use. The Expires header stuff works seamlessly; it is just the 304. When you combine the two it is usually a lot better :)

    Will be interesting to see what you come up with - I suspect Sencha should really try to cache data if 304 support is (optionally?) enabled, but I suspect it could lead to a lot of memory requirements on the browser which might not be easy to support. Not sure.
  6. #6
    Unfortunately, we still have no more concrete things to say here, had no chance to look at that deeper.

    But we still hope to look into it again soon. Apologize for the delay.
  7. #7
    Hi Anup,

    Apologize for the long delay.

    Is it still actual?
  8. #8
    Hi Daniil,

    I guess this is an existing issue, but it is also not an easy one to solve for generic frameworks, so I guess we should maybe close this thread...

    Thanks!
  9. #9
    Ok, I created an Issue.
    https://github.com/extnet/Ext.NET/issues/129

    Hopefully, at some moment we will be able to have another look at the problem.

Similar Threads

  1. How to send a form using Ajax
    By Greg44 in forum 1.x Help
    Replies: 3
    Last Post: Jun 18, 2012, 6:56 AM
  2. Replies: 5
    Last Post: Jul 25, 2011, 6:14 PM
  3. [CLOSED] Adding params as jsonData for jsin Ajax requests
    By r_honey in forum 1.x Legacy Premium Help
    Replies: 7
    Last Post: May 06, 2010, 7:31 AM
  4. Replies: 0
    Last Post: Jul 22, 2009, 4:40 AM
  5. Replies: 2
    Last Post: Jun 30, 2009, 4:03 PM

Posting Permissions