18. 5. 2013

Jak na Python na forpsi.com

Tak jsem si udělal ještě jednu radost. Konečně jsem si pořídil webhosting www.forpsi.com, který podporuje na tvorbu stránek Python. Není to asi moc využívaná služba, protože dnes je standardem dělat weby v PHP. Což umím taky, ale přeci jen, mám rád Python a programuje se mi v něm snadněji.

Python je ale na rozdíl od PHP univerzální jazyk a tak je jeho použití na webu obtížnější. Zvláště když je tato služba ne webu www.forpsi.com prakticky nezdokumentovaná. Naštěstí administrátoři jsou na forpsi vstřícní a ochotní, takže teď už vím jak na to a právě o tyhle znalosti se chci tady podělit.

Které rozhraní

Jak asi každý zájemce o Python na webu ví, tak existuje několik rozhraní mezi Pythonem a webovým serverem (Apache). Je to důležité, protože pro každé se programuje trochu jinak. Ty nejznámější rozhraní jsou CGI, mod_fastcgi, mod_fcgid, mod_python a mod_wsgi. Tohle je právě jedna z komplikací, které programátor PHP aplikace nemusí řešit. I tam je možná variabilita jako mod_php nebo CGI, ale programátor to nemusí řešit, z jeho pohledu to funguje stejně (skoro).

Nejlepší rozhraní pro Python je asi WSGI, ale to zrovna forpsi nepodporuje, stejně jako mod_python. Takže už asi tušíte, že podporováno je CGI a FastCGI. CGI je nejstarší, nejpomalejší a vůbec nedoporučované. Hlavně kvůli záteži serveru. Ptal jsem se adminů, zda do budoucna plánují podporu výkonnějších rozhraní a neplánují. Jak z administračních/bezpečnostních důvodů (i PHP jim kupodivu jede přes CGI a FastCGI) tak prý proto, že servery mají škálovatelné a výkonu mají dost. No prosím, když jim to nevadí, tak mě také ne.

Zbývá tedy volba mezi CGI a FastCGI. Nejprve jsem chtěl používat FastCGI, ale nakonec jsem se rozhodl, že si nebudu komplikovat život. FastCGI neni v Pythonu nějak zvlášť dobře zdokumentované a je docela fuška k tomu najít rozumnou dokumentaci. Nedá sepoužívat přímo, protože nemá přímou podporu. Dá se využít třeba přes handlery WSGI. Ale třeba příklad z oficiální dokumentace:
from flup.server.fcgi import WSGIServer
nefunguje, protože flup není standardním modulem Pythonu , což přináší alší komplikace. A tak jsem si řekl, že na ty mé pokusy a pár dotazů za den, které Python obslouží si docela dobře vystačím se starým dobrým CGI. CGI je jednoduché, spolehlivé a v Pythonu dobře podporované a zdokumentované. 

CGI-BIN

Po výběru rozhraní přichází druhá komplikace. Programátoři PHP aplikací jsou zvyklí, že mohou mít PHP skripty kdekoli na webu a často, že jeden php soubor představuje jednu webovou stránku. Až u pokročilejších aplikací použit model, kdy se volá jeden skript, který vrací  různé stránky. U Pythonu tomu tak není (alespoň na forpsi, protože je to věc nastavení webového serveru, ale je to nastavení obvyklé). Na forpsi musí být python skripty, které spouští webový server (tedy netýká se to modulů) umístěny v adresáři /www/cgi-bin a musí mít nastaveby práva pro spuštění. Z toho důvodu se u Pythonu ani prakticky nedá nepoužívat model, že co skript, to jedna stránka. 

Použít se to samozřejmě může, třeba při pokusech a s omezením na cgi-bin. Ale jinak je vhodné udělat normální webovou aplikaci a před uživateli  skrýt skutečnost, že tato aplikace se volá jako skript z cgi-bin. To abycho nebyli za vidláky, protože webová adresa www.mujweb.cz vypadá lépe než adresa www.mujweb.cz/cgi-bin/aplikace.py.

