V novém seriálu, který ode dneška začíná na serveru mojefedora.cz vycházet, se postupně seznámíme s nejpopulárnějšími a současně i nejužitečnějšími knihovnami dostupnými vývojářům používajícím programovací jazyk Python. První velmi známou a užitečnou knihovnou je knihovna nazvaná Requests.

Obsah

1. Užitečné knihovny a moduly pro Python: knihovna Requests

2. Instalace knihovny Requests pro přihlášeného uživatele

3. Kontrola instalace knihovny Requests

4. Základ protokolu HTTP: metody a stavové kódy

5. Nejjednodušší příklad – poslání požadavku GET na zvolenou adresu

6. Základní atributy objektu typu Response: stavový kód a indikátor korektní odpovědi

7. Přečtení vybrané hlavičky z odpovědi HTTP serveru

8. Předání informací (parametrů) serveru přímo v URL

9. Přečtení těla odpovědi serveru v původní textové podobě

10. Získání metainformací o poslaných datech (typ a délka)

11. Zpracování odpovědi, která byla vrácena ve formátu JSON

12. Použití HTTP metody POST

13. Předání dat serveru ve „formuláři“

14. Předání dat serveru v těle požadavku

15. Vlastní jednoduchý testovací HTTP server

16. Implementace HTTP serveru

17. Volání vlastního HTTP serveru s využitím knihovny Requests

18. Obsah druhé části seriálu

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

20. Odkazy na Internetu

1. Užitečné knihovny a moduly pro Python: knihovna Requests

S knihovnou pojmenovanou Requests, kterou je možné použít v Pythonu 2.x i (a to především) v Pythonu 3.x, se pravděpodobně někteří čtenáři tohoto článku již setkali. Jedná se o knihovnu určenou pro práci s protokolem HTTP, tj. (poněkud zjednodušeně řečeno) k posílání HTTP požadavků na nějaký server s využitím některé HTTP metody (GET, POST atd.). Podobných knihoven existuje i pro Python několik, ovšem knihovna Requests se stala populární především díky její velmi snadné použitelnosti, protože i relativně složité operace je možné provést poměrně jednoduchým způsobem. Dnes se seznámíme pouze se základními vlastnostmi této knihovny, ovšem v navazujícím článku budou popsány i některé pokročilejší možnosti, například správa sezení (session), použití cookies, autentikace, použití SSL apod.

Důležitá poznámka: všechny dále uvedené demonstrační příklady jsou určeny pro Python 3.x. Většina příkladů bude pracovat korektně i v Pythonu 2.x, ovšem implementace jednoduchého HTTP serveru si vyžádá úpravy – odlišné importy atd.

2. Instalace knihovny Requests pro přihlášeného uživatele

Instalace knihovny Requests je velmi jednoduchá a použijeme pro ni nástroj pip3, tj. pip installer určený pro Python 3. Instalaci provedeme s volbou --user, čímž zajistíme, že se všechny soubory nainstalují do adresáře .local a pro instalaci tak nebude zapotřebí mít práva superuživatele:

$ pip3 install --user requests

Průběh instalace může vypadat následovně (jedná se o čistě nainstalovanou Fedoru 27 Server):

Collecting requests
  Downloading https://files.pythonhosted.org/packages/65/47/7e02164a2a3db50ed6d8a6ab1d6d60b69c4c3fdf57a284257925dfc12bda/requests-2.19.1-py2.py3-none-any.whl (91kB)
    100% |████████████████████████████████| 92kB 296kB/s 
Collecting chardet<3.1.0,>=3.0.2 (from requests)
  Downloading https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl (133kB)
    100% |████████████████████████████████| 143kB 898kB/s 
Collecting idna<2.8,>=2.5 (from requests)
  Downloading https://files.pythonhosted.org/packages/4b/2a/0276479a4b3caeb8a8c1af2f8e4355746a97fab05a372e4a2c6a6b876165/idna-2.7-py2.py3-none-any.whl (58kB)
    100% |████████████████████████████████| 61kB 1.8MB/s 
