Lua мікро-фреймворк на Apache

Краще маленька рибка, ніж великий тарган.
Російська приказка

Lua подобається всім, він простий, але не примітивний. Ні, швидше — продуманий, виважений, оптимальний. Р. Иерузалимски (автор мови), у своїй книзі «Програмування мовою Lua» пише: «Lua — це крихітний і проста мова». Це так. І ще він скриптова, переносне, ефективний, розширюваний, склеює (glue). Як же не спробувати?

Як і багато інших, я піддався спокусі заглянути, що ж із себе представляє Lua. Ну а оскільки найкращий спосіб вивчити мову-це написати на ньому програму, я вирішив накидати простий веб-микрофреймворк під сервер Apache (версії 2.3+). Апач обраний тому, що він є на кожному хостингу, і вся настройка під Lua полягає у включенні модуля mod_lua.so в конфігурації сервера. Це рішення, звичайно, буде повільніше, ніж на Nginx, але можливо, нам більше й не треба?

Я з задоволенням прочитав першоквітневий статтю LUA в nginx: лапшакод в стилі inline php про створення сайту на Lua під Nginx в стилі раннього РНР. Як відомо, в кожному жарті… До першого квітня я не дотягнув, але п'ятниця — теж непогано. З написаної статті можна зробити принаймні два однозначних виводу: на Lua можна розробляти сайти (або движки під них), сайти точно будуть швидкими.

Приступаючи до написання фреймворку, а він саме мікро, я не був упевнений, що це в принципі буде працювати взагалі. Було цікаво і цікаво, тим більше, що мову Lua мені був не знайомий. Довелося вивчити мову і на ходу придумати, як його застосувати. Отриманий за пару тижнів результат не претендує на велику і велику (і не розрахований на використання в продакшені), але все ж сподіваюся, що зможу внести малу лепту в популяризацію Lua в середовищі вебпрограммірованія.

Архітектура фреймворку

Хоча слово архітектура і дуже гучна, проте структура у фреймворку є, і я спробую описати її. Зі стартом відбувається ініціалізація і запускається DI. З його допомогою запускається Front — головний контролер. Front отримує від Request запит, передає його в Router і отримує ім'я контролера. У контролера є кілька презентеров (наприклад презентер статті, меню тощо). Зібравши відповіді презентеров, контролер повертає відповідь Front-у, а той відправляє відповідь на висновок. Все досить просто і прозоро.

Front — самий головний, але сам він нічого не робить (роботу роблять презентери). Контролер (один з них), відповідає за те, які презентери будуть на сторінці користувача. Презентер повинен виконати свою місію та дати відповідь, що містить назву, опис і контент (можна ще прикрутити дату, щоб віддавати Last-Modified, так я принаймні робив в PHP). Що з відповіді презентера використовувати, а що ні, вирішує контролер. Шаблонизация реалізована нативно, тому з одного боку — швидка, з іншого, вимагає розробки (каталог package/Demo/View/ поки порожній)

Подробиці
Налаштувавши файл htaccess так, щоб всі запити йшли на index.lua, в індексному файлі ми повинні обов'язково створити функцію handle(r ), яка аргументом приймає таблицю request_rec, структуру її вмісту можна подивитися тут: httpd.apache.org/docs/trunk/mod/mod_lua.html Отримавши запит, можна далі писати код по його обробці. (В роутері до речі на POST запитах і на GET запити, що не потрапили в який-небуть рауса поки стоять заглушки.)

Мені здалося цікавим використовувати можливості Lua по створенню і використанню предкомпилированного коду. У такого підходу очевидний плюс у підвищенні швидкодії, адже фаза предкомпиляции пропускається кожен раз при запуску програми. Ну і мінус в тому, що предкомпилированный файл безпосередньо не відредагуєш — треба правити вихідний код і компілювати файл заново. Програму можна змусити кожний раз порівнювати дату останньої зміни файлів, але для «швидкого» сайту це не зовсім правильне рішення. Варіант з використанням мемкэша я поки не розглядаю, залишу на майбутнє.

