Лабораторная работа №1 Погода

Цель:

  1. Закрепить навыки разработки многофайловыx приложений;
  2. Изучить способы работы с API web-сервиса;
  3. Изучить процесс сериализации/десериализации данных в/из json;
  4. Получить базовое представление о сетевом взаимодействии приложений;

Постановка задачи

Разработать сервис предоставляющий данные о погоде в городе Симферополе на момент запроса. В качестве источника данных о погоде используйте: http://openweathermap.org/. В состав сервиса входит: серверное приложение на языке С++ и клиентское приложение на языке Python.

Серверное приложение (далее Сервер) предназначенное для обслуживания клиентских приложений и минимизации количества запросов к сервису openweathermap.org. Сервер должен обеспечивать возможность получения данных в формате JSON и виде html виджета (для вставки виджета на страницу будет использоваться iframe).

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

Подробности указаны далее.

Ход работы

0. Введение. Структура http-запроса

Общая схема http-запроса представлена на рисунке ниже:

Рис. 1. Структура http-запроса

Протокол: http, https, ftp и другие.

Хост: доменное имя или ip-адрес сервера. Обычно по одному ip-адресу находится несколько сайтов, в этом случае доменное имя используется сервером, чтобы выбрать правильного получателя запроса.

Сетевой порт: число от 0 до 65535. Своеобразное расширения IP-адреса, нужен, чтобы несколько приложений на компьютере одновременно могли работать с сетью. Запрос приходит на IP-адрес, а дальше операционная система отдаёт его тому приложению, которое слушает порт указанный в запросе. Несколько приложений не могут одновременно слушать один и тот же порт благодаря этому пакет Skype не попадёт к Discord и т.д. Обычно веб сервера слушают 80 и 443 порты. 80 - стандартный порт для http запросов, 443 - для https. Браузеры автоматически дописывают номера портов ориентируясь на название протокола, поэтому пользователю это делать не нужно.

Путь к ресурсу: просто строка. Приложение получившее запрос само решает как реагировать на эту строку. В простом случае путь к ресурсу может восприниматься как реальный путь к папке/файлу, где-то на серверном компьютере. В общем случае путь к ресурсу - это способ запустить какую-то функцию в приложении получившем запрос. В результате, функция может как сгенерировать html-страничку и отправить в ответ на запрос, так и запустить процесс форматирования дисков на сервере, если у программы достаточно прав.

query string: набор пар ключ-значение. Идёт после вопросительного знака, пары разделены одним амперсантом (&). Если путь к ресурсу воспринимать как функцию, то query string - это аргументы функции. Порядок пар значения не имеет, лишние пары обычно игнорируются.

Якорь: служит, для того, чтобы промотать у клиента страницу к нужному элементу (заголовку, картинке и т.д.), поэтому данный параметр не попадает в запрос к серверу. В качестве якоря, например можно указать атрибут id html-тега.

Примерно тоже, что и выше, но в видео формате: https://youtu.be/DXB-GJKKNcg и https://youtu.be/N5OKXZvIR1w.

I. Подготовка к работе с сервисом openweathermap.org
  1. Перейдите на главную страницу сервиса http://openweathermap.org;

  2. Зарегистрируйтесь и войдите в сервис;

  3. В разделе меню API keys сгенерируйте API key;

  4. Изучите пример использования API key http://openweathermap.org/appid#use;

  5. Составьте и протестируйте запрос в браузере (для этого понадобится ваш API Key). В ответ вы должны получить текст в формате JSON:

    Рис. 2. Ответ сервиса openweathermap.org
  6. В разделе openweathermap.org -> API -> One Call API изучите:

    • Как составить http-запрос и понять, что пришло в ответ: Current and forecasts weather data;
    • Как получить ответ в градусах Цельсия (по умолчанию Фаренгейты) и на русском языке: Other features;
  7. Составьте запрос на получение прогноза погоды для Симферополя с почасовым интервалом, в градусах Цельсия, на русском языке;

  8. Протестируйте запрос в браузере. Ответ удобнее изучать в виде дерева, например при помощи JSON Viewer или браузерного расширения, например: JsonDiscovery.

