Вам (возможно) не нужен сервер

Андрей Роенко, Яндекс

Вам (возможно)
не нужен сервер

Карты

Андрей Роенко, Разработчик API Яндекс.Карт

Обычный сервер

const express = require('express');
const app = express();

app.get('/...', (req, res) => {
    // ...
});

app.listen(port);

Путь запроса



Lua Nginx

Lua

LuaJIT

Lua Nginx Module

Nginx API for Lua

stuff_by_lua_file path/to/lua/file.lua;
stuff_by_lua_block {
    -- lua code
}

Lua Nginx Module

  1. init_by_lua - проинициализировать, один раз при старте nginx
  2. set_by_lua - поменять значение переменной
  3. rewrite_by_lua - изменить URL
  4. access_by_lua - проверить права доступа
  5. content_by_lua - сгенерировать тело ответа
  6. log_by_lua - залогировать

К делу

Hello, world

app.get('/01/', (req, res) => {
    res.end('Hello from Node.js!\n');
});

Hello, world

location /01/ {
    content_by_lua_block {
        ngx.say('Hello from Lua!')
    }
}

Hello, world

Что за абстрактые RPS?

Параметры

app.get('/02/:id', (req, res) => {
    res.end(`${req.hostname} ${req.params.id}, ${req.query.filter}`);
});

Параметры

location ~ ^/02/(?<id>[^/]*) {
    content_by_lua_block {
        ngx.say(ngx.var.http_host, ', ', ngx.var.id, ', ', ngx.var.arg_filter)
    }
}

Параметры

Ограничение доступа

app.get('/03/', (req, res) => {
    if (req.headers.magic === 'unicorn') {
        res.sendFile(__dirname + '/welcome.html');
    } else {
        res.status(403);
        res.end();
    }
});

Ограничение доступа

location /03/ {
    access_by_lua_block {
        if ngx.var.http_magic ~= 'unicorn' then
            ngx.exit(ngx.HTTP_FORBIDDEN)
        end
    }

    try_files /welcome.html =404;
}

Ограничение доступа

Ограничение доступа

Изменение контента

const fsReadFile = util.promisify(fs.readFile);
app.get('/04/', wrap(async (req, res) => {
    const content = await fsReadFile(__dirname + '/04.js', 'utf8');
    res.end(`(function() { ${content} })()\n`);
}));

Изменение контента

location /04/ {
    content_by_lua_block {
        local f = io.open('.../file', 'r')
        local content = f:read('*all');
        -- . . .

стоп

Изменение контента

location /04/ {
    content_by_lua_block {
        local res = ngx.location.capture('/@04/')
        ngx.say('(function() { ' .. res.body .. ' })()')
    }
}
location /@04/ {
    internal;
    try_files /04.js = 404;
}

Изменение контента

Поинтереснее

Количество хитов

let hits = 0;
app.get('/05/', (req, res) => {
    hits++;
    res.end(`hits: ${hits}\n`);
});

Количество хитов

lua_shared_dict hits 1m;
init_by_lua_block {
    local hits = ngx.shared.hits
    hits:set('value', 0)
}

location /05/ {
    content_by_lua_block {
        local hits = ngx.shared.hits
        local value = hits:get('value') + 1
        hits:set('value', value)
        ngx.say('hits: ' .. tostring(value))
    }
}

Количество хитов

lua_shared_dict hits 1m;
location /05-ideomatic/ {
    content_by_lua_block {
        local hits = ngx.shared.hits
        local value = hits:incr('value', 1)
        ngx.say('hits: ' .. tostring(value))
    }
}

Количество хитов

Бан по рефереру

lua_shared_dict referers 16m;
location /06/ {
    access_by_lua_block {
        local referers = ngx.shared.referers
        local domain = (ngx.re.match(ngx.var.http_referer,
            '([a-z]+://)?([^/]+)', 'i') or {})[2]
        local hits = (referers:get(domain) or 0) + 1
        referers:set(domain, hits, 30) -- 30 seconds

        if hits > 30 then
            ngx.say('Go away')
            ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
        end
    }

Бан по рефереру

$ curl nginx/06/ -H referer:example.com
Welcome
...
$ curl nginx/06/ -H referer:example.com
Welcome
$ curl nginx/06/ -H referer:example.com
Go away
$ sleep 30
$ curl nginx/06/ -H referer:example.com
Welcome

Бан по рефереру

$ ab -n 50000 -c 400 _h referer:example.com nginx/06/
Requests per second:    10300.81 [#/sec] (mean)
Time per request:       38.832 [ms] (mean)

Percentage of the requests served within a certain time (ms)
  50%     38
  66%     39
  75%     40
  80%     41
  90%     42
  95%     43
  98%     44
  99%     44
 100%   1061 (longest request)

Статистика

location /07/ {
    content_by_lua_block {
        ngx.sleep(math.random())
    }

    log_by_lua_block {
        local statistics = ngx.shared.statistics
        local time = ngx.var.request_time

        statistics:incr('request_time-sum', time)
        statistics:incr('request_time-total', 1)
    }
}

Статистика

location = /07-status/ {
    content_by_lua_block {
        local statistics = ngx.shared.statistics
        local sum = statistics:get('request_time-sum')
        local total = statistics:get('request_time-total')

        ngx.say('average request time: ', sum / total, ' for ', total, ' requests')
    }
}

Статистика

$ curl localhost/07/
$ curl localhost/07/
$ curl localhost/07/
$ curl localhost/07/
$ curl localhost/07-status/
average request time: 0.74 for 4 requests

Фоновые задачи

init_by_lua_block {
    local function save ()
        ngx.location.capture('/my-upstream/save', {
            method = ngx.HTTP_POST,
            body = concat_my_stuff()
        })
    end

    ngx.timer.every(60, save)
}

Ну и как это отлаживать?

Примеры из API Яндекс.Карт

API Яндекс.Карт

<script src="//api-maps.yandex.ru/2.1.62/?lang=ru_RU"></script>
<script>
ymaps.ready().then(() => {
    const map = new ymaps.Map(mapEl, { /* state */ }, { /* options */ });
    // . . .
});
</script>

Хитрый роутинг

lua_package_path '/blah/blah/?.lua;;'
init_by_lua_block {
    require('jsapi')
}
location ~ ^/2\.1(?<version>[-.]\w+) {
    set_by_lua_block $real_version {
        local jsapi = require('jsapi')
        -- . . .
        return jsapi.rewrite(ngx.var.version)
    }

    proxy_pass ...2.1$real_version;
}

Мониторинги

location /isalive {
    content_by_lua {
        local jsapi = require('jsapi')
        local responses = ngx.location.capture_multi(jsapi.versions_urls)
        for i, reponse in ipairs(reponses) do
            if response.status ~= ngx.HTTP_OK then
                ngx.exit(reponse.status)
            end
        end
    }
}

Итоги

Итоги

Disclaimer

Контакты

Андрей Роенко

Разработчик API Яндекс.Карт


nginScript

Both modules are not built by default, they should be either compiled from the sources or installed as a Linux package.

Of course, it’s slower than LuaJIT, and it’s slower than JavaScript implementations like V8, SpiderMonkey, and JavaScriptCore which use JIT.

nginScript: A New and Powerful Way to Configure NGINX

OpenResty

https://github.com/openresty/

Целый фреймворк вокруг Nginx Lua