Python request ssl error

Запросы проверяет SSL-сертификаты для HTTPS-запросов, как это делает веб-браузер. По умолчанию проверка SSL включена. Также можно указать локальный сертификат для использования в качестве сертификата на стороне клиента.

Проверка SSL-сертификатов и SSL на стороне клиента.

Содержание:

  • Проверка SSL сертификата;
  • Сертификаты на стороне клиента;
  • Доверенные сертификаты CA.

Проверка SSL сертификата.

>>> import requests
>>> requests.get('https://requestb.in')
# requests.exceptions.SSLError: hostname 'requestb.in' doesn't match ...

На этом домене нет установленного SSL, поэтому запрос создает исключение. Запрос к GitHub проходит без каких либо ошибок:

>>> requests.get('https://github.com')
# <Response [200]>

Можно передать аргументу verify путь к файлу CA_BUNDLE или каталогу с доверенными сертификатами CA:

# указание доверенных сертификатов в запросе
>>> requests.get('https://github.com', verify='/path/to/certfile')

# указание доверенных сертификатов для сессии
>>> sess = requests.Session()
>>> sess.verify = '/path/to/certfile'

Примечание. Если для параметра verify задан путь к каталогу, то этот каталог должен быть обработан с помощью утилиты c_rehash, поставляемой с OpenSSL.

Список доверенных CA также можно указать с помощью переменных сред REQUESTS_CA_BUNDLE. Если REQUESTS_CA_BUNDLE не установлена, то CURL_CA_BUNDLE будет использоваться в качестве запасного варианта.

Запросы также могут игнорировать проверку SSL-сертификата, если для параметра verify задано значение False:

>>> requests.get('https://kennethreitz.org', verify=False)
# <Response [200]>
Обратите внимание

, что если аргумент verify=False, то запросы будут принимать любой TLS-сертификат, представленный сервером, и будут игнорировать несоответствия имен хостов и/или просроченные сертификаты, что сделает приложение уязвимым для атак man-in-the-middle (MitM). Установка значения verify в False может быть полезна во время локальной разработки или тестирования.

По умолчанию для параметра verify установлено значение True. Опция verify применяется только к сертификатам хоста.

Сертификаты на стороне клиента.

Также можно указать локальный сертификат для использования в качестве сертификата на стороне клиента, как один файл (содержащий закрытый ключ и сертификат) или как кортеж путей к обоим файлам:

>>> requests.get('https://kennethreitz.org', 
...                cert=('/path/client.cert', '/path/client.key'))
# <Response [200]>

# для сессии
s = requests.Session()
s.cert = '/path/client.cert'

Если укать неправильный путь или неверный сертификат, то получим SSLError:

>>> requests.get('https://kennethreitz.org', cert='/wrong_path/client.pem')
# SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL...

Предупреждение. Закрытый ключ к локальному сертификату должен быть незашифрованным. В настоящее время библиотека requests не поддерживают использование зашифрованных ключей.

Доверенные сертификаты CA

Библиотека requests используют сертификаты из пакета certifi. Это позволяет пользователям обновлять доверенные сертификаты без изменения версии запросов.

До версии requests-2.16 модуль объединял набор доверенных корневых центров сертификации, полученных из хранилища доверенных сертификатов Mozilla. Сертификаты обновлялись только один раз для каждой версии запросов. Когда не был установлен certifi , это приводило к чрезвычайно устаревшим пакетам сертификатов при использовании значительно более старых версий запросов.

В целях безопасности команда разработчиков библиотеки requests рекомендует почаще обновлять сертификаты!

SSL certificate_verify_failed errors typically occur as a result of outdated Python default certificates or invalid root certificates. We will cover how to fix this issue in 4 ways in this article.

Why certificate_verify_failed  happen?

The SSL connection will be established based on the following process.   We will get errors if any of these steps does not go well.

