Сборка NGINX c HTTP 2.0

Устанавливаем OpenSSL версии 1.0.2 или новее, необходимый для поддержки ALPN. Скачиваем и распаковываем Nginx версии 1.9.5 или новее. Конфигурируем Nginx с необходимыми опциями, например, для сборки вместе с OpenSSL из исходников и статической линковки конфигурация будет выглядеть так:

./configure --with-http_ssl_module --with-http_v2_module --with-openssl=/path/to/openssl-1.0.2 

После этого собираем и устанавливаем сервер.

Настройка NGINX c HTTP 2.0

Для включения HTTP 2.0 добавьте параметры ssl и http2 к директивам listen:

server {
listen 443 ssl http2 default_server;
...
}

Для проверки работоспособности HTTP/2 есть расширения для Google Chrome и Firefox.

Также вы всегда можете открыть DevTools инспектор в браузере или проверить каким-то другим способом ответ от сервера. Например, вы можете установить утилиту nghttp2 для Linux. Эта утилита позволяет смотреть ответ сервера. Пример использования:

nghttp -v https://tutu.ru

Пример полного конфигурационного файла для сайта с включенным HTTP/2:

server {
listen 80;
server_name domain.ru www.domain.ru;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name domain.tld www.domain.tld;
ssl on;
ssl_certificate /etc/nginx/ssl/domain.pem;
ssl_certificate_key /etc/nginx/ssl/domain.key;
ssl_prefer_server_ciphers On;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-↵
AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:↵
ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-↵
GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+↵
AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-↵
AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-↵
AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-↵
AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-↵
AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-↵
SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:↵
DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:↵
!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK;
add_header Strict-Transport-Security max-age=15768000;
ssl_stapling on;
location / {
root /www/site/public;
}
}

Поддержка JavaScript

Я, как и многие айтишники, люблю новинки, поэтому релиз нового софта – это как праздник. Новые возможности, новые способы решать типовые задачи. Разработчики Nginx добавили в новой версии не только поддержку нового протокола, но и возможность писать небольшую логику о бработки запросов на популярном ныне языке – JavaScript. Точнее, на собственном диалекте, который называется nginScript и для которого был разработан свой интепретатор.

Для выполнения скриптов используется собственный движок njs. Для этого команда разработчиков Nginx реализовала свою версию виртуальной машины под урезанное подмножество языка JavaScript. Собственно, этот язык и назвали nginScript, дабы не путали с JavaScript, так как все же это именно подмножество. Что интересно: на каждый запрос запускается отдельная виртуальная машина, что позволяет обойтись без сборщика мусора. Язык JavaScript выбран как наиболее популярный язык программирования. Lua был хорошим претендентом, но он не так широко распространен в кругах веб-разработчиков. Необходимость создания собственной виртуальной машины JavaScript обусловлена тем, что существующие движки оптимизированы для работы в браузере, в то время как для Nginx необходима не просто серверная реализация, но интегрированная в движок.

nginScript обладает виртуальной машиной и компилятором байт-кода с быстрым запуском и завершением работы. Блокирующие операции, такие как подзапросы HTTP, могут быть приостановлены и возобновлены по аналогии с другими блокирующими операциями в JS. Скрипты на JavaScript могут использоваться прямо в файле конфигурации для определения расширенной логики обработки запросов, а также для формирования конфигурации и динамической генерации ответа. Можно даже делать модификацию запроса и ответа. А еще можно быстро создавать заглушки с решением проблем в веб-приложениях.

Сам скрипт запускается посредством директивы js_run и позволяет прямо в самом сервере выполнять многие низкоуровневые операции с запросом, без необходимости написания отдельного модуля на языке Lua или С, создавая свое расширение для сервера.

В язык описания конфигов Nginx добавлены синтаксические инструкции, позволяющие встраивать блоки кода на JS прямо в файл конфигурации. Подобные блоки выполняются по мере обработки HTTP-транзакций и позволяют для каждого запроса выполнять такие операции как корректировка внутренних параметров nginx, создание сложных условий, изменение запроса или ответа.

С помощью nginScript можно описывать конфиги, которые без написания дополнительных расширений и модулей могут динамически блокировать вредоносные запросы, эксплуатирующие уязвимости в веб-приложениях или ограничивать интенсивность определенных запросов. Также можно реализовывать гибкие правила перенаправления трафика, использующие информацию из запроса и не только.

