Hello% habrauser%.
I want to share with you the idea / implementation of an analogue of ajax technology which, unlike the first, is devoid of its main drawbacks. From the
wiki about ajax, we know that:
Benefits
- Traffic saving;
- Reducing server load;
- Accelerate interface response;
disadvantages
- Lack of integration with standard browser tools;
- Dynamically loaded content is not available to search engines;
- Old methods of accounting site statistics become irrelevant;
- The complexity of the project;
')
Invented idea Template client cache (hereinafter referred to as TCC) has the same advantages as ajax, but is completely devoid of its drawbacks.
Idea
In the case of ajax, the main “trick” is to load individual blocks, which significantly saves traffic. But the big problem with ajax is strong incompatibility with both old browsers and search engines, as well as complex integration into existing projects. Solving the puzzle by rewriting the next site into blocks with ajax, I thought, not with the help of modern technologies it is impossible to achieve a similar result, but with less implementation costs and compatibility? So ... let's finish pouring water ... the idea to the point of madness is simple: load only what we need to update (well, really, it looks like an ajax idea). The whole question is in implementation. The basis was taken to modern standards to support some storage for the needs of JS (such as localStorage) and the idea of ​​caching templates on the server side. By crossing them we got a TCC. That is, as in the case of templates on the server, the entire page is usually divided into pieces, which are processed and formed by separate files (pieces of code), are combined and thrown away by the user. Often, some pieces are cached and not re-formed for some time. The idea is to transfer this cache of some pieces to the client side and create infrastructure for transferring information from the client to the server and back to maintain the cache in a liquid state. Those. it turns out that when the page is loaded we will be just as usual to form it on the server side, skipping some blocks, but not taking them from the server cache, but simply leaving them empty, and in this form we will transfer to the client where it will be merged them with the existing cache and display the summary page to the user. From the description it is clear that:
- + We will not form unnecessary blocks and will not waste time on the server side (plus performance, including the cost of caching these blocks);
- + We will not transmit extra (repetitive) information from the server to the killer (plus loading speed and traffic reduction);
- + We essentially do not change the structure and method of document formation (plus the simplicity of integration into existing engines / sites, as well as the absence of problems with search engines and old browsers, since if they do not have TCC support, they will always receive the full version of the page from all blocks);

