Znám jen PHP. Jak napíšu webovou aplikaci v Pythonu? II

python-logo-master-v3-TM

Jste otrávení z psaní webů v PHP a při posedávání v kavárnách slýcháte od svých kamarádek, že kdybyste se naučili Python, bude váš život krásnější, plnější, barevnější, prostě dokonalý od kořínků ke konečkům? Teď je ta pravá chvíle vykročit z komfortní zóny a zkusit to! Pojďme se podívat na to, jak v Pythonu začít s webem.

Následující text je volným překladem článku Python FAQ: Webdev, jehož autorem je Eevee a je zde zveřejněn se souhlasem autora. Článek jsem tu a tam aktualizoval, doplnil, nebo obohatil o nějakou tu větu.

Článek je pokračováním prvního dílu, který vyšel minulý týden.

Unicode

Unicode je na prd. To je univerzální pravda. (Lžu. Práce s kódováním je na prd. Unicode je skvělé. Je sice komplikované, ale skvělé.)

Python (2) má dva typy pro textové řetězce: str a unicode. Existuje jedna chytrá lež, a to že str není vlastně ani tak textový řetězec, jako pouze sada bajtů. Někdy se stane, že vypadá jako textový řetězec, ale ve skutečnosti je to opravdu jen binární reprezentace, stejným způsobem jako je 85 00 00 00 běžnou binární reprezentací čísla 133. Skutečné číslo je int a skutečný řetězec je unicode.

Problém s Unicode je dost složitý na to, aby si zasloužil vlastní článek, zde jen pár rychlých poznámek:

  • Váš program by se měl interně zabývat jen opravdovými řetězci, tzn. unicode. Musíte sice dekódovat řetězce, které do vašeho programu přicházejí, ale naštěstí dělá toto většina frameworků za vás.
  • Pro označení literálu jako unicode je možné uvést jej prefixem u, např. u'řeřicha'.
  • Je možné použít from __future__ import unicode_literals na začátku souboru, což ze všech literálů v tomto souboru udělá unicode. Pokud je potřeba str, musí se uvést pomocí prefixu b.
  • Pokud chcete používat ve svém zdrojovém kódu s Pythonem nějaké znaky mimo ASCII (např. ona písmena ř v literálu u'řeřicha'), uveďte jej magickým komentářem #encoding: utf8. (Předpokládejme, že váš soubor je uložen v UTF-8, což by tedy rozhodně být měl.)

(Pozn. překl.: Dvě doporučení jsem z výčtu odebral, protože popisují věci pro naše prostředí tak samozřejmé, že by zde působila přinejmenším úsměvně :-) )

XSS

V podstatě každé šablony dnes mají v nějaké podobě zabudovaný filtr na automatické ošetřování vstupu. Myšlenka je asi taková, že šablona jako tato:

<p>Hello, ${name}!</p>

vrátí bezpečně ošetřené Hello, &lt;b&gt;!, pokud dostane name = '<b>'. To tedy znamená, že se většinou nemusíte vůbec o nějaké XSS starat.

Většinou. Pokud nic jiného, musíte ověřit v dokumentaci vašeho frameworku a šablonovacího systému, že je toto chování zapnuto ve výchozím stavu, a pokud ne, tak jej zapnout. (Z hlavy: Pyramid, Django a Flask to dělají automaticky. Bottle také, pokud přípona souboru s šablonou zavání nějakým tím HTML.)

Ošidnou záležitostí je rozpoznat onu správnou chvíli, kdy toto chování vypnout. Pokud přímo v Pythonu vytváříte nějaké složité HTML, které chcete následně vlepit do vaší šablony, rozhodně si nepřejete jej nechat escapovat. Jít a pouze vypnout celé ošetření vstupu je ovšem dost pitomé řešení – kdekoliv to uděláte, vytváříte potenciální slabé místo. Naštěstí se mnoho frameworků (minimálně Pyramid a Flask) shodlo na použití knihovny MarkupSafe, která tento problém chytře obchází.

MarkupSafe má jedinou třídu, Markup, která dědí z unicode. Markup(u'Hello!') vytvoří objekt, který se chová skoro úplně jako obyčejný textový řetězec. Metoda třídy Markup.escape funguje v podstatě stejně, pouze v daném řetězci ošetří jakékoliv speciální HTML znaky.

Tento mechanismus má dvě pěkné vlastnosti. Prvním je skutečnost, že Markup objekt nikdy není escapován dvakrát. Sledujte:

>>> s = u'<b>oh noo xss</b>'
>>> Markup.escape(s)
Markup(u'&lt;b&gt;oh noo xss&lt;/b&gt;')
>>> Markup.escape(Markup.escape(s))
Markup(u'&lt;b&gt;oh noo xss&lt;/b&gt;')

Jakmile tedy jednou vytvoříte Markup, můžete jej poslat do své šablony a filtr zajišťující automatické ošetřování vstupu jej nechá na pokoji, ačkoliv obsahuje HTML.

Ta druhá vlastnost je následující: Markup přetěžuje každou metodu textového řetězce a automaticky ošetřuje všechny jejich argumenty. To znamená, že pak v Pythonu můžete klidně dělat následující věci:

>>> user_input = u'<script>alert("pwn");</script>'
>>> Markup(u'<p>Hello, %s!</p>') % user_input
Markup(u'<p>Hello, &lt;script&gt;alert("pwn");&lt;/script&gt;!</p>')

Můžete tedy bezpečně skládat i komplexnější HTML a nemusíte se obávat toho, že by se ošetřilo nedostatečně nebo naopak až moc.

Není to samozřejmě úplně dokonalé – na první chyták narazíte, když budete chtít spojovat víc řetězců. Na sadu Markup objektů musíte použít Markup().join(...), ne ''.join(...). Také nějaké další složitější operace, jako třeba slicing, split nebo regulární výrazy vyplodí tak akorát nějaké nesmyslné výsledky. Nikdy se nesnažte „ručně“ rozebrat Markup objekt nebo jakýkoliv jiný řetězec obsahující HTML – a pokud nevyhnutelně musíte, použijte nějaký opravdový parser jako lxml (pozn. překl. – nemohu se ubránit, prostě musím odkázat na onu legendární odpověď ze StackOverflow). Ve většině případů ale můžete udělat požadované transformace nad obyčejnými řetězci ještě před tím, než je obalíte do HTML.

Formuláře

Nesnáším knihovny pro práci s formuláři, opravdu každou z nich. Všechny vnucují nějaký způsob pojmenovávání, zrovna takový, jaký si vymyslel pan autor. A taky nemám rád takové to foo[] pro název formulářového pole, což se hojně používá v PHP – je to tak neuvěřitelně ošklivé!

Nejméně ze všech nenávidím WTForms. Vynucuje si minimum omezení v návrhu formuláře a celkem jednoduše se používá. Má dokonce vestavěnou podporu pro práci s MarkupSafe. Hlavní nevýhodou je obtížné odstraňování toho mála omezení, které má (každý prvek formuláře dostane atribut id se stejnou hodnotou, jako má jeho nameáááá!). Vytváření vlastního, nového pole může být navíc docela namáhavé.

Bohužel se nemohu moc dělit o zkušenosti s dalšími knihovnami. Za zmínku stojí FormEncode a pod křídly projektu Pyramid lze najít Deform, ale obě dělají pár hloupých věcí, jež mě štvou z poměrně puntičkářských důvodů. Přebírejte a sami uvidíte.

Ať už uděláte cokoliv, hlavně něco použijte, než váš projekt naroste. Věc, kterou nesnáším ještě více než knihovny pro práci s formuláři, je ruční psaní validačního kódu :-)

„Sanitace“

Poznámka k jedné tendenci běžné ve světě PHP:

Nedělejte žádnou „sanitaci“.

Ani to slovo samo o sobě nedává žádný smysl. Neexistuje žádný proces, který by vzal libovolný textový řetězec a udělal z něj nějaký „bezpečný“. Tento způsob myšlení způsobuje, že narážím na bankovní weby s kontaktními formuláři, kde se dočtu, že nemohu používat znak <. Nějaký zabedněný enterprise vývojář zjevně vůbec netuší, jak správně nakládat s příchozími daty, takže si prostě vynucuje, že ta data musí být blbuvzdorná.

Nebuďte blbci.

Většinou je termínem „sanitace“ myšleno „zabezpečování“ uživatelského vstupu tak, aby se dal vložit do HTML, do SQL, nebo použít jako parametr programu v příkazové řádce. Všechno uvedené můžete udělat bez toho, abyste jakkoliv měnili ta původní data. Pro HTML existují filtry jako MarkupSafe – to už jsem zmiňoval výše. Pro SQL máme bound parametry a ORM. No a při spouštění příkazů bychom se měli úplně vyhnout shellu a předat parametry prostě jako seznam (viz subprocess).

Všechny tyto problémy jsou vlastně jen záležitosti „jazykových bariér“: HTML, SQL a shell jsou vše strukturované jazyky a nemůžete do nich jen tak hodit nějaký záhadný balast a doufat, že si s ním poradí. Stejně jako byste nepoužili spojování řetězců na tvorbu JSON dokumentu, nedělejte to ani v případě, kdy chcete spustit convert. Používejte nástroje, které rozumí struktuře toho, co je pod povrchem.

Nechci říct, že nesmíte nikdy měnit nebo filtrovat vstup od uživatele, ale měli byste se toho vyvarovat, jak jen je to možné. No a pokud to možné není, buďte sakra opatrní ohledně toho, co zrovna děláte. Vezměme např. hesla – proč je běžné zakazovat v heslech mezery nebo je omezovat na 16 znaků? Neexistuje žádný rozumný důvod, a přesto se to dělá.

Nejlepší ovšem je, když mi ještě diktují, jak mám vložit číslo kreditky. Na těch samých stránkách, kde mi ani nedovolí v textu napsat <: „Zadejte v kuse šestnáct číslic.“ To si pak ale velice těžko okem zkontroluji, jestli jsem to číslo opsal správně – a krom toho, číslo na mé kreditní kartě mezery obsahuje! Proč prostě automaticky neodstranit pomlčky a mezery?

Pečlivě prostě přemýšlejte nad tím, co děláte, a jaký problém se snažíte vyřešit. Chcete zastavit lidi, kteří se snaží vaše stránky nějak poškodit tím, že do ní vkládají Unicode znaky čtené zprava doleva? Není důvod omezit vstup jen na ASCII. Unicode má kategorie a můžete snadno vyfiltrovat jen znaky z těch podivnějších. Nebo ještě lépe, prostě si opravte stránky tak, aby je mohli používat i lidé píšící hebrejsky :-)

Ladění

Pokud máte štěstí (tzn. používáte Pyramid), tak v případě, že váš program spadne, dostanete k dispozici interaktivní debugger, který vám umožní zkoumat kód za jeho běhu. Můžete spouštět jakýkoliv Python kód, dívat se na stav proměnných, procházet vrstvy vyhozené výjimky, atd.

Pokud štěstí nemáte, nebuďte smutní – stále si můžete podobnou hračku pořídit použitím Werkzeug debuggeru. Je poměrně jednoduchý na použití – dá se do něj zabalit jakákoliv WSGI aplikace a on pak zachytává výjimky. (Vidíte? WSGI je úžasné.)

Jen se, prosím vás, vždy ujistěte, že nenecháváte zapnuté ladění, když dáváte svůj výtvor někam na server nebo sdílíte kamkoliv jinam, kde bude veřejně přístupný. „Jakýkoliv Python kód“ znamená, že kdokoliv, komu se zobrazí ladící obrazovka, může na daném počítači dělat cokoliv, co na něm můžete dělat i vy.

Databáze

Nekonečný problém. Tohle je nejsubjektivnější část článku.

Za prvé: Měli byste používat ORM. To je věcička, která se statečně snaží mapovat databázové tabulky na třídy v Pythonu, řádky na objekty a dotazy na volání metod. Výsledek takového mapování je stručnější, často srozumitelnější a někdy dokonce správnější.

ORM, které byste měli použít, se nazývá SQLAlchemy. Pyramid pro něj má nějakou vestavěnou podporu, ale i když použijete jiný framework, SQLAlchemy je natolik populární, že minimálně v dokumentaci budou určitě zmínky o tom, jak si s psaním kódu pro toto ORM poradit. Pokud používáte Django, tak to má své vlastní ORM. Není tak dobré jako SQLAlchemy, ale snažit se jej nahradit je taková otrava, že se to rozhodně nevyplatí, nemáte-li nějaké opravdu závažné důvody.

Zlé jazyky vám budou tvrdit, že ORM generuje špatné SQL. Ano, pro špatná ORM to platí. Dobrá ORM, jako je SQLAlchemy, rozumí SQL stejně dobře jako vy. Pokud rozumíte SQL, SQLAlchemy pro vás bude skvělou volbou. Pokud si s SQL nevíte rady, SQLAlchemy vám alespoň ušetří rozpaky tím, že to vaše nepěkné SQL poskládá za vás. Pamatujte si, že se na dotazy, které se posílají do databáze, můžete vždy podívat. SQLAlchemy je umí zaznamenávat a nejrůznější ladící nástroje vám je ukážou i se seznamem naměřených délek jejich trvání. (Mimochodem, dávejte si pozor na spouštění stejného dotazu mnohokrát za sebou – je to známka toho, že byste měli načítat více věcí naráz a v předstihu.)

Za druhé, používejte transakce. Naštěstí o tom většinou nemusíte moc přemýšlet – pokud má framework vůbec nějakou podporu pro SQLAlchemy, pravděpodobně už nějak pracuje s transakcemi za vás. Myšlenka je asi taková, že transakce začne spolu s HTTP požadavkem a pokud nastane výjimka, spustí se automaticky rollback. Toto je chování, jaké chcete od základu! Z poloviny (oprava, ze čtvrtiny) na tom stojí celý smysl toho, proč vůbec použít nějakou databázi.

Ještě jedna věc. Vzhledem k tomu, že tento článek je celý o tom, abyste zkoušeli nové věci na základě toho co já napíšu, tak si dovolím ještě následující: Nepoužívejte MySQL. Ve všech směrech jaké si jen dovedu představit je MySQL takovým… PHP všech databází. Dejte šanci PostgreSQL. Není o nic těžší na instalaci, lépe se používá a nenechá vás dělat hloupé věci jako ukládat textové řetězce do sloupců pro datum. (Jedna z nejpěknějších věcí na PostgreSQL je podle mě skutečnost, že umí na UNIXových systémech přímo využívat uživatelské účty – není třeba hesel). Proti PostgreSQL v historii zazněl snad jen jeden jediný argument – že „neškáluje“. Můžete se spolehnout, že já osobně stále čekám, až mi někdo něco takového jednou dokáže prakticky demonstrovat a ať už je to jakkoliv, tímto se můžete trápit až ve chvíli, kdy vám přibude milion uživatelů. (Pozn. překl.: V komentářích pod článkem bylo možno najít větu: „Pokud PostgreSQL dokáže škálovat pro Skype, bude škálovat i pro vás.“)

Session

