html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Web-сервер на базе Cowboy tutorial

Привет!
В этом туториале я планирую показать тем, кто еще не знаком с веб-сервером Cowboy, как им пользоваться. Для людей, которые имеют опыт работы с ним, данный туториал врядли будет интересен, а вот для тех, кто знает о Ковбое лишь по наслышке — welcome!

Что мы будем делать:
  1. Простейшая установка и запуск сервера
  2. Краткий обзор роутинга, обслуживание статики
  3. Шаблонизация с помощью ErlyDTL (Django Template Language для Erlang)


Для удобства работы нам потребуется rebar, установка нехитрая:
> git clone git://github.com/basho/rebar.git && cd rebar && ./bootstrap

Теперь у нас в директории появился исполняемый файл rebar — копируем (а лучше линкуем) его куда-нибудь в $PATH. Например:
> sudo ln -s `pwd`/rebar /usr/bin/rebar


And here we go!

Простейшая установка и запуск сервера


Для начала создадим директорию и скелет для нашего будущего приложения, в этом нам поможет rebar. Переходим куда-нибудь, где будем создавать приложение и выполняем следующую команду:
> mkdir webserver && cd webserver && rebar create-app appid=webserver

Команда rebar create-app appid=webserver создает скелет простейшего Erlang-приложения и теперь наша директория webserver должна выглядеть таким образом:

Следующее, что мы сделаем — добавим зависимость от Cowboy, Sync, Mimetypes и Erlydtl. Cowboy — наш web-сервер, Sync — утилита, которая позволит нам не перезагружать наш сервер при каждом изменении и будет сама перекомпилировать измененные модули при обновлении, Mimetypes — библиотека для определения соответствия расширения с mimetype (пригодится, когда будем заниматься отдачей статики), а Erlydtl — шаблонизатор. Создадим конфигурационный файл для rebar под названием rebar.config:
Содержимое rebar.config
{deps, [
	{cowboy, ".*", {git, "https://github.com/extend/cowboy.git", {branch, "master"}}},
	{sync, ".*", {git, "git://github.com/rustyio/sync.git", {branch, "master"}}},
	{mimetypes, ".*", {git, "git://github.com/spawngrid/mimetypes.git", {branch, "master"}}},
	{erlydtl, ".*", {git, "git://github.com/evanmiller/erlydtl.git", {branch, "master"}}}
]}.


Создадим файл src/webserver.erl, с помощью которого мы пока будем просто запускать и останавливать наш сервер:
Содержимое src/webserver.erl
-module(webserver).

%% API
-export([
	start/0,
	stop/0
]).

-define(APPS, [crypto, ranch, cowboy, webserver]).

%% ===================================================================
%% API functions
%% ===================================================================

start() ->
	ok = ensure_started(?APPS),
	ok = sync:go().

stop() ->
	sync:stop(),
	ok = stop_apps(lists:reverse(?APPS)).

%% ===================================================================
%% Internal functions
%% ===================================================================

ensure_started([]) -> ok;
ensure_started([App | Apps]) ->
	case application:start(App) of
		ok -> ensure_started(Apps);
		{error, {already_started, App}} -> ensure_started(Apps)
	end.

stop_apps([]) -> ok;
stop_apps([App | Apps]) ->
	application:stop(App),
	stop_apps(Apps).


Теперь вызов webserver:start() запустит по-очереди приложения crypto, ranch, cowboy, webserver и автообновление с помощью Sync, а webserver:stop остановит все запущенное в обратном порядке.
Каскад готов, пора уже переходить к Ковбою. Открываем webserver_app.erl и редактируем функцию start/2:
Функция webserver_app:start/2
start(_StartType, _StartArgs) ->
	Dispatch = cowboy_router:compile([
		{'_', [
			{"/", index_handler, []},
			{'_', notfound_handler, []}
		]}
	]),
	Port = 8008,
	{ok, _} = cowboy:start_http(http_listener, 100,
		[{port, Port}],
		[{env, [{dispatch, Dispatch}]}]
	),
	webserver_sup:start_link().


В правилах диспатчинга мы указали, что абсолютно все запросы кроме "/", которые будут приходить на сервер, мы будем обслуживать с помощью notfound_handler (будем отдавать 404 ошибку), а запросы к "/" будем обрабатывать с помощью index_handler. Значит, стоит их создать:
Содержимое src/index_handler.erl
-module(index_handler).
-behaviour(cowboy_http_handler).
%% Cowboy_http_handler callbacks
-export([
	init/3,
	handle/2,
	terminate/3
]).

