Сообщить об ошибке.

Класс OpenerDirector модуля urllib.request в Python

Открыватели/обработчики URL-адреса модуля urllib.request

Когда модуль urllib.request получает URL-адрес, то он использует, так называемый открыватель (Opener) этого URL-адреса - экземпляр urllib.request.OpenerDirector.

Обычно модуль использует открыватель URL-адреса по умолчанию - через функцию urllib.request.urlopen(), но можно создавать собственные открыватели. Открыватели URL-адреса используют обработчики URL. Вся "тяжелая работа" выполняется обработчиками/хендлерами. Каждый обработчик знает, как открывать URL-адреса для определенной схемы URL (http, ftp и т. д.) или как обрабатывать аспект открытия URL, например перенаправления HTTP или файлы cookie.

Создавать открыватели URL нужно будет тогда, когда необходимо получать URL-адреса с определенными установленными обработчиками, например, чтобы получить средство открытия, которое обрабатывает файлы cookie, или получить средство открытия, которое не обрабатывает перенаправления.

Чтобы создать "открыватель" URL-адреса, необходимо создать экземпляр OpenerDirector, а затем вызвать его метод OpenerDirector.add_handler().

В качестве альтернативы можно использовать urllib.request.build_opener() - удобную функцию для создания открывающих объектов с помощью одного вызова функции. Функция urllib.request.build_opener() по умолчанию добавляет несколько обработчиков, но предоставляет быстрый способ добавить и/или переопределить обработчики по умолчанию.

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

Функцию urllib.request.install_opener() можно использовать, чтобы сделать объект "открывателя" URL-адреса - глобальным средством открытия по умолчанию. Это означает, что вызовы urllib.request.urlopen() будут использовать установленную программу открытия.

У объектов OpenerDirector есть метод OpenerDirector.open(), который можно вызвать напрямую для получения URL-адресов так же, как и у функции urllib.request.urlopen(): вызывать функцию urllib.request.install_opener() не нужно, кроме как для удобства.

Объект OpenerDirector.


OpenerDirector.add_handler(handler):

Обработчик handler должен быть экземпляром BaseHandler. Следующие методы ищутся и добавляются к возможным цепочкам (обратите внимание, что ошибки HTTP - это особый случай).