Collecting urllib3<1.24,>=1.21.1 (from requests)
  Downloading https://files.pythonhosted.org/packages/bd/c9/6fdd990019071a4a32a5e7cb78a1d92c53851ef4f56f62a3486e6a7d8ffb/urllib3-1.23-py2.py3-none-any.whl (133kB)
    100% |████████████████████████████████| 143kB 1.8MB/s 
Collecting certifi>=2017.4.17 (from requests)
  Downloading https://files.pythonhosted.org/packages/7c/e6/92ad559b7192d846975fc916b65f667c7b8c3a32bea7372340bfe9a15fa5/certifi-2018.4.16-py2.py3-none-any.whl (150kB)
    100% |████████████████████████████████| 153kB 976kB/s 
Installing collected packages: chardet, idna, urllib3, certifi, requests
Successfully installed certifi-2018.4.16 chardet-3.0.4 idna-2.7 requests-2.19.1 urllib3-1.23

Pokud je již knihovna Requests nainstalována, bude celý proces mnohem kratší:

$ pip3 install --user requests
Requirement already satisfied (use --upgrade to upgrade): requests in /usr/lib/python3/dist-packages
Cleaning up...

Alternativně je samozřejmě možné knihovnu nainstalovat do systémových adresářů, takže bude dostupná pro všechny uživatele:

$ sudo pip3 install requests

3. Kontrola instalace knihovny Requests

Po instalaci si můžeme ověřit, zda je knihovna Requests skutečně korektně nainstalována a zda k ní má interpret Pythonu přístup. Nejprve běžným způsobem spustíme interpret Pythonu:

$ python3
Python 3.6.6 (default, Jul 19 2018, 16:29:00) 
[GCC 7.3.1 20180303 (Red Hat 7.3.1-5)] on linux
Type "help", "copyright", "credits" or "license" for more information.

Následně se pokusíme naimportovat knihovnu Requests a pro jistotu zobrazit i její dokumentaci:

>>> import requests
>>> help("requests")

V případě, že instalace proběhla v pořádku, měl by výstup vypadat přibližně následovně:

Help on package requests:

NAME
    requests

DESCRIPTION
    Requests HTTP Library
    ~~~~~~~~~~~~~~~~~~~~~
    
    Requests is an HTTP library, written in Python, for human beings. Basic GET
    usage:
    
       >>> import requests
       >>> r = requests.get('https://www.python.org')
       >>> r.status_code
       200
       >>> 'Python is a programming language' in r.content
       True
    
    ... or POST:
    
       >>> payload = dict(key1='value1', key2='value2')
       >>> r = requests.post('http://httpbin.org/post', data=payload)
       >>> print(r.text)
       {
         ...
         "form": {
           "key2": "value2",
           "key1": "value1"
         },
         ...
       }
    
    The other HTTP methods are supported - see `requests.api`. Full documentation
    is at <http://python-requests.org>.
    
    :copyright: (c) 2017 by Kenneth Reitz.
    :license: Apache 2.0, see LICENSE for more details.

4. Základ protokolu HTTP: metody a stavové kódy

Protokol HTTP neboli Hypertext Transfer Protocol byl původně navržen pro přenos hypertextových dokumentů napsaných ve formátu/jazyku HTML. Dnes se ovšem používá i pro mnohé další účely; například ho využívají REST API služby atd. Protokol HTTP pracuje způsobem dotaz-odpověď neboli request-response (ostatně právě zde můžeme vysledovat původ názvu knihovny Requests). Jak dotaz, tak i odpověď, jsou reprezentovány formátovaným textem, kde jednotlivé řádky mají předem známý význam. Protokolem HTTP je samozřejmě možné přenášet i data; ostatně pro tento účel slouží pojmenované metody s předem specifikovaným významem (viz tabulku pod tímto odstavcem). Specifikace protokolu HTTP rovněž obsahuje popis takzvaných stavových kódů, kterými server předává klientovi výsledek zpracování dotazu.

V následující tabulce je uveden přehled všech metod HTTP protokolu, přičemž nejpoužívanější jsou první dvě metody GET a POST, s jejichž použitím se seznámíme v demonstračních příkladech popsaných v navazujících kapitolách:

Metoda Příklad použití
GET Základní metoda sloužící k získání dat ze serveru. Může se například jednat o HTML stránku, statický obrázek, ale i výsledek volání REST API služby.
POST Metoda používaná pro odesílání dat na server. Teoreticky je sice možné použít i metodu GET, ovšem sémanticky je vhodnější použít tuto metodu (a REST API služby sémantiku operace většinou dodržují).
   
PUT Tato metoda slouží k nahrání dat na server. S touto metodou se setkáme u některých REST API služeb.
DELETE Slouží ke smazání dat ze serveru. Opět platí – s touto metodou se setkáme méně často u některých REST API služeb.
   
HEAD Tato metoda se částečně podobá metodě GET, ovšem server nevrátí požadovaná data, ale pouze metadata (čas změny, velikost dat, typ/formát dat apod.). Obecně je možné říci, že se tento dotaz zpracuje rychleji než GET.
CONNECT Používá se při použití TCP/IP tunelování.
OPTIONS Poslání dotazu na server, které metody podporuje.
TRACE Server by měl klientovi odeslat požadavek zpět, čehož se používá pro zjištění, které údaje se mění na přenosové cestě.
PATCH Umožňuje změnu dat na serveru, má tady jinou sémantiku než DELETE+PUT.

Stavový kód odpovědi serveru je reprezentován celým číslem, přičemž z první číslice (stovky) lze odvodit základní vlastnost stavu (úspěch, chyba, přesměrování...):

Skupina stavových kódů Význam
1xx informační, potvrzení atd. (ovšem požadavek se prozatím nevykonal)
2xx úspěšné vyřízení požadavku popř. jeho akceptace serverem (202)
3xx přesměrování požadavku, informace o tom, že se objekt nezměnil atd.
4xx různé typy chyb typicky zaviněných klientem (bohužel nejrozsáhlejší skupina)
5xx různé chyby na serveru

5. Nejjednodušší příklad – poslání požadavku GET na zvolenou adresu

V dnešním prvním demonstračním příkladu si ukážeme, jakým způsobem je možné použít základní HTTP metodu GET pro poslání požadavku na server. Použijeme přitom server dostupný na adrese https://httpbin.org/, který je možné využít pro otestování základních HTTP metod i jednoduchých REST API služeb. Konkrétně pošleme požadavek na adresu https://httpbin.org/get; v samotném požadavku nebudou předány žádné parametry. Dále se pokusíme stejný požadavek odeslat na neexistující URL https://httpbin.org/neexistuje:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/get"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# vypis objektu, ktery se vrati
print(response)



# nyni vyzkousime neexistujici endpoint:

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/neexistuje"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# vypis objektu, ktery se vrati
print(response)

Po spuštění tohoto příkladu by se na standardním výstupu (tj. na konzoli či na emulátoru terminálu) měly objevit pouhé dva řádky. První z nich by měl obsahovat textovou podobu prvního objektu typu Response, který představuje úspěšnou odpověď serveru s HTTP kódem 200. Druhý řádek by měl obsahovat textovou podobu objektu typu Response s HTTP kódem 404, což je ovšem pochopitelné, protože jsme se snažili přistoupit k neexistující URL:

<Response [200]>

<Response [404]>

Poznámka: povšimněte si, že je možné bez problémů použít i protokol HTTPS namísto pouhého HTTP. Od této skutečnosti je programátor využívající knihovnu Requests většinou odstíněn.

6. Základní atributy objektu typu Response: stavový kód a indikátor korektní odpovědi

Ve druhém demonstračním příkladu, který bude opět velmi jednoduchý, si ukážeme, jakým způsobem je možné zjistit stav (HTTP status) předaný v odpovědi. Z předchozích kapitol, ale i z běžných zkušeností s browserem, víme, že stavový kód HTTP je představován celým číslem doplněným o textový popisek stavu. V objektu typu Response představujícího odpověď serveru je číselný kód stavu uložen v atributu pojmenovaném status_code. Kromě toho existuje ještě atribut nazvaný ok, který obsahuje pravdivostní hodnotu True v případě, že je číselný kód stavu menší než 400 a hodnotu False v opačném případě. Použití tohoto atributu tedy může být poněkud problematické, protože například některé stavy 3xx je možné v kontextu vyvíjené aplikace považovat za chybové stavy:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/get"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# vypis stavu odpovedi
print(response.status_code)
print(response.ok)