Для зручності, і, напевно, за звичкою, код я організував у псевдо-ООП стилі. Реалізована інкапсуляція, напевно тому, що саме її я ціную найбільше (суб'єктивно). Поліформізм не потрібен, а спадкування зроблю тоді, коли це буде необхідно. Звичайно, для маленького фреймворку, написаного для створення невеликих сайтів, можна було б обійтися і зовсім функціями, але якось незручно це, хоча і цілком можливо.

Іменування класів у фреймворку зроблено класично: воно передає фізичне розташування файлу. Наприклад, клас CoreKernelRoute означає, що файл Route.lua розташований у підкаталозі Kernel пакету Core. Повний шлях від кореня сайту package/Core/Kernel/Route.lua Таким чином простий (так, тут все просте і маленьке) ін'єкторів залежностей легко збирає всі залежні класи для створюваного об'єкта і передає їх йому в конструкторі. Список залежностей зберігається у файлі system/dependency.lua Якщо потрібно, щоб об'єкт був сінглтоном, то його треба прописати у файлі system/single.lua

Демо
Щоб знайомство з фреймворком не означало просте прочитання статті з не дуже зрозумілими поясненнями автора, я в фреймворк додав примітивний модуль Demo, за яким можна зрозуміти і побачити (якщо помістити дистрибутив на сервер) роботу сайту. Всі контролери лежать в каталозі package/Core/Controller/ Всі вони, крім Hello.lua використовують презентер DemoPresenterArticle для генерації списку статей з демонстраційної бази DemoDataDb, а контролер Page також у цього презентера отримує і контент конкретної статті.

Контролер Hello.lua — зроблений саме як Hello world! — це найкоротший шлях, який може виконати скрипт, при цьому повністю задіявши своє ядро (якщо тестувати на максимальну швидкість, то це саме цю сторінку). До речі, щоб правильно працювала головна сторінка, не забудьте в httpd.conf в розділі DirectoryIndex додати index.lua

Приклад
Припустимо, ви хочете показувати час на головній сторінці. Для цього потрібно створити презентер DemoPresenterTime з парою методів, які повертають час на сервері в 24-х або 12-годинному форматі. Можна додати і метод, що повертає дату (назва презентера це цілком передбачає). Розташовувати його потрібно за адресою package/Demo/Presenter/Time.lua

DemoPresenterTime
--[[
DemoPresenterTime
--]]

function genObj()

local M = {}
local L = {}

--[===================[
Public methods
--]===================]

function M. doJob(route, event)
event = event or 'Clock24'
local exe_event = 'exe' .. event
if L[exe_event] then
local id_page = tostring(route.id_page)
return L[exe_event](id_page)
else
return L. conf.Core.array_error_1
end 
end

--[===================[
Private methods
--]===================]

-- Getting time 24
function L. exeClock24()
return {title = "Clock 24",
description = "Time in 24-hour format",
content = os.date("%H:%M:%S"),
status = true}
end

-- Getting time 12
function L. exeClock12()
return {title = "Clock 12",
description = "Time in 12-hour format",
content = os.date("%I:%M:%S"),
status = true}
end

return M 
end


Тепер шаблон CoreTemplatePage зберігаємо як CoreTemplateIndex — робимо індивідуальний шаблон для головної сторінки. У ньому додаємо {server_time} де-небудь під меню. Якщо додати {server_time} CoreTemplatePage, то потрібно підключати презентер до кожного контролера, а в наших планах цього немає.

У контролері CoreControllerIndex додаємо за блоком з сайдбаром
-- time
res = L. obj.server_time.doJob(route, 'Clock24')
tpl = string.gsub (tpl, '{server_time}', res.content, 1)

І у файлі system/dependency.lua для цього контролера підключаємо новий презентер рядком
server_time = 'DemoPresenterTime',


Все, презентер створено, і ви можете спостерігати на головній сторінці час сервера, що буде досить цікаво відвідувачу, особливо якщо ваш хостинг де-небудь на іншій стороні планети :)

Стиль
Боюся, що і на архітектуру, і на стиль оформлення фреймворку занадто сильно вплинув мій попередній досвід написання сценаріїв РНР. Записувати його в «бест практик» не беруся. Але писати на Lua мені сподобалося. Код виходить лаконічний, кількість скобочек невелике, крапки з комою в кінці рядків не потрібні. Також мені здалося дуже зручним використовувати багаторядкові коментарі --[[ --]], що достатньо поставити між пар дужок будь знак і блок коду вийшов з коментарів, видалив — все назад в коментарях.

Функції-класи повертають об'єкти, які по суті — замикання. Мислити такими замиканнями виявилося дуже легко, хоча раніше я цурався подібної практики. Але вважаю, глибинне сприйняття Lua у мене ще попереду, і можливо, чекає разом з корутинами і C API. Взагалі, у Lua все на своєму місці, що залишає чітке відчуття продуманості мови, без прикручування забутої похапцем деталі (суто ІМХО, без холиварности).

Lua і PHP
Цей абзац ніяк не заради вкидання палива у вогонь священних воєн. Просто я трохи писав на PHP і можу провести якусь паралель. Хоча мені сподобався Lua, це не означає, що він краще PHP. Можливо, що мені просто хочеться чогось нового. Крім того, при роботі з PHP з'явилася звичка до докладною високоякісної документації, величезній кількості прикладів, статей, розжовувань самих різних дрібниць, вилизаним бібліотек і модулів, яким несть числа, і які або вже є на самому заштатному хостингу, або вам їх підключать при першій необхідності.

Якби мене хтось запитав, на чому писати великий і складний проект, я б сказав, що Lua мені подобається, але краще пиши на PHP. Тим не менш, вважаю, що Lua повинен зайняти хоч і невелику, але досить значну нішу в вебстроительстве. Він швидко, легкий і простий, і якщо це все, що потрібно, то чому б і ні? Крім того, завдяки початкового проектування мови під інтеграцію з Сі, вирішенню на основі Lua (і LuaJIT) цілком показана дорога на хайлоад. Мені до речі дуже цікаво було прочитати про можливості писати програми на Lua в Tarantool.

Сферичний кінь
Будь-які самостійні бенчи краще розглядати відносно, оскільки налаштування операційної системи, залізо, можуть сильно вплинути на результати. Та й взагалі, тестувати теж треба вміти. Але в порівнянні багато чого стає зрозуміло, у всякому влучае в категоріях «швидше-повільніше», тобто суб'єктивних.

На моєму старенькому ХХХ компі показники:
головної сторінки (2 презентера)
  • ab -n 1000 -c 10 --> 622 r/s
  • ab -n 1000 -c 100 --> 582 r/s
article3.html (2 презентера)
  • ab -n 1000 -c 10 --> 660 r/s
  • ab -n 1000 -c 100 --> 634 r/s
Hello world! (без презентеров, тільки контролер)
  • ab -n 1000 -c 10 --> 736 r/s
  • ab -n 1000 -c 100 --> 727 r/s
Для порівняння
швидкість роботи простого r:puts(«Hello World!!!»), на Lua без фреймворку
  • ab -n 1000 -c 10 --> 2210 r/s
  • ab -n 1000 -c 100 --> 2067 r/s
головна сторінка сайту, зробленого на PHP-фреймворку, схожому по застосованим архітектурним принципам, але трохи складніше влаштованому (з певною системою шаблонізації і т. д.) і з чотирма статичними презентерамі (тобто презентер вантажить статичний контент — текст), не дивлячись на схожість реалізації на цифри прошу дивитися тільки краєм ока
  • ab -n 1000 -c 10 --> 207 r/s
  • ab -n 1000 -c 100 --> 187 r/s
Загальний висновок з поверхневого тестування такий: фреймворк на ініціалізацію ядра витрачає близько мілісекунди, що природно, не мало, але для звичайного сайту ця величина вельми невелика. Також він трохи витрачає і пам'яті: без фреймворку 25Кб, з фреймворком 61Кб, разом 36Кб (з зростанням фреймворку цифра, звичайно, буде зростати). Враховуючи, що фреймворк збирає з десяток різних файлів, думаю, результат можна назвати прийнятним.

Ефективність предкомпиляции
Згенерувавши предкомпилированные .ls файли я зміг подивитися, наскільки ефективно таке рішення. Я прийшов до висновку, що ефект виростає на великих файлах. Тобто якщо б код фреймворку був зібраний в одному файлі, то це дуже сильно вплинуло на швидкість роботи. Предкомпиляция ж маленьких файлів ефекту не дає. У пакеті Demo в якості бази даних використовується проста lua-таблиця. Збільшивши її до мегабайта, я побачив дуже сильний вплив використання файлу в предкомпилированном вигляді на швидкість (не хочу тут наводити ще одного сферичного коня, але різниця була на порядок). Я б сказав, що для невеликого сайту (в сенсі розміру БД), цілком можна обійтися зберіганням даних в lua-таблиці, без використання SQL, це звичайно не панацея, але цілком чергове рішення.

Посилання на GutHub: github.com/claygod/Rumba

чи Доводилося вам працювати з Lua?

/>
/>


<input type=«checkbox» id=«vv71713»
class=«checkbox js-field-data»
name=«variant[]»
value=«71713» />
Так
<input type=«checkbox» id=«vv71715»
class=«checkbox js-field-data»
name=«variant[]»
value=«71715» />
Немає

Проголосувало 45 осіб. Утрималося 4 людини.


Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.


Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.