Czasami przychodzi ten dzień, że mamy dość naszego poczciwego PHP. Czasami nawet nie mamy go dość ale chcielibyśmy spróbować coś innego, albo po prostu w pracy chcą, żebyśmy coś napisali w jakimś innym języku.
W mojej pracy magisterskiej mam między innymi porównać wydajność dwóch języków (vel interpreterów języków) skryptowych – PHP i Pythona w typowych zastosowaniach (pobieranie rekordów z bazy, parsowanie xml etc).
Stanąłem więc przed wyzwaniem nauczenia się Pythona, a co więcej podpięcia go apache-a, aby generował jakieś strony. Artykuł ten będzie krótkim tutorialem, o tym jak skonfigurować Pythona i Apache-a oraz odpalić prostą PHP-like aplikacje. Mam nadzieje również, że ten artykuł przerodzi się w jakąś dłuższą serię o tym jak zrobić to samo w obu językach .
Taka mała uwaga odnośnie tekstu, zakładam, że czytający ten tekst znają podstawy pythona umożliwiające napisanie prostego skryptu „Hello World”, jeżeli nie, to polecam „Zanurkuj w Pythonie” – dobry wstęp/tutorial.
Setup.
Zakładam, że wszyscy mają skonfigurowanego Apache-a w celu tworzenia aplikacji PHP-owych. Pierwszym krokiem jaki będziemy musieli uczynić to ściągnięcie odpowiedniej paczki instalacyjnej Pythona (najlepiej wersji 2.6).
Jako, że pracuje na Windowsie, ściągałem pierwszą z góry paczkę. Jest to fajny instalator, który po kilku kliknięciach zainstaluje i skonfiguruje nam Pythona, nie ma tu żadnych specyficznych opcji więc nie będę się zagłębiał.
Drugim krokiem będzie ściągnięcie mod_wsgi, jest tam kilka paczek dla Windowsa i Linux-a, specyficznych dla konkretnych wersji interpretera, my ściągamy tą dla wersji 2.6. Ściągnięta paczkę rozpakowujemy i kopiujemy do katalogu rozszerzeń apache-a (katalog_apache/modules).
Następnie otwieramy plik httpd.conf i dodajemy następujące linie.
LoadModule wsgi_module modules/mod_wsgi-win32-ap22py26-3.0.so
WSGIScriptAlias /py D:/python_test/index.py
Gdzie oczywiście mod_wsgi-win32… jest nazwą pliku modułu, który skopiowaliśmy. Natomiast dyrektywa WSGIScriptAlias mówi apache-owi, że cały ruch, który przychodzi na adres http://localhost/py ma przekierowywać do skryptu znajdującego się w ścieżce D:/python_test/index.py .
Gdy już powpisywaliśmy wszystko do httpd.conf, zapisujemy plik i restartujemy apache-a. Jeżeli wszystko poszło gładko to nie powinno być żadnych błędów logu błędów apache-a. W razie błędów, polecam Wiki projektu mod_wsgi. Tam znajdziecie odpowiedzi na większość pytań związanych z instalacją.
Nasz pierwszy web-owy skrypt.
Każda aplikacja działająca na mod_wsgi musi stosować się do specyfikacji WSGI (Web Server Gateway Interface), która została opisana w jakimś tam mądrym dokumencie Pythonowym. Jest on długi i nudny więc bez większych ceregieli powiem to co ważne.
Po pierwsze jest jeden plik wejściowy, który w naszym przypadku nazywa się index.py, w pliku tym powinna zostać zdefiniowana funkcja:
def application(environ, start_response):
pass
Funkcja musi nazywać się „application” i pobierać ww. parametry. Zmienna environ to słownik, zawierający tą samą zawartość co PHP-owe $_ENVIROMENT, natomiast start_response jest funkcją, wywołując którą ustawiamy nagłówki i rozpoczynamy wypisywanie strony.
Przykład:
def application(environ, start_response):
output = "
Hello web world
"
response_headers = [('Content-type', 'text/html'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
Tak w zasadzie wygląda szkielet naszej aplikacji „Hello web world”, ustawia ona headery i wypisuje paragraf tekstu o treści „Hello web world”.
Obsługa błędów.
Błędy są częścią życia każdej aplikacji i przydało by się je jakoś obsługiwać. Dzięki temu, że wszystkie błedy w Pythonie są wyjątkami wystarczy osaczyć nasz kod w bloku try…except, który jest odpowiednikiem PHP-owego try…catch, a więc.
def application(environ, start_response):
try:
output = "
Hello web world
"
response_headers = [('Content-type', 'text/html'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
except Exception as e:
output = 'Exception of type: ' + str(e.__class__) + '\nwith message:' + str(e)
status = '200 OK'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
Tak skonstruowany skrypt w wypadku błędu innego niż błąd parsowania czy kompilacji wypisze nam do przeglądarki treść wyjątku. Jeżeli wystąpi błąd parsowania lub kompilacji, polecam zajrzeć do error loga apache-a.
Prosty router
Ostatnią rzeczą jaką chciałbym poruszyć w tym artykule, jest skonstruowanie prostego router-a. Założenia są takie: gdy wpiszemy do przeglądarki http://localhost/py/nazwa_pliku.py, router ma zaimportować plik o nazwie „nazwa_pliku.py”, który jest położony w tym samym katalogu, oraz wywołać z niego funkcje o nazwie „test”.
Najpierw gotowy skrypt:
import imp
import os
import os.path
def application(environ, start_response):
try:
status = '200 OK'
uri = environ['REQUEST_URI']
# @type uri str
target = uri.split('/')[2]
script_path = environ['SCRIPT_FILENAME'] + '/../'
app_path = os.path.realpath(script_path) + '/'
script_path = app_path + target
module = imp.load_source('test_case', script_path)
output = str( module.test(app_path) )
response_headers = [('Content-type', 'text/html'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
except Exception as e:
output = 'Exception of type: ' + str(e.__class__) + '\nwith message:' + str(e)
status = '200 OK'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
W stosunku do poprzednich przykładów doszło kilka rzeczy. Po pierwsze dyrektywy ‘import’, dzięki którym uzyskujemy dostęp do odpowiednich namespace-ów. Po drugie
target = uri.split('/')[2]
script_path = environ['SCRIPT_FILENAME'] + '/../'
app_path = os.path.realpath(script_path) + '/'
script_path = app_path + target
Tniemy systemową ścieżkę do do pliku po ukośniku (/) i tworzymy z niej nazwę pliku do zaimportowania.
Po trzecie:
module = imp.load_source('test_case', script_path)
output = str( module.test(app_path) )
module = … jest php-owym odpowiednikiem „require $zmienna_z_nazwa_pliku;”, niestety nie da się w Pythonie po prostu napisać „import zmienna_z_nazwa_pliku”. W kolejnej linii wywołuje funkcje „test” z zaimportowanego dynamicznie pliku i przypisuje ją do zmiennej output.
Taka mała uwaga – widzimy, że wynik wywołania funkcji test jest rzutowany na string. Robione jest to ponieważ, specyfikacja wsgi, mówi, że wynik musi być stringiem. Niestety albo i stety Python nic nam w locie nie konwertuje, więc sami musimy zadbać o to, aby np. zwrócona liczba stała się stringiem, to samo ma się w wypadku Unicodu.
Żeby przykład działał, zróbmy sobie jakiś plik o nazwie „test_abc.py” w tym samym katalogu co index.py o treści:
def test(app_path):
return app_path+'to ja funkcja test z pliku test_abc'
Teraz wpisując http://localhost/py/test_abc.py powinniśmy otrzymać w przeglądarce odpowiedni komunikat.
Uczę się wciąż Pythona, więc proszę o wyrozumiałość gdyby coś nie działało. Wszelkie uwagi mile widziane