init({tcp, http}, Req, _Opts) ->
	{ok, Req, undefined_state}.

handle(Req, State) ->
	Body = <<"<h1>It works!</h1>">>,
	{ok, Req2} = cowboy_req:reply(200, [], Body, Req),
	{ok, Req2, State}.

terminate(_Reason, _Req, _State) ->
	ok.


Содержимое src/notfound_handler.erl
-module(notfound_handler).
-behaviour(cowboy_http_handler).
%% Cowboy_http_handler callbacks
-export([
	init/3,
	handle/2,
	terminate/3
]).

init({tcp, http}, Req, _Opts) ->
	{ok, Req, undefined_state}.

handle(Req, State) ->
	Body = <<"<h1>404 Page Not Found</h1>">>,
	{ok, Req2} = cowboy_req:reply(404, [], Body, Req),
	{ok, Req2, State}.

terminate(_Reason, _Req, _State) ->
	ok.


Вот и все — мы создали простейший веб-сервер, который умеет обрабатывать запросы на localhost:8008 и localhost:8008/WHATEVER. Теперь осталось скомпилировать и запустить веб-сервер:
> rebar get-deps
> rebar compile
> erl -pa ebin deps/*/ebin -s webserver

rebar get-deps подтянет зависимости из конфига, rebar compile скомпилирует код, а erl -pa ebin deps/*/ebin -s webserver запустит сам сервер. Кстати, самое время создать простенький Makefile для облегчения выполнения вышеперечисленных операций:
Содержимое Makefile
REBAR = `which rebar`

all: deps compile

deps:
	@( $(REBAR) get-deps )

compile: clean
	@( $(REBAR) compile )

clean:
	@( $(REBAR) clean )

