Do not take this post seriously. Despite the fact that it works, in its current form, this solution is an extremely funny concept and nothing more. Also, the post is in no way a grin over php, which is one of my main working tools.I think that all PHP developers (including me) somehow went through a period when the code was a terrible mixture of html and php, crammed into one file. And it's not about the templates, but in general about all the logic in the noodles / spaghetti code.
And as a concept, I decided to sketch on the first of April the implementation of something similar, but on lua under nginx. Just like in the picture.
Scripts can be written about like this (the
link that this code responds to ):
<?lml tmpl:include('sugar') ?> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title> <?lml print(ngx.utctime()) ?></title> </head> <body> <?lml local alc = require('lib.alc') ?> , <?lml print(esc(req:get('name', 'traveler')), '/', ngx.var.remote_addr) ?>. <?lml print(alc:inc('cnt')) ?> . <?lml local hdrs = {} for k,v in pairs(ngx.req.get_headers()) do table.insert(hdrs, '<tr><td style="font-weight:bold;">'..esc(k)..'</td><td>'..esc(v)..'</td></tr>') end ?> <h3> <?lml print(ngx.req.get_method()) ?> <?lml print(esc(ngx.var.request_uri)) ?></h3> <table><?lml print(hdrs) ?></table> <?lml include('footer') ?>
Those. full lua in lapshastila. To test the work were implemented:
- directly the template engine itself;
- close analogue of APC: any store / fetch / cas, etc. + compile_string / compile_file for caching bytecode compiled templates;
- ob_ * functions without nesting support (no need);
- every little thing to replace htmlspecialchars, $ _GET [name], etc.
Perhaps someone will be interested to read about the implementation. Who is interested only in the code - laid out on
github , even though there is a code and a cat cried.
')
All work is based on the following:
- LUA allows you to compile the source code, represented by a string, into a function at runtime (at the input line, at the output function (callable in terms of php / java)). The loadstring function is responsible for this;
- For an existing function, you can get its bytecode at runtime via a call to string.dump ;
- You can get the function back from bytecode through the same loadstring;
- For caching in RAM, ngx.shared.DICT is used, which I have already described the work with;
- A bit twist-twist to connect it all together.
To begin with, we configure nginx itself:
http { lua_shared_dict lml_shared 10m; lua_package_path '/path/to/lml/?.lua;;'; }
Template processing is the simplest: all text outside the <? Lml?> Tags is wrapped in stdout: print (TEXT), and the contents of the tags are left as they are, throwing only the borders of the tags themselves. HTML text in print is wrapped in multi-line literals so that you do not have to escape characters inside:
stdout:print([[Hello world ]])
But, since the situation of using literal borders inside the template (Hello [[<? lml?>]] World) is possible, the template engine searches for a “free” version of the boundaries of a multi-line literal, iteratively increasing its length:
print([[...]]) print([=[...]=]) print([==[...]==]) ...
Bytecode compilation, by analogy with php, has been moved from the templating engine to the opcode kesher, which has been called ALC (
Alternative Lua Cache ).
In the most minimal version, bytecode caching looks like this (this is an extremely truncated version! You should not consider it as a minimal, but a working example):
function M:compile_string(str, filename) local cache_key = 'tmpl_bytecode:' .. filename local bytecode, created_at = cache:get(cache_key) local lua_func = nil if not bytecode then locked = cache:add(key_lock, 1, key_lock_ttl) bytecode, created_at = cache:get(cache_key) if not bytecode then if type(str) == 'function' then str = str(filename) end lua_func = assert(loadstring(str, filename)) bytecode = assert(string.dump(lua_func)) end if locked then if lua_func and bytecode then cache:set(cache_key, bytecode, 0, ngx.now()) end cache:delete(key_lock) end end if (not lua_func) and bytecode then lua_func = loadstring(bytecode, filename) end return lua_func end
Passing the string with the lua code, we get the function at the output, ready for execution, and in the RAM we now have bytecode.
Appropriately, in the template engine it is enough to call the appropriate method, having slipped the necessary data to it:
local function _include_string(str, filename) local lua_func = alc:compile_string(str, filename) if lua_func then lua_func() end end function M:include_string(str, filename) local succ, err = pcall(_include_string, str, filename) if not succ then ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR local errstr = 'Error (' .. filename .. '): ' .. err ngx.log(ngx.ERR, errstr) ngx.say(errstr) return ngx.exit(ngx.HTTP_OK) end return succ end
Passing an anonymous function to alc: compile_string instead of the file contents allows you to not access the disk without the need for if the bytecode is already in the cache. We get lazy delayed loading of the contents of the templates only when necessary.
All functionality is distributed across small modules: the template engine in lib.tmpl, the cache in lib.alc, output and output buffering in lib.stdout, etc. In the templates for working with modules, in general, they must be explicitly loaded and referenced to functions by their full names:
This is obvious and understandable, but as part of the “sugar”, some modules are made mandatory and are connected automatically through the generation in the code of the prefix with the loading of these modules:
local required_libs = {'stdout', 'html', 'req', 'tmpl'}
Now these modules can be immediately used in the template:
In addition to this, the most frequently used functions, such as stdout: print, tmpl: include, html: escape, have been sweetened. This was done for example already at the level of lml templates:
This solution is a double-edged sword and is made to bring the template code closer to the php style.
In conclusion, the spherical test of the performance of this bike in comparison with php-fpm + apc on the simplest “home servach” with the Athlon II,
which is referenced at the beginning of the post.
The comparison took place with an equally primitive php code from 3 files with maximum adaptation.
So far, I have been testing through siege on 100Mbps LAN, so that some where the performance rested on the grid.
Running through siege -cX -t300S -b URL showed the following trans / sec:
| -c10 | -c100 | -c200 | -c500 |
---|
php-fpm | 3350 | 3150 ran into cpu | http 502 * | http 502 * |
lml without opkecher | not tested | 6950 | not tested | not tested |
lml with opkecher | 7,000 | 8100 ran into a network | 8200 ran into a network | 7500 ran into a network |
* bulk connect () to unix: / var / run / php-fpm - *. sock failed (11: Resource temporarily unavailable)It seems not so terrible.
Once again, the
link to github , if someone missed or started from the end, but wants to get the details.
I wish everyone not to give in to provocations :)