Ve druhé části seriálu o nejpopulárnějších a nejužitečnějších knihovnách určených pro vývojáře používající programovací jazyk Python dokončíme popis možností nabízených knihovnou Requests. Zaměříme se především na poněkud složitější dennodenně používané techniky –, čtení binárních dat, práci s cookies, využití session atd. Uvidíme, že tato knihovna většinu těchto operací přímo podporuje, a to takovým způsobem, aby bylo její použití pro vývojáře přímočaré a jednoduché (zejména v porovnání s některými dalšími knihovnami s podobným zaměřením).
Obsah
1. Užitečné knihovny a moduly pro Python: další možnosti nabízené knihovnou Requests
2. Přečtení nezpracovaného těla odpovědi při práci s binárními daty
3. První demonstrační příklad – přečtení bloku binárních dat z odpovědi serveru
4. Získání a uložení rastrového obrázku typu image/png a image/jpeg
5. Využití hlavičky accept posílané v požadavku serveru pro určení formátu dat
6. CRUD operace a jejich obdoba v HTTP metodách
7. Požadavek využívající metodu PATCH
8. Požadavek využívající metodu PUT
9. Požadavek využívající metodu OPTIONS
11. Poslání cookies v GET požadavku
12. Objekt typu Session a jeho využití pro uložení stavu mezi dotazy
13. Příklad na použití objektu typu Session
14. Úprava testovacího HTTP serveru takovým způsobem, aby vypisoval hlavičky posílané klientem
15. Nová podoba testovacího HTTP serveru
16. Skript pro volání testovacího HTTP serveru s hlavičkou Set-Cookie
17. Spuštění skriptu, ukázka celého tvaru požadavku a formát odpovědi serveru
19. Repositář s demonstračními příklady
1. Užitečné knihovny a moduly pro Python: další možnosti nabízené knihovnou Requests
V dnešním článku si ukážeme některé další možnosti nabízené knihovnou Requests, s jejímž základním použitím jsme se seznámili v článku předchozím. Nejprve si řekneme, jak lze přečíst binární data poslaná serverem (typicky se bude jednat o rastrové obrázky, audio data atd.), použijeme HTTP metody pojmenované PATCH, PUT a OPTIONS a dále se seznámíme s konceptem takzvaných cookies a ukážeme si, jakým způsobem je možné využít objekt typu Session, díky němuž je možné s cookies (a obecně se „sezeními“) velmi snadno a rychle pracovat – právě zde se ostatně ukáže snadnost použití knihovny Requests v porovnání s jinými knihovnami a nástroji.
2. Přečtení nezpracovaného těla odpovědi při práci s binárními daty
Při vývoji některých aplikací využívajících REST API popř. při přímém přístupu k prostředkům (obrázkům, animacím, ...) nabízeným na HTTP serverech se někdy setkáme s nutností zpracování binárních dat vrácených serverem pomocí protokolu HTTP. V takovém případě je možné (a také vhodné) obejít většinu funkcí nabízených knihovnou Requests a přečíst binární data vlastními prostředky – bajt po bajtu nebo blok po bloku. Ve skutečnosti je to relativně jednoduché. Nejdříve musíme zjistit typ dat (pro jistotu) a taktéž jejich délku, kterou by měl HTTP server poslat v hlavičce:
# přečtení hlaviček
headers = response.headers
# výpis typu internetoveho media
print("Typ dat:", headers.get("content-type"))
# výpis delky dat predanych v tele
print("Delka dat:", headers.get("content-length"))
Dále již jen využijeme zjištěnou délku a použijeme metodu response.raw.read(délka) pro postupné přečtení binárních dat z těla odpovědi. Zde pro jednoduchost budeme číst data po bajtu, i když výhodnější by bylo čtení po delších blocích:
# délka dat předaných v těle odpovědi
length = int(headers.get("content-length"))
# přečtení těla odpovědi bajt po bajtu
for i in range(length):
byte = response.raw.read(1)
print(hex(byte[0]))
Poznámka: metodě read je možné předat požadavek na přečtení delšího bloku, ovšem stále si hlídejte celkovou délku předaných dat.
3. První demonstrační příklad – přečtení bloku binárních dat z odpovědi serveru
V dnešním prvním demonstračním příkladu využijeme službu dostupnou na adrese https://httpbin.org/bytes/_počet_bajtů_, která v těle odpovědi vrátí požadovaný počet bajtů (ty budou mít náhodnou hodnotu!). Konkrétně budeme vyžadovat sto náhodných bajtů, takže požadavek bude směřován na adresu https://httpbin.org/bytes/100:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
# adresa s testovací REST API službou
URL = "https://httpbin.org/bytes/100"
# poslání HTTP dotazu typu GET
response = requests.get(URL, stream=True)
# přečtení hlaviček
headers = response.headers
# výpis typu internetového media
print("Typ dat:", headers.get("content-type"))
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
length = int(headers.get("content-length"))
# přečtení těla odpovědi bajt po bajtu
for i in range(length):
byte = response.raw.read(1)
print(hex(byte[0]))
Po spuštění příkladu by měl začátek zpráv vypisovaných na terminál začínat takto:
$ ./11_get_binary_data.py Typ dat: application/octet-stream Delka dat: 100
Následuje výpis hexadecimálních hodnot stovky bajtů, které byly poslány serverem v odpovědi:
0x20 0xc1 0x34 0xe6 0x6b 0x0 0x35 0x12 0xf4 0xae 0xed 0xc7 0xe9 0xc5 0x86 0xba 0x94 0x1f 0x4e
4. Získání a uložení rastrového obrázku typu image/png a image/jpeg
Ve druhém demonstračním příkladu si ukážeme způsob zpracování rastrového obrázku typu PNG předaného v binární podobě po poslání HTTP metody GET na adresu https://httpbin.org/image/png. Obrázek je možné uložit do (binárního) souboru po blocích, jejichž délku jsme nastavili na 128 bajtů (lze samozřejmě použít i kratší či naopak delší bloky). Namísto metody response.raw.read() použijeme alternativní přístup přes metodu response.iter_content(), která navíc správně vyřeší i délku posledního bloku (délka samozřejmě může být menší než 128 bajtů):
with open("test1.png", 'wb') as fout:
for block in response.iter_content(chunk_size=128):
fout.write(block)
Úplný zdrojový kód tohoto příkladu vypadá následovně:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
# adresa s testovací REST API službou
URL = "https://httpbin.org/image/png"
# poslání HTTP dotazu typu GET
response = requests.get(URL)
# přečtení hlaviček
headers = response.headers
# výpis typu internetového média
print("Typ dat:", headers.get("content-type"))
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
print(response.raw)
with open("test1.png", 'wb') as fout:
for block in response.iter_content(chunk_size=128):
fout.write(block)
Zcela stejným způsobem můžeme přečíst a uložit rastrový obrázek typu JPEG, který je testovací REST API službou vrácen při poslání HTTP metody GET na adresu https://httpbin.org/image/jpeg:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
# adresa s testovací REST API službou
URL = "https://httpbin.org/image/jpeg"
# poslání HTTP dotazu typu GET
response = requests.get(URL)
# přečtení hlaviček
headers = response.headers
# výpis typu internetového média
print("Typ dat:", headers.get("content-type"))
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
print(response.raw)
with open("test1.jpg", 'wb') as fout:
for block in response.iter_content(chunk_size=128):
fout.write(block)
5. Využití hlavičky accept posílané v požadavku serveru pro určení formátu dat
Testovací REST API služba, kterou používáme ve většině demonstračních příkladů, nabízí i možnost výběru formátu rastrových dat. Výběr se přitom provádí nastavením hlavičky se jménem accept v požadavku (request) poslaném na server. Na základě obsahu (tedy hodnoty) této hlavičky server připraví data v požadovaném formátu a pošle je (v binární podobě) zpět klientovi. Příslušná adresa, na kterou je nutné požadavek s hlavičkou accept poslat, je https://httpbin.org/image.
Jedním z podporovaných formátů je i „image/png“, takže další demonstrační příklad přečte obrázek ve formátu PNG a uloží ho do souboru „test.png“:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
# adresa s testovací REST API službou
URL = "https://httpbin.org/image"
# hlavička posílaná v dotazu
headers = {'accept': 'image/png'}
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers)
# přečtení hlaviček
headers = response.headers
# výpis typu internetového média
print("Typ dat:", headers.get("content-type"))
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
print(response.raw)
with open("test2.png", 'wb') as fout:
for block in response.iter_content(chunk_size=128):
fout.write(block)
Dalším podporovaným formátem dat testovací služby dostupné na adrese https://httpbin.org/image je image/jpeg, který pochopitelně vrací rastrový obrázek uložený ve formátu JPEG:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
# adresa s testovací REST API službou
URL = "https://httpbin.org/image"
# hlavička posílaná v dotazu
headers = {'accept': 'image/jpeg'}
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers)
# přečtení hlaviček
headers = response.headers
# výpis typu internetového média
print("Typ dat:", headers.get("content-type"))
# výpis délky dat předaných v těle odpovědi
print("Delka dat:", headers.get("content-length"))
print(response.raw)
with open("test2.jpg", 'wb') as fout:
for block in response.iter_content(chunk_size=128):
fout.write(block)
6. CRUD operace a jejich obdoba v HTTP metodách
Již minule jsme si popsali dvě základní HTTP metody nazvané GET a POST. Kromě těchto metod však existují i metody další. Některé z nich (například metoda OPTIONS) hrají spíše pomocnou roli související se samotným síťovým protokolem, ovšem metody pojmenované PUT a DELETE a částečně i metoda PATCH hrají důležitou roli v takzvaných CRUD operacích. V tabulce vypsané pod tímto odstavcem jsou vypsány všechny čtyři základní operace typu CRUD neboli Create, Read, Update a Delete používanými při práci s daty uloženými v nějakém perzistentním úložišti, například v databázi umístěné na serveru (je vcelku jedno, o jakou databázi se jedná, zda o relační, dokumentovou atd.). Tyto operace mají své sémantické protějšky ve čtyřech HTTP metodách, které nalezneme ve druhém sloupci:
Operace | HTTP metoda |
---|---|
Create | POST |
Read (Retrieve) | GET |
Update (Modify) | PUT |
Delete (Destroy) | DELETE |
Poznámka: částečný Update zaměřený pouze na modifikaci některých atributů je představován HTTP metodou PATCH, o níž se ve stručnosti zmíníme v sedmé kapitole.
7. Požadavek využívající metodu PATCH
V dalším demonstračním skriptu, jehož úplný zdrojový kód naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/requests/16_patch_method.py, je ukázán základní způsob použití HTTP metody nazvané PATCH. Tato metoda se prozatím nepoužívá příliš často, a to ani v některých REST API službách, ovšem její sémantika předurčuje tuto metodu použít ve chvíli, kdy je nutné modifikovat nějaký již existující zdroj (resource) uložený na serveru (popř. v relační databázi apod.). Pokud by se například jednalo o úpravu jediného atributu, bude požadavek používající metodu PATCH kratší a provedený rychleji, než úplný požadavek založený na metodě PUT (POST má naproti tomu dosti odlišnou sémantiku – slouží k vytvoření nového zdroje):
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
import json
# adresa s testovací REST API službou
URL = "https://httpbin.org/patch"
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
# poslání HTTP dotazu typu PATCH
response = requests.patch(URL, headers=headers)
# přečtení hlaviček odpovědi
headers = response.headers
# výpis všech hlaviček odpovědi
print("Headers:")
for header_name, header_value in headers.items():
print("{:40s} {}".format(header_name, header_value))
print("-" * 60)
print("Content:")
# zpracování odpovědi, která přišla ve formátu JSON
data = response.json()
print(json.dumps(data, indent=4, sort_keys=True))
Ve skriptu je metoda PATCH použita pro přístup na adresu https://httpbin.org/patch sloužící k jejímu základnímu otestování. Odpověď vrácená serverem by měla vypadat přibližně následovně (vypsány jsou napřed hlavičky a poté i tělo odpovědi ve formátu JSON):
Headers: Connection keep-alive Server gunicorn/19.9.0 Date Wed, 08 Aug 2018 15:25:28 GMT Content-Type application/json Content-Length 373 Access-Control-Allow-Origin * Access-Control-Allow-Credentials true Via 1.1 vegur ------------------------------------------------------------ Content: { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "application/json", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Length": "0", "Host": "httpbin.org", "User-Agent": "python-requests/2.13.0" }, "json": null, "origin": "213.175.37.10", "url": "https://httpbin.org/patch" }
8. Požadavek využívající metodu PUT
Požadavek používající HTTP metodu PUT vypadá velmi podobně jako požadavek POST popsaný minule. Liší se však sémantika (přibližně řečeno logický význam) těchto metod, protože zatímco POST typicky slouží k založení nového zdroje (resource) na serveru, je PUT použit k přepisu již existujících údajů. Rozdíl je v některých případech patrný i při pohledu na URL (adresu), protože u metody PUT by se mělo odkazovat přímo na konkrétní resource, kdežto u metody POST se odkazuje spíše na obecnější funkci (můžeme dokonce říci, že na konstruktor), která nový resource vytvoří a vrátí jeho identifikátor v odpovědi serveru. Podívejme se nyní v rychlosti na způsob použití metody PUT, kde opět použijeme testovací službu https://httpbin.org/, konkrétně URL/adresu https://httpbin.org/put:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
import json
# adresa s testovací REST API službou
URL = "https://httpbin.org/put"
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
# posláni HTTP dotazu typu PUT
response = requests.put(URL, headers=headers)
# přečtení hlaviček odpovědi
headers = response.headers
# výpis všech hlaviček odpovědi
print("Headers:")
for header_name, header_value in headers.items():
print("{:40s} {}".format(header_name, header_value))
print("-" * 60)
print("Content:")
# zpracování odpovědi, která přišla ve formátu JSON
data = response.json()
print(json.dumps(data, indent=4, sort_keys=True))
Po spuštění tohoto skriptu dostaneme následující odpověď (vypsány jsou opět jak hlavičky poslané serverem, tak i tělo odpovědi):
Headers: Connection keep-alive Server gunicorn/19.9.0 Date Wed, 08 Aug 2018 15:25:38 GMT Content-Type application/json Content-Length 371 Access-Control-Allow-Origin * Access-Control-Allow-Credentials true Via 1.1 vegur ------------------------------------------------------------ Content: { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "application/json", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Length": "0", "Host": "httpbin.org", "User-Agent": "python-requests/2.13.0" }, "json": null, "origin": "213.175.37.10", "url": "https://httpbin.org/put" }
9. Požadavek využívající metodu OPTIONS
Poslední HTTP metoda, kterou si v dnešním článku alespoň ve stručnosti popíšeme, se jmenuje OPTIONS. Tato metoda se používá pro zjištění všech operací, které server na dané adrese podporuje. Operacemi jsou přitom v tomto kontextu myšleny HTTP metody. V případě, že pošleme serveru HTTP požadavek typu OPTIONS, bude odpověď typicky prázdná (tj. nebude mít žádné tělo), ovšem v hlavičkách odpovědi nalezneme i hlavičku Allow se seznamem podporovaných HTTP metod platných pro danou adresu. Hodnota této hlavičky může vypadat následovně:
Allow OPTIONS, GET, HEAD
Opět se podívejme na příklad, který metodu OPTIONS použije, tentokrát na nám již známou adresu https://httpbin.org/get:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
import json
# adresa s testovací REST API službou
URL = "https://httpbin.org/get"
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
# poslání HTTP dotazu typu OPTIONS
response = requests.options(URL, headers=headers)
# přečtení hlaviček odpovědi
headers = response.headers
# výpis všech hlaviček odpovědi
print("Headers:")
for header_name, header_value in headers.items():
print("{:40s} {}".format(header_name, header_value))
print("-" * 60)
print("Content:")
# výpis těla odpovědi
print("Plain text:")
print("-" * 60)
print(response.text)
print("-" * 60)
Povšimněte si, že tělo odpovědi je skutečně prázdné a všechny (meta)informace tedy musíme získat z hlaviček:
Headers: Connection keep-alive Server gunicorn/19.9.0 Date Wed, 08 Aug 2018 19:31:01 GMT Content-Type text/html; charset=utf-8 Allow OPTIONS, GET, HEAD Access-Control-Allow-Origin * Access-Control-Allow-Credentials true Access-Control-Allow-Methods GET, POST, PUT, DELETE, PATCH, OPTIONS Access-Control-Max-Age 3600 Content-Length 0 Via 1.1 vegur ------------------------------------------------------------ Content: Plain text: ------------------------------------------------------------ ------------------------------------------------------------
10. Základy práce s cookies
Ve druhé části článku si ukážeme základy práce s takzvanými cookies. Jedná se o technologii používanou webovými servery založenými na protokolu HTTP. Samotné cookie je (většinou) malé množství dat, které HTTP server pošle klientovi. Klient si může taková data uložit u sebe, a to buď na delší dobu (cookie jsou ukládány do souboru) nebo po dobu trvání jednoho sezení (session). Cookie mohou sloužit skutečně pouze pro identifikaci sezení (session) a potom je jejich obsah krátký – jen jednoznačný identifikátor session (například JSESSIONID atd.) nebo je možné do cookie uložit i větší množství dat; například obsah nákupního košíku apod. U cookies je možné specifikovat jak dobu jejich trvání, tak i adresu/adresy, pro něž je cookie platná (cookie je totiž nutné poslat zpět serveru a potřebujeme zamezit takzvanému kradení cookies). Knihovna Requests samozřejmě práci s cookies umožňuje a obsahuje i třídu Session, která je využitelná pro udržení kontextu mezi jednotlivými dotazy, které jsou směřovány na HTTP server.
11. Poslání cookies v GET požadavku
Metodu Requests.get() jsme si již vyzkoušeli v předchozím článku, takže víme, že kromě adresy, která je povinná, je možné této metodě předat i další nepovinné údaje, z nichž se následně sestaví požadavek. Jedním z těchto nepovinných údajů je informace o hlavičkách (známe) a taktéž informace o cookies, které klient předává serveru. Tento parametr se jmenuje přímo cookies a předává se v něm buď slovník obsahující dvojice jméno_cookie+hodnota_cookie nebo objekt typu CookieJar. V dalším příkladu je ukázáno, jak se reprezentují cookies formou slovníku, což je samozřejmě (alespoň v Pythonu) nejjednodušší řešení:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
import json
# adresa s testovací REST API službou
URL = "https://httpbin.org/cookies"
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
# příprava cookies
cookies = {'key1': 'value1',
'key2': 'value2',
'key3': 'value3'}
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers, cookies=cookies)
# přečtení hlaviček
headers = response.headers
print("-" * 60)
# výpis všech hlaviček
print("Headers:")
for header_name, header_value in headers.items():
print("{:40s} {}".format(header_name, header_value))
print("-" * 60)
print("Content:")
# zpracování odpovědi, která přišla ve formátu JSON
data = response.json()
print(json.dumps(data, indent=4, sort_keys=True))
print("-" * 60)
print("Cookies:")
print(response.cookies.get_dict())
Server odpoví následujícím způsobem – cookies, které získal, nám vrátí v těle odpovědi, což je praktické zejména s ohledem na ladění aplikací:
------------------------------------------------------------ Headers: Connection keep-alive Server gunicorn/19.9.0 Date Wed, 08 Aug 2018 15:25:42 GMT Content-Type application/json Content-Length 90 Access-Control-Allow-Origin * Access-Control-Allow-Credentials true Via 1.1 vegur ------------------------------------------------------------ Content: { "cookies": { "key1": "value1", "key2": "value2", "key3": "value3" } } ------------------------------------------------------------ Cookies: {}
12. Objekt typu Session a jeho využití pro uložení stavu mezi dotazy
Jak jsme si již řekli v předchozích dvou kapitolách, používají se cookies kromě dalších věcí i ve chvíli, kdy je zapotřebí si nějakým způsobem zapamatovat stav nějaké sekvence operací. Typickým příkladem je webový obchod, u něhož si samozřejmě musíme pamatovat přihlášeného uživatele, obsah jeho košíku, zda již bylo za zboží zaplaceno atd. Ve chvíli, kdy je klientská část naprogramována s využitím knihovny Requests, je možné celý stav (možná lépe řečeno „sezení“) reprezentovat objektem typu Session. Požadavky na server se pak posílají odlišně – nikoli pomocí request._http_metoda_, ale session._http_metoda_:
session = requests.Session()
URL = "..."
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
# poslání HTTP dotazu typu GET
return session.get(URL, headers=headers)
# poslání HTTP dotazu typu POST
return session.post(URL, headers=headers)
atd. atd.
Příklad, který objekt typu Session používá, si ukážeme v navazující kapitole.
13. Příklad na použití objektu typu Session
Další příklad je již poněkud komplikovanější, protože v něm využíváme objekt typu Session, který si pamatuje stav požadavků (nebo možná lépe řečeno kontext, v jehož rámci se požadavky posílají a zpracovávají). V tomto příkladu voláme dvojici endpointů testovací HTTP služby https://httpbin.org/ pro nastavení nových cookies popř. pro jejich odstranění ze sezení (session):
- https://httpbin.org/cookies/set/{name}/{value} pro nastavení cookie se zadaným jménem a hodnotou
- https://httpbin.org/cookies/delete?{name} pro vymazání cookie specifikovaného jména
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
import json
def set_cookie(session, name, value):
# adresa s testovací REST API službou
URL = "https://httpbin.org/cookies/set/{name}/{value}".format(name=name, value=value)
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
# poslani HTTP dotazu typu GET
return session.get(URL, headers=headers)
def delete_cookie(session, name):
# adresa s testovací REST API službou
URL = "https://httpbin.org/cookies/delete?{name}=".format(name=name)
# hlavička posílaná v dotazu
headers = {'accept': 'application/json'}
# poslani HTTP dotazu typu GET
return session.get(URL, headers=headers)
def print_response(response):
# přečtení hlaviček
headers = response.headers
print("-" * 60)
# výpis hlavicek
print("Headers:")
for header_name, header_value in headers.items():
print("{:40s} {}".format(header_name, header_value))
print("-" * 60)
print("Content:")
# zpracovani odpovedi, ktera prisla ve formatu JSON
data = response.text
# zpracovani odpovedi, ktera prisla ve formatu JSON
data = response.json()
print(json.dumps(data, indent=4, sort_keys=True))
print("-" * 60)
def print_session_cookies(session):
cookies = session.cookies
print("Session cookies:")
for cookie_name, cookie_value in cookies.items():
print("{:40s} {}".format(cookie_name, cookie_value))
print("-" * 60)
session = requests.Session()
print("*** set cookie 'foo'=6 ***")
response = set_cookie(session, "foo", "6")
print_response(response)
print_session_cookies(session)
print()
print("*** set cookie 'bar'=7 ***")
response = set_cookie(session, "bar", "7")
print_response(response)
print_session_cookies(session)
print()
print("*** set cookie 'foo'=42 ***")
response = set_cookie(session, "foo", "42")
print_response(response)
print_session_cookies(session)
print()
print("*** delete cookie 'foo' ***")
response = delete_cookie(session, "foo")
print_response(response)
print_session_cookies(session)
print()
print("*** delete cookie 'baz' ***")
response = delete_cookie(session, "baz")
print_response(response)
print_session_cookies(session)
print()
Podívejme se nyní na výsledky. Jsou poněkud delší, protože ukazují, jak se dá zcela jednoduše měnit stav sezení:
*** set cookie 'foo'=6 *** ------------------------------------------------------------ Headers: Connection keep-alive Server gunicorn/19.9.0 Date Wed, 08 Aug 2018 15:25:57 GMT Content-Type application/json Content-Length 38 Access-Control-Allow-Origin * Access-Control-Allow-Credentials true Via 1.1 vegur ------------------------------------------------------------ Content: { "cookies": { "foo": "6" } } ------------------------------------------------------------ Session cookies: foo 6 ------------------------------------------------------------ *** set cookie 'bar'=7 *** ------------------------------------------------------------ Headers: Connection keep-alive Server gunicorn/19.9.0 Date Wed, 08 Aug 2018 15:25:57 GMT Content-Type application/json Content-Length 55 Access-Control-Allow-Origin * Access-Control-Allow-Credentials true Via 1.1 vegur ------------------------------------------------------------ Content: { "cookies": { "bar": "7", "foo": "6" } } ------------------------------------------------------------ Session cookies: bar 7 foo 6 ------------------------------------------------------------ *** set cookie 'foo'=42 *** ------------------------------------------------------------ Headers: Connection keep-alive Server gunicorn/19.9.0 Date Wed, 08 Aug 2018 15:25:57 GMT Content-Type application/json Content-Length 56 Access-Control-Allow-Origin * Access-Control-Allow-Credentials true Via 1.1 vegur ------------------------------------------------------------ Content: { "cookies": { "bar": "7", "foo": "42" } } ------------------------------------------------------------ Session cookies: bar 7 foo 42 ------------------------------------------------------------ *** delete cookie 'foo' *** ------------------------------------------------------------ Headers: Connection keep-alive Server gunicorn/19.9.0 Date Wed, 08 Aug 2018 15:25:57 GMT Content-Type application/json Content-Length 38 Access-Control-Allow-Origin * Access-Control-Allow-Credentials true Via 1.1 vegur ------------------------------------------------------------ Content: { "cookies": { "bar": "7" } } ------------------------------------------------------------ Session cookies: bar 7 ------------------------------------------------------------ *** delete cookie 'baz' *** ------------------------------------------------------------ Headers: Connection keep-alive Server gunicorn/19.9.0 Date Wed, 08 Aug 2018 15:25:58 GMT Content-Type application/json Content-Length 38 Access-Control-Allow-Origin * Access-Control-Allow-Credentials true Via 1.1 vegur ------------------------------------------------------------ Content: { "cookies": { "bar": "7" } } ------------------------------------------------------------ Session cookies: bar 7 ------------------------------------------------------------
14. Úprava testovacího HTTP serveru takovým způsobem, aby vypisoval hlavičky posílané klientem
Na závěr si ukážeme, v jakém formátu se vlastně informace o cookies přenáší na server a jak je server dokáže zpracovat. Již dopředu si řekněme, že je vše řízeno dvěma hlavičkami se jmény Cookie a Set-Cookie. Abychom si ukázali způsob předávání těchto hlaviček, upravíme náš původní lokální (testovací) HTTP server, jehož první variantu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/requests/simple_server.py a upravenou variantu na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/requests/simple_server_2.py. Úprava bude spočívat v tom, že server bude vypisovat všechny hlavičky požadavků, a to nezávisle na tom, jaká HTTP metoda byla pro poslání požadavku použita.
15. Nová podoba testovacího HTTP serveru
Podoba zdrojového kódu nové varianty testovacího HTTP serveru je vypsána pod tímto odstavcem. Oproti původní variantě byly přidány pomocné metody send_headers() a print_request_content(). Server bude otestován skriptem popsaným v navazující kapitole:
#!/usr/bin/python3
# vim: set fileencoding=utf-8
# Original (slightly buggy) code:
# see https://gist.github.com/bradmontgomery/2219997
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer
hostName = ""
hostPort = 8000
class SimpleServer(BaseHTTPRequestHandler):
def print_uri(self):
print("URI: {uri}".format(uri=self.path))
def send_headers(self):
# příprava hlavicky odpovedi
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
def print_request_content(self):
# přečtení těla HTTP požadavku
print(self.headers)
if "Content-Length" in self.headers:
content_length = int(self.headers['Content-Length'])
print("content length: {len}".format(len=content_length))
content = self.rfile.read(content_length)
print("content value: {content}".format(content=content))
def do_GET(self):
self.print_uri()
self.print_request_content()
# odpověď serveru klientovi
self.send_headers()
self.wfile.write("*** get ***".encode("utf-8"))
def do_POST(self):
self.print_uri()
self.print_request_content()
# odpověď serveru klientovi
self.send_headers()
self.wfile.write("*** post ***".encode("utf-8"))
simpleServer = HTTPServer((hostName, hostPort), SimpleServer)
try:
simpleServer.serve_forever()
except KeyboardInterrupt:
pass
simpleServer.server_close()
16. Skript pro volání testovacího HTTP serveru s hlavičkou Set-Cookie
Podívejme se nyní na jednoduchý skript, který zavolá náš testovací HTTP server. Použije přitom metodu GET (koncovka adresy je libovolná) a předá serveru tři cookies:
# příprava cookies
cookies = {'key1': 'value1',
'key2': 'value2',
'key3': 'value3'}
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers, cookies=cookies)
Navíc ještě předá přímo v hlavičkách požadavku hlavička pojmenovaná Set-Cookie se jménem a hodnotou cookie, která by se měla zapamatovat (a většinou poslat zpět klientovi):
# hlavička posílaná v dotazu
headers = {'accept': 'application/json',
'Set-Cookie': "x=y"}
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers, cookies=cookies)
Úplný zdrojový kód tohoto skriptu vypadá následovně:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import requests
# adresa s testovací REST API službou
URL = "http://localhost:8000"
# hlavička posílaná v dotazu
headers = {'accept': 'application/json',
'Set-Cookie': "x=y"}
# příprava cookies
cookies = {'key1': 'value1',
'key2': 'value2',
'key3': 'value3'}
# poslání HTTP dotazu typu GET
response = requests.get(URL, headers=headers, cookies=cookies)
# přečtení hlaviček
headers = response.headers
print("-" * 60)
# výpis všech hlaviček
print("Headers:")
for header_name, header_value in headers.items():
print("{:40s} {}".format(header_name, header_value))
print("-" * 60)
print("Content:")
# zpracování odpovědi, která přišla ve formátu JSON
data = response.text
print(data)
print("-" * 60)
print("Cookies:")
print(response.cookies.get_dict())
17. Spuštění skriptu, ukázka celého tvaru požadavku a formát odpovědi serveru
Před otestováním skriptu popsaného v předchozí kapitole je nutné spustit náš testovací HTTP server v samostatném terminálu (aby bylo vidět jeho výstup). To se provede jednoduše příkazem (skript se serverem je spustitelný):
$ ./simple_server_2.py
Nyní již můžeme (opět v jiném terminálu) spustit zmíněný skript:
$ ./21_cookies_test_against_local_server.py
Tento skript by měl poslat požadavek na testovací HTTP server, získat od něj odpověď a následně vypsat přibližně následující údaje (může se samozřejmě lišit čas, konkrétní verze interpretru Pythonu atd.). Povšimněte si, že server podle všech předpokladů nevrátil žádné cookies:
------------------------------------------------------------ Headers: Server BaseHTTP/0.6 Python/3.6.3 Date Thu, 09 Aug 2018 12:11:39 GMT Content-type text/plain ------------------------------------------------------------ Content: *** get *** ------------------------------------------------------------ Cookies: {}
Zajímavější bude pohled na zprávu vypsanou serverem ve chvíli, kdy přijme dotaz od demonstračního skriptu. Povšimněte si především dvou hlaviček nazvaných Cookie a Set-Cookie. V hlavičce Cookie jsou v textové podobě jména a hodnoty všech předaných cookies, v hlavičce Set-Cookie pak přesný opis textu, který jsme předali v požadavku (což je logické, ovšem je patrné, že zde máme velkou volnost zápisu, tj. serveru lze například předat data, která nebude schopen zpracovat):
Cookie: key1=value1; key2=value2; key3=value3 Set-Cookie: x=y
URI: / Host: localhost:8000 User-Agent: python-requests/2.13.0 Accept-Encoding: gzip, deflate accept: application/json Connection: keep-alive Set-Cookie: x=y Cookie: key1=value1; key2=value2; key3=value3 127.0.0.1 - - [09/Aug/2018 14:11:39] "GET / HTTP/1.1" 200 -
18. Obsah další části seriálu
V navazující části seriálu o nejpopulárnějších a nejužitečnějších knihovnách určených pro vývojáře používající programovací jazyk Python (především Python 3.x) se budeme zabývat další často využívanou a poměrně populární knihovnou. Tato knihovna se jmenuje Pillow a jedná se o fork dnes již poněkud postarší knihovny nazvané Python Imaging Library neboli zkráceně PIL. Jak Python Imaging Library tak i Pillow dokážou pracovat s rastrovými obrázky různých formátů (včetně těch nejpopulárnějších), takže se s těmito knihovnami setkáme například i u webových služeb, které potřebují generovat nějaké grafy či mapy apod.
19. Repositář s demonstračními příklady
Zdrojové kódy všech jedenácti dnes popsaných demonstračních příkladů a testovacího HTTP serveru určeného pro Python 3 a knihovnu Requests byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Requests: HTTP for Humans (dokumentace)
http://docs.python-requests.org/en/master/ - Requests: Introduction
http://docs.python-requests.org/en/latest/user/intro/ - Requests na GitHubu
https://github.com/requests/requests - Requests (software) na Wikipedii
https://en.wikipedia.org/wiki/Requests_%28software%29 - Pip (dokumentace)
https://pip.pypa.io/en/stable/ - 20 Python libraries you can’t live without
https://pythontips.com/2013/07/30/20-python-libraries-you-cant-live-without/ - What are the top 10 most useful and influential Python libraries and frameworks?
https://www.quora.com/What-are-the-top-10-most-useful-and-influential-Python-libraries-and-frameworks - Python: useful modules
https://wiki.python.org/moin/UsefulModules - Top 15 most popular Python libraries
https://keyua.org/blog/most-popular-python-libraries/ - Hypertext Transfer Protocol
https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol - List of HTTP header fields
https://en.wikipedia.org/wiki/List_of_HTTP_header_fields - List of HTTP status codes
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes - Python requests deep dive
https://medium.com/@anthonypjshaw/python-requests-deep-dive-a0a5c5c1e093 - The awesome requests module
https://www.pythonforbeginners.com/requests/the-awesome-requests-module - Send HTTP Requests in Python
https://code-maven.com/http-requests-in-python - Introducing JSON
http://json.org/ - HTTP Request Methods
https://www.w3schools.com/tags/ref_httpmethods.asp - POST (HTTP)
https://en.wikipedia.org/wiki/POST_(HTTP) - The HTTP OPTIONS method and potential for self-describing RESTful APIs
http://zacstewart.com/2012/04/14/http-options-method.html - What is the function of "HTTP OPTIONS method"?
https://www.quora.com/What-is-the-function-of-HTTP-OPTIONS-method - Create, read, update and delete
https://en.wikipedia.org/wiki/Create%2C_read%2C_update_and_delete - CRUD
https://cs.wikipedia.org/wiki/CRUD - Representational State Transfer (Wikipedia)
https://cs.wikipedia.org/wiki/Representational_State_Transfer