run:
	@( erl -pa ebin deps/*/ebin -s webserver )

.PHONY: all deps compile clean run


Теперь компилировать проект можно будет вызовом make, а запускать вызовом make run
После того, как сервер был запущен, можно перейти сначала на localhost:8008, а затем на localhost:8008/whatever и убедиться, что сервер работает ожидаемо, отдавая «It works» на первый запрос и «404 Page Not Found» на второй

Краткий обзор роутинга, обслуживание статики


Роутинг в Ковбое не сказать, что самый удобный, однако вполне сносный — основные фишки вроде передачи параметров в URL и валидация этих параметров доступны. Пока у нас в правилах диспатчинга есть лишь два роута:
{"/", index_handler, []},
{'_', notfound_handler, []}

Которые находится внутри другого, определяющего, для какого хоста мы будем использовать вложенные. Подробнее об этом и о роутинге в целом можно почитать здесь: github.com/extend/cowboy/blob/master/guide/routing.md а здесь я уточню лишь что атом '_' означает, что роут будет матчить запросы к абсолютно всем адресам, notfound_handler — имя модуля, который будет обрабатывать заматченные запросы, а [] — список доп. параметров, передаваемых модулю
Хранить статику мы будем в директории priv в поддиректориях priv/css priv/js, priv/img и матчить ее будем по следующим правилам:
/css/WHATEVER -> /priv/css/WHATEVER
/js/WHATEVER  -> /priv/js/WHATEVER
/img/WHATEVER -> priv/img/WHATEVER

Для этого добавим 3 роута соответственно:
Dispatch = cowboy_router:compile([
	{'_', [
		{"/css/[...]", cowboy_static, [
			{directory, {priv_dir, webserver, [<<"css">>]}},
			{mimetypes, {fun mimetypes:path_to_mimes/2, default}}
		]},
		{"/js/[...]", cowboy_static, [
			{directory, {priv_dir, webserver, [<<"js">>]}},
			{mimetypes, {fun mimetypes:path_to_mimes/2, default}}
		]},
		{"/img/[...]", cowboy_static, [
			{directory, {priv_dir, webserver, [<<"img">>]}},
			{mimetypes, {fun mimetypes:path_to_mimes/2, default}}
		]},
		{"/", index_handler, []},
		{'_', notfound_handler, []}
	]}
]).

функция mimetypes:path_to_mimes/2 отвечает за отдачу верного mimetype по расширению файла.
Легко можно заметить, что предыдущие 3 роута почти полностью копируют друг друга за мелкими исключениями, давайте вынесем генерацию роута для статики в функцию и заменим ей роуты:
Static = fun(Filetype) ->
	{lists:append(["/", Filetype, "/[...]"]), cowboy_static, [
		{directory, {priv_dir, webserver, [list_to_binary(Filetype)]}},
		{mimetypes, {fun mimetypes:path_to_mimes/2, default}}
	]}
end,
Dispatch = cowboy_router:compile([
	{'_', [
		Static("css"),
		Static("js"),
		Static("img"),
		{"/", index_handler, []},
		{'_', notfound_handler, []}
	]}
]).

Теперь, чтобы новые правила диспатчинга вступили в силу, нам нужно либо перезагрузить сервер, либо воспользоваться функцией cowboy:set_env/3
Первое — неспортивно, да и перезагружать сервер на каждый чих в правилах роутинга замучаешься, поэтому добавим функцию для обновления роутинга в нашем файле webserver, чтобы можно было в консоли вызвать webserver:update_routing(). И, чтобы функция webserver:update_routing/0 знала о новых роутах — вынесем их определение в отдельную функцию. В итоге файл webserver_app.erl примет следующий вид:
Содержимое src/webserver_app.erl
-module(webserver_app).
-behaviour(application).

%% Application callbacks
-export([
	start/2,
	stop/1
]).

%% API
-export([dispatch_rules/0]).

%% ===================================================================
%% API functions
%% ===================================================================

dispatch_rules() ->
	Static = fun(Filetype) ->
		{lists:append(["/", Filetype, "/[...]"]), cowboy_static, [
			{directory, {priv_dir, webserver, [list_to_binary(Filetype)]}},
			{mimetypes, {fun mimetypes:path_to_mimes/2, default}}
		]}
	end,
	cowboy_router:compile([
		{'_', [
			Static("css"),
			Static("js"),
			Static("img"),
			{"/", index_handler, []},
			{'_', notfound_handler, []}
		]}
	]).

%% ===================================================================
%% Application callbacks
%% ===================================================================

start(_StartType, _StartArgs) ->
	Dispatch = dispatch_rules(),
	Port = 8008,
	{ok, _} = cowboy:start_http(http_listener, 100,
		[{port, Port}],
		[{env, [{dispatch, Dispatch}]}]
	),
	webserver_sup:start_link().

stop(_State) ->
	ok.


Теперь добавим функцию update_routing в модуль webserver.erl:
Функция webserver:update_routes/0
update_routes() ->
	Routes = webserver_app:dispatch_rules(),
	cowboy:set_env(http_listener, dispatch, Routes).


И не забудьте добавить функцию в аттрибут -export(), после чего он станет выглядеть так:
%% API
-export([
	start/0,
	stop/0,
	update_routes/0
]).

выполняем в консоли webserver:update_routes()., создаем директории для статики
> mkdir priv && cd priv && mkdir css js img

и кладем туда какие-нибудь соответствующие файлы, после чего можно проверить, что они отдаются, как и предполагалось, по адресу localhost:8008/PATH/FILE

Шаблонизация с помощью ErlyDTL (Django Template Language для Erlang)


Evan Miller, автор небезызвестного web-фреймворка Chicago Boss под Erlang, портировал Django Template Language (https://docs.djangoproject.com/en/dev/topics/templates/) на Erlang и получилось это, откровенно говоря, довольно круто. Собственно, именно этот шаблонизатор я бы и порекомендовал к использованию в ваших будущих проектах — альтернатив лучше я пока не видел.
Создаем новую директорию webserver/tpl и сохраняем туда три шаблона:

Содержимое tpl/layout.dtl
<!DOCTYPE html>
<html>
<head>
	<title>Webserver</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>


Содержимое tpl/index.dtl
{% extends "layout.dtl" %}
{% block content %}
<h1>Hello, {{ username | default : "stranger" }}!</h1>
{% endblock %}


Содержимое tpl/404.dtl
{% extends "layout.dtl" %}
{% block content %}
<h1>URL <span style="color:red;">{{ url }}</span> does not exists.</h1>
{% endblock %}



Чтобы использовать шаблоны, их нужно скомпилировать. Делается это с помощью erlydtl:compile/3 следующим образом:
ok = erlydtl:compile("tpl/layout.dtl", "layout_tpl", []),
ok = erlydtl:compile("tpl/index.dtl", "index_tpl", []),
ok = erlydtl:compile("tpl/404.dtl", "404_tpl", []).

Последний аргумент — список опций для компиляции шаблона, прочитать о которых подробнее можно здесь: github.com/evanmiller/erlydtl
Чтобы руками не компилировать все шаблоны каждый раз при изменении, создадим функции в модуле webserver, которые будут заниматься перекомпиляцией:
Функции для перекомпиляции шаблонов
c_tpl() ->
	c_tpl([]).

c_tpl(Opts) ->
	c_tpl(filelib:wildcard("tpl/*.dtl"), Opts).

c_tpl([], _Opts) -> ok;
c_tpl([File | Files], Opts) ->
	ok = erlydtl:compile(File, re:replace(filename:basename(File), ".dtl", "_tpl", [global, {return, list}]), Opts),
	c_tpl(Files, Opts).


и экспортируем их:
%% API
-export([
	start/0,
	stop/0,
	update_routes/0,
	c_tpl/0, c_tpl/1, c_tpl/2
]).

c_tpl/0 будет перекомпилировать все шаблоны из директории tpl без опций, c_tpl/1 будет делать то же самое, только с заданными опциями, а c_tpl/2 будет перекомпилировать заданные файлы с заданными опциями. Давайте скомпилируем все шаблоны выполнив в консоли Эрланга webserver:c_tpl().
Также обновим наш rebar.config, чтобы при компиляции он также компилировал и шаблоны (спасибо за подсказку egobrain):
Обновленный rebar.config
{plugins,[rebar_erlydtl_compiler]}.

{deps, [
    {cowboy, ".*", {git, "https://github.com/extend/cowboy.git", {branch, "master"}}},
    {sync, ".*", {git, "git://github.com/rustyio/sync.git", {branch, "master"}}},
    {mimetypes, ".*", {git, "git://github.com/spawngrid/mimetypes.git", {branch, "master"}}},
    {erlydtl, ".*", {git, "git://github.com/evanmiller/erlydtl.git", {branch, "master"}}}
]}.

{erlydtl_opts,[
    {compiler_options, [debug_info]},
    [
        {doc_root, "tpl"},
        {out_dir, "ebin"},
        {source_ext, ".dtl"},
        {module_ext, "_tpl"}
    ]
]}.


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

Теперь редактируем наши хендлеры, чтобы они отдавали ответом скомпилированные шаблоны, а также передаем в шаблоны нужные переменные:

Содержимое src/index_handler.erl
-module(index_handler).
-behaviour(cowboy_http_handler).
%% Cowboy_http_handler callbacks
-export([
	init/3,
	handle/2,
	terminate/3
]).

init({tcp, http}, Req, _Opts) ->
	{ok, Req, undefined_state}.

handle(Req, State) ->
	{Username, Req2} = cowboy_req:qs_val(<<"username">>, Req, "stranger"),
	{ok, HTML} = index_tpl:render([{username, Username}]),
	{ok, Req3} = cowboy_req:reply(200, [], HTML, Req2),
	{ok, Req3, State}.

terminate(_Reason, _Req, _State) ->
	ok.


Содержимое src/notfound_handler.erl
-module(notfound_handler).
-behaviour(cowboy_http_handler).
%% Cowboy_http_handler callbacks
-export([
	init/3,
	handle/2,
	terminate/3
]).

init({tcp, http}, Req, _Opts) ->
	{ok, Req, undefined_state}.

handle(Req, State) ->
	{URL, Req2} = cowboy_req:url(Req),
	{ok, HTML} = '404_tpl':render([{url, URL}]),
	{ok, Req3} = cowboy_req:reply(404, [], HTML, Req2),
	{ok, Req3, State}.

terminate(_Reason, _Req, _State) ->
	ok.


Вот, собственно, и все. Открываем localhost:8008/?username=world или localhost:8008/qweqweasdasd и радуемся, что все работает ровно так, как мы ожидали.

Полный код проекта можно найти здесь: github.com/chvanikoff/webserver

На этом я завершаю свой рассказ, а в следующей статье расскажу о том, как добавить поддержку мультиязычности в наше написанное сегодня приложение. Вопросы, комментарии, замечания приветствуются ;)
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

0

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции