Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Веб-программирование в Erlang

Нам понадобится веб-сервер YAWS (Yet Another Web Server). Обладатели Убунты могут пакетно установить yaws:
 apt-get build-dep erlang yaws
В большинстве линуксовых дистрибутивов yaws прийдется ставить из исходников, которые лежат тут.

YAWS имеет следуюшие достоинства:

  скорость
  масштабируемость
  генерация статического и динамического контента
  виртуальный хостинг
  отладчик
  кеширование
  ssl
  аутентификация
  сессии
  модульная архитектура
  веб-сокеты
 
Когда вы установите yaws из исходников с помощью команды make install, конфиг будет установлен по умолчанию в /usr/local/etc/yaws/yaws.conf. Лог файлы лежат в каталоге logdir = /usr/local/var/log/yaws. Дополнительные бинарные сборки можно складывать в ebin_dir = /usr/local/lib/yaws/examples/ebin. Для файлов с расширением .hrl определен каталог include_dir = /usr/local/lib/yaws/examples/include. Конфигурация самого сервера
 <server localhost>
 port = 8000
 listen = 127.0.0.1
 docroot = /home/yaws/www
 </server>
Запуск сервера в интерактивном режиме:
 yaws -i
Динамический контент в yaws реализован с помощью расширения .yaws. Страница с таким расширением может содержать как статический html-код, так и динамический эрланговский, обрамленный с помощью тегов <erl> ... </erl>, внутри которого обязательно должна быть эрланговская функция out. Есть два варианта вызова функции out. Первый - она возвращает кортеж .{html, String}, где String - это html-код:
 <html>
 <h1> Example 1 </h1>
 <erl>
 out(A) ->
 Headers = A#arg.headers,
 {html, io_lib:format("You say that you’re running ~p",
   [Headers#headers.user_agent])}.
 </erl>
 </html>
Такая страница выводит что-то типа:
  Example 1
 You say that you are running "Mozilla/5.0 (X11; Linux i686; rv:31.0) Gecko/20100101 Firefox/31.0"  
Второй вариант вызова out - функция out возвращает кортеж{ehtml, EHTML}:
 <erl>
 {ehtml, {table, [{bgcolor, grey}],
     [
       {tr, [],
       [
 	{td, [], "1"},
 	{td, [], "2"},
 	{td, [], "3"}
       ]
     },
     {tr, [],
 [{td, [{colspan, "3"}], "444"}]}]}}.
 <erl>
Такой код вернет следующий html:
 <table bgcolor="grey">
   <tr>
     <td> 1 </td
     <td> 2 </td>
     <td> 3 </td>
   </tr>
   <tr>
     <td colspan="3"> 444 </td>
   </tr>
 </table>
Во втором случае возможен дополнительный вызов других эрланговских функций.

Параметр #arg содержит в себе многие подробности о клиентском запросе - заголовки, данные, пути и т.д. - вот полный список:

 -record(arg, {
 	clisock,	%	the socket leading to the peer client
 	client_ip_port,	%	{ClientIp, ClientPort} tuple
 	headers,	%	headers
 	req,	        %	request
 	orig_req,	%	original request
 	clidata,	%	The client data (as a binary in POST requests)
 	server_path,	%	The normalized server path
                        % 	(pre-querystring part of URI)
 	querydata,	%	For URIs of the form ...?querydata
 	                %	equiv of cgi QUERY_STRING
 	appmoddata,	%	(deprecated - use pathinfo instead) the remainder
 	                %	of the path leading up to the query
 	docroot,	%	Physical base location of data for this request
 	docroot_mount,	%	virtual directory e.g /myapp/ that the docroot
 	                %	refers to.
         fullpath,	%	full deep path to yaws file
 	cont,     	%	Continuation for chunked multipart uploads
 	state,       	%	State for use by users of the out/1 callback
 	pid,      	% pid of the yaws worker process
 	opaque, 	% useful to pass static data
 	appmod_prepath, % (deprecated - use prepath instead) path in front
 	                % of: 
 	prepath,	% Path prior to ’dynamic’ segment of URI.
 	                % or .yaws,.php,.cgi,.fcgi etc script file.
         pathinfo	% Set to ’/d/e’ when calling c.yaws for the request
 	                % http://some.host/a/b/c.yaws/d/e
 	                % equiv of cgi PATH_INFO
 	}).
 
 
 -record(http_request,{method,
 	path,
 	version}).
 	
 -record(headers, {
       connection,
       accept,
       host,
       if_modified_since,
       if_match,
       if_none_match,
       if_range,
       if_unmodified_since,
       range,
       referer,
       user_agent,
       accept_ranges,
       cookie = [],
       keep_alive,
       location,
       content_length,
       content_type,
       content_encoding,
       authorization,
       transfer_encoding,
       x_forwarded_for,
       other = []
       % misc other headers
       }). 
Вывести это на экран с помощью ehtml можно так:
 <html>
 <h2> The Arg </h2>
 <p>This page displays the Arg #argument structure
 supplied to the out/1 function.
 
 <erl>
 
 out(A) ->
 
     Peer = A#arg.client_ip_port,
     Req = A#arg.req,
     H = yaws_api:reformat_header(A#arg.headers),
     {ehtml,
      [{h5,[], "The headers passed to us were:"},
       {hr,[],[]},
       {ol, [],lists:map(fun(S) -> {li,[], {p,[],S}} end,H)},
 
       {h5, [], "The request"},
       {ul,[],
        [{li,[], f("method: ~s", [Req#http_request.method])},
         {li,[], f("path: ~p", [Req#http_request.path])},
         {li,[], f("version: ~p", [Req#http_request.version])}]},
 
       {hr,[],[]},
       {h5, [], "Other items"},
       {ul,[],
        [{li, [], f("Peer: ~p", [Peer])},
         {li,[], f("docroot: ~s", [A#arg.docroot])},
         {li,[], f("fullpath: ~s", [A#arg.fullpath])}]},
       {hr,[],[]},
       {h5, [], "Parsed query data"},
       {pre,[], f("~p", [yaws_api:parse_query(A)])},
       {hr,[],[]},
       {h5,[], "Parsed POST data "},
       {pre,[],  f("~p", [yaws_api:parse_post(A)])}]}.
 
 </erl>
Здесь используются несколько функций из модуля yaws_api. Распарсить GET-запрос можно с помощью функции yaws_api:parse_query. Распарсить POST-запрос можно так:
 out(A) ->
   L = yaws_api:parse_post(A),
   {html, f("~p", [L])}
Сделать upload файла на сервер можно так:
 out(A) ->
   Form =
     {form, [{enctype, "multipart/form-data"},
 	{method, post},
 	{action, "file_upload_form.yaws"}],
 	[{input, [{type, submit}, {value, "Upload"}]},
 	{input, [{type,file}, {width, "50"}, {name, foo}]}]},
   {ehtml, {html,[], [{h2,[], "A simple file upload page"},
       Form]}}.
 
 
yaws поддерживает куки, которые лежат в основе сессий. Для работы с сессиями есть несколько функций:
  yaws_api:new_cookie_session(Opaque) - создает сессию
  yaws_api:cookieval_to_opaque(Cookie)
  yaws_api:replace_cookie_session(Cookie, NewOpaque) 
  yaws_api:delete_cookie_session(Cookie) - удаляет сессию
 
Проверку можно сделать так:
 -record(session, {user,
     passwd,
     udata = []}).
     
 get_cookie_val(CookieName, Arg) ->
     H = Arg#arg.headers,
     yaws_api:find_cookie_val(CookieName, H#headers.cookie).
     
 check_cookie(A, CookieName) ->
     case get_cookie_val(CookieName, A) of
       [] ->
 	  {error, "not logged in"};
       Cookie ->
 	  yaws_api:cookieval_to_opaque(Cookie)
     end.
Редирект на другую страицу делается так:
 <erl>
 out(Arg) ->
 URL = "http://www.erlang.org",
 {redirect, URL}.
 </erl>
Аутентификацию можно организовать следующим образом: имеется стартовая страница с формой, на которой пользователь набирает логин с паролем, после чего перенаправляется на другую страницу, которая обрабатывает запрос. Стартовая страница login.yaws:
 <erl>
 out(A) ->
   {ehtml,
     {html,[],
       [{h2, [], "Login page"},
       {hr},
       {form, [{action,"/login_post.yaws"},{method,post}],
       [{p,[], "Username"}, {input, [{type,text},{name,uname}]},
       {p,[],"Password"}, {input, [{type,password},{name,passwd}]},
       {input, [{type,submit},{value,"Login"}]},
       {input, [{type,hidden},{name,url}, {value, A#arg.state}]}]}]}}.
 </erl>
Вторая страница login_post.yaws, обрабатывающая запрос:
 <erl>
 
 -include("myapp.hrl").
 %% myapp.hrl нужно прописать в yaws.conf
 
 kv(K,L) -> 
   {value, {K, V}} = lists:keysearch(K,1,L), V.
 
 out(A) ->
   L = yaws_api:parse_post(A),
   User = kv(user, L),
   Pwd = kv(passwd, L),
   case myapp:auth(User, Pwd) of
       ok ->
 	  S = #session{user = User,
 		       passwd = Pwd,
 		       udata = []},
 	  %% создаем новую сессию
 	  Cookie = yaws_api:new_cookie_session(S),
 	  [{redirect_local, kv(url, L)},
 	  yaws_api:set_cookie("myapp_sid",Cookie,[])]
       Err ->
 	  {ehtml,
 	  {html, [],
 	  {p, [], f("Bad login: ~p",[Err])}}}
     end.
 </erl>
Appmods позволяет в yaws контролировать адресную строку и делать редирект. Работает это следующим образом: допустим, на сервере загружается адрес:
   http://localhost:8000/my-path/my_url.html
Если мы в конфиге пропишем секцию appmods:
 <server localhost>
 	...
         appmods = <my_path, my_appmod>
 </server>
то вместо загрузки страницы my_url.html будет вызвана функция my_appmod:out. Нужно положить рядом со страницей my_url.html эрланговский модуль my_appmod.erl, в котором нужно реализовать функцияю out:
 -module(my_appmod).
 -compile(export_all).
 
 out(A) ->
     {ehtml,
      [{p,[],
      ...
Если мы хотим обрабатывать все урлы:
 <server localhost>
 	...
         appmods = </, my_appmod >
 </server>
Если мы хотим для некоторых путей оставить обычную обработку:
 <server localhost>
 	...
         appmods = </, my_appmod exclude_paths icons js top/static>
 </server>

Пример

Резюмируя все вышесказанное, рассмотрим простое веб-приложение, которое можно найти в исходниках yaws - оно называется shoppingcart. Стартовая страница index.yaws веб-приложения содержит форму авторизации, после прохождения которой вы попадаете на страницу, предлагающую вам купить товар. В приложении всего 4 страницы:
 index.yaws
 loginpost.yaws
 logout.yaws
 shopcart_form.yaws
На всех страницах контент генерится динамически, и все страницы выглядят похожим образом - в них совершенно отсутствует статический html-код:
 <erl>
 
 out(A) ->
     case shopcart:top(A) of
         ok ->
             shopcart:index(A);
         X ->
             X
     end.
 
 </erl>
И рядом лежит эрланговский модуль shopcart.erl, в котором реализована вся эта динамика. На каждой странице вначале всегда вызывается функция top(), которая проверяет куки, и если не находит их, то перенаправляет пользователя на страницу авторизации:
 top(A) ->
     case check_cookie(A) of
         {ok, _Session, _Cookie} ->
             ok;
         {error, _Reason} ->
             login(A)
     end.
Функция Login, в которой используется Ehtml:
 login(A) ->
     CSS = css_head("Shopcart"),
     Head = head_status("Not lgged in"),
     Top = toprow(),
     Login =
         {ehtml,
          [{h2, [], "Shopcart login"},
           {form, [{method, get},
                   {action, "loginpost.yaws"}],
            [
             {p, [], "Username"},
             {input, [{name, user},
                      {type, text},
                      {value, "Joe Junk shopper"},
                      {size, "48"}]},
 
 
             {p, [], "Password"},
             {input, [{name, password},
                      {type, text},
                      {value, "xyz123"},
                      {size, "48"}]},
 
             {input, [{type, submit},
                      {value, "Login"}]},
 
             {input, [{name, url},
                      {type, hidden},
                      {value, xpath((A#arg.req)#http_request.path, A)}]}
            ]
            }
          ]},
     [CSS, Head, Top, Login, bot(), break].

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье