13.1. Теория

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

Большинство сетевых программ работают по одной из двух схем:

  • точка-точка: одна и та же программа выполняется на разных машинах;
  • клиент/сервер: программы-клиенты отправляют запросы программе-серверу.

13.1.1. Передача данных, стек протоколов TCP/IP, сокеты

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

Протокол передачи данных — набор соглашений интерфейса логического уровня, которые определяют обмен данными между различными программами. Эти соглашения задают единообразный способ передачи сообщений и обработки ошибок при взаимодействии программного обеспечения разнесенной в пространстве аппаратуры, соединенной тем или иным интерфейсом.

13.1.1.1. TCP/IP

Наиболее широко распространенным набором сетевых протоколов передачи данных, используемых в сетях, включая сеть Интернет является стек протоколов TCP/IP (англ. Transmission Control Protocol, TCP и англ. Internet Protocol, IP).

Впервые передачу данных с использованием протокола TCP в июле 1977 г. продемонстрировали американские ученые Винт Серф и Боб Кан (Рисунок 13.1.1). Пакет прошел по маршруту Сан-Франциско - Лондон - Университет Южной Калифорнии, и, пройдя путь более 150 тысяч км., не потерял ни одного бита.

_images/13_01_01.png

Рисунок 13.1.1 - Винт Серф и Боб Кан [6]

В 1978 г. в TCP было выделено два отдельных уровня, транспортный (TCP) и Интернет (IP):

  • TCP, назначение:

    • разбивка сообщения на пакеты (блоки);
    • соединение их в конечном пункте отправки.
  • IP, назначение:

    • передача (с контролем получения) отдельных пакетов.

Сегодня стек протоколов TCP/IP включает в себя ряд уровней. Стековая структура означает, что протокол, располагающийся на уровне выше, работает «поверх» нижнего, используя механизмы инкапсуляции (Рисунок 13.1.2).

_images/13_01_02.png

Рисунок 13.1.2 - Структура стека протоколов TCP/IP

Примечание

Сетевая модель OSI (англ. Open Systems Interconnection Basic Reference Model - базовая эталонная модель взаимодействия открытых систем) - сетевая модель стека сетевых протоколов OSI/ISO (ГОСТ Р ИСО/МЭК 7498-1-99) [7] (концептуальная модель сетевого взаимодействия, не сумевшая получить распространение, как TCP/IP).

В протоколе TCP/IP строго зафиксированы правила передачи информации от отправителя к получателю и обратно. Передача сообщений осуществляется в рамках уровней, проходя определенные этапы (Видео 13.1.1, Рисунок 13.1.3).

Видео 13.1.1 - What is TCP/IP?

_images/13_01_03.png

Рисунок 13.1.3 - Передача информации от приложения целевому устройству и обратно

Общий ход передачи информации выглядит следующим образом:

  1. Данные от приложения отправляются протоколу транспортного уровня.
  2. Получив данные от приложения, протокол разделяет всю информацию на небольшие блоки (пакеты). К каждому пакету добавляется адрес назначения, а затем пакет передается на следующий уровень - уровень протоколов Интернет (сетевой уровень).
  3. На сетевом уровне пакет помещается в дейтаграмму протокола Интернет (IP), к которой добавляется заголовок и концевик. Протокол сетевого уровня определяет адрес следующего пункта назначения IP-дейтаграммы и отправляет его на уровень сетевого интерфейса.
  4. Уровень сетевого интерфейса принимает IP-дейтаграмму и передает их в виде кадров с помощью аппаратного обеспечения (например, сетевой карты).
  5. Пакеты доставляются на компьютер получателя, после чего проходят все уровни протоколов в обратном порядке. На каждом уровне удаляются соответствующие этому уровню заголовки, после чего данные передаются на уровень приложения.

На транспортном уровне используется один из двух протоколов:

  • TCP - «гарантированный» протокол с предварительным установлением соединения («рукопожатием»).

    Протокол:

    • дает уверенность в безошибочности получаемых данных и соблюдении последовательности отправки;
    • позволяет регулировать нагрузку на сеть и уменьшать время ожидания данных при передаче на большие расстояния.
  • UDP (англ. User Datagram Protocol — протокол пользовательских дейтаграмм) - «негарантированный» протокол передачи без установления соединения («рукопожатия»).

    Протокол:

    • в отличие от TCP не позволяет удостовериться в доставке сообщения адресату, а также порядке получения пакетов;
    • обычно быстрее, чем UDP и полезен для серверов, отвечающих на небольшие запросы от огромного числа клиентов (онлайн-игры, потоковые мультимедиа приложения и др.).

Протокол TCP/IP имеет важное преимущество - аппаратную независимость (не привязан к особенностям сетевого аппаратного обеспечения). Используя данный протокол, можно организовать сеть между любыми устройствами (обычно называемых хостами или узлами).

13.1.1.2. Сокеты, IP-адрес и порт

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

Сокет - это один конец двустороннего канала связи между двумя программами, работающими в сети.

Сокеты бывают 2-х типов:

  1. Потоковые.

    Сокеты с установленным соединением (базируются на протоколе TCP). Состоят из потока байтов, который может быть двунаправленным - приложение может и передавать, и получать данные.

  2. Дейтаграммные.

    Сокеты, не требующие установления явного соединения между ними (базируются на протоколе UDP). Сообщение отправляется указанному сокету и, соответственно, может получаться от указанного сокета.

Сокет состоит из IP-адреса и порта.

  1. IP-адрес - уникальный сетевой адрес узла в компьютерной сети, построенной по протоколу IP.

    В версии протокола IPv4 IP-адрес имеет длину 4 байта (например, 192.168.0.3), а в версии протокола IPv6 IP-адрес имеет длину 16 байт (например, 2001:0db8:85a3:0000:0000:8a2e:0370:7334).

    В любой сети требуется уникальность адреса.

    Существует ряд специальных IP-адресов, например, 127.0.0.1 - IP-адрес, с помощью которого компьютер может обратиться по сети к самому себе, независимо от наличия у него подключения к сети, вида сети и адреса компьютера в ней.

  2. Порт - натуральное число, записываемое в заголовках протоколов транспортного уровня (TCP, UDP и др.).

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

    За определенными службами номера портов зарезервированы - это широко известные номера портов, например, порт 80, использующийся в протоколе HTTP. Любое приложение может пользоваться любым номером порта, который не был зарезервирован и пока не занят. Агентство IANA (англ. Internet Assigned Numbers Authority - Администрация адресного пространства Интернет) ведет перечень широко известных номеров портов.

Поскольку IP-адрес уникален в пределах сети, а номера портов уникальны на отдельной машине, номера сокетов также уникальны во всей сети (в т.ч. сети Интернет). Эта характеристика позволяет процессу общаться через сеть с другим процессом исключительно на основании номера сокета.

Примечание

Для просмотра TCP- и UDP-соединений компьютера можно использовать команду netstat в терминале ОС.

Совет

Принципы работы стека протоколов TCP/IP относительно просты и напоминают работу обычной почты, где:

  • пакет: бумажное письмо - содержит передаваемые данные и адресную информацию (адрес отправителя и адрес получателя);
  • IP-адрес: адрес дома;
  • номер порта: номер квартиры.

На почте для отправки письма каждый придерживается следующей схемы:

  • пишет на листе письмо, кладет его в конверт, заклеивает;
  • на обратной стороне конверта пишет адреса отправителя и получателя;
  • относит конверт в ближайшее почтовое отделение;
  • в момент доставки письмо проходит через цепочку почтовых отделений до ближайшего почтового отделения получателя, откуда почтальоном доставляется по указанному адресу получателя и опускается в его почтовый ящик (с номером его квартиры) или вручается лично - письмо дошло до получателя.

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

13.1.1.3. Прикладной уровень стека TCP/IP

Большинство приложений работает на прикладном уровне стека TCP/IP и используют свои протоколы (Таблица 13.1.1).

Таблица 13.1.1 - Некоторые известные протоколы прикладного уровня
Наименование Описание
1 HTTP (англ. HyperText Transfer Protocol — протокол передачи гипертекста) Используется при пересылке веб-страниц между компьютерами, подключенными к одной сети
2 HTTPS (англ. HyperText Transfer Protocol Secure) Расширение протокола HTTP, поддерживающее шифрование. Данные, передаваемые по протоколу HTTPS, «упаковываются» в криптографический протокол SSL или TLS
3 FTP (англ. File Transfer Protocol - протокол передачи файлов) Дает возможность абоненту обмениваться двоичными и текстовыми файлами с любым компьютером сети
4 POP (англ. Post Office Protocol) Используется клиентами электронной почты для получения почты с удаленного сервера
5 IMAP (англ. Internet Message Access Protocol - протокол прикладного уровня для доступа к электронной почте) Используется клиентами электронной почты для манипуляции письмами на почтовом сервере с компьютера пользователя (клиента) без постоянной пересылки с сервера и обратно файлов с полным содержанием писем
6 SMTP (англ. Simple Mail Transfer Protocol - простой протокол передачи почты) Используется клиентами электронной почты для передачи электронной почты
7 TELNET (англ. Terminal Network) Протокол удаленного доступа, позволяющий абоненту работать на любой ЭВМ находящейся с ним в одной сети, как на своей собственной, то есть запускать программы, менять режим работы и т.д.

13.1.2. Интернет и Всемирная паутина

1 января 1983 года компьютерная сеть ARPANET (создана в 1969 году в США Агентством Министерства обороны США по перспективным исследованиям (DARPA) и явившаяся прототипом сети Интернет) перешла на протокол TCP/IP, и этот день принято считать официальной датой рождения Интернета.

Интернет - всемирная система объединенных компьютерных сетей для хранения и передачи информации. Является массовой распределенной сетью, архитектуры клиент/сервер, построенной на основе протокола TCP/IP. Информация в сети Интернет передается с использованием различных протоколов.

На основе Интернета работает Всемирная паутина (англ., World Wide Web, WWW) - распределенная система, предоставляющая доступ к связанным между собой документам, расположенным на различных компьютерах, подключенных к Интернету, использующая протокол HTTP. Системы, работающие на основе других протоколов, например, P2P-сети (используемые, в частности, в протоколе BitTorrent), также работают на базе Интернета, но не относятся к WWW.

На Рисунке 13.1.4 приведена обобщенная схема работы сети Интернет.

_images/13_01_04.png

Рисунок 13.1.4 - Схема сети Интернет [8]

При доступе к какому-либо компьютеру в сети Интернет задействуется доменная система имен (англ. Domain Name System, DNS) - компьютерная распределенная система для получения информации о доменах.

DNS, в частности, используется для поиска IP-адреса по доменному имени (например, преобразует www.example.com в адрес 93.184.216.34), по которому компьютер может связываться в пределах сети. Благодаря множеству DNS-серверов нет необходимости запоминать IP-адрес конкретного сервера, достаточно знать его имя. Схема взаимодействия клиента и DNS-серверов приведена на Рисунке 13.1.5.

_images/13_01_05.png

Рисунок 13.1.5 - Схема взаимодействия клиента и DNS-серверов [9]

13.1.2.1. HTTP, HTTPS и URI

HTTP

HTTP (англ. HyperText Transfer Protocol) - протокол прикладного уровня передачи данных (изначально в виде гипертекстовых документов в формате HTML, в настоящий момент используется для передачи произвольных данных). Определяется международным стандартом RFC 2616 (рус., англ.).

HTTP в марте 1991 года предложил Тим Бернерс-Ли, британский ученый, работающий тогда в CERN (ЦЕРН) - Европейской организации по ядерным исследованиям (Рисунок 13.1.6). Бернерс-Ли также является разработчиком языка HTML (англ. HyperText Markup Language — язык гипертекстовой разметки), который является стандартным языком разметки документов во Всемирной паутине.

_images/13_01_06.png

Рисунок 13.1.6 - Тим Бернерс-Ли [10]

Основой HTTP является технология «клиент-сервер» - предполагается, что существует множество потребителей (клиентов), которые инициируют соединение и посылают запрос, и поставщиков (серверов), которые ожидают соединения для получения запроса, производят необходимые действия и возвращают обратно сообщение с результатом (Рисунок 13.1.6).

_images/13_01_07.png

Рисунок 13.1.7 - Схема HTTP-протокола [8]

Ключевые особенности:

  • использует порт 80;
  • основной объект манипуляции (отправки/получения) - ресурс, на который указывает в запросе клиент (ресурс - это не только файл, а любые данные, например, динамически полученный текст и т.д.);
  • возможность указать в запросе и ответе способ представления одного и того же ресурса по различным параметрам: формату, кодировке, языку и т.д;
  • не имеет состояний - никакой запрос не связан с другим запросом и не знает, что было сделано до этого.

HTTPS

HTTPS (англ. HyperText Transfer Protocol Secure) - расширение протокола HTTP, поддерживающее шифрование.

Данные, передаваемые по протоколу HTTPS, «упаковываются» в криптографический протокол SSL (англ. Secure Sockets Layer — уровень защищенных сокетов) или TLS (англ. Transport Layer Security — безопасность транспортного уровня), позволяя клиент-серверным приложениям осуществлять связь в сети таким образом, чтобы предотвратить прослушивание и несанкционированный доступ.

Ключевые особенности:

  • использует порт 443;
  • не является отдельным протоколом; это обычный HTTP, работающий через шифрованные транспортные механизмы;
  • обеспечивает защиту от атак, основанных на прослушивании сетевого соединения — от снифферских атак и атак типа man-in-the-middle, при условии, что будут использоваться шифрующие средства и сертификат сервера проверен и ему доверяют;
  • традиционно на одном IP-адресе может работать только один HTTPS сайт.

URI

Одно из ключевых понятий в протоколе - это адрес ресурса, в качестве которого выступает URI. URI (англ. Uniform Resource Identifier) - унифицированный (единообразный) идентификатор ресурса.

Пример URI приведен на Рисунке 13.1.8 Обязательными атрибутами являются: имя, схема, путь, и Userinfo в случае авторизации.

_images/13_01_08.png

Рисунок 13.1.8 - Пример URI [11]

13.1.2.2. HTTP: Типовое взаимодействие

В качестве типового HTTP-клиента выступает веб-браузер. Когда в адресной строке указывается URI, браузер преобразует его в специальный запрос и отправляет на HTTP-сервер. Сервер обрабатывает запрос и возвращает ответ, содержащий запрошенный ресурс или ошибку (и то, и другое в конечном итоге отображает браузер, Рисунок 13.1.9).

_images/13_01_09.png

Рисунок 13.1.9 - Открытие веб-страницы в веб-браузере [8]

HTTP-протокол работает поверх TCP-протокола, поэтому каждый запрос связан с открытием соединения (со стороны клиента) и его закрытием (со стороны сервера после ответа).

13.1.2.3. HTTP: Структура протокола

Как HTTP-запрос (англ. Request), так и HTTP-ответ (англ. Response) имеют следующий формат:

  1. Стартовая строка (англ. Starting Line) — определяет тип сообщения (обязательна);
  2. Заголовки (англ. Headers) — характеризуют тело сообщения, параметры передачи и прочие сведения (могут отсутствовать);
  3. Пустая строка;
  4. Тело сообщения (англ. Message Body) — непосредственно данные сообщения (например, код HTML-страницы или файл).

На Рисунке 13.1.10 приведены примеры запроса и ответа по протоколу HTTP.

_images/13_01_10.png

Рисунок 13.1.10 - Пример HTTP-запроса и HTTP-ответа [8]

  1. Стартовая строка.

    Стартовая строка записывается по-разному в зависимости от типа сообщения:

    • запрос:

      Типичная стартовая строка запроса выглядит следующим образом:

      GET /path/to/file/index.html HTTP/1.0
      

      где (по порядку, через пробел):

      • GET - наиболее часто используемый HTTP-метод, обозначающий «дай мне вот этот ресурс»;
      • /path/to/file/index.html - часть URI после имени сервера;
      • HTTP/1.0 - HTTP-версия, строка формата «HTTP/x.x», в верхнем регистре.

      Кроме метода GET часто используются методы HEAD и POST:

      • HEAD: аналогичен GET, но запрашивает у сервера только заголовки (без тела сообщения); полезен для проверки соединения с сервером без нагрузки на канал;

      • POST: предназначен для отправки данных на сервер и их дальнейшей обработки:

        • в качестве тела сообщения отправляются какие-либо данные; обычно их формат определяется дополнительными заголовками, определяющими тип данных, размер и т.д.;
        • POST-запросы часто используются в HTML-формах для отправки каких-либо данных на сервер.

      HTTP-запрос также может содержать параметры: в URI они в виде пар ключ-значение через знак амперсанда:

      http://example.com/enter?login=admin&password=qwerty
      
    • ответ:

      Типичная стартовая строка ответа выглядит следующим образом:

      HTTP/1.0 200 OK
      

      где (по порядку, через пробел):

      • HTTP/1.0 - HTTP-версия, строка формата «HTTP/x.x», в верхнем регистре;
      • 200 - код ответа (англ. HTTP status code) (машиночитаемый);
      • OK - сообщение (человекочитаемое).

      Код ответа и сообщения образуют код состояния, по которому можно определить содержимое ответа сервера. Например:

      • 200 OK: запрос выполнен успешно, ресурс содержится в теле сообщения;
      • 404 Not Found: запрошенный ресурс не существует.
  2. Заголовки

    Заголовки HTTP - строки в HTTP-сообщении, содержащие разделенную двоеточием пару параметр-значение (регистр параметра не важен). Формат заголовков соответствует общему формату заголовков текстовых сетевых сообщений (RFC 822).

    Как правило, клиент в запросах указывает следующие заголовки:

    • From: e-mail человека или программы, выполняющей запрос;
    • User-Agent: наименование программы, выполняющей запрос в формате 'Program-name/x.xx', где 'x.xx' - версия приложения; пример: 'User-Agent: Mozilla/20.1'.

    В свою очередь, в ответе, сервер, обычно указывает следующие заголовки:

    • Server: аналог User-Agent в запросе; пример: 'Server: Apache/1.2b3-dev'
    • Last-Modified: дата/время по Гринвичу последнего обновления запрошенного ресурса (может использоваться клиентом для кеширования ресурса); пример: 'Last-Modified: Fri, 31 Dec 1999 23:59:59 GMT'
  3. Тело сообщения

    Тело HTTP сообщения, если присутствует, используется для передачи данных, связанных с запросом или ответом.

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

    • Content-Type: MIME-тип тела сообщения (англ. Multipurpose Internet Mail Extensions — многоцелевые расширения интернет-почты) (например, "text/html", "application/pdf");
    • Content-Length: размер тела сообщения в байтах.

13.1.2.4. HTTP: Управлением состоянием и сессия

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

Сохранение информации между запросами связано с понятием сессии, позволяющей осуществлять связную «беседу» клиента и сервера между собой.

Существует несколько техник, позволяющих реализовать сессию:

  1. Куки (англ. Cookie).
  2. Скрытые поля в HTML-формах.
  3. Изменение URL.
  1. Куки

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

    Куки обычно используется для:

    • аутентификации пользователя;
    • хранения персональных предпочтений и настроек пользователя;
    • отслеживания состояния сеанса доступа пользователя;
    • ведения статистики о пользователях.

    Впервые хранение данных на стороне клиента было представлено компанией Netscape Communications. Само слово cookies произошло от англ. Magic Cookies («волшебные печеньки») - пакеты данных файла, встроенные в сам файл в качестве скрипта, но им не являющиеся.

    Особенности:

    • куки могут быть отправлены обратно только тому клиенту, от которого были получены, что позволяет однозначно идентифицировать клиента;
    • по формату являются набором ключ=значение, где в качестве ключа выступают специальные идентификаторы, например, наименование домена, пути, даты окончания действия, версии и т.д.;
    • основным ограничением куки является возможность их отключения в клиенте (в частности, в веб-браузере);
    • сервер в качестве имени заголовка использует ключ Set-Cookie, клиент - Cookie.

    Общий вид взаимодействия клиента и сервера с учетом куки приведен на Рисунок 13.1.11.

    _images/13_01_11.png

    Рисунок 13.1.11 - Открытие веб-страницы в веб-браузере

    Некоторые аспекты безопасности для куки:

    • это не программа (как, например, JavaScript), а простой текст, поэтому не может причинить какой-либо вред компьютеру;
    • не могут заполнить весь жесткий диск (есть ограничение на количество и объем);
    • могут быть отправлены только оригинальному хосту и никакому другому;
    • передаются и хранятся в открытом виде; в виду этого не должны содержать конфиденциальные данные (например, пароли или номера кредитных карт).
  2. Скрытые поля в HTML-формах

    Другим способом передачи информации внутри сессии являются скрытые поля в HTML-формах. Основной принцип - включить ID сессии во все HTML-страницы, отправляемые клиенту. Недостаток данного подхода заключается в том, что он требует дополнительных усилий команды разработки, особенно, если страницы создаются динамически; плюсом же является поддержка HTML-форм любыми браузерами.

  3. Изменение URL

    Принцип данного способа заключается в том, что ID сессии включается в URL-адрес всех страниц, отправляемых клиенту, например:

    http://host:port/shopping.html;sessionid=value
    

    Преимущества и недостатки данного способа аналогичны скрытым полям в HTML-формах.

13.1.2.5. HTTP: Пример взаимодействия

Для взаимодействия по HTTP-протоколу можно использовать любой клиент, например:

  • клиент Putty;
  • веб-браузер;
  • веб-сервис hurl.it.

Putty

Putty - свободно распространяемый клиент для различных протоколов удаленного доступа.

При запуске клиента необходимо установить параметры соединения (Рисунок 13.1.12).

_images/13_01_12.png

Рисунок 13.1.12 - Настройки Putty для выполнения HTTP-запроса

После нажатия кнопки Open откроется терминал, предназначенный для ввода команд. На Рисунках 13.1.13 и 13.1.14 приведен пример выполнения запросов HEAD и GET на сервер example.com.

_images/13_01_13.png

Рисунок 13.1.13 - Выполнение HTTP-метода HEAD

_images/13_01_14.png

Рисунок 13.1.14 - Выполнение HTTP-метода GET

Веб-браузер

Браузеры позволяют увидеть как запросы, отправленные на сервер, так и результаты их выполнения. Для этого в браузере Firefox или Chrome необходимо открыть инструменты разработчика, нажав клавишу F12, и выбрать вкладку Сеть/Network.

Перейдя по адресу example.com содержимое вкладки изменится (Рисунок 13.1.15).

_images/13_01_15.png

Рисунок 13.1.15 - HTTP-сообщения в Firefox (слева) и Chrome (справа)

Веб-сервис hurl.it

Веб-сервис hurl.it позволяет легко выполнять HTTP-запросы, настраивая их содержимое. Для выполнения простого GET-запроса необходимо ввести адрес сайта в соответствующее поле и нажать кнопку Launch Request. После выполнения запроса сервис должен отобразить запрос и ответ в «сыром» и отформатированном виде (Рисунок 13.1.16).

_images/13_01_16.png

Рисунок 13.1.16 - Выполнение запроса в веб-сервисе hurl.it

13.1.2.6. API веб-сервисов

HTML, являясь основным «языком общения» по HTTP-протоколу, обладает рядом недостатков, среди которых следует отметить трудоемкость отправки/получения структурированных данных с помощью HTML-кода. В частности, поиск необходимой информации на странице сопряжен с выполнением синтаксического анализа кода HTML-страницы и зачастую трудоемок из-за относительной сложности HTML-кода.

В качестве альтернативного «способа общения» многие сервисы предоставляют специальный интерфейс (API), позволяющий обмениваться данными по HTTP-протоколу в отличном от HTML формате, который простым образом можно было бы использовать в собственных приложениях.

В целом API (англ. Application Programming Interface, интерфейс программирования приложений) представляет собой набор готовых классов, процедур, функций, структур и констант, предоставляемых приложением (библиотекой, сервисом) для использования во внешних программных продуктах. API существуют как на уровне операционных систем (например, Windows API для ОС Windows), так и на уровне отдельных библиотек/приложений (например, Qt) и определяют интерфейс взаимодействия, предоставляя соответствующую документацию.

Веб-сервисы, предоставляющие API, чаще всего используют в качестве форматов обмена XML или JSON. Сегодня все наиболее популярные сервисы имеют API, что позволяет взаимодействовать им с другими сервисами посредством общедоступного интерфейса.

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

Использование API (на примере ВКонтакте)

Социальная сеть ВКонтакте (ВК) предоставляет API, позволяющее выполнять функции сервиса, как с использованием токена, так и без него (ограниченный набор функций). Для использования API-функций достаточно воспользоваться любым HTTP-клиентом, например, браузером.

Вызов API-функции происходит путем отправки GET-запроса на адрес https://api.vk.com/method/ с указанием нужной функции. Каждый метод требует определенных разрешений, поэтому перед использованием какой-либо функции необходимо изучить ее описание. На запрос приходит ответ в формате JSON, содержащий запрошенные данные или сообщение об ошибке.

Подробнее про осуществление запросов см. https://vk.com/dev/api_requests, где также есть указание на ограничения и рекомендации использования API.

1. Без использования токена

Для функций API, поддерживающих вызов без токена, достаточно вызвать их с необходимыми параметрами (Рисунок 13.1.17).

Запрос: https://api.vk.com/method/users.get?user_ids=1

_images/13_01_17.png

Рисунок 13.1.17 - Выполнение API-функции ВКонтакте users.get() с параметром user_ids=1 без авторизации

2. С использованием токена

Для получения токена необходимо:

  • иметь аккаунт ВКонтакте;
  • создать Standalone-клиент (автономное приложение) (один из возможных вариантов);
  • получить токен для созданного клиента.
  1. Регистрация приложения

    Для регистрации приложения необходимо:

    • перейти по ссылке https://vk.com/apps?act=manage и нажать кнопку Создать приложение;

    • указать название приложения, подтвердить действия и дождаться перехода в панель управления приложением (Рисунок 13.1.18).

      _images/13_01_18.png

      Рисунок 13.1.18 - Создание Standalone-приложения ВКонтакте

    • запомнить ID приложения (страница Настройки в панели управления приложением).

  2. Получение токена

    Совет

    Подробное описание получения токена описано в официальной документации: https://vk.com/dev/access_token.

    Ниже рассматривается один из возможных токенов - Implicit flow.

    Для получения токена необходимо выполнить запрос на адрес https://oauth.vk.com/authorize, указав необходимые параметры:

    https://oauth.vk.com/authorize?&client_id=ID_ПРИЛОЖЕНИЯ&display=page&redirect_uri=https://oauth.vk.com/blank.html&scope=ПРАВА_ДОСТУПА&response_type=token&v=5.62

    где:

    Параметр Описание
    client_id ID приложения
    scope Права доступа приложения: значения из списка через запятую
    v Версия API

    Подставив вместо ID_ПРИЛОЖЕНИЯ ID своего приложения, а вместо ПРАВА_ДОСТУПА набор разрешений (например, wall), необходимо перейти по полученному URL в браузере и подтвердить запрошенные права, после чего в адресной строке появится access_token (на месте TOKEN):

    https://oauth.vk.com/blank.html#access_token=TOKEN&expires_in=86400&user_id=...

    «Время жизни» токена в секундах указано в параметре expires_in; после этого времени необходимо будет получить токен заново.

    Предупреждение

    Обратите внимание на предупреждение, которое выдает ВКонтакте. Полученный токен должен храниться в секрете.

  3. Выполнение запроса

    При выполнении вызова API-функции с авторизацией каждый раз необходимо передавать токен в качестве параметра запроса access_token. На Рисунке 13.1.19 приведено сравнение вызова функции без и с авторизацией.

    Запросы:

    _images/13_01_19.png

    Рисунок 13.1.19 - Получение последней записи на стене пользователя/сообщества с помощью API-функции ВКонтакте wall.get() без и с авторизацией

13.1.3. Электронная почта

Электронная почта (англ. electronic mail, email, e-mail) — технология и предоставляемые ею услуги по пересылке и получению электронных сообщений («писем» или «электронных писем») по компьютерной сети.

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

Достоинствами электронной почты являются:

  • легко воспринимаемые и запоминаемые человеком адреса вида имя_пользователя@имя_домена (например, somebody@example.com);
  • возможность передачи как простого текста, так и форматированного, а также произвольных файлов;
  • независимость серверов (в общем случае они обращаются друг к другу непосредственно);
  • достаточно высокая надежность доставки сообщения;
  • простота использования человеком и программами.

Недостатками электронной почты являются:

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

13.1.3.1. История

В 1965 г. сотрудники Массачусетского технологического института (MIT) Ноэль Моррис и Том ван Влек (Рисунок 13.1.20) написали программу Mail для операционной системы CTSS (англ. Compatible Time-Sharing System), установленную на компьютере IBM 7090/7094.

Пользователи системы могли, используя программу Mail, пересылать друг другу сообщения в пределах одного мейнфрейма. После появления распределенной глобальной системы имен DNS, для указания адреса стали использоваться доменные имена:

user@example.com — "пользователь user на машине example.com"

Одновременно с этим происходило переосмысление понятия «на машине»: для почты стали использоваться выделенные серверы, на которые имели доступ только администраторы, а пользователи работали на своих машинах, при этом почта приходила не на рабочие машины пользователей, а на почтовый сервер, откуда пользователи забирали свою почту по различным сетевым протоколам.

Позже, в 1971 г. Рэй Томлинсон (Рисунок 13.1.20), сотрудник компании Bolt Beranek and Newman, Inc. написал первую почтовую программу для пересылки сообщений по распределенной сети, и первое письмо. 2 октября 1971 г. принято считать официальным днем рождения электронной почты.

_images/13_01_20.png

Рисунок 13.1.20 - Том ван Влек, Ноэль Моррис (фото 1968 г.) и Рей Томлинсон [12]

Общедоступные почтовые сервисы появились сравнительно недавно (Таблица 13.1.2).

Таблица 13.1.2 - Даты появления некоторых общедоступных почтовых сервисов
Дата Почтовый сервис / Компания
1 1996 год, 4 июля Hotmail
2 1997 год, 8 марта RocketMail (Yahoo! Mail)
3 1998 год, 15 октября Mail.Ru
4 2000 год, 26 июня Яндекс.Почта
5 2004 год, 1 апреля Gmail

13.1.3.2. Архитектура

В системе электронной почты выделяется ряд компонентов (Рисунок 13.1.21).

_images/13_01_21.png

Рисунок 13.1.21 - Принципиальная схема электронной почты [13]

  • MTA (англ. Mail Transfer Agent — агент пересылки почты) — отвечает за пересылку почты между почтовыми серверами; как правило, первый MTA в цепочке получает сообщение от MUA, последний передает сообщение к MDA;
  • MDA (англ. Mail Delivery Agent - агент доставки почты) — отвечает за доставку почты конечному пользователю;
  • MUA (англ. Mail User Agent — почтовый агент пользователя; в русской нотации закрепился термин почтовый клиент) — программа, обеспечивающая пользовательский интерфейс, отображающая полученные письма и предоставляющая возможность отвечать, создавать, перенаправлять письма;
  • MRA (англ. Mail Retrieve Agent) — почтовый сервер, забирающий почту с другого сервера по протоколам, предназначенным для MDA.

Простейшая схема отправки почты выглядит следующим образом (Рисунок 13.1.22).

_images/13_01_22.gif

Рисунок 13.1.22 - Простейшая схема отправки почты [14]

13.1.3.3. Протоколы отправки/получения почты (SMTP, POP3 и IMAP)

Отправка и получение почты осуществляется через специальные протоколы: SMTP, POP3 и IMAP - протоколы стека TCP/IP, предназначенные для работы с электронной почтой. Каждый протокол представляет собой набор правил для взаимодействия между почтовыми клиентом и сервером (Таблица 13.1.3).

Таблица 13.1.3 - Протоколы работы с электронной почтой
Протокол Описание Порт Стандарт
SMTP (англ. Simple Mail Transfer Protocol - простой протокол передачи почты) Используется для отправки почты с клиента (например, Microsoft Outlook, Mozilla Thunderbird или веб-интерфейс) на почтовый сервер
  • 25
  • 465/587 (c шифрованием)
RFC 5321
POP3 (англ. Post Office Protocol Version 3 — протокол почтового отделения, версия 3) Позволяет почтовому клиенту загружать сообщения с почтового сервера. Протокол довольно прост и в целом не содержит других функций кроме загрузки; спроектирован так, что после подключения к серверу, сразу загружаются все сообщения, после чего они удаляются с сервера и соединение завершается
  • 110
RFC 1939
IMAP (англ. Internet Message Access Protocol - протокол прикладного уровня для доступа к электронной почте) Реализует ряд возможностей протокола POP3 и является протоколом, используя который клиент может загружать почтовые сообщения с сервера. Более функционален: загружаемые письма могут сохранятся на севере, можно читать письма из разных папок (например, Отправленные или Спам) и др. В связи с этим, IMAP требует большего пространства на жестком диске, больше нагружает процессор, чем POP3
  • 143
  • 993 (c шифрованием)
RFC 3501

Примечание

Используя приведенные протоколы, типичный цикл работы отправки/приема между двумя пользователями (допустим, это Анна и Кирилл) письма выглядит следующим образом:

  • в почтовом клиенте Анна набирает письмо и нажимает кнопку Отправить;
  • почтовый клиент доставляет письмо на почтовый сервер провайдера Анны (SMTP);
  • почтовый сервер Анны отправляет письмо на почтовый сервер Кирилла (SMTP);
  • Кирилл нажимает в почтовом клиенте кнопку Получить письма, и клиент загружает их с его сервера (POP3 или IMAP).

13.1.3.4. Формат электронного письма

При передаче по протоколу SMTP электронное письмо состоит из следующих частей:

  1. Данные SMTP-конверта, полученные сервером (служебная информация для сервера).
  2. Сообщение (DATA): состоит из двух частей, разделенных пустой строкой:
    • заголовки письма (англ. Headers): служебная информация и пометки почтовых серверов, через которые прошло письмо, пометки о приоритете, указание на адрес и имя отправителя и получателя письма, тема письма и другая информация;
    • тела (англ. Body) письма: непосредственно сообщение.

Данные SMTP-конверта

Данные SMTP-конверта содержат в себе следующую информацию (Таблица 13.1.4).

Таблица 13.1.4 - Данные SMTP-конверта
Параметр Описание
HELO/EHLO Имя отправляющего узла (имя сервера или компьютера пользователя, который обратился к серверу)
MAIL FROM Адрес отправителя. Значение используется для формирования уведомлений об ошибках доставки сообщений, а, не значение из поля From заголовка сообщения)
RCPT TO Адрес одного или нескольких получателей

Сообщение - Заголовки письма

Заголовки письма формируются и интерпретируются пользовательскими агентами и описываются стандартами RFC:

  • RFC 2076 — Common Internet Message Headers (общепринятые стандарты заголовков сообщений);
  • RFC 4021 — Registration of Mail and MIME Header Fields (регистрация почты и поля заголовков MIME) — стандарт, описывающий передачу различных типов данных по электронной почте.

Каждый заголовок состоит из ключевого слова и значения, разделенных двоеточием (аналогично HTTP). В заголовках обычно указывается следующая информация:

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

В Таблице 13.1.5 приведены наиболее часто используемые заголовки.

Таблица 13.1.5 - Наиболее часто используемые заголовки электронного сообщения
Заголовок Описание
From Имя и адрес отправителя (именно в этом заголовке появляется текстовое поле с именем отправителя)
Sender Отправитель письма. Добавлено для возможности указать, что письмо от чьего-то имени отправлено другой персоной (например, секретарем от имени начальника)
To Имя и адрес получателя; может содержаться несколько раз (если письмо адресовано нескольким получателям)
Сc (англ. Carbon Copy - точная копия) Содержит имена и адреса вторичных получателей письма, к которым направляется копия
Bcc (англ. Blind Carbon Copy) Содержит имена и адреса получателей письма, чьи адреса не следует показывать другим получателям
Subject Тема письма
Date Дата отправки письма
Content-Type Тип содержимого письма (HTML, RTF, Plain text) и кодировка, в которой создано письмо
Reply-To Имя и адрес, куда следует адресовать ответы на это письмо (например, письмо рассылается роботом, то в качестве Reply-To будет указан адрес почтового ящика, готового принять ответ на письмо)
Return-Path Адрес возврата в случае неудачи, когда невозможно доставить письмо по адресу назначения
Received Данные о прохождении письма через каждый конкретный почтовый сервер (MTA): при прохождении через несколько почтовых серверов, новые заголовки дописываются над предыдущими, в конечном итоге, журнал перемещения будет записан в обратном порядке (от ближайшего к получателю узла к самому дальнему)
MIME-Version Версия MIME, с которым это сообщение создано

Кроме стандартных, почтовые клиенты, серверы и роботы обработки почты могут добавлять свои собственные заголовки, начинающиеся с X- (например, X-Mailer или X-Spamassasin-Level).

Сообщение - Тело письма

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

Пример электронного письма приведен в Листинге 13.1.1.

Листинг 13.1.1 - Пример электронного письма
Delivered-To: yuripetrov***@***.com
Received: by 10.64.149.4 with SMTP id tw4csp2092905ieb;
        Tue, 26 Jan 2016 10:24:42 -0800 (PST)
X-Received: by 10.25.31.136 with SMTP id f130mr9419641lff.5.1453832682516;
        Tue, 26 Jan 2016 10:24:42 -0800 (PST)
Return-Path: <***.anna@***.ru>
Received: from f358.i.mail.ru (f358.i.mail.ru. [217.69.140.254])
        by mx.google.com with ESMTPS id bc8si1247482lbc.14.2016.01.26.10.24.42
        for <yuripetrov***@***.com>
        (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
        Tue, 26 Jan 2016 10:24:42 -0800 (PST)
Received-SPF: pass (google.com: domain of ***.anna@***.ru designates 217.69.140.254 as permitted sender) client-ip=217.69.140.254;
Authentication-Results: mx.google.com;
       spf=pass (google.com: domain of ***.anna@***.ru designates 217.69.140.254 as permitted sender) smtp.mailfrom=***.anna@***.ru;
       dkim=pass header.i=@mail.ru;
       dmarc=pass (p=NONE dis=NONE) header.from=mail.ru
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mail.ru; s=mail2;
	h=Content-Type:Message-ID:Reply-To:Date:MIME-Version:Subject:To:From; bh=DovbZRO+OVOL8H2kiRKyq+NkZllClu2lxjciEbllcgk=;
	b=goms27ZY78021KlPe5Me9QcGQ3fK8jNFfMfr5rTVTTqLneXHYvO9gxSAlbIK/UFjQNEmDhqA3KYKyenzu2s6PEgn/FxC/DNSp1xhZ2U9ld+OmjXdatVzyaNYB0ppGsV24fGSMH+0hWiFNNKt3pWwuS07P3wN2Y+J/3nBANShUVM=;
Received: from [79.164.245.49] (ident=mail)
	by f358.i.mail.ru with local (envelope-from <***.anna@***.ru>)
	id 1aO8ID-00021A-Ra
	for yuripetrov***@***.com; Tue, 26 Jan 2016 21:24:42 +0300
Received: from [79.164.245.49] by e.mail.ru with HTTP;
	Tue, 26 Jan 2016 21:24:41 +0300
From: =?UTF-8?B?0JDQvdC90LAg0KHRg9GF0L7RgNGD0LrQvtCy0LA=?= <***.anna@***.ru>
To: =?UTF-8?B?eXVyaXBldHJvdmVkdUBnbWFpbC5jb20=?= <yuripetrov***@***.com>
Subject: =?UTF-8?B?0J/QtdGA0LXRgdC00LDRh9Cw?=
MIME-Version: 1.0
X-Mailer: Mail.Ru Mailer 1.0
X-Originating-IP: [79.164.245.49]
Date: Tue, 26 Jan 2016 21:24:41 +0300
Reply-To: =?UTF-8?B?0JDQvdC90LAg0KHRg9GF0L7RgNGD0LrQvtCy0LA=?= <***.anna@***.ru>
X-Priority: 3 (Normal)
Message-ID: <1453832681.353140980@f358.i.mail.ru>
Content-Type: multipart/alternative;
	boundary="--ALT--7f2e32271453832681"
X-Mras: Ok
X-Spam: undefined


----ALT--7f2e32271453832681
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: base64

CtCX0LTRgNCw0LLRgdGC0LLRg9C50YLQtSEK0JTQsNGC0LAsINCy0YDQtdC80Y8g0Lgg0LDRg9C0
0LjRgtC+0YDQuNGPINC00LvRjyDQv9C10YDQtdGB0LTQsNGH0Lgg0YPQttC1INC90LDQt9C90LDR
h9C10L3Riz8g0JXRgdC70Lgg0LTQsCwg0YLQviDRgdC+0L7QsdGJ0LjRgtC1INC80L3QtSwg0L/Q
vtC20LDQu9GD0LnRgdGC0LAuINCh0L/QsNGB0LjQsdC+LgrQoSDRg9Cy0LDQttC10L3QuNC10Lws
CtCQ0L3QvdCw

----ALT--7f2e32271453832681
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: base64

CjxIVE1MPjxCT0RZPjxwIHN0eWxlPSdtYXJnaW4tdG9wOiAwcHg7JyBkaXI9Imx0ciI+JiMxMDQ3
OyYjMTA3NjsmIzEwODg7JiMxMDcyOyYjMTA3NDsmIzEwODk7JiMxMDkwOyYjMTA3NDsmIzEwOTE7
JiMxMDgxOyYjMTA5MDsmIzEwNzc7ITwvcD4KPHAgZGlyPSJsdHIiPiYjMTA0NDsmIzEwNzI7JiMx
MDkwOyYjMTA3MjssICYjMTA3NDsmIzEwODg7JiMxMDc3OyYjMTA4NDsmIzExMDM7ICYjMTA4MDsg
JiMxMDcyOyYjMTA5MTsmIzEwNzY7JiMxMDgwOyYjMTA5MDsmIzEwODY7JiMxMDg4OyYjMTA4MDsm
IzExMDM7ICYjMTA3NjsmIzEwODM7JiMxMTAzOyAmIzEwODc7JiMxMDc3OyYjMTA4ODsmIzEwNzc7
JiMxMDg5OyYjMTA3NjsmIzEwNzI7JiMxMDk1OyYjMTA4MDsgJiMxMDkxOyYjMTA3ODsmIzEwNzc7
ICYjMTA4NTsmIzEwNzI7JiMxMDc5OyYjMTA4NTsmIzEwNzI7JiMxMDk1OyYjMTA3NzsmIzEwODU7
JiMxMDk5Oz8gJiMxMDQ1OyYjMTA4OTsmIzEwODM7JiMxMDgwOyAmIzEwNzY7JiMxMDcyOywgJiMx
MDkwOyYjMTA4NjsgJiMxMDg5OyYjMTA4NjsmIzEwODY7JiMxMDczOyYjMTA5NzsmIzEwODA7JiMx
MDkwOyYjMTA3NzsgJiMxMDg0OyYjMTA4NTsmIzEwNzc7LCAmIzEwODc7JiMxMDg2OyYjMTA3ODsm
IzEwNzI7JiMxMDgzOyYjMTA5MTsmIzEwODE7JiMxMDg5OyYjMTA5MDsmIzEwNzI7LiAmIzEwNTc7
JiMxMDg3OyYjMTA3MjsmIzEwODk7JiMxMDgwOyYjMTA3MzsmIzEwODY7LjwvcD4KPHAgZGlyPSJs
dHIiPiYjMTA1NzsgJiMxMDkxOyYjMTA3NDsmIzEwNzI7JiMxMDc4OyYjMTA3NzsmIzEwODU7JiMx
MDgwOyYjMTA3NzsmIzEwODQ7LDxicj4KJiMxMDQwOyYjMTA4NTsmIzEwODU7JiMxMDcyOzwvcD4K
PC9CT0RZPjwvSFRNTD4K

----ALT--7f2e32271453832681--

Совет

Веб-клиент, как правило, отображает только тело письма в декодированном виде.

Посмотреть электронное письмо в «сыром» виде, например, в GMail, можно, открыв письмо, и выбрав в выпадающем меню справа от даты получения письма пункт Показать оригинал.

13.1.4. Поддержка стека TCP/IP в Python

Python поддерживает работу со стеком TCP/IP от сокетов до конкретных прикладных протоколов, используя соответствующие модули и пакеты.

13.1.4.1. Сокеты

Работа с сокетами реализуется в Python низкоуровневым модулем socket, поддерживающим как адреса IPv4, так и адреса IPv6.

Одной из основных функций модуля является функция socket(), возвращающая объект (сокет), обладающий соответствующими функциями для работы с соединением.

class socket.socket
socket.bind(address)

Привязывает сокет к адресу address (инициализирует IP-адрес и порт). Сокет не должен быть привязан до этого.

socket.listen([backlog])

Переводит сервер в режим приема соединений.

Параметры:backlog (int) – количество соединений, которые будет обслуживать сервер.
socket.accept()

Принимает соединение и блокирует приложение в ожидании сообщение от клиента.

Результат:кортеж:
  • conn: объект соединения (сокет), который можно использовать для отправки/получения данных;
  • address: адрес клиента.
socket.recv(bufsize[, flags])

Читает и возвращает данные в двоичном формате (набор байтов) из сокета.

Параметры:bufsize (int) – максимальное количество байтов в одном сообщении.
socket.send(bytes[, flags])

Отправляет данные клиенту и возвращает количество отправленных байт.

Параметры:bytes (bytes) – двоичные данные.
socket.close()

Закрывает сокет.

Работа с сокетом во многом схожа с работой с файловым объектом.

Пример работы с сокетами приведен в Листинг 13.1.2 (а) (сервер) и (б) (клиент).

Листинг 13.1.2 (а) - Пример работы с сокетами - простой чат в локальной сети (сервер) | скачать
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# Серверная часть

# recv() и send() работают с типом bytes, который требует
# преобразования из/в строку при помощи encode()/decode() соответственно

import socket


def get_ip_address():
    """Вернуть IP-адрес компьютера.

    Фиктивное UDP-подключение к google's DNS,
    после подключения getsockname() вернет локальный IP.
    """
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("8.8.8.8", 80))
    return s.getsockname()[0]


if __name__ == "__main__":

    sock = socket.socket()

    # Слушаем подключение 1 клиента
    print("IP: {}. Жду подключения...".format(get_ip_address()))
    sock.bind(("", 9090))
    sock.listen(1)  # Макс. кол-во соединений - 1

    conn, addr = sock.accept()
    # 'addr' в конечном итоге содержит отличный от указанного в bind()
    # порт за счет того, что ОС назначает его самостоятельно
    try:
        print("Соединение установлено:", addr)

        # Отправляем/получаем данные пока клиент не напишет "Выход"
        while True:
            # 'client_mes' перестает приходить при закрытии соединения
            # со стороны клиента
            client_mes = conn.recv(1024).decode()
            if not client_mes:
                break

            print("Клиент:", client_mes)
            if client_mes == "Выход":
                break

            conn.send(input("Сервер: ").encode())
        print("Соединение закрыто.")
    except Exception as err:
        print("Ошибка: ", err)
    finally:
        conn.close()
Листинг 13.1.2 (б) - Пример работы с сокетами - простой чат в локальной сети (клиент) | скачать
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# Клиентская часть

# recv() и send() работают с типом bytes, который требует
# преобразования из/в строку при помощи encode()/decode() соответственно

import socket


def get_ip_address():
    """Вернуть IP-адрес компьютера.

    Фиктивное UDP-подключение к google's DNS,
    после подключения getsockname() вернет локальный IP.
    """
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("8.8.8.8", 80))
    return s.getsockname()[0]


if __name__ == "__main__":

    sock = socket.socket()
    try:
        print("IP: {}.".format(get_ip_address()))
        # Подключаемся к серверу
        server_ip = input("Введите IP-адрес сервера (например, 192.168.0.3): ")
        print("Подключаюсь к серверу...")
        sock.connect((server_ip, 9090))
        print("Соединение установлено!")

        # Отправляем/получаем данные пока не напишем "Выход"
        while True:
            client_mes = input("Клиент: ")
            sock.send(client_mes.encode())
            if client_mes == "Выход":
                break

            server_mes = sock.recv(1024).decode()
            print("Сервер:", server_mes)
    except Exception as err:
        print("Ошибка: ", err)
    finally:
        sock.close()

Иллюстрация работы приложений приведена на Рисунке 13.1.23.

_images/13_01_23.png

Рисунок 13.1.23 - Простой чат в локальной сети с использованием сокетов

13.1.4.2. HTTP/HTTPS

Для работы с HTTP/HTTPS одной из наиболее функциональных является библиотека requests, предоставляющая удобный объектно-ориентированный интерфейс для HTTP-взаимодействия.

Для установки библиотеки необходимо выполнить:

pip3 install requests

Функции

Все функции возвращают объект requests.Response. инкапсулирующий информацию об HTTP-ответе сервера.

requests.request(method, url, **kwargs)

Выполняет HTTP-запрос.

Некоторые параметры:

Параметры:
  • method (str) – HTTP-метод, например, "GET";
  • url (str) – URI запроса.

В качестве kwargs могут быть переданы:

Параметры:
  • headers (dict) – заголовки запроса;
  • params – параметры запроса;
  • files (dict or None) – словарь вида {наименование: файловый объект}.
Результат:

объект Response

Тип результата:

requests.Response

requests.get(url, params=None, **kwargs)

Отправляет GET-запрос.

Параметры:
  • url (str) – URI запроса;
  • params (dict or bytes) – параметры запроса.
  • kwargs – дополнительные аргументы (аналогичны requests.request().
Результат:

объект Response

Тип результата:

requests.Response

requests.post(url, data=None, json=None, **kwargs)

Отправляет POST-запрос.

Параметры:
  • url (str) – URI запроса;
  • params (dict or bytes) – параметры запроса.
  • kwargs – дополнительные аргументы (аналогичны requests.request().
Результат:

объект Response

Тип результата:

requests.Response

Класс requests.Response

class requests.Response
status_code

HTTP-статус ответа сервера (целое число).

Пример: 200 или 400.

reason

Сообщение в HTTP-ответе.

Пример: "OK" или "Not Found".

headers

Словарь заголовков ответа.

encoding

Кодировка для атрибута text.

text

Содержимое ответа сервера (Юникод).

content

Содержимое ответа в байтах.

url

Итоговый адрес запрошенного ресурса.

json(**kwargs)

Содержимое ответа сервера в виде json (если есть).

Параметр kwargs аналогичен параметру, используемому в функции json.loads().

Исключения

При работе с библиотекой могут возникнуть следующие исключения.

exception requests.ConnectionError

Проблема сетевого подключения.

exception requests.HTTPError

Ошибка при выполнении HTTP-запроса.

Пример работы с библиотекой requests приведен в Листинге 13.1.3.

Листинг 13.1.3 - Пример использования библиотеки requests | скачать
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import requests

if __name__ == "__main__":

    print("Сервис httpbin.org позволяет тестировать запросы.\n"
          "Наличие переданных параметров в ответе означает, "
          "что сервер их принял и обработал.")

    # ---------------------------------------------------------------
    print("\n1. Простой GET-запрос и отображение результата")
    r = requests.get("https://httpbin.org/")
    # или используя метод r = requests.request("GET", "httpbin.org/")

    print("URI:", r.url)
    print("\nСтатус и сообщение:", r.status_code, r.reason)
    print("\nЗаголовки:")
    for key in sorted(r.headers):
        print("  - {}: {}".format(key, r.headers[key]))
    print("\nКодировка:", r.encoding)
    print("\nПервые 500 символов тела сообщения: ")
    print(r.text[:500])

    # ---------------------------------------------------------------
    print("\n2. Передача параметров")
    my_params = {"key1": "value1", "key2": "value2"}
    r = requests.get("http://httpbin.org/get", params=my_params)
    print("URI с параметрами:", r.url)
    print(r.text)
    print("Ответ как json:", r.json())

    # ---------------------------------------------------------------
    print("\n3. Закачка файлов")
    r = requests.get("https://httpbin.org/image/jpeg")
    if r.status_code == requests.codes.ok:  # 200
        with open("1.jpeg", "wb") as fh:
            fh.write(r.content)
        print("Файл сохранен!")

    # ---------------------------------------------------------------
    print("\n4. Произвольные заголовки запроса")
    headers = {"user-agent": "my-app/0.0.1"}
    r = requests.get("https://httpbin.org/get", headers=headers)
    print(r.text)

    # ---------------------------------------------------------------
    print("\n5. Простой POST-запрос")
    data = {"data1": "value1", "data2": "value2", "data3": "value3"}
    r = requests.post("http://httpbin.org/post", data=data)
    print(r.text)

    # ---------------------------------------------------------------
    print("\n6. POST-запрос отправка файла")
    files = {"file": open("1.jpeg", "rb")}
    r = requests.post("http://httpbin.org/post", files=files)
    print(r.text)

# -------------
# Пример вывода:
#
# Сервис httpbin.org позволяет тестировать запросы.
# Наличие переданных параметров в ответе означает, что сервер их принял и обработал.
#
# 1. Простой GET-запрос и отображение результата
# URI: https://httpbin.org/
#
# Статус и сообщение: 200 OK
#
# Заголовки:
#   - Access-Control-Allow-Credentials: true
#   - Access-Control-Allow-Origin: *
#   - Connection: keep-alive
#   - Content-Length: 12150
#   - Content-Type: text/html; charset=utf-8
#   - Date: Mon, 13 Mar 2017 13:01:11 GMT
#   - Server: nginx
#
# Кодировка: utf-8
#
# Первые 500 символов тела сообщения:
# <!DOCTYPE html>
# <html>
# <head>
#   <meta http-equiv='content-type' value='text/html;charset=utf8'>
#   <meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
#   <title>httpbin(1): HTTP Client Testing Service</title>
#   <style type='text/css' media='all'>
#   /* style: man */
#   body#manpage {margin:0}
#   .mp {max-width:100ex;padding:0 9ex 1ex 4ex}
#   .mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
#   .mp h2 {margin:10px 0 0 0}
#   .mp > p,.mp > pre,.mp > ul,.mp > ol,.mp
#
# 2. Передача параметров
# URI с параметрами: http://httpbin.org/get?key2=value2&key1=value1
# {
#   "args": {
#     "key1": "value1",
#     "key2": "value2"
#   },
#   "headers": {
#     "Accept": "*/*",
#     "Accept-Encoding": "gzip, deflate",
#     "Host": "httpbin.org",
#     "User-Agent": "python-requests/2.13.0"
#   },
#   "origin": "109.188.89.206",
#   "url": "http://httpbin.org/get?key2=value2&key1=value1"
# }
#
# Ответ как json: {'url': 'http://httpbin.org/get?key2=value2&key1=value1',
#                  'headers': {'Host': 'httpbin.org', 'Accept': '*/*',
#                  'User-Agent': 'python-requests/2.13.0', 'Accept-Encoding': 'gzip, deflate'},
#                  'args': {'key2': 'value2', 'key1': 'value1'}, 'origin': '109.188.89.206'}
#
# 3. Закачка файлов
# Файл сохранен!
#
# 4. Произвольные заголовки запроса
# {
#   "args": {},
#   "headers": {
#     "Accept": "*/*",
#     "Accept-Encoding": "gzip, deflate",
#     "Host": "httpbin.org",
#     "User-Agent": "my-app/0.0.1"
#   },
#   "origin": "109.188.89.206",
#   "url": "https://httpbin.org/get"
# }
#
#
# 5. Простой POST-запрос
# {
#   "args": {},
#   "data": "",
#   "files": {},
#   "form": {
#     "data1": "value1",
#     "data2": "value2",
#     "data3": "value3"
#   },
#   "headers": {
#     "Accept": "*/*",
#     "Accept-Encoding": "gzip, deflate",
#     "Content-Length": "38",
#     "Content-Type": "application/x-www-form-urlencoded",
#     "Host": "httpbin.org",
#     "User-Agent": "python-requests/2.13.0"
#   },
#   "json": null,
#   "origin": "109.188.89.206",
#   "url": "http://httpbin.org/post"
# }
#
#
# 6. POST-запрос отправка файла
# {
#   "args": {},
#   "data": "",
#   "files": {
#     "file": "data:application/octet-stream;base64,/9j/4AAQSkZJRgABAQIAH
#              //gA1RWRpdGVkIGJ5IFBhdWwgU2hlcm1hbiBmb3IgV1BDbGlwYXJ0LCBQd
#              CAQEBAQECAQEBAgICAgIEAwICAgIFBAQDBAYFBgYGBQYGBgcJCAYHCQcGB
#              ..."
#   },
#   "form": {},
#   "headers": {
#     "Accept": "*/*",
#     "Accept-Encoding": "gzip, deflate",
#     "Content-Length": "35730",
#     "Content-Type": "multipart/form-data; boundary=3eb9008a18ae42fdb51f19d3e40e9733",
#     "Host": "httpbin.org",
#     "User-Agent": "python-requests/2.13.0"
#   },
#   "json": null,
#   "origin": "109.188.89.206",
#   "url": "http://httpbin.org/post"
# }

Предупреждение

Результат requests.request() зачастую отличается от HTML-кода страницы, который можно увидеть в браузере (браузер, как правило, передает свои заголовки, по-своему обрабатывает результат HTTP-запроса - выполняет скрипты и т.д.). В виду этого, ориентироваться (искать нужные фрагменты текста и т.д.) нужно по результату requests.request().

Совет

Для расширенного парсинга HTML-кода по результатам HTTP-запроса и/или эмуляции браузера рекомендуется ознакомиться с библиотеками Beautiful Soup и selenium.

13.1.4.3. SMTP/POP3/IMAP

Примечание

Во всех примерах в качестве почтового сервера используется Gmail. Рекомендуется завести тестовый GMail-аккаунт или самостоятельно скорректировать код согласно его настройкам SMTP/POP3/IMAP.

При первой работе с GMail с использованием Python следующие ссылки могут оказаться полезными:

Настройки для различных почтовых серверов могут быть найдены в каждом сервисе отдельно или, например, здесь: http://www.mailout.ru/560/.

К наиболее часто используемым модулям/пакетам относятся:

Модуль / Пакет Описание
email Объектно-ориентированная работа с электронными письмами
smtplib Поддержка протокола SMTP
poplib Поддержка протокола POP3
imaplib Поддержка протокола IMAP

13.1.4.3.1. SMTP

class smtplib.SMTP(host='', port=0, local_hostname=None, [timeout, ]source_address=None)

Создает SMTP-объект, который инкапсулирует работу с SMTP-протоколом.

Некоторые параметры:

Параметры:
  • host (str) – сервер, на котором работает SMTP-сервер (IP-адрес или доменное имя);
  • port (int) – порт SMTP-сервера (обычно 25).
sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])

Выполняет отправку почты.

Некоторые параметры:

Параметры:
  • from_addr (str) – адрес отправителя;
  • to_addrs (list) – адреса получателей;
  • msg – текст сообщения в специальном формате.
send_message(msg, from_addr=None, to_addrs=None, mail_options=[], rcpt_options=[])

Выполняет отправку почты.

Некоторые параметры:

Параметры:msg (email.message.Message) – сообщение.

Прочие параметры аналогичны функции smtplib.SMTP.sendmail().

exception smtplib.SMTPException

Базовый класс-исключение для ошибок внутри модуля.

Пример работы с протоколом SMTP, используя модуль smtplib и пакет email приведен в Листинге 13.1.4, результат - на Рисунке 13.1.24.

Листинг 13.1.4 - Пример работы с протоколом SMTP в Python | скачать
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import os.path
# "Скрывает" ввод в консоли (может не работать в IDE, например, PyCharm)
import getpass

import smtplib

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.application import MIMEApplication


if __name__ == "__main__":

    # 1. Данные сервера и письма
    # 'email_from'         : адрес отправителя
    # 'email_to'           : адрес получателя
    # 'email_from_password': пароль для email_from
    email_from = input("Адрес отправителя (Ваш): ")
    email_to = input("Адрес получателя: ")
    # Для ввода пароля используется функция getpass.getpass,
    # не отображающая ввод
    email_from_password = getpass.getpass("Пароль для {}: ".format(email_from))

    # Текст сообщения для отправки в HTML-формате.
    # Возможно использовать и простой текст.
    html = """\
    <html>
      <body>
        <h1>Привет!</h1>
        <p>Как дела?</p>
        <p>
          Вот ссылка на сайт Python:
          <a href="http://www.python.org">http://www.python.org</a>.
        </p>
        <p>
          А еще к письму прикреплены:.
          <ul>
            <li>рисунок;</li>
            <li>исходный код.</li>
          </ul>
        </p>
      </body>
    </html>
    """

    # 2. Создание контейнера с содержимым
    msg = MIMEMultipart()
    msg["Subject"] = "Ссылка на сайт Python"
    msg["From"] = email_from
    msg["To"] = email_to

    # 2.1. Текст
    # Добавление вложения в контейнер, используя метод attach контейнера.
    # Для MIMEText если вместо HTML используется текст,
    # вторым параметром будет "plain".
    msg.attach(MIMEText(html, "html"))

    # 2.2. Файл - Рисунок
    img_filename = input("Имя файла с рисунком: ")
    with open(img_filename, "rb") as image:
        attachment = MIMEImage(image.read())
    # Обозначаем, что это вложение и указываем имя
    attachment.add_header("Content-Disposition", "attachment",
                          filename=os.path.basename(img_filename))
    msg.attach(attachment)

    # 2.3. Файл - Код
    with open(__file__, "rb") as f:
        attachment = MIMEApplication(f.read())
    # Необходимо обозначить, что это вложение и его имя
    attachment.add_header("Content-Disposition", "attachment",
                          filename=os.path.basename(__file__))
    msg.attach(attachment)

    # 3. Подключение к серверу и отправка письма
    server = smtplib.SMTP("smtp.gmail.com", 587)
    try:
        # Установление защищенного соединения и авторизация
        server.starttls()
        server.login(email_from, email_from_password)
        # Отправка сообщения
        server.send_message(msg)
        print("Письмо успешно отправлено!")
    except smtplib.SMTPException as err:
        print("Ошибка при отправке письма:", err)
    finally:
        server.quit()

# -------------
# Пример вывода:
#
# Адрес отправителя (Ваш): YuriPetrov***@gmail.com
# Адрес получателя: alex***@gmail.com
# Пароль для YuriPetrov***@gmail.com:
# Имя файла с рисунком: chuvak.png
# Письмо успешно отправлено!
_images/13_01_24.png

Рисунок 13.1.24 - Отправленное письмо в почтовом ящике получателя

13.1.4.3.2. POP3

class poplib.POP3(host, port=POP3_PORT[, timeout])

Создает POP3-объект, при создании инициализируется соединение.

Параметры:
  • host (str) – сервер, на котором работает POP3-сервер (IP-адрес или доменное имя);
  • port (int) – порт POP3-сервера (по умолчанию 110);
  • timeout – время ожидания соединения.
class poplib.POP3_SSL(host, port=POP3_PORT[, timeout])

Создает POP3-объект, использует шифрование SSL.

Параметры:
  • host (str) – сервер, на котором работает POP3-сервер (IP-адрес или доменное имя);
  • port (int) – порт POP3-сервера (по умолчанию 995);
  • timeout – время ожидания соединения.
exception poplib.error_proto

Класс-исключение для любых ошибок модуля.

class poplib.POP3
getwelcome()

Возвращает приветствие сервера.

capa()

Возвращает словарь, содержащий возможности сервера (см. RFC 2449).

user(username)

Отправляет имя пользователя на сервер. Ответ должен содержать запрос пароля.

pass_(password)

Отправляет пароль на сервер. Ответ должен содержать количество сообщений и размер почтового ящика.

После входа, ящик остается заблокированным до вызова poplib.POP3.quit().

stat()

Возвращает статус почтового ящика в формате:

(кортеж: (количество сообщений, размер почтового ящика)).
list([which])

Запрашивает и возвращает список сообщений в формате:

(response, ['mesg_num octets', ...], octets).

Если параметр which установлен, возвращается конкретное сообщение.

retr(which)

Загружает сообщение под номером which в формате:

(response, ['line', ...], octets)

и отмечает его прочитанным.

dele(which)

Ставит сообщение под номером which в очередь на удаление. На большинстве серверов удаление происходит во время выхода (poplib.POP3.quit()).

noop()

Не делает ничего. Обычно периодически вызывается для поддержания соединения с сервером во избежании обрыва соединения.

quit()

Выход: применение изменений, разблокирование почтового ящика и закрытие соединения.

Пример работы с протоколом POP3 приведен в Листинге 13.1.5.

Листинг 13.1.5 - Пример работы с протоколом POP3 в Python | скачать
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import sys
import getpass

import poplib
import email
from email.header import decode_header


def do_decode_header(header):
    """Декодировать заголовок по ключу 'header'.

    Параметры:
      header (str): заголовок вида
                    "=?UTF-8?B?UmU6INCT0LvQtdCxINCf0L7Rh9GC0LA=?=".

    Базовые стандарты RFC 822 и RFC 2822 предусматривают, что
    любой заголовок представляет собой набор ASCII-символов, и до сих пор
    все символы обязательно преобразуются из/в этот формат.

    Python позволяет декодировать заголовки с использованием
    функции decode_header модуля email.header.
    """
    header_parts = decode_header(header)
    # 'decode_header' возвращает список кортежей вида
    # [(b'\xd0\x9f\xd0\xb0\xd0\xb2\xd0\xb5\xd0\xbb
    #     \xd0\x9f\xd0\xb0\xd0\xbd\xd1\x84\xd0\xb8\xd0\xbb
    #     \xd0\xbe\xd0\xb2', 'utf-8'),
    #  (b' <***@gmail.com>', None)]
    # Которые необходимо преобразовать в правильную кодировку

    res = []
    for decoded_string, encoding in header_parts:
        if encoding:
            decoded_string = decoded_string.decode(encoding)
        elif isinstance(decoded_string, bytes):
            decoded_string = decoded_string.decode("ascii")
        res.append(decoded_string)

    # На выходе 'res' содержит заголовок в "привычном", декодированном виде
    return "".join(res)


def get_part_info(part):
    """Получить текст сообщения в правильной кодировке.

    Параметры:
      - part: часть сообщения email.Message.

    Результат:
      - message (str): сообщение;
      - encoding (str): кодировка сообщения;
      - mime (str): MIME-тип.

    """
    encoding = part.get_content_charset()
    # Если кодировку определить не удалось, используется по умолчанию
    if not encoding:
        encoding = sys.stdout.encoding
    mime = part.get_content_type()
    message = part.get_payload(decode=True).decode(encoding, errors="ignore").strip()

    return message, encoding, mime


def get_message_info(message):
    """Получить текст сообщения в правильной кодировке.

    Параметры:
      - message: сообщение email.Message.

    Результат:
      - message (str): сообщение или строка "Нет тела сообщения";
      - encoding (str): кодировка сообщения или "-";
      - mime (str): MIME-тип или "-".

    """
    # Алгоритм получения текста письма:
    # - если письмо состоит из нескольких частей
    #   (message.is_multipart()) - необходимо пройти по составным
    #   частям письма: "text/plain" или "text/html"
    # - если нет - текст можно получить напрямую

    message_text, encoding, mime = "Нет тела сообщения", "-", "-"

    if message.is_multipart():
        for part in message.walk():
            if part.get_content_type() in ("text/html", "text/plain"):
                message_text, encoding, mime = get_part_info(part)
                break  # Только первое вхождение
    else:
        message_text, encoding, mime = get_part_info(message)

    return message_text, encoding, mime


if __name__ == "__main__":

    # 1. Данные почтового сервера
    server = "pop.gmail.com"
    port = 995
    login = input('Адрес почты: ')
    password = getpass.getpass("Пароль для {}: ".format(login))

    # Вся информация сохраняется в файл
    with open(__file__ + "_data.txt", "w", encoding="utf-8") as fh:
        try:
            # 2. Создание защищенного соединения и авторизация
            mailserver = poplib.POP3_SSL(server, port)
            mailserver.user(login)
            mailserver.pass_(password)

            # 2.1. Информация о сервере и почтовом ящике
            print(mailserver.capa(), file=fh)

            stat = mailserver.stat()
            print("\nВсего писем: {}, объем: {:.2f} Мб.".
                  format(stat[0], stat[1] / 2**20), file=fh)

            # 2.2. Полный список писем
            response, messages, octets = mailserver.list()
            print("\nСписок писем: ", response.decode(), file=fh)

            # 2.3. Чтение первых 3 сообщений
            #      Каждое письмо в почтовом ящике имеет
            #      номер от 1 (самое старое) до len(messages) (самое новое)
            for message_num in range(3):
                message_num = len(messages) - message_num

                # Получение письма под номером 'message_num'
                # 'response' содержит флаг "успешности" запроса
                # 'raw_message' список строк письма (bytes)
                response, raw_message, octets = mailserver.retr(message_num)
                if response.decode() != "+OK message follows":
                    print("Не удалось получить письмо №", message_num, file=fh)
                    continue

                # Преобразование сообщения в объект email.Message
                raw_message = b"\n".join(raw_message)
                message = email.message_from_bytes(raw_message)
                # Получение текста письма
                text, encoding, mime = get_message_info(message)

                # print(raw_message, file=fh) # Сообщение в "сыром" виде
                # Все заголовки сообщения
                # for key in message:
                #     print(key, message[key], file=fh)

                # Вывод информации о письме
                print("\n{} ---"
                      "\n  - от: '{}'"
                      "\n  - тема: '{}'"
                      "\n  - дата: '{}'"
                      "\n  - объем: {:.2f} Кб."
                      "\n  - содержимое (тип): MIME: {}, Кодировка: {}, multipart: {}"
                      "\n  - содержимое (первые 100 симв.):\n\"{}\"".
                      format(message_num,
                             do_decode_header(message["From"]),
                             do_decode_header(message["Subject"]),
                             message["Date"],
                             octets / 2**10,
                             mime, encoding, message.is_multipart(),
                             text[:100]),
                      file=fh)

            print("Информация о почтовом ящике и письма прочитаны и сохранены.")
        except poplib.error_proto as err:
            print("Возникла следующая ошибка:", err)
        finally:
            mailserver.quit()

# -------------
# Пример вывода (файл):
#
# {'EXPIRE': ['0'], 'USER': [], 'UIDL': [], 'PIPELINING': [],
#  'RESP-CODES': [], 'TOP': [], 'X-GOOGLE-RICO': [], 'LOGIN-DELAY': ['300']}
#
# Всего писем: 183, объем: 358.56 Мб.
#
# Список писем:  +OK 183 messages (375978425 bytes)
#
# №183 ---
#   - от: '- slava <ya.***@***.ru>'
#   - тема: 'Лабораторная 7.2'
#   - дата: 'Fri, 03 Mar 2017 18:19:02 +0300'
#   - объем: 12.11 Кб.
#   - содержимое (тип): MIME: text/html, Кодировка: utf-8, multipart: True
#   - содержимое (первые 100 симв.):
# "<div><span style="background-color:#ffffff;color:#000000;display:inline
# !important;float:none;font-f"
#
# №182 ---
#   - от: 'Наталия Пинчук <nata_***@***.ru>'
#   - тема: 'Программирование л/б'
#   - дата: 'Thu, 24 Nov 2016 22:49:12 +0300'
#   - объем: 5.62 Кб.
#   - содержимое (тип): MIME: text/plain, Кодировка: utf-8, multipart: True
#   - содержимое (первые 100 симв.):
# "Доброго времени суток
#
#  Хочу сдать 4.2 (те файлы, которые нужно было испр"

13.1.4.3.3. IMAP

class imaplib.IMAP4(host='', port=IMAP4_PORT)

Создает IMAP-объект, при создании инициализируется соединение.

Параметры:
  • host (str) – сервер, на котором работает IMAP-сервер (IP-адрес или доменное имя);
  • port (int) – порт IMAP-сервера (по умолчанию 143).
class imaplib.IMAP4_SSL(host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None)

Создает IMAP-объект, использует шифрование SSL.

Основные параметры аналогичны imaplib.IMAP4.

exception IMAP4.error

Класс-исключение для любых ошибок модуля.

class imaplib.IMAP4

Каждая команда возвращает кортеж вида

(type, [data, ...])

где

  • type - обычно, строка "OK" or "NO";
  • data - текст ответа команды или соответствующий результат запроса (строка или кортеж); если возвращается кортеж, первым элементом идет заголовок, а второй содержит данные.
login(user, password)

Осуществляет вход в систему (подключение к серверу).

list([directory[, pattern]])

Возвращает список почтовых ящиков (ярлыков) в папке directory, названия которых соответствуют шаблону pattern.

select(mailbox='INBOX', readonly=False)

Выбирает почтовый ящик и возвращает количество сообщений.

Параметры:
  • mailbox (str) – наименование почтового ящика (по умолчанию "INBOX" - «Входящие»);
  • readonly (bool) – включение режима «только для чтения» (True).
search(charset, criterion[, ...])

Выполняет поиск сообщений в почтовом ящике.

Основные параметры:

Параметры:
  • charset – кодировка сообщения; если None, кодировка запрашивается у сервера;
  • criterion – условие поиска (не может отсутствовать). Например, 'ALL' будет искать все сообщения, '(UNSEEN)' - непрочитанные, а '(FROM "Отправитель" SUBJECT "Тема сообщения")' - все сообщения от 'Отправитель' с темой 'Тема сообщения'.
fetch(message_set, message_parts)

Загружает и возвращает контейнер с сообщениями (кортеж).

Параметры:
  • message_set – номер(а) сообщений;
  • message_parts – часть сообщений, которая должна быть получена (например '(RFC822)' определяет получение тела сообщения).
store(message_set, command, flag_list)

Устанавливает флаги для указанных сообщений.

Параметры:
  • message_set – номер(а) сообщений;
  • command – команда ("FLAGS", "+FLAGS", или "-FLAGS");
  • flag_list – список флагов (например, '\Seen' (прочитано) или '\Deleted' (удалено)).
noop()

Отправляет команду "NOOP" на сервер, аналогично poplib.POP3.noop().

close()

Закрывает выбранный почтовый ящик, удаляя поставленные в очередь на удаление сообщения.

logout()

Закрывает подключение к серверу и возвращает ответ.

Пример работы с протоколом IMAP приведен в Листинге 13.1.6.

Листинг 13.1.6 - Пример работы с протоколом IMAP в Python | скачать
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import sys
import getpass

import re

import imaplib
import email
from email.header import decode_header


def do_decode_header(header):
    """Декодировать заголовок по ключу 'header'.

    Параметры:
      header (str): заголовок вида
                    "=?UTF-8?B?UmU6INCT0LvQtdCxINCf0L7Rh9GC0LA=?=".

    Базовые стандарты RFC 822 и RFC 2822 предусматривают, что
    любой заголовок представляет собой набор ASCII-символов, и до сих пор
    все символы обязательно преобразуются из/в этот формат.

    Python позволяет декодировать заголовки с использованием
    функции decode_header модуля email.header.
    """
    header_parts = decode_header(header)
    # 'decode_header' возвращает список кортежей вида
    # [(b'\xd0\x9f\xd0\xb0\xd0\xb2\xd0\xb5\xd0\xbb
    #     \xd0\x9f\xd0\xb0\xd0\xbd\xd1\x84\xd0\xb8\xd0\xbb
    #     \xd0\xbe\xd0\xb2', 'utf-8'),
    #  (b' <***@gmail.com>', None)]
    # Которые необходимо преобразовать в правильную кодировку

    res = []
    for decoded_string, encoding in header_parts:
        if encoding:
            decoded_string = decoded_string.decode(encoding)
        elif isinstance(decoded_string, bytes):
            decoded_string = decoded_string.decode("ascii")
        res.append(decoded_string)

    # На выходе 'res' содержит заголовок в "привычном", декодированном виде
    return "".join(res)


def get_part_info(part):
    """Получить текст сообщения в правильной кодировке.

    Параметры:
      - part: часть сообщения email.Message.

    Результат:
      - message (str): сообщение;
      - encoding (str): кодировка сообщения;
      - mime (str): MIME-тип.

    """
    encoding = part.get_content_charset()
    # Если кодировку определить не удалось, используется по умолчанию
    if not encoding:
        encoding = sys.stdout.encoding
    mime = part.get_content_type()
    message = part.get_payload(decode=True).decode(encoding, errors="ignore").strip()

    return message, encoding, mime


def get_message_info(message):
    """Получить текст сообщения в правильной кодировке.

    Параметры:
      - message: сообщение email.Message.

    Результат:
      - message (str): сообщение или строка "Нет тела сообщения";
      - encoding (str): кодировка сообщения или "-";
      - mime (str): MIME-тип или "-".

    """
    # Алгоритм получения текста письма:
    # - если письмо состоит из нескольких частей
    #   (message.is_multipart()) - необходимо пройти по составным
    #   частям письма: "text/plain" или "text/html"
    # - если нет - текст можно получить напрямую

    message_text, encoding, mime = "Нет тела сообщения", "-", "-"

    if message.is_multipart():
        for part in message.walk():
            if part.get_content_type() in ("text/html", "text/plain"):
                message_text, encoding, mime = get_part_info(part)
                break  # Только первое вхождение
    else:
        message_text, encoding, mime = get_part_info(message)

    return message_text, encoding, mime


if __name__ == "__main__":
    # 1. Подключение к серверу
    server = 'imap.gmail.com'
    port = '993'
    login = input('Адрес почты: ')
    password = getpass.getpass("Пароль для {}: ".format(login))

    # Вся информация сохраняется в файл
    with open(__file__ + "_data.txt", "w", encoding="utf-8") as fh:
        try:
            # 2. Создание защищенного соединения и авторизация
            mailserver = imaplib.IMAP4_SSL(server, port)
            mailserver.login(login, password)

            # 2.2. Полный список писем
            # Выбор папки ("Входящие" - по умолчанию)
            mailserver.select()
            # Список писем: кодировка не важна (None), все письма ("ALL")
            response, messages_nums = mailserver.search(None, "ALL")
            if response != "OK":
                raise imaplib.IMAP4.error("Не удалось получить список писем.")

            # Каждое письмо в почтовом ящике имеет
            # номер от 1 (самое старое) до len(messages) (самое новое)
            # 'message_nums' - список вида
            # [b'1 2 3 4 5 6 7 8 9 10 11 12']
            messages_nums = messages_nums[0].split()
            print("\nВсего писем: {}".format(len(messages_nums)), file=fh)

            # 2.3. Чтение первых 3 сообщений
            for message_num in reversed(messages_nums[-3:]):
                # message_parts = "(RFC822)" - получение письма целиком
                response, data = mailserver.fetch(message_num,
                                                  message_parts="(RFC822)")
                message_num = int(message_num.decode())
                if response != "OK":
                    print("Не удалось получить письмо №", message_num, file=fh)
                    continue

                # 'data' - список кортежей из строк (bytes):
                #      0 - номер и размер сообщения, b'1460 (RFC822 {80970}'
                #      1 - Сообщение (в "сыром" виде)
                # print(data, file=fh)

                message_size = int(re.findall(r"{(\d+)}", data[0][0].decode())[0])
                raw_message = data[0][1]

                # print(raw_message, file=fh) # Сообщение в "сыром" виде
                # Все заголовки сообщения
                # for key in message:
                #     print(key, message[key], file=fh)

                # Преобразование сообщения в объект email.Message
                message = email.message_from_bytes(raw_message)
                # Получение текста письма
                text, encoding, mime = get_message_info(message)

                # Вывод информации о письме
                print("\n{} ---"
                      "\n  - от: '{}'"
                      "\n  - тема: '{}'"
                      "\n  - дата: '{}'"
                      "\n  - объем: {:.2f} Кб."
                      "\n  - содержимое (тип): MIME: {}, Кодировка: {}, multipart: {}"
                      "\n  - содержимое (первые 100 симв.):\n\"{}\"".
                      format(message_num,
                             do_decode_header(message["From"]),
                             do_decode_header(message["Subject"]),
                             message["Date"],
                             message_size / 2**10,
                             mime, encoding, message.is_multipart(),
                             text[:100]),
                      file=fh)

            print("Информация о почтовом ящике и письма прочитаны и сохранены.")
        except imaplib.IMAP4.error as err:
            print("Возникла следующая ошибка:", err)
        finally:
            mailserver.logout()

# -------------
# Пример вывода (файл):
#
# Всего писем: 183
#
# №183 ---
#   - от: '- slava <ya.***@***.ru>'
#   - тема: 'Лабораторная 7.2'
#   - дата: 'Fri, 03 Mar 2017 18:19:02 +0300'
#   - объем: 12.11 Кб.
#   - содержимое (тип): MIME: text/html, Кодировка: utf-8, multipart: True
#   - содержимое (первые 100 симв.):
# "<div><span style="background-color:#ffffff;color:#000000;display:inline
# !important;float:none;font-f"
#
# №182 ---
#   - от: 'Наталия Пинчук <nata_***@***.ru>'
#   - тема: 'Программирование л/б'
#   - дата: 'Thu, 24 Nov 2016 22:49:12 +0300'
#   - объем: 5.62 Кб.
#   - содержимое (тип): MIME: text/plain, Кодировка: utf-8, multipart: True
#   - содержимое (первые 100 симв.):
# "Доброго времени суток
#
#  Хочу сдать 4.2 (те файлы, которые нужно было испр"

[1]Sebesta, W.S Concepts of Programming languages. 10E; ISBN 978-0133943023.
[2]Python - официальный сайт. URL: https://www.python.org/.
[3]Python - FAQ. URL: https://docs.python.org/3/faq/programming.html.
[4]Саммерфилд М. Программирование на Python 3. Подробное руководство. — М.: Символ-Плюс, 2009. — 608 с.: ISBN: 978-5-93286-161-5.
[5]Лучано Рамальо. Python. К вершинам мастерства. — М.: ДМК Пресс , 2016. — 768 с.: ISBN: 978-5-97060-384-0, 978-1-491-94600-8.
[6]ГОСТ Р ИСО/МЭК 7498-1-99. Взаимосвязь открытых систем. Базовая эталонная модель. Часть 1. Базовая модель. URL: http://protect.gost.ru/document.aspx?control=7&id=132355.
[7]Internet vs. everyone. URL: http://mikaprok.livejournal.com/239997.html.
[8](1, 2, 3, 4) HTTP (HyperText Transfer Protocol). URL: https://www.ntu.edu.sg/home/ehchua/programming/webprogramming/HTTP_Basics.html.
[9]DNS сервер BIND (теория). URL: https://habrahabr.ru/post/137587/.
[10]Сэр Тимоти Джон Бернерс-Ли. URL: http://www.nemiga.info/internet/berners-lee.htm.
[11]SIP URI и URL. Часть 1 (URI, URL и URN). URL: https://habrahabr.ru/post/190154/.
[12]Электронная почта: 40 лет на службе. URL: http://obzor.westsib.ru/article/354516.
[13]Архитектура современных почтовых систем. URL: http://housecomputer.ru/technology/internet/mail_systems/the_architecture_of_modern_postal_systems.html.
[14]Простейший случай пересылки почты. URL: https://upload.wikimedia.org/wikipedia/commons/1/1b/Email-map-simple-animation.gif.