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.
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ć. ;]
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).
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ć.
port = 81 # to u mnie tylko ;]
listen = 0.0.0.0
docroot = "D:/tmp/yaws"
allowed_scripts = yaws
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:
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:
allowed_scripts = yaws php
Do tego, namierzamy plik php.ini i umieszczamy w nim linijkę:
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.
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.
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:
-module(rewriter).
-export([arg_rewrite/1]).
-include_lib("../include/yaws_api.hrl").
arg_rewrite(Arg) ->
% Decode request path
Req = Arg#arg.req,
case Req#http_request.path of
{abs_path, RawPath} ->
case (catch yaws_api:url_decode_q_split(RawPath)) of
{'EXIT', _} ->
Arg1 = Arg;
{Path, _Query} ->
% Do not parse file types matching the regexp rule below.
% This rule is an exact copy of Apache .htaccess rule.
% You can modify it to match your needs.
case regexp:first_match(Path, "\.(js|ico|gif|jpg|png|css|html|htm)$") of
{match, _Start, _Lenght} ->
Arg1 = Arg;
_Else ->
Elems = string:tokens(Path, "/"),
case Elems of
[] ->
% Rewrite request to index.php
NewPath = "/index.php" ++ RawPath,
Req1 = Req#http_request{path = {abs_path, NewPath}},
Arg1 = Arg#arg{req = Req1};
[H|T] ->
% Check if request already begins with index.php
case string:equal(H, "index.php") of
true ->
% Check for possible index.php duplicates
case T of
[] ->
Arg1 = Arg;
[H1|T1] ->
case string:equal(H1, "index.php") of
true ->
% If duplicates found, remove them
L = lremove("index.php", [T1]),
NewPath = "/index.php?query=" ++ join(L, "/"),
Req1 = Req#http_request{path = {abs_path, NewPath}},
Arg1 = Arg#arg{req = Req1};
false ->
Arg1 = Arg
end
end;
false ->
% Rewrite request to index.php
NewPath = "/index.php?query=" ++ RawPath,
Req1 = Req#http_request{path = {abs_path, NewPath}},
Arg1 = Arg#arg{req = Req1}
end
end
end
end;
_Else ->
Arg1 = Arg
end,
Arg1.
% Remove all occurences of Elem from the beginning of the list
lremove(_Elem, []) ->
[];
lremove(Elem, List) ->
[H|T] = List,
case string:equal(H, "index.php") of
true ->
lremove(Elem, T);
false ->
List
end.
% Join list into a string with elements separated with Sep
join([], Sep) ->
Sep;
join([H|T], Sep) ->
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:
ebin_dir = "C:/bin/server/Yaws-1.84/ebin"
a dla hosta:
arg_rewrite_mod = rewriter
Teraz pora na restart serwera i test:
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.
składnia Erlanga przypomina pomieszanie R i Pascala moim zdaniem – jest to na pewno ciekawy jezyk ale w sieci sie gdzies natknalem na opinie, ze testy byly subiektywne (Apache vs. Yaws). Tak tylko mysle czy nie dalo by sie uzyc jakiegos fast CGI zamiast CGI? :> Ogolnie fajnie poczytac o czyms nowym ;).