Protože já na jednom webu chci provozovat víc drobnějších aplikací, které chci mít od sebe oddělené, přeji si mít tyto spouštěcí adresy: aplikace1.mujweb.cz nebo aplikace2.mujweb.cz.  Toho lze naštěstí na forpsi dosáhnout, i když je to trochu náročné (hlavně na to přijít, pak už je to snadné).

Na forpsi má uživatel k dispozici tuto adresářovou strukturu:

/data
/stat
/tmp
/www
/www/cgi-bin
/www/static

Přičemž platí, že webový server Apache má přístup pouze do adresáře www a jeho podadresářů. Python má pak přístup do všech adresářů. Python skripty jde spustit pouze z adresáře cgi-bin. Budou-li jinde, tak je Apache buď vůbec neuvidí (jsou mimo www) nebo je uvidí, ale nespustí a místo spuštění vypíše jejich obsah (tedy zdrojový kód). Což může být dosti nepříjemné, protože pak kdokoli na internetu může vidět zdrojové kódy vaší aplikace a v nich bývají občas tajné informace jako hesla do databáze a podobně.

Proto je velmi vhodné, žádoucí a bezpečné (prostě nejlepší) mít aplikaci umístěnou v adresáři data (třeba /data/aplikace1). Tam Apache nevidí a nikdo se přes něj nedostane k obsahu vašich zdrojových kódů. Protože tam ale Apache nevidí, tak ji odtud také neumí spustit. Proto je potřeba spouštěcí skript umístit do /www/cgi-bin. Tam ho Apache spustí, čímž ho předá Pythonu, který už vidí do /data a tak si může importovat moduly aplikace.

Souhrn:
Aplikace, tedy *.py soubory, budou v /data/aplikace
Spouštěcí skript bude v /www/cgi-bin
Data webové stránky, na které se bude odkazovat html kód vygenerovaný z aplikace, jako *.png, *.jpg, *.css, *.js a podobně budou v /www/static.

Import

Tedy vlastně nemůže. Aby python mohl importovat moduly z nějakého jiného adresáře, který není totožný s adresářem, kde je umístěn spouštěcí skript, musí být tento jiný adresář uveden v proměnné sys.path. A protože jste jediní, kdo víte, jak jste tento adresář pojmenovali a kde leží, jste také jediní, kdo ho do té proměnné může přidat. Přidává se v onom spouštěcím skriptu a přidává se před tím, než z něj cokoli importujete. Tedy by v něm mělo být něco jako:

import sys
sys.path.insert(0, '/absolutni/cesta/k/aplikaci')

POZOR: Cesta musií být absolutní, tedy od kořenového adresáře operačního systému, kterou běžně nevidíte. Pokud ji neznáte, je možno ji zjistit příkazem:

os.path.realpath('.')

Spouštění

Když to uděláte dobře, tak už bude webová aplikace chodit. Ale bude mít ošklivou spouštěcí adresu. Něco jako:

www.mujweb.cz/cgi-bin/aplikace.py

Toho se zbavíte rewrite pravidlem apache, kterým se můžete z hezké adresy, kterou uvidí uživatel přesměrovat na ošklivou, kterou nikdo neuvidí. Rewrite pravidlo se píše do souboru /www/.htaccess, což je konfigurační soubor webového serveru Apache. V něm by mělo být něco jako:

#Udela z mujweb.cz/aplikace1... mujweb.cz/cgi-bin/aplikace1.py...
RewriteRule "(.*)aplikace1(.*)" "$1cgi-bin/aplikace1.py$2" [PT]

A to je celé. Na závěr článku dám příklad ukázkové aplikace, z které to bude všechno jasné.

Chybové hlášení