# nyni vyzkousime neexistujici endpoint:

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/neexistuje"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# vypis stavu odpovedi
print(response.status_code)
print(response.ok)

Podívejme se, jaké hodnoty se vypíšou pro korektní URL https://httpbin.org/get a jaké hodnoty pro nekorektní URL https://httpbin.org/neexistuje. Pro korektní URL získáme podle očekávání stavový kód 200 a atribut ok bude mít hodnotu True:

200
True

Pro nekorektní URL je stavový kód HTTP roven 404 a tím pádem je i atribut ok nastaven na hodnotu False:

404
False

7. Přečtení vybrané hlavičky z odpovědi HTTP serveru

Odpověď serveru ve formě pouhého HTTP stavu (číselného kódu) samozřejmě většinou není příliš přínosná (kromě dotazů na to, zda se nějaký zdroj nezměnil), protože serveru posíláme dotaz za účelem získání nějakých dat. Protokol HTTP je navržen takovým způsobem, že dokáže přenášet data v různých formátech, přičemž formát se rozpozná na základě hodnoty uložené do hlavičky content-type (opět viz předchozí kapitoly s popisem této hlavičky). Údaje ze všech hlaviček získaných z odpovědi serveru je samozřejmě možné získat, protože objekt typu Response obsahuje mj. i atribut headers, ve kterém jsou všechny hlavičky uloženy ve formě slovníku (jméno hlavičky:hodnota). V dnešním třetím demonstračním příkladu je ukázáno, jakým způsobem se přistupuje právě k hlavičce content-type obsahující typ/formát dat, které server odeslal klientovi:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/get"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# precteni hlavicek
headers = response.headers

# vypis typu internetoveho media
print(headers.get("content-type"))

Webová služba dostupná na adrese https://httpbin.org/get vrací hodnoty uložené v těle odpovědi, přičemž tyto hodnoty jsou reprezentovány ve známém a velmi často využívaném formátu JSON. Služba je tedy správně nakonfigurována takovým způsobem, že vrací typ dat:

application/json

8. Předání informací (parametrů) serveru přímo v URL

Serveru, jehož služby potřebujeme přes knihovnu Requests využívat, je samozřejmě možné předat nějaká data. Protokol HTTP podporuje dva základní způsoby předání dat. Pokud se jedná o několik parametrů s relativně malým (krátkým) obsahem, je možné takové parametry předat přímo v URL (v prohlížeči přes adresní řádek). Zápis URL by v takovém případě měl vypadat následovně:

protokol://adresa.serveru/endpoint?parametr1=hodnota1&parametr2=hodnota2&parametr2=hodnota2

Konkrétně v našem případě, kdy používáme testovací server https://httpbin.org/:

https://httpbin.org/get?x=6&y=7&answer=42

Tento způsob přináší některá omezení. Zejména je nutné zajistit, aby se ve jménech a hodnotách parametrů nevyskytovaly některé znaky, které slouží jako oddělovače ve vlastní URL. Touto problematikou, kterou lze opět řešit automaticky, se budeme zabývat příště. Také je nutné zajistit (a zjistit), zda server neomezuje délku URL, například na 1024 znaků atd.

Příklad, který serveru předá parametry přes URL, se prakticky žádným způsobem neodlišuje od prvního příkladu, takže jen ve stručnosti:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/get?x=6&y=7&answer=42"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# vypis objektu, ktery se vrati
print(response)

9. Přečtení těla odpovědi serveru v původní textové podobě

Ve chvíli, kdy serveru předáme nějaká data (či parametry), server typicky odpoví tak, že klientovi pošle zpět vyžadované údaje. V tomto případě není možné tyto údaje předat v URL (ta je jen součástí dotazu, nikoli odpovědi), takže všechna vyžadovaná data server pošle v těle odpovědi a popř. do hlaviček doplní potřebná metadata (například již zmíněný content-type apod.). V odpovědi, která je v knihovně Requests reprezentována objektem typu Response, lze k nezpracovaným datům přistupovat přes atribut text:

response = requests.get(URL)
data = response.text

Opět se podívejme na jednoduchý demonstrační příklad, který testovacímu serveru pošle tři parametry x=6, y=7 a answer=42 a následně zobrazí odpověď serveru:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/get?x=6&y=7&answer=42"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# vypis tela odpovedi
print("Plain text:")
print("-" * 60)        # horizontalni oddelovac
print(response.text)
print("-" * 60)        # horizontalni oddelovac

Příklad odpovědi:

Plain text:
------------------------------------------------------------
{
  "args": {
    "answer": "42", 
    "x": "6", 
    "y": "7"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.13.0"
  }, 
  "origin": "213.175.37.10", 
  "url": "https://httpbin.org/get?x=6&y=7&answer=42"
}

------------------------------------------------------------

Poznámka: povšimněte si, že testovací server nám vlastně jen vrací údaje, které od nás získal, což je dobře, protože ho skutečně budeme moci použít pro další pokusy.

10. Získání metainformací o poslaných datech (typ a délka)

Zajímavé a v některých případech i užitečné bude zjištění základních metainformací o údajích, které nám server zaslal ve své odpovědi. Tyto metainformace se předávají formou hlaviček, a to zejména hlavičky content-type (typ/formát dat, již známe), content-length (délka dat) a popř. i date (datum vygenerování dat). Údaje z těchto hlaviček získáme velmi jednoduše, což je ostatně ukázáno i v pořadí již pátém demonstračním příkladu:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/get?x=6&y=7&answer=42"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# precteni hlavicek
headers = response.headers

print("Metadata:")
print("-" * 60)

# vypis typu internetoveho media
print("Typ dat:", headers.get("content-type"))

# vypis delky dat predanych v tele
print("Delka dat:", headers.get("content-length"))

# vypis delky dat predanych v tele
print("Datum:", headers.get("date"))

print("-" * 60)

# vypis tela odpovedi
print("Plain text:")
print("-" * 60)
print(response.text)
print("-" * 60)

Po spuštění tohoto demonstračního příkladu získáme přibližně následující výstup (ve vašem konkrétním případě bude odlišné datum a popř. i hlavička User-Agent):

Metadata:
------------------------------------------------------------
Typ dat: application/json
Delka dat: 385
Datum: Sat, 04 Aug 2018 07:26:26 GMT
------------------------------------------------------------
Plain text:
------------------------------------------------------------
{
  "args": {
    "answer": "42", 
    "x": "6", 
    "y": "7"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate, compress", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.2.1 CPython/3.4.3 Linux/3.13.0-139-lowlatency"
  }, 
  "origin": "37.48.1.40", 
  "url": "https://httpbin.org/get?x=6&y=7&answer=42"
}

------------------------------------------------------------

11. Zpracování odpovědi, která byla vrácena ve formátu JSON

Mnoho webových služeb, především těch, které jsou postaveny na architektuře REST (Representational state transfer), vrací údaje ve formátu JSON. Přesněji řečeno – odpovědi serveru obsahují stavovou informaci, všechny potřebné hlavičky s metainformacemi a taktéž tělo představující data serializovaná právě do formátu JSON. Ve skutečnosti je zpracování těchto dat velmi jednoduché, protože lze využít metodu json() objektu typu Response. V případě, že server skutečně odeslal data ve formátu JSON, jsou tato data deserializována a vrácena programu ve formě seznamu či (častěji) slovníku. Následně je možné tato data zpracovat. V případě, že data nejsou ve formátu JSON, vyvolá se výjimka ValueError:


# zpracovani odpovedi, ktera prisla ve formatu JSON
data = response.json()

# celý desrializovaný obsah JSONu
print(data)

# vybraná část
args = data["args"]
print(args)

print("x =", args["x"])
print("y =", args["y"])
print("answer =", args["answer"])

Zpracování údajů vrácených testovacím serverem https://httpbin.org/ je ukázáno v dnešním šestém demonstračním příkladu:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/get?x=6&y=7&answer=42"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# zpracovani odpovedi, ktera prisla ve formatu JSON
data = response.json()

print(data)

args = data["args"]
print(args)

print("x =", args["x"])
print("y =", args["y"])
print("answer =", args["answer"])

Po spuštění tohoto příkladu by se nejprve měl vypsat obsah celého těla odpovědi (deserializovaný z JSONu):

{'args': {'answer': '42', 'x': '6', 'y': '7'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.13.0'}, 'origin': '213.175.37.10', 'url': 'https://httpbin.org/get?x=6&y=7&answer=42'}

Následně by se měly vypsat jen vybrané údaje:

{'answer': '42', 'x': '6', 'y': '7'}
x = 6
y = 7
answer = 42

12. Použití HTTP metody POST

Kromě metody GET protokolu HTTP je samozřejmě možné použít i metodu POST. Tato metoda se typicky používá ve chvíli, kdy je zapotřebí předat serveru větší množství parametrů a/nebo rozsáhlejších dat. Existuje několik způsobů, jak tato data předávat, ovšem v prvním příkladu, v němž metodu POST použijeme, se žádná data prozatím předávat nebudou. Úplný zdrojový kód tohoto příkladu vypadá následovně:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/post"

# poslani HTTP dotazu typu POST
response = requests.post(URL)

# vypis odpovedi v plain textu
print(response.text)

Zajímavá je odpověď serveru. Povšimněte si především toho, že nám server vrátil klíč form a json. K těmto klíčům se dostaneme později:

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "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/post"
}

13. Předání dat serveru ve „formuláři“

První metoda poslání parametrů od klienta na server používá takzvané „formulářové položky“. Tento poněkud nepřesný název je odvozen od toho, že se podobným způsobem posílají data z HTML formuláře (bez použití JavaScriptu, pouze čistě HTML prostředky). Pokud budeme chtít simulovat posílání dat tímto způsobem, můžeme použít nepovinný parametr data funkce requests.post():

payload = {
    "klic": "hodnota",
    "answer": 42,
    "question": None,
    "correct": True}

# poslani HTTP dotazu typu POST se specifikaci hodnot formulare
response = requests.post(URL, data=payload)

Tento způsob je použit i v dnešním osmém demonstračním příkladu, jehož úplný zdrojový kód vypadá následovně:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/post"

payload = {
    "klic": "hodnota",
    "answer": 42,
    "question": None,
    "correct": True}

# poslani HTTP dotazu typu POST se specifikaci hodnot formulare
response = requests.post(URL, data=payload)

# vypis tela odpovedi v plain textu
print(response.text)

Odpověď serveru opět obsahuje položku form. Povšimněte si, že server získal a následně vrátil pouze tři hodnoty – chybí zde ovšem hodnota question=None, která se ve skutečnosti ve formulářových datech nepředala (neexistuje zde totiž ekvivalent pro speciální hodnoty None či null):

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "answer": "42", 
    "correct": "True", 
    "klic": "hodnota"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Connection": "close", 
    "Content-Length": "35", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.13.0"
  }, 
  "json": null, 
  "origin": "213.175.37.10", 
  "url": "https://httpbin.org/post"
}

14. Předání dat serveru v těle požadavku

Pokud budeme chtít serveru předat větší množství dat, a to potenciálně včetně speciálních hodnot, je lepší takové údaje předat přímo v těle požadavku. Pro tento účel se ve funkci requests.post() použije nepovinný parametr nazvaný json a nikoli parametr pojmenovaný data (jako tomu bylo v příkladu předchozím). Opět se podívejme na jednoduchý demonstrační příklad, který se od příkladu předchozího odlišuje pouze v jediném detailu – nepovinném parametru funkce requests.post():


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa s testovaci REST API sluzbou
URL = "https://httpbin.org/post"

