eRIZ’s weblog

PHP, webdesign, Linux, Windows i inne, bo nie samym chlebem człowiek żyje
Serdecznie zapraszam do udziału w ANKIECIE

Alternatywa dla Apache - YAWS + PHP + Rewrite

Nie da się temu zaprzeczyć - Erlang ma dość dziwną składnię, momentami naprawdę się ciężko połapać po siedzeniu w językach typowo klamerkowych (vide: C, PHP, JS, etc), ale możliwości napisanych w nim aplikacji naprawdę poraża.

I jak by tu nie próbować czegoś nowego? Ejabberd (demon Jabbera) świetnie się sprawuje, przez samą konstrukcję języka, w którym został napisany, koder ma dostęp do potężnego środowiska, które poradzi sobie w naprawdę trudnych warunkach. Dla przykładu - ktoś się pofatygował, aby przetestować Apache w porównaniu do YAWS. Wyniki - szczerze mówiąc - mnie zszokowały…

Apache wysiadło przy 4 000 jednoczesnych żądaniach. Jednak na tym samym sprzęcie, YAWS wytrzymał do ponad 80 000 połączeń. Ktoś jeszcze wątpi w zarządzanie wątkami w Erlangu? Ja nie, zresztą - autor testu powiedział wprost - słabość Apache wynika prawdopodobnie w sposobie zarządzania wątkami w Linuksie. Erlang wykonuje to niezależnie od platformy poprzez własną maszynę wirtualną. Radzi sobie tak, jak została zaprojektowana, czego dowodzą przeprowadzone testy.

Przy pierwszym zetknięciu miałem pewne obawy - maszyna wirtualna, od razu przed oczami ślamazarność Javy. Jednak byłem mile zaskoczony, że jednak są środowiska, w których wydajność i skalowalność jednocześnie mają znaczenie, do tego zasoby dynamicznie typowane… Szok…

środowisko uruchomieniowe

Instalacja pod Uniksami będzie bardzo podobna, sprowadzi się tylko do innego sposobu instalacji pakietów. Repozytoria, paczki - to już zależy od konkretnej dystrybucji. Ja pokażę instalację na przykładzie Windows, gdyż w tym środowisku na co dzień pracuję.

Wspomniałem, że Erlang bazuje na maszynie wirtualnej. Aby zacząć od czegokolwiek, trzeba najpierw ją zainstalować. Wszystko jest na oficjalnej stronie - wystarczy pobrać binarkę dla Windows. Jakieś 60 MiB, ale warto się uzbroić w cierpliwość. ;] Dla środowiska Unix wystarcza paczka erlang-lite, która zawiera wyłącznie środowisko uruchomieniowe.

erlang

demon

Po instalacji maszyny wirtualnej przychodzi pora na właściwą instalację serwera. Jest ona dość banalna, wystarczy nieco popychać instalator klikając przycisk dalej. Ściągamy instalkę ze strony developerów i w zasadzie nie ma czego opisywać. ;]

YAWS

Myśleliście, że będzie ciężko posadzić? Przykro mi, że rozczarowałem. ;]

pierwsze uruchomienie

YAWS nie posiada trybu usługi pod Windows (w Uniksie podejrzewam, że działa ze standardowymi skryptami startowymi), z tego co się rozeznałem, więc będzie trzeba odpalać przez konsolkę. Wystarczy uruchomić wiersz poleceń, przejść do katalogu bin instalacji demona i wydać komendę yaws i otworzyć w przeglądarce stronę localhosta (127.0.0.1).

YAWS strona startowa

Wszystko działa. ;] Teraz pora zajrzeć do konfiguracji. W przeciwieństwie do tej, do której przyzwyczaił nas np. Ejabberd, wszystko jest napisane bardzo czytelnie i intuicyjnie. Zmianę miejsca przechowywania stron tak samo, jak i wirtualne hosty można bardzo łatwo przeprowadzić.

  1. <server 0.0.0.0>
  2.         port = 81 # to u mnie tylko ;]
  3.         listen = 0.0.0.0
  4.         docroot = "D:/tmp/yaws"
  5.         allowed_scripts = yaws
  6. </server>

Trudne? ;]

we wnętrznościach bestii

Serwis bez języków skryptowych dzisiaj nie utrzyma się za długo (choćby przez problem redagowania, pewnych standardowych rozwiązań, itp). YAWS umożliwia korzystanie w serwisach bezpośrednio z kodu Erlanga. Mało tego - powstało już kilka frameworków z myślą o dynamicznych serwisach WWW napisanych właśnie w tym języku, sam serwer udostępnia bogatą bibliotekę API, z której możemy korzystać.

Otwiera to ogromne możliwości, o których - mam nadzieję - będę miał okazję napisać w przyszłości. Jednak Erlanga jeszcze nie znam zbyt dobrze, więc spróbujemy podpiąć do YAWS-a stary, dobry PHP. Nie jest to arcytrudne zadanie, wystarczy dopisać parę linijek do pliku konfiguracyjnego, najważniejsza:

  1. php_exe_path = "C:/bin/server/php5/php-cgi.exe"

to wskazująca na ścieżkę do interpretera (koniecznie do wersji CGI). Umieszczamy ją poza deklaracją danego hosta. Jeśli chodzi o dalszą konfigurację, to wystarczy tylko włączyć obsługę PHP dla hosta poprzez modyfikację jednej linijki do postaci:

  1. allowed_scripts = yaws php

Do tego, namierzamy plik php.ini i umieszczamy w nim linijkę:

  1. cgi.force_redirect = 0

Teraz uruchomienie skryptu PHP powinno spowodować jego wykonanie. :] I tu kieruję podziękowania dla członków listy mailingowej YAWS, gdyby nie Oni - nie byłbym w stanie uruchomić PHP w YAWS.

PHP

Domyślnie plik php.ini jest poszukiwany w katalogu systemowym, aby ustawić własną lokalizację, dodajemy zmienną środowiskową PHPRC ze ścieżką do katalogu z plikiem konfiguracyjnym.

PHPRC

dance me!

Pozostała jeszcze jedna kwestia - przepisywanie adresów (aka. mod_rewrite). I tu dopiero zaczyna się artyleria. Wspomniałem wcześniej o tym, że YAWS umożliwia wykonywanie skryptów Erlanga w locie. I właśnie w celu przepisania adresów go wykorzystamy.

Niestety, nie ma takiej wygody, jak w przypadku htaccess - bez znajomości składni Erlanga może być ciężko. Jeśli chodzi o np. Zend Framework - jest praktycznie gotowiec.