Total, the sequence of the TCC can be represented as follows:
- The client requests a page;
- The server generates html by dividing it into logical blocks with special block markers;
- The client receiving this page separates it by markers and places it in the cache;
- The client follows the link to another page of the same site;
- Server. knowing which blocks are placed in the cache, forms the page consisting of empty block markers and filled block markers only for those blocks that are not relevant in the cache (that is, in fact, forms a diff between the old and the new page);
- The client, having received such a diff, retrieves the necessary blocks from the cache and fills the page;
- Profit;
Implementation
It is clear that I will collect a demo example and for short lines, and therefore I will close my eyes to many things (such as, for example, using jQuery for parsing html, as well as some relief for getting quick results).
A list of what can be improved and as I will give below, so you shouldn’t treat the demo example critically (in terms of application in production).
First we need to decide how we will label the blocks for caching. For simplicity, I took the standard html-element span, since it has little effect on the blocks it contains (that is, it works fine with floating nested blocks and with inline context). For marking that this is a marker, a special class was chosen, named for the following principle:
tcc_ <ID> _ <MODE> [_ <TTL>];
Where:
<ID> - Unique block number;
<MODE> - Caching mode (about it below);
<TTL> - cache lifetime;
The caching mode is designed to indicate to the client which block it is dealing with. Three caching modes were distinguished:
r (realtime) is a block that cannot be cached (updated every time the page is reloaded);
s (server) - a block formed on the server and subject to caching (created if it is necessary to form or replace a block in the cache);
c (client) - the block formed on the server is empty, in which the client must place the cached data;
Let's move on to the implementation (the server code will be in PHP, but I think it will not be difficult to rewrite it to anything). To begin with, we will write a simple function of forming the beginning of a block (in fact, this function should form a standard span with a special class based on some input data).
function blockStart($id, $ttl = false)
{
global $curTime;
global $tcc;
if (!$ttl)
{
echo '<span class="tcc_' . $id . '_r">';
return true;
}
else if (isset($tcc[$id]) && ($tcc[$id] > $curTime))
{
echo '<span class="tcc_' . $id . '_c_' . $tcc[$id] . '">';
return false;
}
else
{
echo '<span class="tcc_' . $id . '_s_' . $ttl . '">';
return true;
}
}
As you can see, the function uses two external variables, $ curTime (the current time is in the right format), as well as $ tcc (the list of client caches, and we'll get it below on the server from the client). The rest of the logic of the function is very simple and straightforward.
Next, we need on the client side (i.e., on the JS side) to write some set of service functions for working with the stack (in this case with localStorage).
function storSet(id, ttl, value)
{
localStorage.setItem('tcc_' + id, ttl + ':' + value);
}
function storGet(id, ttl)
{
var data = localStorage.getItem('tcc_' + id);
if (data == null) return null;
var data_ttl = data.substr(0, 12).split(':', 1)[0];
if ((data_ttl != ttl) || (data_ttl <= curTime)) return null;
return data.substr(data_ttl.length + 1);
}
Here, too, everything is simple ... in storSet we save the data without forgetting to specify id and save ttl to check the validity of the blocks. In storGet, we extract the block by its id, check the validity of ttl and, if successful, return the cached block.
Then we need a megaparser who will parse everything received from the server.
function tcc()
{
var item;
while ((item = $('[class^=tcc_]:first')) && (item.length != 0))
{
var cn = item.attr('class').split('_');
if (cn[2] == 's')
{
var nItem = item.clone();
var cItem;
while ((cItem = nItem.find('[class^=tcc_]:first')) && (cItem.length != 0))
{
cItem.replaceWith('<span class="cache_' + cItem.attr('class') + '"></span>');
}
storSet(cn[1], cn[3], nItem.html());
}
else if (cn[2] == 'c')
{
var data = storGet(cn[1], cn[3])
if (data == null)
{
alert('critical error');
}
else
{
var nItem = $('<span>' + data + '</span>');
var cItem;
while ((cItem = nItem.find('[class^=cache_tcc_]:first')) && (cItem.length != 0))
{
var ncn = cItem.attr('class').split('_');
var rItem = item.find('[class^=tcc_' + ncn[2] + '_]');
if (rItem.length != 0)
{
cItem.replaceWith('<span class="' + rItem.attr('class') + '">' + rItem.html() + '</span>');
}
else
{
cItem.remove();
}
}
item.replaceWith(nItem.html());
}
}
if (cn[2] != 'c')
{
item.replaceWith(item.html());
}
}
...
}
There’s nothing to talk about here either, since the code is fairly transparent. Found a block -> understood that it was -> made the necessary action.
As a result, the only thing left for us to do on the client side is to let the server know that we have a cache so that the server could correctly form the next pages. In the form of transport was chosen simple and elementary - cookies.
function tcc()
{
...
var storage = '';
for (var i = localStorage.length - 1; i >= 0; i--)
{
var key = localStorage.key(i);
var prefix = key.split('_', 1)[0];
var key = key.substr(prefix.length + 1);
if (prefix != 'tcc') continue;
var ttl = storExist(key);
if (ttl != null)
{
if (storage.length != 0) storage += ',';
storage += key + '_' + ttl;
}
else
{
localStorage.removeItem('tcc_' + key);
}
}
var cookie = 'tcc=' + escape(storage) + ';path=/';
if (storage == '') cookie += ';expires=Thu, 01-Jan-1970 00:00:01 GMT';
document.cookie = cookie;
}
Cookies are formed quite transparently, we go through the repository looking for our blocks and fill out the list in cookies indicating the ID and TTL of each block. In the given piece, the code uses another service function storExist, which is almost similar to storGet with the only exception that it returns the TTL of the block if this block is valid.
function storExist(key)
{
var data = localStorage.getItem('tcc_' + key);
if (data == null) return false;
var data_ttl = data.substr(0, 12).split(':', 1)[0];
if (data_ttl <= curTime) return null;
return data_ttl;
}
Well, on the client side, we essentially did everything, now we will return to the server. At the very beginning I mentioned a certain array of $ tcc (for the function of forming marker blocks), which we need to form.
if (isset($_COOKIE['tcc']))
{
$list = explode(',', $_COOKIE['tcc']);
for ($i = 0; $i < count($list); $i++)
{
$item = explode('_', $list[$i]);
if (count($item) == 2)
{
$tcc[$item[0]] = $item[1];
}
}
}
Everything is elementary.
Summarizing
I must say that I am quite pleased with the result of the experiment. In the final script, I got a simple system that automatically processes the cache, merges and transmits as little traffic as possible. The final JS script (which in the idea should always be transferred from the client to the server) takes 2k of compressed YUV traffic but uncompressed GZip, which is quite acceptable for me. It is also worth noting that in the case of old browsers or search engines, on the client side there will be no support for JS, localStorage or Cookie, which means the server will always think that there is no client-side cache and will always form a full page (i.e. got an automatic compatibility system with oldies, which for me is so very cool). It is also worth noting a fairly simple server implementation, which can easily be combined with any template maker and produce block marking and caching both on the client side and the server without unnecessary hemorrhoids for the programmer. Also, optionally, you can cache so JS and CSS placed directly into the document body (in the document body, by the way, in this case, you can include all the necessary scripts and styles). In this case, dividing and separating each JS or CSS into a separate block, we get a convenient and elementary system for loading only the necessary JS and CSS code by minimizing server requests (even if this query is just checking the validity of the cache).
Cons (or what can be done otherwise)
Obviously, you need to get rid of jQuery (well, this is understandable).
Change the marking of blocks to something like <! - cc_ <ID> _ <MODE> [_ <TTL>] ->, in this case we will be able to mark the blocks without worrying about compatibility with standards and the integrity of the shared html ( as with the span blocks, we would have to cut out only finished html blocks otherwise, in the case of no support from the TCC browser, everything would have collapsed).
The main disadvantages of the idea itself are:
- The need on the server side to monitor the relevance of the cache for each client (for example, in the case of a basket of goods in the online store, must also change when the basket changes and be unique to the client, that is, we must somehow understand that the client cache is outdated);
- The potential growth of cookies, which leads to an increase in traffic from the client to the server (but this is a potential problem, since in the end everything depends on the selected TTL blocks and their number;
Well, actually that's all ...
I would like to hear reasonable criticism and ideas of optimization. I am pleased to answer all questions.
PS Unfortunately, I can not put an example of a working script, if someone tells me a place for 1 php file, I will be grateful.UPD. PS Link to the working example:
catsmile.heliohost.org/Mear/tcc.php (thanks to the user
catmile for the place)
UPD.2 PPS To eliminate the vagueness of understanding the goals of this topic I will clarify. I do not suggest replacing ajax in the literal sense of the word. I propose only to consider the option of client caching in a specific implementation. The code also demonstrated does not claim to completeness and was created only for the purpose of demonstrating the approach. The main goals of this topic are to collect ideas and comments about the idea of ​​caching some blocks on the client side and the methods of working with these blocks.