II. Подготовка к работе с сервисом worldtimeapi.org

Сервис позволяет получить время в формате json, без регистрации. Составьте запрос для получения времени в Симферополе и изучите формат ответа: http://worldtimeapi.org/pages/schema.

III. Подготовка серверного приложения
  1. Для работы с сетью используйте библиотеку: https://github.com/yhirose/cpp-httplib.

    1. Клонируйте или скачайте в виде архива репозиторий библиотеки https://github.com/yhirose/cpp-httplib;

    2. Создайте консольный C++ проект;

    3. Перейдите в папку с главным .cpp файлом проекта и создайте папку include. В ней будут размещаться заголовочные файлы библиотек;

    4. В папке include создайте папку cpp_httplib и скопируйте туда файл httplib.h из скаченного ранее репозитория. В дальнейшем сам репозиторий больше не потребуется, его можно удалить;

    5. Перейдите в свойства проекта, в раздел Свойства конфигурации 🠖 C/C++ 🠖 Общие. Справа в разделе Дополнительные каталоги включаемых файлов добавьте путь к каталогу include;

    6. Скопируйте и вставьте в главный .cpp файл следующий код:

    7. Запустите программу и перейдите по ссылке: http://localhost:1234/. Если вы увидели сообщение: "Hello, World!", значит всё сделано правильно.

  2. Это базовый проект сервера, в дальнейшем его нужно будет модифицировать.

  3. Для получения данных от сервисов http://openweathermap.org/ и http://worldtimeapi.org нужно будет посылать им get-запросы. Для этих целей воспользуемся скачанной ранее библиотекой cpp-httplib.

    1. Создайте консольный C++ проект и настройте его аналогичным предыдущему проекту способом.

    2. Скопируйте и вставьте в главный .cpp файл следующий код:

  4. Если вы всё сделали правильно, увидите ответ от worldtimeapi.org в виде JSON.

  5. В этом примере мы отправляем get-запрос по адресу http://worldtimeapi.org/api/timezone/Europe/Simferopol. Для этого выполняем три действия:

    • Создаём переменную с именем cli типа Client и указываем сайт куда будем отправлять запросы. Это обычная переменная и создавать их можно сколько нужно и где нужно.
      Нужно указывать только название сайта и только протокол http. Для работы с https придётся выполнить дополнительные настройки.

      • У переменной cli используем метод Get, чтобы отправить get-запрос к сайту с которым связана данная переменная. Для этого в скобочках указываем путь к ресурсу и если нужно query string. Например, чтобы обратиться к корню сайта пишем: "/". Метод Get будет ждать ответа от сервера и сохранит его в переменную res.
      • Обрабатываем ответ. Важно понимать, что если сервер хоть что-то ответит, то res в if-e будет true. Ошибка может возникнуть, например, если запрос/ответ на дошёл, нет интернета и т.д. Ответ от сервера будет находиться в res->body. Это обычное значение типа std::string.
  6. Попробуйте отправить запрос к сервису openweathermap.org который вы составили в пункте I.7

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

  8. Для работы с JSON используйте библиотеку: https://github.com/nlohmann/json.

    1. Скачайте файл json.hpp отсюда: https://github.com/nlohmann/json/releases. Его также можно найти в репозитории проекта https://github.com/nlohmann/json в папке: single_include/nlohmann.

    2. Создайте консольный C++ проект;

    3. Перейдите в папку с главным .cpp файлом проекта и создайте папку include. В ней будут размещаться заголовочные файлы библиотек;

    4. В папке include создайте папку nlohmann и скопируйте туда файл json.hpp.

    5. Перейдите в свойства проекта, в раздел Свойства конфигурации 🠖 C/C++ 🠖 Общие. Справа в разделе Дополнительные каталоги включаемых файлов добавьте путь к каталогу include;

    6. Скопируйте и вставьте в главный .cpp файл следующий код:

    7. В примере используется сырой (raw) строкой литерал для записи JSON в тексте программы. Всё, что написано после R"( и перед )"; воспринимается не как часть кода, а как текст, то есть не нужно экранировать кавычки и все нажатия клавиши Enter будут записаны в строку как '\n'. Это НЕ является обязательным требованием, и сделано, чтобы не загромождать код лишними символами.

    8. Если значение нужно достать и передать в функцию или выполнить с ним какое-то действие, то нужно использовать метод get<тип>() с указанием типа данных, например как тут: j["answer"]["everything"].get<int>() или j["name"].get<string>().

    9. Изучите пример. Принимать и отправлять данные мы будем в виде строк.

  9. Алгоритм работы серверного приложения (далее Сервера):

    1. Сервер запускается и слушает get запросы приходящие на 3000 порт localhost;

    2. На запрос Сервер должен ответить информацией о погоде на текущий момент. При этом считается, что данные указанные в прогнозе тоже точны и нет необходимости делать лишний запрос к openweathermap.org.

    3. Если приходит запрос на "/", сервер формирует и отправляет html-виджет:

      1. Выполняется запрос на сервис worldtimeapi.org для получения точного текущего времени. Для простоты используйте поле unixtime;

      2. Т.к. запрос на сервис openweathermap.org возвращает прогноз на ближайшие двое суток с интервалом в 1 час, то после запроса к сервису openweathermap.org будем сохранять ответ в переменную (далее Кэш).

      3. Если запрос к openweathermap.org ещё не делали, то делаем и ответ сохраняем Кэш.

      4. Если запрос к openweathermap.org уже был, то проверяем, что в Кэше есть информация для требуемого времени. Для простоты будем брать информацию о погоде, на начало следующего часа. То есть в Кэше, в массиве "hourly" нужно найти наименьший элемент с меткой времени больше текущей, если просматривать элементы с конца.

      5. Если такого элемента нет, значит Кэш устарел. Обновляем Кэш и выполняем пункт 4.

      6. После того, как нужные данные найдены загружаем шаблон виджета;
        В данных видеоуроках показано, как работать с файлами: загружать и сохранять. Чтобы загрузить весть текст шаблона в одну строковую переменную можно написать так:

        В текстовых файлах не должно быть символа '\0' или он может присутствовать в конце файла, поэтому getline прочитает весь файл до конца.

      7. Далее ищем в шаблоне следующие фрагменты и заменяем на конкретные данные:{hourly[i].weather[0].description}, {hourly[i].weather[0].icon}, {hourly[i].temp};
        Температуру округлить до целых. Так должен выглядеть готовый виджет.
        Могут пригодиться функции: find, replace, round, to_string.

      8. Заполненный шаблон отправляется в ответ на запрос. Не забудьте поменять MIME на text/html.

    4. Если приходит запрос на "/raw", сервер формирует и отправляет json:

      1. Выполняется запрос на сервис worldtimeapi.org для получения точного текущего времени. Для простоты используйте поле unixtime;
      2. Т.к. запрос на сервис openweathermap.org возвращает прогноз на ближайшие двое суток с интервалом в 1 час, то после запроса к сервису openweathermap.org будем сохранять ответ в переменную (далее Кэш).
      3. Если запрос к openweathermap.org ещё не делали, то делаем и ответ сохраняем Кэш.
      4. Если запрос к openweathermap.org уже был, то проверяем, что в Кэше есть информация для требуемого времени. Для простоты будем брать информацию о погоде, на начало следующего часа. То есть в Кэше, в массиве "hourly" нужно найти наименьший элемент с меткой времени больше текущей, если просматривать элементы с конца.
      5. Если такого элемента нет, значит Кэш устарел. Обновляем Кэш и выполняем пункт 4.
      6. После того, как нужные данные найдены формируем новый json содержащий два поля: значения полей hourly[i].temp и hourly[i].weather[0].description исходных данных. Названия для полей в новом json придумайте самостоятельно.
      7. Полученный json отправляем в ответ на запрос. Не забудьте поменять MIME на text/json.
