Разработчик облачных сервисов в ||
Владимир Протасов
OpenResty
Превращаем NGINX в
полноценный сервер приложений
1
О себе
• ¾ жизни пишу код
• ¼ жизни – промышленная разработка
• 2+ ТБ базы
• 20+ языков
2
О компании
• 13 офисов по всему миру
• 300+ сотрудников
• Разработка в Москве, Таллине и на Мальте
3
На чем мы пишем?
• Python 2
• Django
• MySQL
• Redis
• NGINX
4
Коротко о главном
• Зачем изобретать велосипед?
• Что это и с чем едят?
• Примеры из жизни
5
OpenResty
6
NGINX
• Полноценный веб-сервер
• Скорость
• Асинхронный I/O
• Кеширование, статический контент, …
• Удобство деплоя
• Структурированная обработка запросов
7
OpenResty
+
8
Lua
• Размер
• Скорость
• Простота в освоении
9
OpenResty
10
Hello world
location /hello {
content_by_lua_block {
local hello = string.format('Hello, %s!', ngx.var.remote_addr)
ngx.say(hello);
}
}
11
Hello world
location /hello {
content_by_lua_block {
local hello = string.format('Hello, %s!', ngx.var.remote_addr)
ngx.say(hello);
}
}
12
Hello world
location /hello {
content_by_lua_block {
local hello = string.format('Hello, %s!', ngx.var.remote_addr)
ngx.say(hello);
}
}
13
Hello world
location /hello {
content_by_lua_block {
local hello = string.format('Hello, %s!', ngx.var.remote_addr)
ngx.say(hello);
}
}
14
Hello world
location /hello {
content_by_lua_block {
local hello = string.format('Hello, %s!', ngx.var.remote_addr)
ngx.say(hello);
}
}
15
Hello world
location /hello {
content_by_lua_block {
local hello = string.format('Hello, %s!', ngx.var.remote_addr)
ngx.say(hello);
}
}
16
Hello world
$ curl http://127.0.0.1/hello
Hello, 127.0.0.1!
17
Возвращаемся в реальный мир
18
Реальное приложение
• Поиск картинок
• Расширенный список ключевых слов для поиска
19
Получаем данные
https://awesome.example.com/search?query=котята
location /search {
content_by_lua_block {
local args = ngx.req.get_uri_args()
local search = args.search
-- …
}
}
20
Расширение набора ключевых слов
• База данных
• Слово -> набор схожих
котята -> котики, коты, пушистики
21
Расширение набора ключевых слов
local mysql = require "resty.mysql”
local db, err = mysql:new()
local ok, err, errcode, sqlstate = db:connect{
path = "/path/to/mysql.sock",
database = ”keywords",
}
22
Расширение набора ключевых слов
local sql = string.format([[
SELECT kwd
FROM keywords
WHERE search = %s
]], ngx.quote_sql_str(search))
local res, err, errcode, sqlstate = db:query(sql, 10)
-- res = [ { kwd = ‘котики’}, {kwd = ‘пушистики’} ]
23
Находим картинки по всем запросам
local reqs = {}
for , keyword in ipairs( keywords_list ) do
table.insert (reqs, {
'/fetch', { args = { search_q = keyword } }
})
end
resps = { ngx.location.capture_multi( reqs ) }
24
Находим картинки по всем запросам
location /fetch {
internal;
proxy_pass http://example.com/search?$request_args;
-- кеширование и т.п.
}
25
Выводим данные пользователю
local links = parse_responses(resps)
ngx.headers['Content-Type'] = 'application/json’
ngx.say(cjson.encode(links))
26
Выводим данные пользователю
• Пользователь не хочет читать JSON
• SEO-специалисты негодуют
• Что делать?
• Отдаем пользователю HTML
27
Выводим данные пользователю
$ opm install lua-resty-template
local links = parse_responses(resps)
local template = require "resty.template"
template.render("view.html", { links = links })
28
Выводим данные пользователю
29
Фазы обработки запроса
• access
• rewrite
• content
• headers filter
• body filter
• log
30
Авторизация
access_by_lua_block {
-- …
}
31
Авторизация
local ck = require "resty.cookie”
local cookie, err = ck:new()
local token = cookie:get('token')
if not token then
return ngx.redirect("/auth")
end
32
Авторизация
local redis = require "resty.redis”
local r = redis:new()
local ok, err = r:connect("127.0.0.1", 6379)
local res, err = r:get(string.format('token:%s', token))
if not res then
return ngx.redirect("/auth")
end
33
Авторизация
location /auth {
content_by_lua_file "/path/to/auth.lua";
}
34
Авторизация
ngx.req.read_body()
local args, err = ngx.req.get_post_args()
if args.login ~= "admin" and args.password ~= "secret" then
return ngx.redirect("/auth")
end
35
Авторизация
local redis = require "resty.redis”
local r = redis:new()
local ok, err = red:connect("127.0.0.1", 6379)
local token = "admintoken”
red:set(string:format('token:%s', token), '1')
36
Авторизация
local ck = require "resty.cookie”
local cookie, err = ck:new()
local token = cookie:set{
key='token',
value=token,
}
return ngx.redirect("/")
37
Что ещё можно сделать?
• Минималистичный бекенд
• Препроцессинг данных
• Фасад для микросервиса
• Статистика и аналитика
• Многопользовательские системы
• Фильтрация запросов (WAF)
38
Сообщество
• NGINX-коммьюнити
• Lua-разработчики
• Рассылка на китайском
• Github
• Список рассылки (google groups)
39
Итого
• Удобный фреймворк, заточенный под веб
• Низкий порог вхождения
• Асинхронный I/O без коллбеков
• Большое отзывчивое сообщество
• Легкий деплой
40
Спасибо за внимание

OpenResty: превращаем NGINX в полноценный сервер приложений / Владимир Протасов (Parallels)