Теперь мы можем получить с значение в application: get_env (sample, facebook_app_id,»)
Вызов серверного кода
Для авторизации через социальные сети в n2o проектах есть библиотека avz, которая поддерживает Twitter, Google, Facebook, Github и Microsoft авторизацию. Но, avz требует определенную схему в БД, которой у нас пока нет, а поэтому мы релизуем авторизацию самостоятельно.
Функция wf: wire (#api{name=login}) позволяет привязывает вызов функции login на клиенте к событию api_event (login, Response, Term) на сервере.
В функции main/0 мы объявляем событие login, которое потом обрабатываем в api_event. Мы декодируем json строку, авторизируем пользователя и направляем его на главную страницу. В priv/templates/login.html код который скопирован с образца на facebook, в котором главная магия в вызове login (response).
<script>
// This is called with the results from from FB.getLoginStatus().
function statusChangeCallback(response) {
console.log('statusChangeCallback');
if (response.status === 'connected') {
// Logged into your app and Facebook.
FB.api('/me', function(response) {
login(response);
});
} else if (response.status === 'not_authorized') {
document.getElementById('status').innerHTML = 'Please log ' +
'into this app.';
} else {
document.getElementById('status').innerHTML = 'Please log ' +
'into Facebook.';
}
}
window.fbAsyncInit = function() {
FB.init({
appId : '{{ app_id }}',
cookie : true,
version : 'v2.2' // use version 2.2
});
FB.getLoginStatus(function(response) {
statusChangeCallback(response);
});
};
// Load the SDK asynchronously
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
function onLoginClick() {
FB.login(function(response) {
statusChangeCallback(response);
}, {scope: 'public_profile,email'});
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
Login
<script>
// This is called with the results from from FB.getLoginStatus().
function statusChangeCallback(response) {
console.log('statusChangeCallback');
if (response.status === 'connected') {
// Logged into your app and Facebook.
FB.api('/me', function(response) {
login(response);
});
} else if (response.status === 'not_authorized') {
document.getElementById('status').innerHTML = 'Please log ' +
'into this app.';
} else {
document.getElementById('status').innerHTML = 'Please log ' +
'into Facebook.';
}
}
window.fbAsyncInit = function() {
FB.init({
appId : '{{ app_id }}',
cookie : true,
version : 'v2.2' // use version 2.2
});
FB.getLoginStatus(function(response) {
statusChangeCallback(response);
});
};
// Load the SDK asynchronously
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
function onLoginClick() {
FB.login(function(response) {
statusChangeCallback(response);
}, {scope: 'public_profile,email'});
};
</script>
{% endblock %}
Обновление компонентов на клиенте
Теперь мы попробуем с сервера обновить компонент на клиенте. Для этого мы на главной (index.erl) сделаем хеадер, на котором будет кнопка выхода. Хеадер будет обновляться после того, как данные сессии очищены:
В событии event (logout) мы очищаем данные сессии и обновляем компонент.
База данных и зависимости
Для доступа к базе мы будем использовать kvs. kvs позволяет хранить связанные списки и поддерживает разные бекенды (Mnesia, Riak, KAI, Redis, MongoDB). Дальше в примере я буду использовать mnesia, потому что она идет в комплекте поставки и ее не надо настраивать.
Зависимости в Эрланг проектах лежат в файле rebar.config, добавляем туда kvs:
Для хранения связанных списков в kvs используется концепция контейнеров и итераторов. Итератор хранит указатели двусвязных списков, а контейнер хранит указатели на голову и хвост списка.
Обновим наши записи в records.hrl добавим итератор комментарий и контейнер пост:
В функции comments (), мы проверяем автризирован ли пользователь. В event (init) мы выбираем все комментарии, которые относяться к данному посту и передаем их в событии event ({client, Comment}), то есть комментарии у нас загружаються после загрузки страницы.
В событии event (comment) мы не только выводим комментарий, но и сохраняем его в БД.
Создание своих элементов
Для постраничной навигации мы добавим в DSL свой элемент pagination. В файле /apps/sample/include/elements.hrl добавим запись, в которой укажем какой модуль отвечает за отображение этого элемента:
Огранизуем постраничный вывод на главной странице. Еще раз отмечу, что kvs плохо подходит для постраничной навигации, и этот код просто демонстрация того, как применения несоответствующих инструментов приводит к запутыванию кода:
-define(POST_PER_PAGE, 3).
page() ->
case wf:q(<<"page">>) of
undefined -> 1;
Page -> wf:to_integer(Page)
end.
pages() ->
Pages = kvs:count(post) div ?POST_PER_PAGE,
case kvs:count(post) rem ?POST_PER_PAGE of
0 -> Pages;
_ -> Pages + 1
end.
posts() -> [
#panel{body=[
#h2{body = #link{body = wf:html_encode(P#post.title), url = "/post?id=" ++ wf:to_list(P#post.id)}},
#p{body = wf:html_encode(P#post.author)}
]} || P <- lists:reverse(kvs:traversal(post, kvs:count(post) - (page() - 1) * ?POST_PER_PAGE, ?POST_PER_PAGE, #iterator.prev))].
Деплой и производительность
Mad позволяет создавать бандл — один файл, в котором храниться код и все необходимые для приложения файлы (шаблоны, статика). Создадим и зальем на удаленный сервер:
mad deps compile plan bundle sample
scp sample root@46.101.117.36:/var/www/sample/
Установим на удаленном сервере Эрланг и запустим наше приложение:
Для тестирования производительности я создал самый маленький дроплет на DigitalOcean (512 MB Memory / 20 GB Disk). Для теста мы сделаем 20 тысяч запросов, по 50 параллельно:
root@ubuntu-1gb-fra1-01:~# ab -l -n 20000 -c 50 -g gnuplot.dat http://46.101.118.21:8001/
...
Concurrency Level: 50
Time taken for tests: 15.131 seconds
Complete requests: 20000
Failed requests: 0
Total transferred: 78279988 bytes
HTML transferred: 76399988 bytes
Requests per second: 1321.80 [#/sec] (mean)
Time per request: 37.827 [ms] (mean)
Time per request: 0.757 [ms] (mean, across all concurrent requests)
Transfer rate: 5052.26 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.3 0 9
Processing: 9 37 4.9 37 65
Waiting: 9 37 4.9 37 65
Total: 11 38 4.9 37 65
Percentage of the requests served within a certain time (ms)
50% 37
66% 38
75% 39
80% 40
90% 44
95% 47
98% 53
99% 56
100% 65 (longest request)
Сервер обрабатывал около 1300 запросов в секунду, 95% запросов выполнено за меньше чем 50 мс, что очень неплохо для хостинга за 5$ в месяц. Тоже самое в виде графика: