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

10. Základy práce s cookies

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

18. Obsah další části seriálu

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

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):

  1. https://httpbin.org/cookies/set/{name}/{value} pro nastavení cookie se zadaným jménem a hodnotou
  2. 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:

# Demonstrační příklad Popis Cesta
1 11_get_binary_data.py použití HTTP metody GET pro získání binárních dat https://github.com/tisnik/most-popular-python-libs/blob/master/requests/11_get_binary_data.py
2 12_binary_data_png_image.py přečtení a uložení obrázku typu PNG https://github.com/tisnik/most-popular-python-libs/blob/master/requests/12_binary_data_png_image.py
3 13_binary_data_jpeg_image.py přečtení a uložení obrázku typu JPEG https://github.com/tisnik/most-popular-python-libs/blob/master/requests/13_binary_data_jpeg_image.py
4 14_binary_data_by_header_png.py specifikace požadovaného typu/formátu dat v hlavičce https://github.com/tisnik/most-popular-python-libs/blob/master/requests/14_binary_data_by_header_png.py
5 15_binary_data_by_header_jpeg.py specifikace požadovaného typu/formátu dat v hlavičce https://github.com/tisnik/most-popular-python-libs/blob/master/requests/15_binary_data_by_header_jpeg.py
6 16_patch_method.py použití HTTP metody PATCH https://github.com/tisnik/most-popular-python-libs/blob/master/requests/16_patch_method.py
7 17_put_method.py použití HTTP metody PUT https://github.com/tisnik/most-popular-python-libs/blob/master/requests/17_put_method.py
8 18_options_method.py použití HTTP metody OPTIONS https://github.com/tisnik/most-popular-python-libs/blob/master/requests/18_options_method.py
9 19_cookies.py poslání cookies společně s požadavkem https://github.com/tisnik/most-popular-python-libs/blob/master/requests/18_cookies.py
10 20_session_cookie.py využití objektu typu Session https://github.com/tisnik/most-popular-python-libs/blob/master/requests/19_session_cookie.py
11 21_cookies_test_against_local_server.py skript, který se má spustit oproti testovacímu HTTP serveru https://github.com/tisnik/most-popular-python-libs/blob/master/requests/20_cookies_test_against_local_server.py
12 simple_server_2.py testovací HTTP server s výpisem hlaviček požadavků https://github.com/tisnik/most-popular-python-libs/blob/master/requests/simple_server_2.py

20. Odkazy na Internetu

  1. Requests: HTTP for Humans (dokumentace)
    http://docs.python-requests.org/en/master/
  2. Requests: Introduction
    http://docs.python-requests.org/en/latest/user/intro/
  3. Requests na GitHubu
    https://github.com/requests/requests
  4. Requests (software) na Wikipedii
    https://en.wikipedia.org/wiki/Requests_%28software%29
  5. Pip (dokumentace)
    https://pip.pypa.io/en/stable/
  6. 20 Python libraries you can’t live without
    https://pythontips.com/2013/07/30/20-python-libraries-you-cant-live-without/
  7. 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
  8. Python: useful modules
    https://wiki.python.org/moin/UsefulModules
  9. Top 15 most popular Python libraries
    https://keyua.org/blog/most-popular-python-libraries/
  10. Hypertext Transfer Protocol
    https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
  11. List of HTTP header fields
    https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
  12. List of HTTP status codes
    https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
  13. Python requests deep dive
    https://medium.com/@anthonypjshaw/python-requests-deep-dive-a0a5c5c1e093
  14. The awesome requests module
    https://www.pythonforbeginners.com/requests/the-awesome-requests-module
  15. Send HTTP Requests in Python
    https://code-maven.com/http-requests-in-python
  16. Introducing JSON
    http://json.org/
  17. HTTP Request Methods
    https://www.w3schools.com/tags/ref_httpmethods.asp
  18. POST (HTTP)
    https://en.wikipedia.org/wiki/POST_(HTTP)
  19. The HTTP OPTIONS method and potential for self-describing RESTful APIs
    http://zacstewart.com/2012/04/14/http-options-method.html
  20. What is the function of "HTTP OPTIONS method"?
    https://www.quora.com/What-is-the-function-of-HTTP-OPTIONS-method
  21. Create, read, update and delete
    https://en.wikipedia.org/wiki/Create%2C_read%2C_update_and_delete
  22. CRUD
    https://cs.wikipedia.org/wiki/CRUD
  23. Representational State Transfer (Wikipedia)
    https://cs.wikipedia.org/wiki/Representational_State_Transfer