IV. Подготовка клиентского приложения
  1. Графический интерфейс реализуйте при помощи Tkinter стандартного модуля Python. Для выполнения лабораторной работы достаточно базового умения работы с Tkinter, а именно создание текстовых меток (Label), компоновки виджетов (pack или grid) и привязки событий мышки (bind).
    Видео лекции объясняющее основы работы с Tkinter можно посмотреть тут. Макет клиентского приложения представлен на рисунке ниже. Полное (попиксельное) совпадения не нужно, достаточно общего сходства.

Рис. 3. Интерфейс клиентского приложения
  1. Для работы с json используйте стандартный модуль json описание модуля на английском языке приведено в документации.
    Про базовые способы работы с модулем json на руссом языке можно почитать тут: https://python-scripts.com/json.
    Видео пример работы с модулем json: https://youtu.be/rIhygmw9HZM и https://youtu.be/Wt4WAcqEje8

  2. Для работы с сетью используйте модуль requests. Прежде, чем начать работать с данным модулем его необходимо установить. Для этого откройте окно командной строки/терминала и наберите команду:

    Описание модуля на руссом языке приведено например тут.

  3. Алгоритм работы Клиента:

    1. Клиент запускается.

    2. Клиент отправляет запрос на Сервер, например http://localhost:3000/raw. Адрес можно записать прямо в коде.

    3. Клиент получает от Сервера данные в формате json и заполняет элементы интерфейса. Текстовые данные полученные от Сервера могут ошибочно выводится в неправильной кодировке (Latin-1 (ISO 8859-1)), чтобы поправить кодировку используйте:

    4. Если пользователь щелкнет в любом месте окна правой кнопкой мыши, перейти на пункт 2.