payload = {
    "klic": "hodnota",
    "answer": 42,
    "question": None,
    "correct": True}

# poslani HTTP dotazu typu POST s telem
response = requests.post(URL, json=payload)

# vypis tela odpovedi v plain textu
print(response.text)

Odpověď serveru nyní vypadá odlišně, protože nám testovací server v odpovědi říká, jak parametry získal (resp. jak mu byly předány). Povšimněte si, že nyní je pod klíčem form uložen prázdný slovník, ovšem naše parametry jsou nyní předány pod klíčem data a současně i ve zpracované podobě pod klíčem json:

{
  "args": {}, 
  "data": "{"klic": "hodnota", "answer": 42, "question": null, "correct": true}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Connection": "close", 
    "Content-Length": "68", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.13.0"
  }, 
  "json": {
    "answer": 42, 
    "correct": true, 
    "klic": "hodnota", 
    "question": null
  }, 
  "origin": "213.175.37.10", 
  "url": "https://httpbin.org/post"
}

15. Vlastní jednoduchý testovací HTTP server

Aby bylo možné zjistit, jak přesně vlastně vypadá požadavek posílaný z klienta na server pomocí metody GET nebo POST, vytvoříme si vlastní velmi jednoduchou implementaci HTTP serveru založenou na existující (velmi užitečné) třídě BaseHTTPRequestHandler. Ta samozřejmě nebude v žádném případě určena pro produkční nasazení, protože například nijak neřeší HTTPS, souběžné zpracování většího množství požadavků, zabezpečení, autorizaci, kontrolu korektnosti požadavků atd. Server pouze velmi jednoduše zpracuje všechny požadavky typu GET a POST; klientovi přitom vrátí odpověď se stavem 200 OK a jednořádkovou (plain textovou) zprávou, která klienta pouze informuje o tom, jakou HTTP metodu při volání serveru použil. Zcela základní implementace serveru by tedy mohla vypadat následovně (bez importů, spuštění atd.):


class SimpleServer(BaseHTTPRequestHandler):

    def do_GET(self):
        # priprava hlavicky odpovedi
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()

        # odpoved serveru klientovi
        self.wfile.write("*** get ***".encode("utf-8"))

    def do_POST(self):
        # priprava hlavicky odpovedi
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()

        # odpoved serveru klientovi
        self.wfile.write("*** post ***".encode("utf-8"))

16. Implementace HTTP serveru

Ve skutečnosti bude implemetace našeho testovacího serveru nepatrně komplikovanější, protože budeme potřebovat, aby se u metody POST získalo i tělo požadavku, které může obsahovat data poslaná klientem. To se dá provést relativně jednoduše – nejprve zjistíme délku obsahu (v bajtech) a posléze tento obsah načteme metodou rfile.read(), které se předá délka těla požadavku:


content_length = int(self.headers['Content-Length'])
print("content length: {len}".format(len=content_length))

content = self.rfile.read(content_length)

Následně může server tato data zobrazit ve svém terminálu či do logovacího souboru, což je přesně to, co potřebujeme – získat nezpracovaný formát požadavku vytvořený knihovnou Requests.

Úplná implementace našeho HTTP serveru je založena na zdrojovém kódu, který byl poslán na https://gist.github.com/bradmontgomery/2219997, ovšem provedl jsem v něm několik úprav a oprav. Výsledek je použitelný s Pythonem 3.x:


#!/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 do_GET(self):
        # priprava hlavicky odpovedi
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()

        # odpoved serveru klientovi
        self.wfile.write("*** get ***".encode("utf-8"))

    def do_POST(self):
        print("URI: {uri}".format(uri=self.path))

        # precteni tela HTTP pozadavku
        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))

        # priprava hlavicky odpovedi
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()

        # odpoved serveru klientovi
        self.wfile.write("*** post ***".encode("utf-8"))


simpleServer = HTTPServer((hostName, hostPort), SimpleServer)

try:
    simpleServer.serve_forever()
except KeyboardInterrupt:
    pass

simpleServer.server_close()

17. Volání vlastního HTTP serveru s využitím knihovny Requests

Výše popsaný HTTP server zavoláme celkem třikrát – jednou se použije metoda GET, podruhé metoda POST s předáním „formulářových dat“ a nakonec se opět použije metoda POST, ovšem tentokrát se data předají v těle požadavku s využitím formátu JSON:


#!/usr/bin/env python3
# vim: set fileencoding=utf-8

import requests

# adresa lokalne beziciho serveru
URL = "http://localhost:8000"

# poslani HTTP dotazu typu GET
response = requests.get(URL)

# vypis zakladnich informaci ziskanych z odpovedi
print(response)
print(response.status_code)
print(response.ok)
print(response.text)

payload = {
    "klic": "hodnota",
    "answer": 42,
    "question": None,
    "correct": True}

# poslani dat jako hodnot formulare
response = requests.post(URL, data=payload)

print(response.text)

# poslani dat v tele dotazu
response = requests.post(URL, json=payload)

print(response.text)

Na konzoli, ze které spouštíme testovací skript, se vypíšou tyto údaje. První čtyři řádky platí pro první volání GET, další dva pro volání POST:

<Response [200]>
200
True
*** get ***
*** post ***
*** post ***

Na konzoli serveru (ovšem nikoli na konzoli, kde spouštíme testovací skript!) by se měly vypsat následující řádky, z nichž je patrný jak formát požadavku typu GET, tak i formát požadavku typu POST při předávání údajů přes formulářová data a nakonec formát požadavku předaného v těle (JSON):

127.0.0.1 - - [03/Aug/2018 13:57:57] "GET / HTTP/1.1" 200 -
URI: /
content length: 35
content value:  b'klic=hodnota&answer=42&correct=True'
127.0.0.1 - - [03/Aug/2018 13:57:57] "POST / HTTP/1.1" 200 -
URI: /
content length: 68
content value:  b'{"klic": "hodnota", "answer": 42, "question": null, "correct": true}'
127.0.0.1 - - [03/Aug/2018 13:57:57] "POST / HTTP/1.1" 200 -

18. Obsah druhé části seriálu

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ší techniky – autorizaci, 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é (v porovnání s některými dalšími knihovnami s podobným zaměřením).

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

Zdrojové kódy všech dnes popsaných demonstračních příkladů určených 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 01_basic_usage.py použití metody GET pro komunikaci se serverem https://github.com/tisnik/most-popular-python-libs/blob/master/requests/01_basic_usage.py
2 02_check_status.py kontrola stavového kódu odpovědi serveru https://github.com/tisnik/most-popular-python-libs/blob/master/requests/02_check_status.py
3 03_content_type.py získání typu/formátu odpovědi serveru https://github.com/tisnik/most-popular-python-libs/blob/master/requests/03_content_type.py
4 04_response_content.py získání obsahu odpovědi serveru https://github.com/tisnik/most-popular-python-libs/blob/master/requests/04_response_content.py
5 05_response_content_and_headers.py získání obsahu odpovědi serveru včetně hlaviček https://github.com/tisnik/most-popular-python-libs/blob/master/requests/05_response_content_and_headers.py
6 06_response_json.py práce s odpovědí ve formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/requests/06_response_json.py
7 07_post_method.py použití metody POST pro komunikaci se serverem https://github.com/tisnik/most-popular-python-libs/blob/master/requests/07_post_method.py
8 08_post_method_with_payload.py metoda POST a data poslaná ve formuláři https://github.com/tisnik/most-popular-python-libs/blob/master/requests/08_post_method_with_payload.py
9 09_post_method_with_payload.py metoda POST a data poslaná v těle zprávy https://github.com/tisnik/most-popular-python-libs/blob/master/requests/09_post_method_with_payload.py
10 10_against_test_local_server.py test metod GET a POST vůči lokálnímu serveru https://github.com/tisnik/most-popular-python-libs/blob/master/requests/10_against_test_local_server.py
11 simple_server.py velmi jednoduchý lokální HTTP server https://github.com/tisnik/most-popular-python-libs/blob/master/requests/simple_server.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/

Autor: Pavel Tišnovský   2018