For this error certificate_verify_failed, it usually happens during step 2 and step 3.

  • The client sends a request to the server for a secure session. The server responds by sending its X.509 digital certificate to the client.
  • The client receives the server’s X.509 digital certificate.
  • The client authenticates the server, using a list of known certificate authorities.
  • The client generates a random symmetric key and encrypts it using server’s public key.
  • The client and server now both know the symmetric key and can use the SSL encryption process to encrypt and decrypt the information contained in the client request and the server response.

When the client receives the server’s certificate, it begins chaining that certificate back to its root. It will begin by following the chain to the intermediate that has been installed, from there it continues tracing backwards until it arrives at a trusted root certificate.

If the certificate is valid and can be chained back to a trusted root, it will be trusted. If it can’t be chained back to a trusted root, the browser will issue a warning about the certificate.

Related: Check SSL Certificate Chain with OpenSSL Examples

Error info about certificate_verify_failed

We will see the following error.

<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:777)>

What is SSL certificate

Server certificates are the most popular type of X.509 certificate. SSL/TLS certificates are issued to hostnames (machine names like ‘ABC-SERVER-02’ or domain names like google.com).

A server certificate is a file installed on a website’s origin server. It’s simply a data file containing the public key and the identity of the website owner, along with other information. Without a server certificate, a website’s traffic can’t be encrypted with TLS.

Technically, any website owner can create their own server certificate, and such certificates are called self-signed certificates. However, browsers do not consider self-signed certificates to be as trustworthy as SSL certificates issued by a certificate authority.

Related: 2 Ways to Create self signed certificate with Openssl Command

How to fix certificate_verify_failed?

If you receive the “certificate_verify_failed” error when trying to connect to a website, it means that the certificate on the website is not trusted. There are a few different ways to fix this error.

We will skip the SSL certificate check in the first three solutions.  For the fourth solution, we are going to install the latest CA certificate from certifi.

Create unverified context in SSL

import ssl
context = ssl._create_unverified_context()
urllib.request.urlopen(req,context=context)

Create unverified https context in SSL

import ssl
ssl._create_default_https_context = ssl._create_unverified_context
urllib2.urlopen(“https://google.com”).read()

Use requests module and set ssl verify to false

requests.get(url, headers=Hostreferer,verify=False)

Update SSL certificate with PIP

we can also update our SSL certificate With PIP.  All we would have to do is  to update our SSL certificate directory with the following piece of code: pip install –upgrade certifi

What this command does is update our system’s SSL certificate directory.

Reference:

Understanding SSL certificates

Check SSL Certificate Chain with OpenSSL Examples

5 ways to check SSL Certificate

В этом материале описаны продвинутые функции библиотеки Requests.

Объекты Session

Объект Session позволяет сохранять определенные параметры между запросами. Он же сохраняет куки всех запросов, сделанных из экземпляра Session.

Объект Session включает все методы основного API Requests.

Попробуем передать куки между запросами:

s = requests.Session()

s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get("https://httpbin.org/cookies")

print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'

Session могут также использоваться для предоставления данных по умолчанию для методов запроса. Для этого их нужно передать в параметры объекта:

s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})

# отправка с 'x-test' и 'x-test2'
s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})

Любые словари, переданные методу запроса? будут объединены с заданными значениями уровня сессии. Параметры уровня методов перезаписывают параметры сессии.

Удалите значение из параметра словаря:
Иногда нужно будет не включать ключи уровня сессии в параметры словаря. Для этого необходимо установить значения ключа None в параметре уровня методов. Они будут пропускаться автоматически.

Все значения, содержащиеся в сессии, прямо доступны. Подробнее об этом в документации API Session.

Объекты запросов и ответов

Каждый раз при вызове запроса происходят две вещи. Во-первых, создается объект Request, который будет направлен на сервер, чтобы сделать запрос или вернуть определенный ресурс. Во-вторых, когда библиотека получает ответ от сервера, генерируется объект Response. Он содержит всю информацию, которую вернул сервер и оригинальный объект Request. Вот простой запрос для получения крайне важной информации с серверов Википедии:

>>> r = requests.get('https://ru.wikipedia.org/wiki/Монти_Пайтон')

Если нужно получить доступ к заголовкам, которые вернул сервер, делается следующее:

>>> r.headers
{'Date': 'Fri, 06 Mar 2020 10:40:21 GMT', 'Content-Type': 'text/html; charset=UTF-8', 
'Server': 'mw1258.eqiad.wmnet', 'X-Powered-By': 'PHP/7.2.26-1+0~20191218.33+debian9~1.gbpb5a340+wmf1', 
'X-Content-Type-Options': 'nosniff', 'P3P': 'CP="See [https://ru.wikipedia.org/wiki/Special:CentralAutoLogin/P3P](https://ru.wikipedia.org/wiki/Special:CentralAutoLogin/P3P) for more info."', 
'Content-language': 'ru', 'Vary': 'Accept-Encoding,Cookie,Authorization', 
'Last-Modified': 'Fri, 21 Feb 2020 10:40:21 GMT', 'Backend-Timing': 'D=181758 t=1583491220993575', 
'X-ATS-Timestamp': '1583491221', 'Content-Encoding': 'gzip', 
'X-Varnish': '1017342992 492505475', 'Age': '82909', 'X-Cache': 'cp3050 miss, cp3056 hit/43', 
'X-Cache-Status': 'hit-front', 'Server-Timing': 'cache;desc="hit-front"',
'Strict-Transport-Security': 'max-age=106384710; includeSubDomains; preload', 
'X-Client-IP': '111.111.111.1', 'Cache-Control': 'private, s-maxage=0, max-age=0, must-revalidate', 
'Accept-Ranges': 'bytes', 'Content-Length': '41759', 'Connection': 'keep-alive'}

А если нужны те, что были направлены серверу, тогда сперва нужно получить доступ к запросу, а потом — к его заголовкам:

>>> r.request.headers
{'User-Agent': 'python-requests/2.21.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}

Подготовка запросов

При получении объекта Response от вызова API или Session, используется атрибут PreparedRequest функции request. В некоторых случаях над телом и заголовками (и чем угодно еще) можно будет провести дополнительную работу перед отправкой запроса. Простейший способ следующий:

s = Session()

req = Request('POST', url, data=data, headers=headers)
prepped = req.prepare()

# делаем что-то с prepped.body
prepped.body = 'No, I want exactly this as the body.'

# делаем что-то с prepped.headers
del prepped.headers['Content-Type']

resp = s.send(prepped,
    stream=stream,
    verify=verify,
    proxies=proxies,
    cert=cert,
    timeout=timeout
)

print(resp.status_code)

Поскольку с объектом Request не происходит ничего особенного, его можно сразу подготовить и изменить объект PreparedRequest. Затем он отправляется с остальными параметрами, которые вы бы отправили в requests.* или Session.*.

Однако этот код лишен кое-каких преимуществ использования объекта Session. Если точнее, состояние уровня Session, например куки, не будет применено к запросу. Чтобы получить PreparedRequest с примененным состоянием, замените вызов к Request.prepare() вызовом к Session.prepare_request():

from requests import Request, Session

s = Session()
req = Request('GET',  url, data=data, headers=headers)

prepped = s.prepare_request(req)

# делаем что-то с prepped.body
prepped.body = 'Seriously, send exactly these bytes.'

# делаем что-то с prepped.headers
prepped.headers['Keep-Dead'] = 'parrot'

resp = s.send(prepped,
    stream=stream,
    verify=verify,
    proxies=proxies,
    cert=cert,
    timeout=timeout
)

print(resp.status_code)

При использовании потока “prepared request” помните, что он не учитывает окружение. Это может привести к проблемам в том случае, если переменные окружения используются для изменения поведения запросов. Например: самозаверенные SSL-сертификаты, определенные в REQUESTS_CA_BUNDLE, учитываться не будут. Результат — SSL: CERTIFICATE_VERIFY_FAILED. Обойти это поведение можно, явно объединив настройки окружения с сессией:

from requests import Request, Session

s = Session()
req = Request('GET', url)

prepped = s.prepare_request(req)

# Объединяем настройки среды в сессию
settings = s.merge_environment_settings(prepped.url, {}, None, None, None)
resp = s.send(prepped, **settings)

print(resp.status_code)

Проверка сертификата SSL

Библиотека Requests может верифицировать SSL-сертификаты для HTTPS-запросов так же, как и браузер. Для проверки сертификата хоста, нужно просто добавить аргумент verify:

>>> requests.get('https://kennethreitz.com', verify=True)
requests.exceptions.SSLError: hostname 'kennethreitz.com' 	
doesn't match either of '*.herokuapp.com', 'herokuapp.com'

Если такового нет или он недействителен, вернется ошибка SSLError. Но у Github, например, есть:

>>> requests.get('https://github.com', verify=True)

verify можно передать и файлу CA_BUNDLE для частных сертификатов. Или же настроить переменную среды REQUESTS_CA_BUNDLE.

Библиотека может игнорировать проверку SSL-сертификатов, если значение verifyFalse.

>>> requests.get('https://kennethreitz.com', verify=False)

По умолчанию значение verifyTrue. Параметр подходит только для сертификатов хостов.

Можно также определить файл локального сертификата в виде пути или пары ключ-значение:

>>> requests.get('https://kennethreitz.com', cert=('/path/server.crt', '/path/key'))

Если указан неправильный путь или недействительный сертификат, произойдет следующее:

>>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem')
SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib

Работа с содержанием ответа

По умолчанию при запросе тело ответа загружается сразу же. Переписать это поведение и отсрочить загрузку тела ответа до того момента, пока не будет получен доступ к атрибуту Response.content, можно с помощью параметра stream:

tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
r = requests.get(tarball_url, stream=True)

Сейчас загружаются только заголовки ответа, а соединение остается открытым. Это позволяет сделать получение контента по условию:

if int(r.headers['content-length']) < TOO_LONG:
  content = r.content
  # ...

Можно и дальше контролировать процесс работы с помощью методов Response.iter_content и Response.iter_lines или чтения их из лежащей в основе библиотеки urllib3 urllib3.HTTPResponse в Response.raw.

Постоянное соединение

Благодаря urllib3 постоянное соединение поддерживается на 100% автоматически прямо в сессии. Любые запросы в сессии будут автоматически использовать соответствующее соединение.

Стоит отметить, что соединения сбрасываются и возвращаются в пул для повторного использовать только после чтения данных тела. Важно задать значение stream равным False или читать свойство property объекта Response.

Потоковые загрузки

Requests поддерживает потоковые загрузки, которые позволяют отправлять крупные потоки или файлы без их чтения прямо в память. Для этого нужно предоставить файловый объект в data:

with open('massive-body') as f:
    request.post('http://some.url/streamed', data=f)

Запросы для данных, разбитых на части (chunk-encoded)

Requests также поддерживает механизм передачи с разбиением на части для входящих и исходящих запросов. Для отправления такого нужно предоставить генератор (или любой итератор без определенной длины) в data:

def gen():
    yield 'hi'
    yield 'there'

request.post('http://some.url/chunked', data=gen())

POST для нескольких файлов типа multipart

Можно отправить несколько файлов одним запросом. Например, предположим, необходимо загрузить файлы изображений в HTML-форму images для нескольких файлов :

<input type="file" name="images" multiple="true" required="true"/>

Чтобы сделать это, просто представьте файлы в виде списка кортежей такого формата (form_field_name, file_info):

>>> url = 'https://httpbin.org/post'
>>> multiple_files = [
...     ('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
...     ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
>>> r = requests.post(url, files=multiple_files)
>>> r.text
{
 ...
 'files': {'images': 'data:image/png;base64,iVBORw ....'}
 'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a',
 ...
}

Предупреждение:
Рекомендуется открывать файлы в бинарном режиме. Это важно, потому что Requests может попробовать предоставить заголовок Content-Length. В таком случае значением будет количеством байт в файле. А ошибки возникнут, если открыть файл в текстовом режиме.

Хуки (перехват управления)

В Requests есть система хуков, которую можно использовать для управления частями процесса запроса или обработки событий.

Доступные хуки:

  • response: ответ, сгенерированный из объекта Request.

Можно назначать функцию перехвата для каждого запроса, передавая словарь {hook_name: callback_function} в параметр запроса hooks:

hooks=dict(response=print_url)

callback_function получит кусок данных в качестве первого аргумента.

def print_url(r):
    print(r.url)

Если при выполнении обратного вызова произойдет ошибка, отобразится предупреждение.

Если функция вернет значение, предполагается, что оно должно заменить данные, которые были переданы. Когда функция не возвращает ничего, нет никакого эффекта.

Выведем некоторые аргументы метода запроса:

>>> requests.get('http://httpbin.org', hooks=dict(response=print_url))
http://httpbin.org

Собственная аутентификация

Requests позволяет указать собственный механизм аутентификации.

Любой вызываемый объект, передаваемый в качестве аргумента auth методу запроса, может изменить запрос до его отправки.

Реализации аутентификации — это подклассы requests.auth.AuthBase, и их легко определить. Requests предоставляет две общие схемы реализации аутентификации в requests.auth:HTTPBasicAuth и HTTPDigestAuth.

Представим, что есть веб-сервис, который отвечает только в том случае, если значение заголовка X-Pizza — значение пароля. Такое маловероятно, но мало ли.

from requests.auth import AuthBase

class PizzaAuth(AuthBase):
    """Прикрепляет аутентификацию HTTP Pizza к данному объекту запроса."""
    def __init__(self, username):
        # любые данные, связанные с аутентификацией
        self.username = username

    def __call__(self, r):
        # изменить и вернуть запрос
        r.headers['X-Pizza'] = self.username
        return r

Теперь можно сделать запрос с помощью PizzaAuth:

>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))

Потоковые запросы

С помощью requests.Response.iter_lines() можно запросто перебирать потоковые API, такие как Twitter Streaming API.

Используем его для отслеживания ключа словаря requests:

import requests
import json

r = requests.post('https://stream.twitter.com/1/statuses/filter.json',
    data={'track': 'requests'}, auth=('username', 'password'), stream=True)

for line in r.iter_lines(decode_unicode=True):
    if line: 
        print(json.loads(line))

Прокси

Если есть необходимость использовать прокси, можно настроить индивидуальные запросы с помощью аргумента proxies для любого метода запроса:

import requests

proxies = {
  "http": "10.10.1.10:3128",
  "https": "10.10.1.10:1080",
}

requests.get("http://example.org", proxies=proxies)

Также их можно настроить с помощью переменных среды HTTP_PROXY и HTTPS_PROXY.

$ export HTTP_PROXY="10.10.1.10:3128"
$ export HTTPS_PROXY="10.10.1.10:1080"
$ python
>>> import requests
>>> requests.get("http://example.org")

Для использования HTTP Basic Auth (аутентификации) со своим прокси, используется синтаксис http://user:password@host/:

proxies = {
    "http": "http://user:pass@10.10.1.10:3128/",
}

SOCKS

Новое в версии 2.10.0

В дополнение к базовым прокси HTTP Requests также поддерживает прокси с помощью протокола SOCKS. Это опциональная функция, требующая дополнительных библиотек. Их можно получить с помощью pip:

$ pip install requests[socks]

После установки использовать прокси SOCKS так же просто, как и HTTP:

proxies = {
    'http': 'socks5://user:pass@host:port',
    'https': 'socks5://user:pass@host:port'
}

При использовании socks5 разрешение DNS работает на стороне клиента, а не на стороне прокси-сервера. Это работает в соответствии с curl, который использует схему, чтобы определять, на чьей стороне разрешать DNS. Если необходимо заниматься преобразованием на стороне прокси-сервера, тогда используется socks5h.

Соответствие стандартам

Requests соответствует всем актуальным спецификациям и RFC (технические стандарты, применяемые в сети) там, где подобное соответствие не создает трудностей для пользователей. Такое внимание к спецификациям может привести к необычному поведению, которое покажется необычным для тех, кто не знаком с ними.

Кодировки

Когда вы получаете ответ, Requests предполагает, какую кодировку использовать для декодирования во время вызова метода Response.text. Библиотека сначала проверит кодировку в заголовке HTTP, и если там ничего не указано, воспользуется charade, чтобы попробовать угадать.

Она не будет вести себя подобным образом только в одном случае — если кодировка не указано явно, а значение Content-Typetext. В таком случае, согласно RFC 2616, кодировка по умолчанию — ISO-8859-1. Библиотека следует этому правилу. Если вам требуется другая кодировка, вы можете вручную настроить свойство Response. encoding или использовать сырой Response.content.

Методы HTTP

Requests предоставляет доступ ко всем методам HTTP: GET, OPTIONS, HEAD, POST, PUT, PATCH, DELETE. Далее будут детальные примеры того, как их использовать с GitHub API.

Начнем с самого популярного метода — GET. GET — это идемпотентный метод, который возвращает ресурс по заданному URL. Он используется для получения данных из определенного места. Пример — попытка получить информацию об определенном коммите из GitHub. Пусть будет коммит a050faf. Это будет выглядеть вот так:

>>> import requests
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')

Нужно подтвердить, что GitHub ответил правильно. Если да — необходимо определить тип контента. Это делается следующим образом:

>>> if (r.status_code == requests.codes.ok):
...     print(r.headers['content-type'])
...
application/json; charset=utf-8

Итак, GitHub возвращает JSON. Можно использовать метод r.json для парсинга его в объекты Python.

>>> commit_data = r.json()
>>> print(commit_data.keys())
['committer', 'author', 'url', 'tree', 'sha', 'parents', 'message']
>>> print(commit_data['committer'])
{'date': '2012-05-10T11:10:50-07:00', 'email': 'me@kennethreitz.com', 'name': 'Kenneth Reitz'}
>>> print(commit_data['message'])
makin' history

Пока что все просто. Но посмотрим, что еще есть в API GitHub. Можно просто почитать документацию, но еще интереснее, если просто поэкспериментировать с Requests. Используем метод OPTIONS, чтобы увидеть какие еще методы HTTP поддерживаются для этого ресурса.

>>> verbs = requests.options(r.url)
>>> verbs.status_code
500

Оказывается, что у GitHub, как и у многих API, не реализован метод OPTIONS. Так что придется все-таки использовать документацию. Но если бы метод OPTION был реализован, он вернул бы примерно следующее.

>>> verbs = requests.options('http://a-good-website.com/api/cats')
>>> print(verbs.headers['allow'])
GET,HEAD,POST,OPTIONS

В документации указано, что единственные разрешенные методы для коммитов — POST. Они создают новые коммиты. Но поскольку используется репозиторий Requests, лучше не делать туда бесполезные POST. Вместо этого можно поиграть с функцией Issues.

Эта документация была добавлена в ответ на “Issue #482”. Возьмем ее в качестве примера.

>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482')
>>> r.status_code
200
>>> issue = json.loads(r.text)
>>> print(issue['title'])
Feature any http verb in docs
>>> print(issue['comments'])
3

Есть три комментария. Рассмотрим последний из них.

>>> r = requests.get(r.url + '/comments')
>>> r.status_code
200
>>> comments = r.json()
>>> print(comments[0].keys())
['body', 'url', 'created_at', 'updated_at', 'user', 'id']
>>> print(comments[2]['body'])
Probably in the "advanced" section

Можем сообщить автору, что он не прав. Но сперва узнаем, кто это.

>>> print(comments[2]['user']['login'])
kennethreitz

Теперь скажем этому kennethreitz, что ему лучше отправляться в раздел для начинающих. Согласно документации API GitHub это делается с помощью метода POST.

>>> body = json.dumps({"body": "Sounds great! I'll get right on it!"})
>>> url = "https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
>>> r = requests.post(url=url, data=body)
>>> r.status_code
404

Похоже, нужно авторизоваться. В Requests можно выполнить любой вид аутентификации, включая базовую.

>>> from requests.auth import HTTPBasicAuth
>>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password')
>>> r = requests.post(url=url, data=body, auth=auth)
>>> r.status_code
201
>>> content = r.json()
>>> print(content['body'])
Sounds great! I'll get right on it.

Теперь попробуем отредактировать сообщение. Для этого нужен метод PATCH.

>>> print(content["id"])
5804413
>>> body = json.dumps({"body": "Sounds great! I'll get right on it once I feed my cat."})
>>> url = "https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413"
>>> r = requests.patch(url=url, data=body, auth=auth)
>>> r.status_code
200

С баловством покончено. Используем DELETE для удаления сообщения.

>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'

Напоследок можно посмотреть, как много запросов было использовано. Для этого нужно сделать запрос HEAD к заголовкам и не скачивать целую страницу.

>>> r = requests.head(url=url, auth=auth)
>>> print(r.headers)
...
'x-ratelimit-remaining': '4995'
'x-ratelimit-limit': '5000'
...

Осталось написать программу на Python, которая бы использовала остальные 4995 запросов.

Заголовки Link

Многие API используют заголовки Link. Они делают API более описательными и простыми в использования. GitHub используют пагинацию в своем API, например:

>>> url = 'https://api.github.com/users/kennethreitz/repos?page=1&per_page=10'
>>> r = requests.head(url=url)
>>> r.headers['link']
; rel="next", ; rel="last"

Requests автоматически парсит эти ссылки и позволяет с легкостью их использовать.

>>> r.links["next"]
{'url': 'https://api.github.com/users/kennethreitz/repos?page=2&per_page=10', 'rel': 'next'}

>>> r.links["last"]
{'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'}

Пользовательские HTTP-методы

Иногда вы будете работать с сервером, который по какой-то причине требует использовать методы HTTP за исключение базовых. Например, метод MKCOL, который используют сервера WEBDAV. Однако с ними также можно работать в Requests. В данном случае используется встроенный метод .request. Например:

>>> r = requests.request('MKCOL', url, data=data)
>>> r.status_code
200 # если вызов был правильный

Таким образом можно использовать любой метод, разрешенный сервером.

Transport Adapters

Начиная с версии v1.0.0, Requests использует внутренний модульный дизайн. Одна из причин — внедрение Transport Adapters. Они предоставляют средство для определения методов взаимодействия с HTTP. В частности, позволяют применять настройку для каждого сервиса по отдельности.

Requests поставляются с одним таким Transport Adapter — HTTPAdapter. Он предоставляет возможность взаимодействия с HTTP и HTTPS посредством библиотеки urllib3 из Requests по умолчанию. При инициализации Session один из них «крепится» к объекту Session HTTP, а второй — к HTTPS.

Requests дают возможность пользователям создавать и использовать собственные Transport Adapters с конкретной функциональностью. После создания Transport Adapter может быть прикреплен к объему Session вместе с указанием сервисов, к которым он должен применяться.

>>> s = requests.Session()
>>> s.mount('https://github.com/', MyAdapter())

Вызов mount регистрирует экземпляр Transport Adapter в префиксе. После этого HTTP-запросы, сделанные с помощью этого Session и URL которых начинается с этого префикса, будут использовать указанный Transport Adapter.

Многие подробности использования Transport Adapter лежат за рамками этого материала, но вы сможете разобраться лучше на следующем примере.

Пример: конкретная версия SSL

Разработчики Requests заранее определили, какая версия SSL будет использоваться по умолчанию в urllib3. Обычно это работает так, как нужно, но иногда требуется подключиться к конечной точке, которая использует версию, не совместимую с той, что указана по умолчанию.

В этом случае можно задействовать Transport Adapter, используя большую часть существующей реализации HTTPAdapter и добавив параметр ssl_version, который передается через urllib3. Настроим Transport Adapter, чтобы библиотека использовала SSLv3:

from urllib3.poolmanager import PoolManager

from requests.adapters import HTTPAdapter

class Ssl3HttpAdapter(HTTPAdapter):
    """"Transport adapter" который позволяет использовать SSLv3."""

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(
            num_pools=connections, maxsize=maxsize,
            block=block, ssl_version=ssl.PROTOCOL_SSLv3)

Блокирующий или не-блокирующий

С Transport Adapter по умолчанию Requests не предоставляет никакого не-блокирующего IO (ввода-вывода). Свойство Response.content будет блокировать до тех пор, пока весь ответ не загрузится. Если требуется большая детализация, потоковые возможности библиотеки позволяют получать маленькие порции ответа в определенное время. Но и эти вызовы будут блокироваться.

Если не хочется использовать блокировку IO, есть масса проектов, совмещающих Requests с одним из асинхронных фреймворков Python. Например, requests-threads, grequests, requests-futures и requests-async.

Порядок заголовков

В необычных обстоятельствах может понадобится предоставить заголовки в определенном порядке. Если передать OrderedDict в headers, это и будет обозначенный порядок. Но порядок заголовков по умолчанию в Requests будет иметь более высокий приоритет, поэтому если их перезаписать в headers, они могут отображаться беспорядочно в сравнении с теми, что указаны в аргументе.

Чтобы решить эту проблему, необходимо настроить заголовки по умолчанию для объекта Session, предоставив ему OrderedDict. Этот порядок и станет приоритетным.

Таймауты

У большинства запросов к внешнем серверам есть прикрепленный таймаут в том случае, если сервер не отвечает вовремя. По умолчанию запросы не прерываются, только если время не указано явно. Без таймаута код может висеть по несколько минут.

connect — это количество секунд, которые Requests будет выжидать для настройки соединения с вызовом удаленной машины (соответствующей connect()) в сокете. Хорошей практикой считается настраивать это время чуть больше значения кратного 3, что является стандартным окном ретрансляции пакета TCP.

Когда клиент подключился к серверу и отправил HTTP-запрос, таймаут read — это количество секунд, которые клиент будет ждать ответа от сервера. (Если точнее, это то количество секунд, которое клиент прождет между отправкой байтов с сервера. В 99,9% случаев оно меньше того времени с момента, когда сервер отправляет первый байт).

Если определить одно значение для таймаута, вот так:

r = requests.get('https://github.com', timeout=5)

Оно будет применено к таймауту connect и read. Если нужны отдельные значения, стоит определить их в кортеже:

r = requests.get('https://github.com', timeout=(3.05, 27))

Если сервер очень медленный, можно указать Requests, чтобы он ждал вечно, передав значение None.

r = requests.get('https://github.com', timeout=None)

Понравилась статья? Поделить с друзьями:
  • Python request http error
  • Python request error 403
  • Python request connection error
  • Python redirector was called with command line arguments printing error message and exiting
  • Python raise error with traceback