Обратите внимание, что ниже протокол следует заменить фактическим протоколом для обработки, например http_response() будет обработчиком ответа протокола HTTP. Также тип следует заменить фактическим кодом HTTP, например http_error_404() будет обрабатывать ошибки HTTP 404.

  • BaseHandler.<protocol>_open() - сигнализирует, что обработчик знает, как открывать URL-адреса протокола.
  • BaseHandler.http_error_<type>() - сигнализирует, что обработчик знает, как обрабатывать ошибки HTTP с типом кода ошибки HTTP.
  • <protocol>_error() - сигнализирует, что обработчик знает, как обрабатывать ошибки протокола (не http).
  • `BaseHandler._request() - сигнализирует, что обработчик знает, как предварительно обрабатывать запросы протокола.
  • BaseHandler.<protocol>_response() - сигнализирует, что обработчик знает, как обрабатывать ответы протокола.

OpenerDirector.open(url, data=None[, timeout]):

Метод OpenerDirector.open() открывает указанный URL-адрес (который может быть объектом Request или строкой), при необходимости передавая данные.

Метод имеет аргументы, возвращаемые значения и возникшие исключения такие же, как и у функции urllib.request.urlopen() (которая просто вызывает метод OpenerDirector.open() в установленном в данный момент глобальном OpenerDirector).

Необязательный параметр тайм-аута timeout указывает тайм-аут в секундах для блокирующих операций, таких как попытка подключения (если не указан, то будет использоваться глобальная настройка тайм-аута по умолчанию). Тайм-аут фактически работает только для соединений HTTP, HTTPS и FTP).

OpenerDirector.error(proto, *args):

Метод OpenerDirector.error() обрабатывает ошибку данного протокола.

Метод вызовет зарегистрированные обработчики ошибок для данного протокола с заданными аргументами (которые зависят от протокола). Протокол HTTP - это особый случай, который использует код ответа HTTP для определения конкретного обработчика ошибок BaseHandler.http_error_<type>().

Возвращаемые значения и возникающие исключения такие же, как у функции urllib.request.urlopen().

Как OpenerDirector открывает URL-адрес?

Объекты OpenerDirector открывают URL-адреса в три этапа:

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

  1. Каждый обработчик имеющий метод с именем .<protocol>_request(), вызывается для предварительной обработки запроса.

  2. Для обработки самого запроса вызываются обработчики с именем метода .<protocol>_open(). Этот этап заканчивается, когда обработчик либо возвращает значение, отличное от None (то есть ответ), либо вызывает исключение (обычно URLError). Допускается распространение исключений вызывающей стороне.

    Фактически, описанный выше алгоритм сначала пробуется для методов с именем BaseHandler.default_open(). Если все такие методы возвращают None, то алгоритм повторяется для методов, названных как .<protocol>_open(). Если все такие методы возвращают None, то алгоритм повторяется для методов с именем BaseHandler.unknown_open().

    Обратите внимание, что реализация этих методов может включать вызовы методов .open() и .error() родительского экземпляра OpenerDirector.

  3. У каждого обработчика с именем метода .<protocol>_response(), этот метод вызывается для последующей обработки ответа.

Пример базовой аутентификации.

Чтобы проиллюстрировать создание и установку обработчика, будем использовать HTTPBasicAuthHandler.

Когда требуется аутентификация, то сервер отправляет заголовок (а также код ошибки 401) с запросом аутентификации. Это определяет схему и область аутентификации. Заголовок выглядит примерно так: WWW-Authenticate: SCHEME realm = 'REALM'.

В том числе для cPanel:

WWW-Authenticate: Basic realm="cPanel Users"

Затем клиент должен повторить запрос, указав соответствующее имя и пароль для области realm, включенные в запрос в качестве заголовка. Это базовая аутентификация. Чтобы упростить этот процесс, можно создать экземпляр HTTPBasicAuthHandler и средство открытия для использования этого обработчика.

Класс HTTPBasicAuthHandler использует объект, называемый диспетчером паролей, для обработки сопоставления URL-адресов и областей с паролями и именами пользователей. Если заранее знать, что это за область (из заголовка аутентификации, отправленного сервером), то можно использовать класс HTTPPasswordMgr.

Но часто никого не волнует, что это за область realm. В этом случае удобно использовать класс HTTPPasswordMgrWithDefaultRealm, что позволяет указать имя пользователя и пароль по умолчанию для URL-адреса. Имя пользователя и пароль будут использоваться в случае, если не предоставить альтернативную комбинацию для конкретной области realm. Для того, что бы имена и пароли работали для конкретного URL-адреса, а не для области realm необходимо указать в качестве аргумента метода HTTPPasswordMgrWithDefaultRealm.add_password() - realm=None.

URL-адрес верхнего уровня - это первый URL-адрес, требующий аутентификации. URL-адреса глубже, чем URL-адрес, который передается в метод .add_password(), также будут совпадать.

import urllib.request

# создаем менеджер паролей
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()

# Добавляем имя пользователя и пароль.
# Если бы мы знали область, то могли бы использовать ее вместо `None`.
top_level_url = "http://example.com/foo/"
password_mgr.add_password(None, top_level_url, username, password)

handler = urllib.request.HTTPBasicAuthHandler(password_mgr)

# создаем открыватель (экземпляр `OpenerDirector`)
opener = urllib.request.build_opener(handler)

# используем экземпляр `OpenerDirector`) для получения URL
opener.open(a_url)

# Можно установить открыватель (экземпляр `OpenerDirector`).
# Теперь все вызовы `urllib.request.urlopen()` будут использовать
# установленный открыватель по умолчанию
urllib.request.install_opener(opener)

Примечание. В приведенном выше примере в функцию build_opener() был передан только HTTPBasicAuthHandler. По умолчанию открыватели имеют обработчики для обычных ситуаций - ProxyHandler (если установлен параметр прокси, такой как переменная среды http_proxy), UnknownHandler, HTTPHandler, HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, DataHandler, HTTPErrorProcessor.

Аргумент top_level_url на самом деле является либо полным URL-адресом (включая компонент схемы http:, имя хоста и, возможно, номер порта), например http://example.com/ или "основание" (т.е. имя хоста, необязательно включая номер порта), например example.com или example.com:8080 (последний пример включает номер порта). Основание URL, если оно присутствует, НЕ должно содержать компонент userinfo - например, 'joe:password@example.com' неверен.

Пример аутентификации через сторонний прокси сервер.

Модуль urllib.request автоматически определит настройки прокси, используемого системой и будет использовать их. Это происходит через класс обработчика ProxyHandler, который является частью обычной цепочки обработчиков при обнаружении настройки прокси. Обычно это хорошо, но бывают случаи, когда это может быть бесполезно.

Настройка подключения через сторонний прокси сервер делается с помощью шагов, аналогичных настройке обработчика базовой аутентификации. Смотрим пример одного из способов настроить собственный обработчик ProxyHandler и залогинится через него:

import urllib.request

# создаем обработчик подключения через сторонние прокси
proxy_handler = urllib.request.ProxyHandler({'http': 'http://http-proxy.com:3128/', 
                                            'https': 'https://https-proxy.com:3128/'})
# создаем обработчик аутентификации через сторонний прокси
proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
proxy_auth_handler.add_password('realm', 'host', 'username', 'password')
# создаем цепочку обработки URL-адреса 
opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)
# используем `OpenerDirector` для открытия URL-адреса
opener.open('http://www.example.com/login.html')

Примечание. В настоящее время urllib.request не поддерживает получение https-адресов через прокси. Однако это можно настроить, используя модуль socket и http.client.

Для этого необходимо создать пользовательский класс ProxyHTTPConnection() унаследовав его от http.client.HTTPConnection и переопределить методы .connect() и .request() и на его основе уже создать пользовательский класс ProxyHTTPSConnection().

# как то так...
class ProxyHTTPSConnection(ProxyHTTPConnection):
    
    default_port = 443

    def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None):
        super().__init__(self, host, port)
        self.key_file = key_file
        self.cert_file = cert_file
    
    def connect(self):
        ProxyHTTPConnection.connect(self)
        # socket с поддержкой ssl
        ssl = socket.ssl(self.sock, self.key_file, self.cert_file)
        self.sock = httplib.FakeSocket(self.sock, ssl)

Примечание. HTTP_PROXY будет проигнорирован, если установлена ​​переменная REQUEST_METHOD. Подробнее смотрите документацию по функции urllib.request.getproxies().