Aplikacje webowe w Pythonie dla programistów PHP

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 😉

  1. Luken pisze:

    Świetny tutorial! Dzięki!

  2. Softi pisze:

    Ciekawy wpis. Pozwolił mi trochę przybliżyć Pythona. Ja jednak dalej się będę upierać przy starym dobrym PHP.

  1. There are no trackbacks for this post yet.

Leave a Reply

Informuj mnie o odpowiedziach poprzez e-mail. Możesz również subskrybować wpis bez zostawiania komentarza.