Пользователь
0,0
рейтинг
20 марта 2013 в 22:36

Разработка → 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

На этом я завершаю свой рассказ, а в следующей статье расскажу о том, как добавить поддержку мультиязычности в наше написанное сегодня приложение. Вопросы, комментарии, замечания приветствуются ;)
Роман @Chvanikoff
карма
26,5
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (39)

  • +5
    Мне кажется, что вместо самописанных c_tpl/0, c_tpl/1, c_tpl/2
    лучше использовать встроенный в rebar плагин и поправить 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, "_dtl"}
        ]
    ]}.
    
    
    • +1
      Спасибо, не знал о такой возможности. А как после изменения шаблона его перекомпилировать? Простой rebar compile сделает это «на живую»? И справится ли с этим Sync?
      • 0
        Да. rebar compile перекомпилит и Sync все замечательно должен подцепить. У меня все всегда работало :)
        • 0
          Окей, тогда сейчас проверю и обновлю статью. Спасибо.
  • +3
    Ковбой — очень коварное существо, высовывать его голой задницей в интернет — чревато.

    1) Боится slow lorry, если кто-то об этом специально не подумал, причём в отличие от апача, который просто выедает префорки, жрёт память одним-единственным процессом, пока не приходит оом и не говорит beam.smp, «довольно».
    2) Не имеет специальной защиты от postdata, то есть вместо url-encoded формочки вам зальют 20Гб (при 16 доступных памяти) и — ваш добрый эрланг всё это всосёт.
    3) Не имеет защиты от излишнего числа коннектов (вариант п.1)
    • +2
      • 0
        Была пофикшена специальная ситуация с slow lorry в середине заголовка, то есть когда метод уже передали, а заголовки нет. И slow lorry это не человек, который «ничего не передаёт» после метода, а человек, который это делает очень медленно.

        что опцию --post-swap пофиксили, это хорошо.

        А фикс к третьему варианту (ab с большой конкуренцией) я не вижу.
        • 0
          Хм… По-моему, cowboy_protocol:recv/3, используемый для получения данных из сокета, без разбору на ситуации ограничивает время их ожидания. Или я что-то путаю и некорректно понял суть проблемы?
          3 — не уверен, что нет лучшего варианта, последнее время не так активно слежу за обновлениями Ковбоя (уж больно их много), но, как вариант, можно последовать совету из комментария к проблеме: github.com/extend/cowboy/issues/213#issuecomment-7427912
          • 0
            А посадить его под nginx?
            • +1
              Как самый простой вариант. Но все, как всегда, зависит от контекста. В контексте данного треда я хотел показать, что nginx не является обязательным звеном.
    • +1
      Мораль — что бы там на бэкенде ни крутилось, за проксирование соединений нгинксом ещё никого не уволили.
  • –4
    Все таки с web frameworks на эрланге все плохо. Столько ерунды надо руками писать.
    • +4
      Ковбой — это не фреймворк, а сервер. Фреймворк — это, например, Chicago Boss.
      • –5
        Да какая разница. Хочешь написать приложение, то либо берешь сервер и делаешь руками убивая кучу времени, либо фреймворк типа чикагы, где лютый оверкилл и ты опять же тратишь время на то, что бы выкинуть половину ненужного говна. Более-менее посередине нитроген, к которому все никак ни одной db не прикрутят.
        В общем по сравнению с тем что есть на питоне — грустно.
        • 0
          Под конкретную задачу — конкретное решение.
          Фреймворки плохо, ибо ограничивают в средствах.
          Библиотеки (втч) аксепторы — хорошо, ибо есть много что готового, но это готовое, ты прозрачно можешь заменить на свое.
          • 0
            В каких средствах меня ограничивает pyramid например? Но даже если это и так, то бывает когда это не главное. Главное быстро писать что хочешь. Вот тут fw и замечательно пригождается.
            • 0
              Главное быстро

              Да, но как понимаю, в этом случае подойдет что угодно, втч «замечательные проекты на php». На тему быстро — требуется время на изучение фреймворка. Во некоторых подобных случаях удобнее и быстрее решить свою конкретную задачу руками.
              Фремворки, кроме того, не всегда прозрачны (если, фреймворк написали не вы сами). Времени на понимание и отладку может уйди безумно много. А может и не уйти.

              Про pyramid ничего сказать не смогу, — не пользовался.
              А вот та же самая Django:
              • ограничение на схему бд;
              • крайне тяжело адаптировать к существующей базе;
              • сложно адаптировать к существующей инфраструктуре;
              • не удобно, и не всегда возможно оптимизировать;
              • есть проблемы с потокобезопасностью, не всегда ясно как их разруливать;
              • ...

              писать что хочешь.

              Опять зависит от того, что Вы конкретно хотите.
              Не всегда задача авторов фреймворка совпадает и вашей текущей задачей.

              Очень хорошо изучать подходы, а не их конкретные реализации. И потом использовать в своих проектах.
  • 0
    А зачем в webserver.erl в функции ensure_started делать pattern-matching если не зависимо от результата вызывается ensure_started(Apps)?
    • +2
      Потому что он может не отматчить вернувшееся от application:start(App) значение (например — {error, {not_started, _}}) и тогда мы упадем в функции ensure_started, как и надо поступать в данной ситуации.
      • 0
        Спасибо за объяснение, думал чего-то, что ok и {error, {already_started, _}} это все что умеет возвращать application:start. Знакомлюсь с эрлангом, пока не знаю всех тонкостей. А вообще спасибо за статью отличное введение в ковбой.

        Чем больше узнаю людей, тем больше я люблю эрланг!
  • 0
    Было бы интересно увидеть аналогичную статью, но DTL заменить на XSLT.
    • +1
      Я бы с радостью, но у меня на него аллергия.
  • 0
    Кстати, мне кажется, что ./tpl лучше убирать в ./priv
    • +1
      Хотя структура канонического Эрланг-приложение это подразумевает, тут все же скорее дело вкуса.
  • 0
    На тему горячей замены кода попробуйте вот это:
    github.com/zavr/nodectl
    Мы пользуемся, пока нареканий не было.
    • 0
      Спасибо, посмотрю. У Sync есть минусы, но плюсов, по сравнению с некоторыми рассмотренными мной ранее решениями, также хватает.
      • 0
        А расскажите подробнее о тех и о других, если можно. Sync никогда не использовал.
        • +1
          Из плюсов основных 2:
          -возможность легко патчить удаленную ноду (правишь код на одной — он подхватывается на остальных)
          -уведомления через Growl (хотя вроде еще какие-то есть для тех, кто не пользует Growl)
          Из минусов основных тоже 2:
          -не умеет апгрейдить код из вложенных директорий (например — если есть модули во вложенных в src/ директориях, то они не будут обновляться на лету). Давно собираюсь посмотреть, да попатчить (или разобраться, как его научить этому), но все никак руки не дойдут.
          -не особо понятно, как интегрировать свои дополнительные действия на апгрейд кода (допустим применимо к данной статье я бы хотел при обновлении src/webserver_app.erl дергать webserver:update_routes/0), тут снова упираемся в то, что надо патчить сорцы как-то.
  • 0
    У меня выдаёт:

    Erlang R15B01 (erts-5.9.1) [source] [64-bit] [smp:2:2] [async-threads:0] [kernel-poll:false]

    Eshell V5.9.1 (abort with ^G)
    1>
    =INFO REPORT==== 24-Apr-2013::20:30:50 ===
    application: webserver
    exited: {bad_return,
    {{webserver_app,start,[normal,[]]},
    {'EXIT',
    {{badmatch,
    {error,
    {shutdown,
    {child,undefined,
    {ranch_listener_sup,http_listener},
    {ranch_listener_sup,start_link,
    [http_listener,100,ranch_tcp,
    [{port,8008}],
    cowboy_protocol,
    [{env,
    [{dispatch,
    [{'_',[],
    [{[],[],index_handler,[]},
    {'_',[],notfound_handler,[]}]}]}]}]]},
    permanent,5000,supervisor,
    [ranch_listener_sup]}}}},
    [{webserver_app,start,2,
    [{file,«src/webserver_app.erl»},{line,20}]},
    {application_master,start_it_old,4,
    [{file,«application_master.erl»},{line,274}]}]}}}}
    type: temporary
    {«init terminating in do_boot»,{{case_clause,{error,{bad_return,{{webserver_app,start,[normal,[]]},{'EXIT',{{badmatch,{error,{shutdown,{child,undefined,{ranch_listener_sup,http_listener},{ranch_listener_sup,start_link,[http_listener,100,ranch_tcp,[{port,8008}],cowboy_protocol,[{env,[{dispatch,[{'_',[],[{[],[],index_handler,[]},{'_',[],notfound_handler,[]}]}]}]}]]},permanent,5000,supervisor,[ranch_listener_sup]}}}},[{webserver_app,start,2,[{file,«src/webserver_app.erl»},{line,20}]},{application_master,start_it_old,4,[{file,«application_master.erl»},{line,274}]}]}}}}}},[{webserver,ensure_started,1,[{file,«src/webserver.erl»},{line,29}]},{webserver,start,0,[{file,«src/webserver.erl»},{line,16}]},{init,start_it,1,[]},{init,start_em,1,[]}]}}

    Crash dump was written to: erl_crash.dump

    после попытки запустить
    soif@LubuntuVM:~/webserver$ erl -pa ebin deps/*/ebin -s webserver

    • 0
      Затруднюсь с диагнозом. Попробуйте с гитхаба взять финальный код (git clone git://github.com/chvanikoff/webserver.git) и запустить (cd webserver && make all run) — если заработает, то ищите неточности в следовании материалам статьи, я все перед публикацией проверял. Если нет, то пишите в личку.
      • +2
        У вас в финальном коде
        Static = fun(Filetype) -> {lists:append(["/", Filetype, "/[...]"]), cowboy_static, [ {directory, {priv_dir, webserver, [list_to_binary(Filetype)]}}, {mimetypes, {fun mimetypes:path_to_mimes/2, default}} ]}

        а в тексте статьи
        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,

        Падает из-за отсутствия квадратных скобок в lists:append(["/", Filetype, "/[...]"])
        • +1
          Спасибо, поправил.
          • 0
            Особенно забавно выглядит после этого мой следующий коммент =)
  • 0
    Проблема решилась установкой пакета R16B от Erlang Solutions (по умолчанию в Убунту ставится R15B).
    Спасибо за статью!
  • 0
    Rebar может сам компилить шаблоны, erlydtl_opts описывается в rebar.config.
  • 0
    У меня erl -pa ebin deps/*/ebin -s webserver падает с ошибкой (установлен Erlang 17.1)

    {«init terminating in do_boot»,{{case_clause,{error,{not_started,cowlib}}},[{webserver,ensure_started,1,[{file,«src/webserver.erl»},{line,26}]},{webserver,start,0,[{file,«src/webserver.erl»},{line,13}]},{init,start_it,1,[]},{init,start_em,1,[]}]}}

    Помогло дописать в webserver.erl ссылку на cowlib:

    -define(APPS, [crypto, ranch, cowlib, cowboy, webserver]).
    • 0
      Статья уже старенькая, давно стоит обновить, все руки не дойдут. Ну может сегодня вечером… :)
      • 0
        Да, сейчас иду по твоему тутору и, например, настройки для статики в ковбое уже по-другому задаются.

        Раз уж зашла речь… У меня не находится priv_dir, сходу не подскажешь почему? :(
        • 0
          Разобрался с priv_dir сам.

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