Skrypt ten kopiujemy do katalogu ebin serwera. Co robi? Routuje wszystkie ścieżki prócz paru rozszerzeń jak ZF: /index.php/param…. Można zmodyfikować, no problem. ;] Osobiście sprowadziłem to do takiej postaci:

  1. -module(rewriter).
  2.  
  3. -export([arg_rewrite/1]).
  4.  
  5. -include_lib("../include/yaws_api.hrl").
  6.  
  7. arg_rewrite(Arg) ->
  8.     % Decode request path
  9.     Req = Arg#arg.req,
  10.     case Req#http_request.path of
  11.         {abs_path, RawPath} ->
  12.             case (catch yaws_api:url_decode_q_split(RawPath)) of
  13.                 {'EXIT', _} ->
  14.                     Arg1 = Arg;
  15.                 {Path, _Query} ->
  16.                     % Do not parse file types matching the regexp rule below.
  17.                     % This rule is an exact copy of Apache .htaccess rule.
  18.                     % You can modify it to match your needs.
  19.                     case regexp:first_match(Path, "\.(js|ico|gif|jpg|png|css|html|htm)$") of
  20.                         {match, _Start, _Lenght} ->
  21.                             Arg1 = Arg;
  22.                         _Else ->
  23.                             Elems = string:tokens(Path, "/"),
  24.                             case Elems of
  25.                                 [] ->
  26.                                     % Rewrite request to index.php
  27.                                     NewPath = "/index.php" ++ RawPath,
  28.                                     Req1 = Req#http_request{path = {abs_path, NewPath}},
  29.                                     Arg1 = Arg#arg{req = Req1};
  30.                                 [H|T] ->
  31.                                     % Check if request already begins with index.php
  32.                                     case string:equal(H, "index.php") of
  33.                                         true ->
  34.                                             % Check for possible index.php duplicates
  35.                                             case T of
  36.                                                 [] ->
  37.                                                     Arg1 = Arg;
  38.                                                 [H1|T1] ->
  39.                                                     case string:equal(H1, "index.php") of
  40.                                                         true ->
  41.                                                             % If duplicates found, remove them
  42.                                                             L = lremove("index.php", [T1]),
  43.                                                             NewPath = "/index.php?query=" ++ join(L, "/"),
  44.                                                             Req1 = Req#http_request{path = {abs_path, NewPath}},
  45.                                                             Arg1 = Arg#arg{req = Req1};
  46.                                                         false ->
  47.                                                             Arg1 = Arg
  48.                                                     end
  49.                                             end;
  50.                                         false ->
  51.                                             % Rewrite request to index.php
  52.                                             NewPath = "/index.php?query=" ++ RawPath,
  53.                                             Req1 = Req#http_request{path = {abs_path, NewPath}},
  54.                                             Arg1 = Arg#arg{req = Req1}
  55.                                     end
  56.                             end
  57.                     end
  58.             end;
  59.         _Else ->
  60.             Arg1 = Arg
  61.     end,
  62.     Arg1.
  63.  
  64. % Remove all occurences of Elem from the beginning of the list
  65. lremove(_Elem, []) ->
  66.     [];
  67. lremove(Elem, List) ->
  68.     [H|T] = List,
  69.     case string:equal(H, "index.php") of
  70.         true ->
  71.             lremove(Elem, T);
  72.         false ->
  73.             List
  74.     end.
  75.  
  76. % Join list into a string with elements separated with Sep
  77. join([], Sep) ->
  78.     Sep;
  79. join([H|T], Sep) ->
  80.     H ++ lists:concat([Sep ++ X || X <- T]).

co przekształca URL do postaci /index.php?query=param/…. Zapisany skrypt rewriter.erl należy teraz skompilować. Odpalamy wiersz poleceń w katalogu ebin YAWS, wydajemy komendę erl. Uruchomi się interpreter, który posłuży nam do wygenerowania binarki dla serwera. Wklepujemy komendę c(rewriter.erl). i po naciśnięciu enter powinniśmy otrzymać komunikat z potwierdzeniem oraz plik rewriter.beam, który będzie modułem przekierowań.

Pozostaje jeszcze kwestia konfiguracji. Linijkę z ebin_dir modyfikujemy do postaci:

  1. ebin_dir = "C:/bin/server/Yaws-1.84/ebin"

a dla hosta:

  1. arg_rewrite_mod = rewriter

Teraz pora na restart serwera i test:

rewrite

Jak widać, wszystko działa. ;]

epilog

Erlang jest naprawdę fajnym środowiskiem (tylko ta składnia… :S), udostępnia świetne API (m.in. bazę Mnesia, która jest transakcyjną, odporną na błędy bazą danych, zorientowaną obiektowo)… Mam nadzieję, że będzie za jakiś czas poważną konkurencją nie tylko dla Ruby, czy Pythona, ale również i dla samego PHP.

Oby tylko czas na wszystko pozwolił… ;]

Ewentualne pomyłki wybaczcie - dopiero raczkuję w Erlangu, zapraszam do dyskusji.

5 komentarzy

dopisz swój :: trackback :: RSS z komentarzami

RSS z komentarzami :: trackback

Skomentuj

Możesz używać znaczników XHTML. Dozwolone są następujące tagi: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Wszystkie komentarze przechodzą przez moderację oraz filtry antyspamowe. Nie zostanie opublikowany komentarz, jeśli:

  • Jego treść obraża kogokolwiek.
  • W treści znajdują się wulgaryzmy i słownictwo ogólnie uznane za nieprzyzwoite.
  • Mam wątpliwości co do autora wpisu (Wszelkie anonimy są kasowane - niezależnie od zawartości - wpisz prawdziwy e-mail. Jeśli usunąłem, Twoim zdaniem, komentarz niesłusznie - daj znać). Zdarza się, iż sprawdzam kim jest komentujący.
  • Zawiera jakąkolwiek formę reklamy.

Szufladka