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

в 6:09, , рубрики: Apache, Lua

Лучше маленькая рыбка, чем большой таракан.
Русская поговорка
Lua микро-фреймворк на Apache - 1
Lua нравится всем, он простой, но не примитивный. Нет, скорее — продуманный, выверенный, оптимальный. Р.Иерузалимски (автор языка), в своей книге «Программирование на языке Lua» пишет: «Lua — это крошечный и простой язык». Это так. И ещё он скриптовый, переносимый, эффективный, расширяемый, склеивающий (glue). Как же такой не попробовать?

Как и многие другие, я поддался искушению заглянуть, что же из себя представляет Lua. Ну а поскольку самый лучший способ изучить язык, это написать на нём программу, я решил набросать простой веб-микрофреймворк под сервер Apache (версии 2.3+). Апач выбран потому, что он есть на каждом хостинге, и вся настройка под Lua заключается во включении модуля mod_lua.so в конфигурации сервера. Это решение конечно, будет медленнее, чем на Nginx, но возможно, нам больше и не надо?

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

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

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

Lua микро-фреймворк на Apache - 2
Хотя слово архитектура и слишком громкое, тем не менее структура у фреймворка есть, и я попробую её описать. Со стартом происходит инициализация и запускается 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

Автор: claygod

Источник

Поделиться новостью

* - обязательные к заполнению поля