The subject of block caching and ssi more than once slipped on Habré. Below I will introduce another implementation using block caching, as well as the source code of the framework using these principles that can be found
here . And how it works - read below.

A little bit about MVC approaches
The basis for building the framework is the principle “as simple as better”. If we consider the build process of the output HTML using the MVC pattern, then there are two approaches: push & poll.
In the first approach, the front controller calls up many controllers that run models and form multiple blocks using the View, which are then assembled into the front View.
The second approach is to process one View template, in which there are functions of reverse output (callback), which are called by the corresponding controllers, and they, in turn, form the HTML block. This approach, for example, is used in ZendFramework.
')
In this case, the first approach is used, but with the difference that the PHP script does not build, but the WEB server (nginx) directly via ssi (Server side include). SSI directives are used: include and echo. This allows:
- simplify the controller code, reduce the load on PHP scripts
- run multiple scripts at the same time
- reduce the stream of bytes transferred between the WEB server and the backend
- Cache blocks by server means, without jerking backend scripts (PHP)
The framework itself (gave it a name quickly, since it is 4 times faster than ZF in performance) is closely integrated with nginx, and part of the nginx config is a direct part of the project. (
someone does not like it ... There is no friend to the taste and color ) For this, the include /path/to/project/conf/local.nginx.conf directive should be included in nginx.conf
A little about how it works at all
The classic MVC scheme in WEB - each controller is tied to a specific part of the url. Part of the url is tied to the action and part to the parameters.
As mentioned above, part of the controller functions takes on nginx. Url taxiing is done using location directives. It is possible to settle everything by controllers (pages), actions (actions) and parameters. But it is more flexible than in the same ZF. Be sure to include in the location directive fastcgi the page parameter, by the value of which the corresponding class is selected. We believe that the corresponding controller of the block has been called.
Example
set $app_script run_app.php;
. . .
location ~ ^/catalog/(\w+)/? {
fastcgi_pass localhost:9000;
fastcgi_param page catalog;
fastcgi_param cat_name $1;
include fastcgi_params;
}
part:
set $app_script run_app.php;
. . .
location ~ ^/catalog/(\w+)/? {
fastcgi_pass localhost:9000;
fastcgi_param page catalog;
fastcgi_param cat_name $1;
include fastcgi_params;
}
set $app_script run_app.php;
. . .
location ~ ^/catalog/(\w+)/? {
fastcgi_pass localhost:9000;
fastcgi_param page catalog;
fastcgi_param cat_name $1;
include fastcgi_params;
}
This example shows that the page = catalog fcgi parameters will be passed, cat_name is equal to the last part of the url. The PHP script will be called: run_app.php, which from the page directory instantiates the class catalogPage (location page / catalogPage.php) and runs the run () method. In the environment variable cat_name for url / catalog / bmv will take the value bmv.
How it works with SSI
Taxiing by location are divided into two parts: external and internal. External is the choice of the corresponding ssi template using rewright. Internal - these are private controllers' locations.
Example SSI Template (index.tpl):
< script >
<!--# include virtual = "$js" -->
</ script >
< table >
< tr >
< td > left block
<!--#include virtual="$top" -->
</ td >
content
< td valign ="top" > content block < br >
<!--#include virtual="$int" -->
</ td >
</ tr >
</ table >
* This source code was highlighted with Source Code Highlighter .
Example of the config part:
set $ int "/ssi$request_uri" ;
set $top "/ssi/top10$request_uri" ;
. . .
location /catalog {
set $js "js/catalog.js" ;
rewrite ^(.*)$ /index.tpl;
}
location /ssi {
internal ; # ,
location /ssi/catalog/(\w+)/? {
fastcgi_pass localhost:9000;
fastcgi_param page catalog;
fastcgi_param cat_name $1;
fastcgi_param ssi 1;
include fastcgi_params;
}
location /ssi/top10/(\w+)/? {
fastcgi_pass localhost:9000;
fastcgi_param page top10;
fastcgi_param top_name $1;
fastcgi_param ssi 1;
include fastcgi_params;
}
}
* This source code was highlighted with Source Code Highlighter .
For the first location, a rewrite is performed at index.tpl with the variable $ js = js / catalog.js set.
In the index.tpl template, the substitution of the necessary js script is performed, as well as the calling of the necessary blocks by using the #include ssi directives. In our example, the internal location / ssi / catalog / will trigger and the PHP script run_app will be invoked, which will instantiate the catalogPage class and run the run () method, and will also execute the top10 block in the same way.
How it works with memcached
look drawing. everything is very clear there: instead of addressing the location / top10, we are directly contacting the memkey on location / mc. If the cache is invalid (empty), then the ngx_memcache_module module gives us a 404 error. We process a 404 error and do an internal redirect to the named location
mcb . The PHP script should generate HTML and put it into the cache. You don’t need to worry about it, it happens in the base class if you specify the following parameters in our class:
protected $ _Cached = true;
public $ CachingKey = '/ top_ $ top_name';
Config example:
location ~ ^/catalog/(\w+) {
rewrite ^(.*)$ /index.tpl;
set $memkey "top_$1" ;
}
location /mc {
set $memcached_key $memkey;
default_type text/html;
memcached_pass localhost:11211;
error_page 404 @mcb; // ,
//
}
location @mcb {
fastcgi_pass localhost:9000;
fastcgi_param page block;
fastcgi_param blocknum $blocknum;
include fastcgi_params;
}
* This source code was highlighted with Source Code Highlighter .
Caching features:if you use the php_memcache extension, then there are no special features.
If the libmemcached and php_memcached library is used, then by default it processes the content compression.
The following options are possible:
- do not use compression, set the parameter Memcached :: OPT_COMPRESSION = false.
- set gzip / deflate to location default_type; But with small volumes, somewhere up to 64 bytes, no compression is performed.
- patch ngx_memcache_module. Depending on the value of the parameter received from memcache, output the header text / html or gzip / deflate (patch 10 lines)
Acknowledgments
First of all, I would like to express my gratitude to Igor Sysoev at
sysoev.ru , without it there would not have been this code and many high-performance runet projects.
And also thanks to Konstantin Baryshnikov (fixxxer) for the ideas of using location as a front controller.
I want to thank Alexei Rybak (fisher) for his
blitz , which I actively use in my projects, in particular in this framework, for more than three years.
Well, the author of php-fpm, Andrei Nigmatulin (anight), who made no small contribution to the hiload runet with his project.
Ps. If you do not start something, it does not matter. It will turn out another time, the main thing is not to lose heart. In the meantime, take a break and read Habr.