V. Туннель

Для защиты работы скачайте программу ngrok https://ngrok.com/. Даная программа позволит создать туннель к localhost, то есть из интернета можно будет получить доступ к Серверу запущенному на вашем компьютере, без необходимости иметь "белый" IP- адрес.

  1. Скачайте программу и распакуйте в любое место;

  2. Запустите. Откроется окно командной строки;

  3. Введите команду:

  4. ngrok запустится и по ссылке указанной в Forwarding ваш сервер будет доступен из интернета;

  5. Если прописать в браузере http://127.0.0.1:4040 вы увидите статистику по запросам проходящим через туннель.

Видео пример использования (всё что касается регистрации можно пропустить): https://youtu.be/e4igm6JQiHw

Что должно быть в отчёте

  1. Отчёт по лабораторной работе оформляется в соответствии с указанными в разделе Правила оценивания требованиями.

  2. В отчёте создайте раздел (заголовок второго уровня) Постановка задачи и продублируйте туда соответствующий блок из этого документа.

  3. Создайте раздел (заголовок второго уровня) Выполнение работы и текстом подробно опишите всё, что делали в процессе выполнения. В описании обязательно должны присутствовать:

    1. API key полученный на сервисе openweathermap.org;
    2. Запрос составленный в пункте I.7;
    3. Запрос составленный в пункте II;
    4. Полный исходный код серверного приложения;
    5. Полный исходный код клиентского приложения;
    6. Скриншот графического интерфейса клиентского приложения. Только окно программы, лишнее обрезать;
    7. Скриншот браузера с загруженными виджетом.
  4. В папке с лабораторной работой должно быть:

    1. Отчёт в файле ReadMe.md;
    2. Каталог с серверным приложением на языке С++;
    3. Каталог с клиентским приложением на языке Python.