Включаем поддержку nginScript

Вам необходим установленный Mercurial. Если его нет, ставим и далее следуем инструкции:

wget http://nginx.org/download/nginx-1.9.5.tar.gz
tar -xzvf nginx-1.9.5.tar.gz hg clone http://hg.nginx.org/njs cd nginx-1.9.5 ./configure --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --user=www --group=www --with-ipv6 --with-pcre-jit --with-http_gzip_static_module --with-http_ssl_module --with-http_v2_module --add-module=../njs/nginx make -j2 make install

Пишем первые программы на nginScript

Мы можем заранее предопределять переменные с результатами вычисления через директиву js_set

js_set $msg "
	var m = 'Hello ';
	m += 'world!';
	m;
";
server {
	…
	location /hello {
	add_header Content-Type text/plain;
	return 200 $msg;
	}
	…
}

Вы можете сразу из конфигурационного файла сгенерить контент и отдать его в браузер:

location /hello {
	js_run "
		var res;
		res = $r.response;
		res.contentType = 'text/plain';
		res.status = 200;
		res.sendHeader();
		res.send( 'Hello, world!' );
		res.finish();
	";
}

Расширенный пример, печатаем информацию о реквесте:

js_set $summary "
	var a, s, h;
	s = 'Request summary\n\n';
	s += 'Method: ' + $r.method + '\n';
	s += 'HTTP version: ' + $r.httpVersion + '\n';
	s += 'Host: ' + $r.headers.host + '\n';
	s += 'Remote Address: ' + $r.remoteAddress + '\n';
	s += 'URI: ' + $r.uri + '\n';
	s += 'Headers:\n';
	for (h in $r.headers) {
		s += ' header \"' + h + '\" is \"' + ↵
		$r.headers[h] + '\"\n';
	}
	s += 'Args:\n';
	for (a in $r.args) {
	s += ' arg \"' + a + '\" is \"' + $r.args[a] + '\"\n';
	}
	s;
";

А давайте напишем сервис расчета чисел Фибоначчи. Если есть микроскоп, то почему бы им не забить гвоздь? На вопрос зачем? настоящий программист/ученый/изобретатель всегда ответит: потому что я могу это сделать! Да, я могу написать такой сервис, не выходя из конфига Nginx, не устанавливая Nodejs, PHP или Ruby. Я могу все это сделать прямо внутри сервера, используя только конфигурационные файлы!

location /js/getfib {
	js_run "
		function fib(n) {
			var prev = 1,
				curr = 1,
				newc,
				i;
			if (n < 2) return n;
			for (i = 3; i <= n; i++) {
				newc = prev + curr;
				prev = curr;
				curr = newc;
			}
			return curr;
		}
		var n = +$r.args['n'],
			txt = 'Fibonacci( ' + n + ' ) = ' + fib(n),
			res = $r.response;
		res.contentType = 'text/plain';
		res.status = 200;
		res.sendHeader();
		res.send(txt);
		res.send('\n');
		res.finish();
	";
}

Я хотел написать вместо:

newc = prev + curr;
prev = curr;
curr = newc;

деструктурированный код:

[ prev, curr ] = [ curr, prev + $curr ];

Но nginScript ничего не знает про ES2015/ES6. Тогда я написал так:

prev = [ curr, prev += curr ][0];

И тут тоже была неудача. Так писать тоже нельзя. Но, как мы помним, у нас же не оригинальный Javascript, а диалект, со своими особенностями, достоинствами и недостатками.

А зачем может быть нужен такой функционал в реальных применениях? Например, для привязки апстримов и динамического роутинга:

upstream my_upstream0 {
	server server1.example.com;
	server server2.example.com;
}
upstream my_upstream1 {
	server server3.example.com;
	server server4.example.com;
}
js_set $my_upstream "
	var s, upstream, upstream_num;
	upstream = $r.args.upstream;
	// convert upstream number to integer
	upstream_num = +upstream | 0;
	if (upstream_num < 0 || upstream_num > 1) {
		upstream_num = 0;
	}
	s = 'my_upstream' + upstream_num;
	s;
";
server {
	listen 80;
	location / {
		proxy_set_header Host $host;
		proxy_pass http://$my_upstream;
	}
}

Как видите, новая версия горячо любимого многими системными администраторами веб-сервера Nginx получилась очень интересной и достойной.