Každý framework má nějakou podporu pro session a vždy to bude variace na následující scénář: V cookie je session token, no a na backendu se nám někde magicky objeví slovník, kam můžeme odkládat jakékoliv naše nesmysly. Užívejte dle libosti. Zkuste to nepoužívat jako odkladiště dat – ukazuje se, že databáze jsou docela dobré na takové to, však víte… ukládání dat.

Mezi bonusové vlastnosti patří zabudovaná podpora pro ochranu před CSRF (Pyramid, Django na to má modul) a flash zprávy (Pyramid, Flask, Django). Jděte a přečtěte si dokumentaci.

Jedno malé varování: Pokud použijete Beaker sessions (Pyramid), tak ty mají nepěknou tendenci nasbírávat svinčík. Ve výchozím nastavení je to v podobě souboru na disku pro úplně každou session, ale i pokud použijete jako úložiště databázi, skončíte s ohromnou tabulkou, která se pouze zvětšuje. Je to příšerný a na první pohled ne úplně zřejmý problém, jehož řešení jsou všechna v podstatě manuální. Bohužel.

Deployment

Nachytali jste mě. Existuje spousta způsobů jak nahrát Python aplikaci na server a určitě by si zasloužily více místa, než kolik jim jsem zde schopen věnovat.

(Pozn. překl.: Tuto kapitolu jsem výrazně přizpůsobil českým podmínkám a některé věty se odkazují na mé vlastní zkušenosti, ne autorovy.)

Pokud můžete, buďte ochotni v tomto směru utrácet nějaké peníze, protože poskytování služby má už ze své podstaty nějaké náklady. Ze všeho nejjednodušší je dávat aplikace na nějaký svůj vlastní stroj (klidně virtuální) a hrát si s ním – navíc, mít po ruce nějaký svůj server je tak jako tak skvělá věc. Můžete začít třeba na základním VPS od WEDOSu za 120 korun měsíčně.

Pokud se na vlastní server necítíte, podívejte se třeba na české Roští.cz, které nabízí hostování aplikací jak v Pythonu, tak i v PHP.

Úplně jinou kapitolou jsou potom cloudové PaaS služby. Oblíbené je Heroku, kam stačí kód akorát poslat přes Git a dát nějak najevo, že se jedná o Python aplikaci. V základu, který je zdarma, máte k dispozici něco jako jeden proces (tzv. dyno) a z mé vlastní zkušenosti se do toho vleze i jeden malý, každodenní cron. Za každý další proces už platíte. (Počet HTTP požadavků, jaký jste schopni obsloužit, je zhruba úměrný počtu procesů, které si spustíte. Kolik jich ovšem potřebujete, záleží už přímo na vaší aplikaci a na tom, jak ji spouštíte).

Dalším cloudem je Google App Engine, ale tam je mnoho různých omezení na použité knihovny, nástroje, apod. To nejenže znepříjemňuje vývoj aplikace, ale navíc i téměř znemožňuje váš výtvor vzít a během okamžiku nasadit někam jinam.

Jak se říká (a říká se to vůbec vlastně?), deployment je krásný problém. Znamená totiž, že jste nakonec dokázali vytvořit něco použitelného. Takže se tím teď až tak netrapte a radši už běžte a tvořte.

Závěrem

Web je komplikovaná záležitost, stroj se spoustou součástek. Mnoho chytrých hlav za vás už vyřešilo hromadu problémů a ta řešení jsou vám k dispozici jako pěkné knihovny. Jděte a šťourejte se v nich, hrajte si, učte se.

Doufám, že toto celé bude stačit k tomu, abyste dokázali začít! A jako obvykle, vůbec nevím, co dělám, takže prosím, napište mi, jak to dělat lépe.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 6

Přehled komentářů

anonym
Honza Javorek Re:
diverman framework
sim Anti-programming....
Honza Javorek Re: Anti-programming....
Elijen Re: Anti-programming....
Zdroj: https://www.zdrojak.cz/?p=9251