Až zkusíte napsat pár řádek kódu aplikace v Pythonu, nevyhnete se nějaké té chybě, která skončí nezachycenou výjimkou. Tady se objeví první vážná slabina forpsi. Zatímco doma se vám taková chyba projeví výpisem podrobného chybového hlášení, tak na forpsi obdržíte ve webovém prohlížeči univerzální chybové hlášení, že skript byl předčasně ukončen. Podrobné hlášení je pouze v logu, ke kterému běžný uživatel nemá přístup a otravovat adminy s každou chybou nelze.

Pokud se jedná o běhovou chybu, tak je řešení jednoduché. Stačí na začátek spouštěcího skriptu vložit tento řádek:

import cgitb; cgitb.enable(1, "app1.errors")

Pak bude každá běhová výjimka zachycena a podle nastavení bude poruchové hlášení  zasláno webovému prohlížeči a/nebo uloženo do uživatelského logu v zadaném adresáři (zde app1.errors).

Co se týče ostatních chyb, tak máte na forpsi smůlu. Můžete jen hádat co se děje. Zpravidla to je taková chyba v programu, že ani neproběhne kompilace zdrojového kódu a ten se vůbec nespustí. Nebo mohou být špatně nastavena práva ke spouštěcímu skript. Takž je dobré na server nahrávat skripty, které jde alespoň spustit a nezapomínat na nastavení práv spouštěcím skriptům.

Vzorová aplikace

Tak to všechno shrňme dohromady a ukažme si vzorovou aplikaci, kterou si pojmenujme testApp a která nebude dělat nic menšího, než že bude poskytovat informaci o verzi Pythonu, který ji pohání:

1) Vytvoříme adresáře:
/www/static/app_testApp
/data/app_testApp
/data/app_testApp/logerr

POZOR: adresář app_TestApp se nesmí jmenovat _testApp a testApp, to by vzniknul konflikt jmen při importu a aplikace by nefungovala. Samozřejmě se nesmí jmenovat ani jako žádný jiný modul do něj vložený. Ale to je snad dobře známá vlastnost Pythonu, kterou si každý už několikrát vyzkoušel na vlastní kůži. Adresář musí mít jedinečný název, pokud jej samotný nechceme načítat jako modul.

V /www/static/app_testApp budou statická data (obrázky, css, js, ...) na která se budou odkazovat dynamicky vytvořené html stránky. Tyto data bude na požadavek vracet prohlížeči webový server Apache.

V /data/app_testApp budou python scripty a ostatní tajná data (např. databázové soubory), ke kterým bude přistupovat Python při dynamickém generování html stránek. Sem Apache nemá přístup a tak jsou taková data poměrně bezpečně skryta před světem (ale nikoli třeba před adminy webhostingu).

2) Vytvoříme soubor /www/cgi-bin/testApp.py
#!/usr/bin/python
# encoding: utf-8


# Ziskani realnych cest
import os
runPath  = os.path.realpath('.')
homePath = runPath[:runPath.find('www')]
appPath  = homePath + 'data/app_TestApp'
errPath  = homePath + 'data/app_TestApp/logerr'


Zapnuti logovani a vypsani zakladni http hlavicky
# Ten druhy prazdny print je dulezity, nemazat
import cgi
import cgitb; cgitb.enable(1, errPath)
print "Content-Type: text/html"
print


# Osetreni sys.path aby fungoval import
import sys
sys.path.insert(0, appPath)


# A konecne samotne spusteni aplikace a "vytisknuti" jejiho vystupu
import _testApp
print  _testApp.main()

Toto je spouštěcí skript Pythonu, který Apache vidí a umí ho spustit. Výstup ze skriptu je vrácen jako stránka internetovému uživateli. V podstatě se jedná o aplikaci a proto musí mít skript nastaveny práva pro spuštění.

3) Souboru  /www/cgi-bin/testApp.py nastavíme práva: rwxr-xr-x

4) Vytvoříme soubor /www/data/app_testApp/_testApp.py
import _info  as i

def main(argv=[]):
    out = i.info()
    return out

Krok 4 a 5 jsou rádoby simulace nějaké reálné wevové aplikace.

