Автор Анна Евкова
Преподаватель который помогает студентам и школьникам в учёбе.

Модель клиент-сервер (Применение модели «клиент-сервер» в разработке web-приложения)

Содержание:

Введение

Развитие систем передачи и обработки информации позитивно сказалось на общедоступности различных полезных и нужных знаний. В наши дни информационные ресурсы передают огромные массы данных по всему миру, а пользователи могут получать необходимые тексты, файлы, изображения и другой контент практически мгновенно. Разумеется, благодаря внушительным размерам и мощности сети Интернет. Он состоит из множества вычислительных устройств, объединенных в сети. Информация между ними передаётся как на прямую, так и через посредников. Например, сообщения в мессенджерах передаются отправителем сначала на сервер, а затем с сервера на клиентское приложение получателю.

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

1. Модель «клиент-сервер»

1.1. Понятие клиента и сервера

1.1.1 Основные определения

Архитектура информационной системы - концепция, которая определяет модель, структуру, выполняемые функции и взаимосвязь компонентов информационной системы.

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

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

Клиент – это программа, использующая услугу, представляемую программой сервера.

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

Редирект – перенаправление окна браузера на другую страницу.

Виджет – визуальный компонент страницы отображающий небольшой объем информации.

1.1.2. История

Основным недостатком персональных компьютеров является их невысокая вычислительная мощность и надежность, а также необходимость

приобретать дополнительные аппаратные средства для устранения изолированности отдельных персональных компьютеров друг от друга.

Как правило, пользователям компьютеров необходимы и высокая вычислительная мощность и превосходные свойства персональных компьютеров. Посему, там, где для выполнения сложных вычислений используются мощные изолированные центральные компьютеры с терминалами, их пользователям периодически приходится использовать персональные компьютеры для редактирования текстов или выполнения задач, использующих электронные таблицы. Это заставляет пользователей освоить две различные операционные системы (на больших машинах обычно установлены OC MVS, VMS, VM, UNIX, а на персональных - MS DOS/MS Windows, OS/2 или Mac) и не решает задачи совместного использования данных.

В результате опроса представителей 300 наикрупнейших фирм США, использующих персональные компьютеры, выяснилось, что для 81% опрошенных необходим доступ к данным более чем одного компьютера. Чтобы решить эту задачу, персональные компьютеры начали объединять в локальные сети и устанавливать на них специальные операционные системы, например, NetWare фирмы Novell, для совместного использования компьютерами сети файлов, размещенных в различных узлах сети. Эта технология называется файл-сервер.

Однако данная технология имеет ряд недостатков. Файл-серверы не позволяют в полной мере обеспечить конфиденциальность доступа и целостность данных. По сети файлы передаются в цело виде, независимо от того, какая часть данных внутри файлов нужна пользователю. Это сильно перегружает сеть и уменьшает быстродействие системы. Невысока также и надежность системы на основе файл-серверов. Сбой на одной из рабочих станций в момент записи файла приводит к потере или искажению данных. Для обеспечения непротиворечивости данных приходится блокировать файлы, что также приводит к замедлению работы. Естественным желанием специалистов в области вычислительной техники было совместить преимущества персональных компьютеров и мощных центральных компьютеров.

Первым шагом в этом направлении являлсь использование персональных компьютеров в качестве интеллектуальных терминалов. При таком подходе в персональном компьютере, соединенном с центральным компьютером, запускается специальное программное обеспечение, позволяющее этому персональному компьютеру работать в режиме эмуляции терминала. При этом мы получаем архитектуру, реализующую все достоинства архитектуры с мощным центральным компьютером, но, кроме того, персональный компьютер может использоваться и самостоятельно, по своему прямому назначению. Теперь нет необходимости иметь на столе 2 дисплея, однако большинство недостатков, присущих архитектуре с центральным компьютером, все еще сохраняется. Кроме того, хотя персональные компьютеры, имеющие дисплеи с картой VGA, позволяют работать с графикой, однако использовать их в качестве графического терминала большой центральной машины неудобно. Задача выполняется в центральном компьютере и по проводам передаются графические образы экрана. Эти образы довольно велики и скорость смены изображения на экране может быть очень низкой.

Следующим шагом в решении описанной выше проблемы стало использование архитектуры клиент-сервер. В такой архитектуре все компьютеры сети разделены на 2 группы: клиенты и серверы. Компьютер-сервер - это мощный компьютер с большой оперативной памятью и большим количеством дискового пространства. На нем хранится база данных и выполняется сложная обработка, требующая больших вычислительных ресурсов. На компьютерах-клиентах выполняются первичная обработка данных при вводе, форматирование данных, а также окончательная (финишная) обработка данных, извлеченных с сервера. В качестве компьютеров-клиентов обычно используются персональные компьютеры типа IBM PC или Macintosh. Преимущества архитектуры клиент-сервер очевидны. Каждый тип компьютера используется по своему назначению, следовательно, обеспечивается более полное использование возможностей компьютеров.

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

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

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

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

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

1.1.3 Понятие архитектуры клиент-сервер

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

Модель «клиент-сервер» означает такую архитектуру программных средств, в которой его функциональные части взаимодействуют по схеме «запрос-ответ». Если рассмотреть две взаимодействующие части этой модели, то одна из них (клиент) выполняет активную функцию, то есть инициирует запросы, а другая (сервер) пассивно на них отвечает. По мере развития системы роли могут меняться, например некоторый программный блок будет одновременно выполнять функции сервера по отношению к одному блоку и клиента по отношению к другому.

Рис. 1.1. Модель клиент-сервер

Клиент и сервер взаимодействую друг с другом в сети Интернет или в любой другой компьютерной сети при помощи различных сетевых протоколов, например, IP протокол, HTTP протокол, FTP и другие. Клиент и сервер физически представляют собой программы, например, типичным клиентом является браузер. В качестве сервера можно привести следующие примеры: все HTTP сервера (в частности NodeJS), MySQL сервер, Elasticsearch сервер для полнотекстового.

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

1.2 Двухуровневая архитектура клиент-сервер

Данная архитектура получила распространение с начала 1990-х годов на фоне роста рынка персональных компьютеров и снижения спроса на мэйнфреймы.

Первоначально вышеописанные системы базировались на классической двухуровневой клиент-серверной архитектуре (Two-tier architecture). Под клиент-серверным приложением в этом случае понимается информационная система, основанная на использовании серверов баз данных.

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

Рис. 1.2. Двухуровневая модель взаимодействия клиент-сервер

На рисунке чётко видно два уровня: первый – клиент, отправляющий запросы и второй – сервер, отвечающий на запросы.

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

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

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

В качестве альтернативы возникла также двухзвенная архитектура "с тонким клиентом". При этом в идеале программа-клиент реализует лишь графический интерфейс пользователя (GUI) и передает/принимает запросы, а вся бизнес-логика выполняется сервером. В идеале клиентом является просто интернет-браузер, который имеется в стандартной операционной среде любого пользовательского компьютера и не требует специальной настройки, установки специализированного ПО и т.п. К сожалению, такая схема тоже не свободна от недостатков, хотя бы уже потому, что серверу иногда приходится брать на себя несвойственные для него функции реализации бизнес логики приложения (например, серверу СУБД приходится выполнять расчеты).

1.3. Многоуровневая архитектура клиент-сервер

Multitier architecture - разновидность архитектуры клиент-сервер, в которой функция обработки данных вынесена на один или несколько отдельных серверов. Это позволяет разделить функции хранения, обработки и представления данных для более эффективного использования возможностей серверов и клиентов.

Среди многоуровневой архитектуры клиент-сервер наиболее распространена трехуровневая архитектура (трехзвенная архитектура, three-tier), предполагающая наличие следующих компонентов приложения: клиентское приложение (обычно говорят "тонкий клиент" или терминал), подключенное к серверу приложений, который в свою очередь подключен к серверу базы данных.

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

На рисунке наглядно отражены три уровня: первый – клиент, отправляющий запросы и второй – сервер приложения, отвечающий на запросы клиента и отправляющий запросы на чтение/запись, третий – сервер базы данных, отвечающий на запросы сервера приложения

Рис. 1.3. Многоуровневая модель взаимодействия клиент-сервер

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

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

Третий уровень – это хранилище данных. Сервер базы данных обеспечивает хранение данных и выносится на третий уровень. Обычно это стандартная реляционная или объектно-ориентированная СУБД. Если третий уровень представляет собой базу данных вместе с хранимыми процедурами, триггерами и схемой, описывающей приложение в терминах реляционной модели, то второй уровень строится как программный интерфейс, связывающий клиентские компоненты с прикладной логикой базы данных.

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

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

Плюсами данной архитектуры являются:

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

Минусами данной архитектуры являются:

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

Начало процессу развития корпоративного программного обеспечения в многозвенной архитектуре было положено еще в рамках технологии "клиент-сервер". В них наряду с клиентской частью приложения и сервером баз данных появились серверы приложений (Application Servers). В идеале:

• программа-клиент реализует GUI, передает запросы серверу приложений и принимает от него ответ,

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

Даная архитектура также может быть рассмотрена с позиции сайта: первый уровень можно считать браузером, с помощью которого посетитель заходит на сайт, второй уровень – это связка Nginx + NodeJS, а третий уровень – это база данных.

1.4. Преимущества и недостатки

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

  • пониженные требования к машинам клиентов, так как большая часть вычислительных операций будет производиться на сервере
  • гибкость, которая позволяет администратору сделать локальную сеть более защищенной;
  • возможность внести изменения на каждом из звеньев можно осуществлять независимо;
  • снижение нагрузки на сеть на сеть, поскольку звенья не обмениваются между собой большими объемами информации;
  • обеспечение масштабирования и простая модернизация оборудования и программного обеспечения, поддерживающего каждое из звеньев, в том числе обновление серверного парка и терминального оборудования, СУБД и т.д.;
  • возможность создавать приложения на стандартных языках третьего или четвертого поколения (Java, C/C++).

К недостаткам модели взаимодействия клиент-сервер можно отнести:

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

1.5. Актуальность

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

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

2. Применение модели «клиент-сервер» в разработке web-приложения

2.1. Описание задачи

Для демонстрации преимуществ модели клиент-сервер создадим клиент-серверное приложение «Виджет погоды». Необходимо написать виджет, при размещении кода которого на любой странице любого сайта появлялся бы прогноз погоды для Москвы, Санкт-Петербурга, либо Нижнего Новгорода.

API для получения данных – api.openweathermap.org. Данные виджетов будем хранить в базе Redis, серверный код должен быть написан на node.js

Должен быть реализован web-интерфейс, реализующий следующие возможности:

- создание любого количества экземпляров виджетов со своими настройками;

- изменение настроек ранее созданных виджетов;

- получение кода виджета для вставки его на страницу;

Настройки, доступные из интерфейса:

- Город

- На сколько дней выдавать прогноз (1, 3 или на неделю)

- Горизонтальный или вертикальный блок

Технологии, которые понадобятся для выполнения задания:

Окружение:

- http://nodejs.org – сервер на JavaScript

- http://redis.io – кеш-сервер

Библиотеки и фреймворки:

- http://getbootstrap.com – готовый набор css стилей

- http://expressjs.com – библиотека для создания веб-сервера на JavaScript

- https://github.com/NodeRedis/node-redis – обертка для работы с кеш-сервером Redis

- https://github.com/request/request – http-клиент для сервера NodeJS

- https://github.com/pugjs/pug – html шаблонизатор

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

2.2. Алгоритм реализации решения

  1. Опишем необходимые элементы проекта.
  2. Создадим и настроим проект, установим зависимости.
  3. Напишем серверную часть кода.
  4. Напишем клиентскую часть кода.
  5. Проверим работу приложения и сравним с описанием задачи.

2.3. Описание необходимых элементов проекта

Разделим механики необходимого приложения на две части – клиентскую и серверную. Клиент будет представлять из себя веб-страницы в браузере, сервер – expressjs веб-сервер на базе NodeJS, база данных – локальное in-memory хранилище на базе Redis.

Для начала сформируем список необходимых страниц для клиента в браузере:

список виджетов

форма добавления виджета

форма редактирования виджета

виджет

Для них необходимо будет создать шаблоны для отображения в браузере.

Затем перейдем к списку запросов, на которые сможет отвечать сервер:

- GET / – главная страница, отображение списка виджетов

- GET /widget/add – отображение формы добавление виджета

- POST /widget/add – непосредственно сохранение данных нового виджета и редирект на главную страницу

- GET /widget/edit – отображение формы редактирования виджета

- POST /widget/edit – непосредственно сохранение данных виджета и редирект на главную страницу

- GET /widget/show – инициализирует виджет на странице, на которой установлен

- POST /widget/get – возвращает данные виджета в виде заполненного шаблона

- GET /widget/delete – удаляет виджет из базы даных

Для отображения контента страниц и виджета нам понадобятся pug html шаблоны, они содержат:

- layout.pug – общие для всех страниц элементы

- main.pug –элементы главной страницы

- addwidget.pug – форма добавления виджета

- editwidget.pug – форма редактирования виджета

- widgetlistitem.pug – элемент списка виджета на для главной страницы

- widgetview.pug – содержит заголовок контента виджета

- widgetviewday.pug – содержит контент виджета с погодой за один день

Для стилизации html тегов интерфейса будем использовать библиотеку bootstrap, также нам понадобится файл widget.css для реализации нужного отображения элементов виджета.

2.4. Создание и настройка проекта

Для начала работы на компьютере должны быть установлены nodejs и redis для веб-сервера и базы данных соответственно. Для начала создадим папку weatherapp. Запустим терминал и перейдем в эту папку. Затем инициализируем проект через npm и добавим в него зависимости:

mkdir weatherapp

cd weatherapp

npm init

npm install express body-parser request pug redis

В созданном package.json в поле scripts добавим строку, указывающую на основной скрипт сервера, а также создадим и сам этот файл:

// выдержка из package.json:

"scripts": {

"start": "node app" // необходимая строка

}

// команда для создания файла app.js

touch app.js

Наконец, создадим папку для шаблонов и публичных файлов веб-сервера (мы разместим в ней css стили):

mkdir views

mkdir public

Итак, мы закончили с настройкой проекта. У нас есть файл основного скрипта сервера, необходимые библиотеки для работы и нужная структура папок. Теперь можно запустить в новом окне терминала сервер redis с помощью команды redis-server и перейти к написанию кода серверной части нашего приложения.

2.5. Реализация серверной части

Откроем app.js (программный код вынесен в приложение А) и перейдем к написанию серверной части приложения. Первым делом сделаем импорт зависимостей. Затем объявим константы: учетные данные api погодного сервиса, города для которых будет возможность получать погоду в виджетах, временные диапазоны прогнозов, имена месяцев и порт, на котором будет запущено приложение.

Теперь создадим переменную app веб-сервера express и настроим его: добавим возможность парсинга body приходящих запросов, установим движок pug для работы шаблонов страниц, откроем доступ к директории public, откроем прослушивание на выбранном порту. Затем создадим в переменной client подключение к хранилищу Redis. Мы получили минимально настроенное express веб-приложение – переходим к написанию функциональной части.

Напишем функцию addUpdateWidget которая получает данные виджета на вход и сохраняет их (с заменой при совпадении id) в базе данных Redis при помощи client. Мы задействуем эту функцию для создания и редактирования виджетов далее.

Перейдём к написанию обработчиков запросов. Начнём с «GET /» запроса. В его обработчике установим тип контента, получим из базы данные виджетов. Если виджеты найдены, отрендерим их с помощью шаблона widgetlistitem.pug и отправим ответ, используя шаблон main.pug. Если виджеты не найдены, отправим ответ, используя шаблон main.pug с соответствующим сообщением.

Для «GET /widget/add» установим тип контента и отправим ответ, используя шаблон addwidget.pug, содержащий форму создания виджета.

Для «POST /widget/add» и «POST /widget/edit» выполним функцию addUpdateWidget и сделаем редирект на главную страницу.

Для «GET /widget/edit» установим тип контента, проверим есть ли в базе виджет с указанным в параметрах запроса id. Если виджет не найден, отправим в ответ соответствующее сообщение. Если найден ¬отправим ответ, используя шаблон editwidget.pug и данные виджета, полученные из базы.

Для «GET /widget/show» установим тип контента и в качестве ответа вернём js-код, который дожидается загрузки библиотеки jQuery после чего добавляет на страницу файл стилей виджетов widget.css, затем получает данные виджета и отображает их на странице в соответствующем блоке.

Для «POST /widget/get» установим тип контента, затем проверим есть ли в базе виджет с указанным в параметрах запроса id. Если виджет не найден, отправим в ответ соответствующее сообщение. Если найден, то пробуем получить ответ от api сервиса прогнозов погоды. В случае ошибки получения данных погоды отправим в ответ сообщение о том, что запросы на получение погоды отправляются слишком часто и порекомендуем повторить попытку позже. Если же данные погоды пришли, то вернем ответ, используя шаблоны widgetviewday.pug и widgetview.pug.

Для «GET /widget/delete» удалим виджет с указанным в параметрах запроса id и сделаем редирект на главную страницу. На этом запросе завершаем работу над серверной частью приложения и переходим к клиентской.

2.6. Реализация клиентской части

В данном разделе мы будем работать в директориях public и views, в которых будут размещены файл стилей widget.css и файлы html шаблонов соответственно. Листинг файлов приведён в приложении Б. Для начала добавим в widget.css отступы и стиль отображения для css-класса card, чтобы элементы виджета (день с данными о погоде) могли выстраиваться в горизонтальный ряд.

Перейдём к написанию html шаблонов. В коде будем по возможности использовать синтаксис шаблонизатора pug. Для стиллизации будем использовать классы библиотеки bootstrap.

В файле layout.pug объявим теги html, head и body, добавим link на файл стилей bootstrap, теги script со ссылками на скрипты jquery и bootstrap, а главное – тег h1 для вывода в него заголовка и блок content для содержимого страниц.

В файле main.pug унаследуем шаблон layout.pug, в блоке content разместим ссылку на страницу добавления виджета и тег div для вывода списка виджетов. Также можно разместить полученный в приложении код виджета для встраивания, чтобы проверить его работу.

В файле addwidget.pug унаследуем шаблон layout.pug, в блоке content разместим ссылку на главную страницу и форму добавления виджета с полями id (уникальный идентификатор), name (название), city (город), days (количество дней, за которые будет отображаться прогноз), type (тип отображения – вертикальный или горизонтальный).

В файле editwidget.pug унаследуем шаблон layout.pug, в блоке content разместим ссылку на главную страницу и форму редактирования виджета с полями, аналогичными заданным в форме добавления.

В файле widgetlistitem.pug разместим тег h5 состоящий из названия виджета, его типа и количества дней за которые будет отображаться прогноз. Ниже разместим код для встраивания, ссылки на редактирование и удаление.

В файле widgetview.pug разместим тег h4 состоящий из слов «Weather in» и названия города для которого будет показан прогноз погоды. Ниже разместим тег div с классом, который зависит от type виджета и кодом прогноза погоды по дням.

В файле widgetviewday.pug разместим данные о погоде за один день: день недели, описание состояния погоды, температура ночью и днём. На этом шаблоне мы заканчиваем разработку клиентской части приложения и можем приступить к проверке и запуску.

2.7. Проверка и запуск решения

Для проверки решения на соответствие задаче необходимо произвести запуск приложения. Для этого воспользуемся приведенной ниже инструкцией (система macOS с установленным пакетным менеджером brew).

1. Установить сервер nodejs: `brew install node`

2. Устновить и запустить redis: `brew install redis` для установки, `redis-server` для запуска

3. Установить зависимости проекта через npm: `npm install`

4. Запустить проект и открыть в браузере: `npm start` для запуска, `http://localhost:3033` страница приложения в браузере

Рис. 2.7.1. Главная страница, база виджетов пуста

Откроем главную страницу, убедимся, что у нас есть кнопка «Add widget» для добавления виджетов. Нажимаем на неё.

Рис. 2.7.2. Страница добавления виджета

Должна открыться страница с формой добавления виджета. Вводим в ней данные виджета: «Widget ID» – 1, «Name» – MSC, «City» – Moscow, «Days count» – 3 days, «View type» – Horizontal. Нажимаем кнопку «Save».

Рис. 2.7.3. Главная страница, в базе есть виджет

Мы видим главную страницу, но теперь на ней виден список созданных виджетов, а также установленный виджет с id=1. Нажмём на кнопку «EDIT».

Рис. 2.7.4. Страница редактирования виджета

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

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

ЗАКЛЮЧЕНИЕ

Подробное рассмотрение модели «клиент-сервер» в теоретическом и практическом ключе в этой работе даёт уверенное понимание работы приложений, реализующих такую архитектуру.

Получившееся в результате разработки приложение успешно использует модель «клиент-сервер» и позволяет создавать виджеты с прогнозом погоды, которые можно встраивать в любой сайт. Разработка подобных решений даёт уверенное понимание веб-разработки с использованием браузеров-клиентов и приложения-сервера.

Клиенты в виде веб-браузеров, мобильных приложений и серверы в виде компьютеров установленных в дата-центрах в настоящее время составляют большую часть сети Интернет также обширно используют многоуровневую модель клиент-серверной архитектуры. Работа различных процессов, таких как обработка изображений и видео, хранение и передача текстовых сообщений, аудио и видео трансляции и многих других, обеспечивается группами специализированных серверов: файловые хранилища, кеш-хранилища, базы данных, поисковые движки и другие виды. Данная модель несомненно будет применяться в будущем, развиваться и решать сложные проблемы работы с данными, передаваемыми между ноутбуками, пк, планшетами, смартфонами, устройствами «Умного дома», самолетами, самоуправляемыми автомобилями и многими другими, в том числе совершенно новыми.

Список использованных источников

  1. Д.Васкевич Стратегии клиент/сервер. Диалектика, Киев, 1997.
  2. Валерий Коржов. Многоуровневые системы клиент-сервер. (https://www.osp.ru/nets/1997/06/142618/) Издательство Открытые системы (17 июня 1997)
  3. http://www.glossary.ru
  4. http://www.nadprof.ru/school/client-server.shtml
  5. http://mrivkin.narod.ru/Publ/CLSERV1.htm
  6. http://www.intuit.ru/department/se/crosspl/1/2.html

Приложение А

app.js

const express = require('express');

const bodyParser = require('body-parser');

const pug = require('pug');

const path = require('path');

const redis = require('redis');

const request = require("request");

// Config OpenWeatherMap

const openWeatherMapApiKey = 'bbf89a2ce7b2d36edaac82d86df3ca8e';

const openWeatherMapApiUri = 'http://api.openweathermap.org/data/2.5/forecast/daily?';

// Setup widget options values

const cityNames = {

"524901": "Moscow",

"498817": "Saint Petersburg",

"520555": "Nizhniy Novgorod"

}

const forecastDurations = {

1: "Today",

3: "3 days",

7: "Week"

}

const widgetTypes = {

"vertical": "Vertical",

"horizontal": "Horizontal"

}

var monthNames = ["January", "February", "March", "April", "May", "June",

"July", "August", "September", "October", "November", "December"

];

// Create redis client

let client = redis.createClient();

client.on('connect', function () {

console.log('Connected to Redis ...');

})

// Set port

const port = 3033;

// Init app

const app = express();

// body parser

app.use(bodyParser.urlencoded({ extended: false }));

app.use(bodyParser.json());

// View engine

app.set('view engine', 'pug')

// Add public static files access

app.use(express.static(__dirname + '/public'));

app.listen(port, function () {

console.log('Server started on port ' + port);

})

// add/update widget function

function addUpdateWidget(req,callback) {

let id = 'widget_'+req.body.id;

let name = req.body.name;

let city = req.body.city;

let days = req.body.days;

let type = req.body.type;

client.hmset(id, [

'name', name,

'city', city,

'city_name', cityNames[city],

'days', days,

'type', type

], function (err, reply) {

if (err) {

console.log(err);

}

callback(reply);

})

}

app.get('/', function (req, res) {

res.set('Content-Type', 'text/html');

var widgets = [];

var widgets_code = "";

// get widgets ids SCAN 0 MATCH widget* COUNT 100

client.scan(0, 'MATCH', 'widget*', 'COUNT', 100, function (err, reply) {

if (err) {

res.render('main', { title: 'Widget app', message: 'Manage widgets', error: err })

}

if(reply[1].length>0) {

// loop to render them

let promise = new Promise(function (resolve) {

reply[1].map(function (val, index, arr) {

client.hgetall(val, function (err, obj) {

obj.id = val;

pug.renderFile('views/widgetlistitem.pug', {

id: obj.id,

name: obj.name,

city: cityNames[obj.city],

type: obj.type,

days: obj.days,

code: '<div id="weatherwidget_'+obj.id+'"></div><script type="text/javascript" src="http://localhost:3033/widget/show?id='+obj.id+'"></script>'

}, function (empty, res) {

widgets += obj;

widgets_code += res;

if (index == arr.length - 1) resolve(true);

});

});

});

});

promise

.then(function (value) {

res.render('main', { title: 'Widget app', message: 'Manage widgets', widgets_code: widgets_code });

return "widgets load success"

})

.then(console.log);

} else {

res.render('main', { title: 'Widget app', message: 'Manage widgets', widgets_code: '<div class="alert alert-warning" role="alert">No widgets yet</div>' });

}

});

})

// Add widget page

app.get('/widget/add', function (req, res) {

res.set('Content-Type', 'text/html');

res.render('addwidget', {

title: 'Widget app',

message: 'Add widget',

cityNames: cityNames,

forecastDurations: forecastDurations,

widgetTypes: widgetTypes })

})

// Process Add widget page

app.post('/widget/add', function (req, res) {

addUpdateWidget(req,function(reply){

console.log(reply);

res.redirect('/');

});

})

// Edit widget page

app.get('/widget/edit', function (req, res) {

res.set('Content-Type', 'text/html');

client.hgetall(req.query.id, function (err, obj) {

if (err || obj===null) res.send('Widget not found!'); else {

obj.id = req.query.id;

res.render('editwidget', {

title: 'Widget app',

message: 'Edit widget',

id: obj.id.replace('widget_',''),

name: obj.name,

city: obj.city,

days: obj.days,

type: obj.type,

cityNames: cityNames,

forecastDurations: forecastDurations,

widgetTypes: widgetTypes

});

}

});

})

// Process Edit widget page

app.post('/widget/edit', function (req, res) {

addUpdateWidget(req,function(reply){

console.log(reply);

res.redirect('/');

});

})

// widget code script

app.get('/widget/show', function (req, res) {

res.set('Content-Type', 'text/javascript');

res.send(`function defer(method){if (window.jQuery) {method();}else{setTimeout(function() { defer(method) }, 50);}}

defer(function () {

if($('#weatherwidgetstyle').length<1) $('head').append('<link id="weatherwidgetstyle" rel="stylesheet" type="text/css" href="http://localhost:3033/widget.css"/>');

$(function(){

var widget_xhr = $.post( "http://localhost:3033/widget/get?id=`+req.query.id+`", function(data) {

console.log( "widget loaded" );

$("#weatherwidget_`+req.query.id+`").html(data);

}, 'text')

.fail(function(xhr, status, error) {

console.log( xhr, status, error, "error - widget not loaded" );

});

});

});`);

});

// widget code ajax content

app.post('/widget/get', function (req, res) {

res.set('Content-Type', 'text/javascript');

var output = "";

var view = "";

if (req.query.id !== null) {

let promise = new Promise(function (resolve) {

client.hgetall(req.query.id, function (err, obj) {

if (err || obj===null) res.send('Widget not found!'); else {

obj.id = req.query.id;

resolve(obj);

}

});

});

promise.then(function (obj) {

var url = openWeatherMapApiUri +

"APPID=" + openWeatherMapApiKey +

"&id=" + obj.city +

"&units=metric&cnt=" + obj.days;

request({

url: url,

json: true

}, function (error, response, body) {

if (!error && response.statusCode === 200) {

let weatherdata = body;

weatherdata = weatherdata.list;

let daysweather_code = "";

for (var key in weatherdata) {

if (weatherdata.hasOwnProperty(key)) {

date = new Date(weatherdata[key].dt * 1000),

datevalues = [

date.getFullYear(),

date.getMonth()+1,

date.getDate(),

],

todaydate = new Date(),

todaydatevalues = [

todaydate.getFullYear(),

todaydate.getMonth()+1,

todaydate.getDate(),

],

daytitle=date.getDate()+' '+monthNames[date.getMonth()];

if (datevalues.toString()==todaydatevalues.toString()) daytitle = 'Today';

pug.renderFile('views/widgetviewday.pug', {

daytitle: daytitle,

city: cityNames[obj.city],

weathertitle: weatherdata[key].weather[0].description,

daytemp: weatherdata[key].temp.day>0?'+'+weatherdata[key].temp.day:weatherdata[key].temp.day,

nighttemp: weatherdata[key].temp.night>0?'+'+weatherdata[key].temp.night:weatherdata[key].temp.night

}, function (empty, res) {

daysweather_code += res;

});

}

}

view = pug.renderFile('views/widgetview.pug', {

city: cityNames[obj.city],

daysweather_code: daysweather_code,

type: obj.type

}, function (empty, res) {

output = res;

});

res.send(output);

} else {

res.send('Too many requests to openweathermap.org api, try later...');

}

});

return true;

}).then(function (value) {

// null

});

} else {

res.send('Widget ID is not defined');

}

})

// delete widget

app.get('/widget/delete', function (req, res) {

console.log(req.query.id+' deleted');

client.del(req.query.id);

res.redirect('/');

});

Приложение Б

widget.css

.days-container--horizontal .card {

display: inline-block !important;

margin-right: 10px !important;

}

.card{

margin-bottom: 20px;

}

layout.pug

doctype html

html

head

title= title

link(href='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css' rel='stylesheet')

body

div.container

h1= message

block content

script(src='https://code.jquery.com/jquery-2.2.4.min.js')

script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js')

main.pug

extends layout.pug

block append content

p.

<a href="/widget/add" class="btn btn-primary" role="button">Add widget</a>

div.widgets!= widgets_code

h3 Widget example:

// insert here widget code for testing, example:

// <div id="weatherwidget_widget_test"></div><script type="text/javascript" src="http://localhost:3033/widget/show?id=widget_test"></script>

<div id="weatherwidget_widget_1"></div><script type="text/javascript" src="http://localhost:3033/widget/show?id=widget_1"></script>

addwidget.pug

extends layout.pug

block append content

p.

<a href="/" class="btn btn-secondary" role="button">Back</a>

<form method="POST" action="/widget/add">

<div class="form-group">

<label>Widget ID</label>

<input type="text" class="form-control" name="id" placeholder="Unique ID" required>

</div>

<div class="form-group">

<label>Name</label>

<input type="text" class="form-control" name="name" placeholder="Widget name" required>

</div>

<div class="form-group">

<label>City</label>

<select class="form-control" name="city" required>

each val, index in cityNames

option(value=index)= val

</select>

</div>

<div class="form-group">

<label>Days count</label>

<select class="form-control" name="days" required>

each val, index in forecastDurations

option(value=index)= val

</select>

</div>

<div class="form-group">

<label>View type</label>

each val, index in widgetTypes

div.radio

label

input(type="radio", name="type", value=index, required=true)

span= val

</div>

<button type="submit" class="btn btn-primary">Save</button>

</form>

editwidget.pug

extends layout.pug

block append content

p.

<a href="/" class="btn btn-secondary" role="button">Back</a>

form(method="POST", action="/widget/edit?id="+id)

<div class="form-group">

<label>Widget ID</label>

input(type="text", class="form-control", name="id", value=id, placeholder="Unique ID")

</div>

<div class="form-group">

<label>Name</label>

input(type="text", class="form-control", name="name", value=name, placeholder="Widget name")

</div>

<div class="form-group">

<label>City</label>

<select class="form-control" name="city">

each val, index in cityNames

option(value=index, selected=index==city?true:false)= val

</select>

</div>

<div class="form-group">

<label>Days count</label>

<select class="form-control" name="days">

each val, index in forecastDurations

option(value=index, selected=index==days?true:false)= val

</select>

</div>

<div class="form-group">

<label>View type</label>

each val, index in widgetTypes

div.radio

label

input(type="radio", name="type", value=index, checked=index==type?true:false, required=true)

span= val

</div>

<button type="submit" class="btn btn-primary">Save</button>

widgetlistitem.pug

<div class="card">

<div class="card-body">

<h5 class="card-title">

strong= name+' '

span(class='label label-success')= type

| &nbsp;

span(class='label label-info')= 'for '+days+' days'

| &nbsp;

span(class='label label-primary')= city

</h5>

p Insert following code on your page:

pre= code

div.pull-right

a(href="/widget/edit?id="+id, class="btn btn-primary btn-sm", role="button") EDIT

| &nbsp;

a(href="/widget/delete?id="+id, class="btn btn-danger btn-sm", role="button") REMOVE

</div>

</div>

widgetview.pug

h4= 'Weather in '+city

- let layout = 'days-container ';

- layout += type=='horizontal'?'days-container--horizontal':'days-container--vertical';

div(class=layout)!= daysweather_code

widgetviewday.pug

div.card

div.card-body

h3.card-title= daytitle

div.card-text

mark= weathertitle

br

strong= daytemp+' at day'

br

span= nighttemp+' at night'