5) Vytvoříme soubor /www/data/app_testApp/_info.py
import sys

def info():
    return sys.version

6) Do souboru /www/.htaccess doplníme řádky:
# Presmerovani satickych dat (odpoved zajisti Apache):
# Pozadavek na testApp.mujweb.cz/.../filename.ext
#     se presmeruje na mujweb.cz/static/app_testApp/.../filename.ext
#     kde ext == jedna ze zadanych pripon
# Napriklad testApp.mujweb.cz/favicon.ico se bude na serveru hledat jako
#                   mujweb.cz/static/app_testApp/favicon.ico
RewriteCond %{HTTP_HOST} testApp\.mujweb\.cz$ [NC]
RewriteCond %{REQUEST_URI}      !static.*$
RewriteRule ^(.*)([\w-]+\.)(jpg|png|js|css|gif|ico)$  /static/app_testApp/$1$2$3 [PT]

# Presmerovani ostatnich pozadavku na cgi script (Python):
# Pozadavek na testApp.mujweb.cz 
#     se presmeruje na mujweb.cz/cgi-bin/testApp.py
# Pozadavek na testApp.mujweb.cz/aaa/bbb?c=ddd&e=fff
#     se presmeruje na mujweb.cz/cgi-bin/testApp.py/aaa/bbb?c=ddd&e=fff
#     pricemz ve skriptu bude:
#         os.environ['REDIRECT_URL'] == '/aaa/bbb'
#       a os.environ['REDIRECT_QUERY_STRING'] == 'c=ddd&e=fff'
#     coz snadno osefuje cgi.FieldStorage()
RewriteCond %{HTTP_HOST} testApp\.mujweb\.cz$ [NC]
RewriteCond %{REQUEST_URI} !static/.*$
RewriteCond %{REQUEST_URI} !cgi-bin/.*$
RewriteRule ^(.*)$ cgi-bin/testApp.py/$1 [L]

7) Otestujeme aplikaci testApp

Do prohlížeče napíšeme URL: testApp.mujweb.cz

HOTOVO

Poznámka 1: v reálu bude aplikace samozřejmě složitější. Funkce main() bude především zkoumat předaný požadavek (GET/POST?) a podle předaných parametrů pak bude volat jednotlivé funkce podle požadovaní akce. Více viz dokumentace k modulu cgi.

Poznámka 2: v adresáři /data/testApp/logerr budou uloženy jednotlivé chybové hlášení jako html soubory. Bude dobré jej čas od času zkontrolovat a promazat. Více viz dokumentace k modulu cgitb.

Poznámka 3: v příkladu je výstup chybových hlášení z cgitb nastaven nejen do logu, ale i na webovou stránku. Je to příjemnější pro odladění aplikace. Ale nepoužívejte to na ostrém webu. Součástí chybového výpisu je i několik řádek zdrojového kódu z oblast, kde se vyskytla chyba a co kdyby tam bylo zrovna uvedeno tajné heslo pro přístup do databáze? Pak by se ho mohl jakýkoli uživatel vaší webové aplikace dozvědět. A to by nebylo příjemné.

7 komentářů:

  1. Nechi Vám kazit radost z prvního funkčního python webu, ale tento způsob nemá prespektivu. Jakmile web začne růst, tak bude vyžadovat mnohem komplexnější řešení, než je print "Content-Type: text/html".

    Doporučuji nastudovat si nějaký webový framework, například Django nebo Cherrypy. Investice vložená do studia frameworku a jeho nasazení ve vašem projektu se vám rozhodně vyplatí. Protože jinak si budete muset všechno napsat sám a znova vymýšlet kolo.

    To, že by mi hosting neumožnil dostat se na error logy, bych považoval za tolik závadnou věc, že bych si takový hosting nevybral. Skutečnost, že vám neumožní používat WSGI, hovoří sama za sebe. Podpora pythonu na českých hostinzích je natolik tristní, že mě donutila si pronajmout rovnou virtuální server, na kterém si mohu nasadit, co chci.

    OdpovědětVymazat
  2. To je jenom jeden pohled na věc, který mě osobně není blízký, ale spíš právě naopak protivný. Vyhovuje mi jednoduchost a přímočarost a nejen na webu a nejen v Pythonu. Chápu, že někomu vyhovuje bloatware, a vrstvení knihoven, kde jedna obklopuje druhou, ta třetí a celé to zastřešuje framework. Má to své výhody, člověk nemusí tolik rozumět tomu co dělá, může to být i pohodlnější, alespoň na první pohled, ale já preferuji jednoduchost. Dlouhodobě se mi to osvědčuje jako spolehlivější a flexibilnější řešení, výhodnější z dlouhodobého hlediska a při řešení problémů.

    Tvrzení, že CGI nemá perspektivu je mladicky nerozvážné. CGI je standard, které tu byl, je a bude. Kam se proti němu poděly módní výstřelky, jako mod_python, dnes mod_wsgi a kdo ví co bude za deset let? CGI určitě ano. Jestliže nárokům webové aplikace postačí CGI, pak je to dobrá volba. V případě vyšších nároků je pak zde FastCGI. Je dobré CGI aplikaci psát tak, aby šla snadno převést na FastCGI, ale to profesionál ví a laik to nepotřebuje.

    Co se týká webhostingu, jedná se o levnou laickou záležitost. Služby odpovídají ceně, či spíš jsou dle mých zkušeností nadstandardní, zvláště v přístupu k drobnému zákazníkovi. Samozřejmě, není to ten samý komfort, jako mám třeba v práci, ale ten ani nečekám a nepožaduji (nejsem ochoten za něj platit). Dokážete za stejnou cenu doporučit lepší webhosting? Jestli ne, není jeho kritika na místě.

    OdpovědětVymazat
  3. Přihřeju si polívčičku, když už se otevřela diskuse. Článek jsem dočetl asi do poloviny a usmíval jsem se nad krkolomností celého procesu. To nemyslím nějak zle, jen FORPSI je prostě FORPSI. Přesně z tohoto důvodu jsem před pár lety založil Roští.cz ( http://rosti.cz ). Na rozdíl od FORPSI je to primárně Python hosting podporující i PHP. Aplikace je spouštěna pomocí uWSGI a přes uWSGI je také propojena s webovým serverem. Každá aplikace má vlastní prostředí (virtualenv), které si může uživatel měnit. Prostředí může být založeno na Pythonu 2.6, 2.7 nebo 3.3. K aplikaci dostaneš SSH/SFTP přístup a v rámci její adresářové struktury máš k dispozici i všechny logy. Když se registruješ, můžeš si tam nějaký čas hrát zdarma, tak to vyzkoušej.

    OdpovědětVymazat
    Odpovědi
    1. Nabídka je to zajímavá, děkuji za ni, ale zdá se mi, že to je one man show. Takových serverů jsem už pár vystřídal (byl nucen vystřídat, protože nedovedou poskytnout dlouhodobě stabilní zázemí) a dnes už preferuji větší zavedenou společnost, která nestojí a nepadá s jedním člověkem.

      Také koukám, že se tam platí za každou aplikaci zvlášť, to by se mi docela prodražilo. Jednorázová paušální platba za období a dělej si co chceš mi vyhovuje víc. Doporučuji to zavést jako variantu k tomu co tam je a zohledňovat velikost aplikací. Omezení na 5 GB na disku je docela kruté, to bych asi vyčerpal do několika měsíců. Při 50 uživatelích to je jen 100MB na jednoho.

      Co se týče složitosti, nezadá si to nijak s tímto http://wiki.rosti.cz/python_aplikace a to jsem se vlastně ani nedozvěděl, jakou url bude aplikace vlastně mít. Máte pravdu, forpsi je víc basic, člověk si to musí udělat sám, ale zase si to může udělat po svém a tedy flexibilněji.

      Tím nechci tento projekt shazovat, jen chci ukázat, že všechno má své pro a proti. Vy se forpsi smějete, ale mě vaše nabídka lákavější nepřijde. Vám je prostředí rosti.cz důvěrně známé a tak ho považujete za jednoduché, ale pro neznalého je to velká neznámá a rovněž bude muset studovat návod jak na to. Pozitivní je, že tam ten návod najde.

      Vymazat
    2. Už pár dní přemýšlím, co ti na to napsat. Na jednu stranu preferuješ minimální cenu, na druhou maximální kvalitu a ani jeden z těchto parametrů ti na Roští nevyhovuje. Navíc druhý je velmi fuzzy.

      Tak já to zkusím shrnout bodově.

      * Roští je two man show
      * Letos v září to budou 4 roky, co Roští existuje
      * Python hosting jako takový fungoval ještě o dva roky dříve pod jiným jménem
      * Cena je přiměřená typu služby. Můžeme se domluvit na paušálu, ale takové podmínky mají uživatelé s větším počtem aplikací.
      * 5 GB je oficiální číslo, ale v praxi vymáháno nějak není. V případě výrazného překročení je to opět o domluvě. Ze 189 aplikací na Roští překračují limit pouze tři.
      * Wiki co odkazuješ popisuje domény v druhém odstavci, chápu ale, že to může být nejednoznačné, proto jsem obsah upravil


      Svou reakci jsem vůči FORPSI napsal zbytečně agresivně. FORPSI není python hosting a pro rozumnou práci s Pythonem není vhodné. Pokud si někdy čuchnul k pythonu na webu i mimo FORPSI, třeba na vlastním serveru nebo na skutečném Python hostingu, musí ti to být jasné. Pokud ne, měl by si to zkusit, přicházíš o hrozně moc.

      Vymazat
    3. Tvoje odpověď je OK a nemám k tomu co říct, až na jednu věc. FORPSI opravdu není python hosting, je to webový hosting, ale na python vhodný je. Už mi na něm běží moje vlastní RSS čtečka napsaná v Pythonu a jsem s tím plně spokojen.

      Vymazat
  4. Každopádně musím poděkovat za Vaši diskuzi. Celkem aktuálně teď řeším hosting pro svůj web/weby v pythonu. PHP jsem nikdy neměl rád, no ale to je teď jedno. Pochopitelně jako každý jsem řešil jak ten python nasadím. S Forpsi jsem komunikoval a těšilo mě, že se mi snažili pomoct. Konec konců wsgi podporují přes flup. Ale ani tak mi to nepříjde moc dobré python verze 2.6 sqlite2 framework žádný - což nevadí můžu si ho naimportovat sám.
    No a cena úměrná kvalitě - necelou stovku za dynamic.
    Na druhou stranu mi to příjde všechno takové slepované, prostě zákazník příjde tak to nějak polepíme a bude :) Což mě trošku odrazuje, ale je to asi jen můj subjektivní názor.
    Pokud jde o rosti i ten jsem řešil. Trošku mě děsí ten two men show, pro malý server to asi stačí, ale co když se něco "posere" ? promiňte ten výraz.
    Taky beru cenu, ono 50/měsic za jednu aplikaci není zas tolik, ale zas je to jen jedna aplikace. Neříkám že jsem nějaké guru, který programuje 10 webů za měsíc, ale tady se už asi vyplatí úplně poslední varianta. Svůj server. A i když bych do začátku mohl použít svůj starý nevhodný pc co mám doma pod stolem (linux debian pochopitelně) musíme počítat s výdaji za něj. Největší položka je tu elektrika a to by mohlo být mnohem víc než jen 50kaček měsíčně, počítám spíš tak 300 měsíčně a to připadá jen v úvahu, když těch webů bude víc a budou výdělečný. No možná lepší virtuál co myslíte ? jenže ta cena je astronomická.
    Budu rád když se k mi vyjádříte a poradíte co je vhodnější. Díky

    OdpovědětVymazat