One place for hosting & domains

      приложений

      Обслуживание приложений Flask с Gunicorn и Nginx в Ubuntu 20.04


      Введение

      В этом обучающем модуле вы создадите приложение Python с использованием микроструктуры Flask в Ubuntu 20.04. Основная часть этой статьи посвящена настройке сервера приложений Gunicorn, запуску приложения и настройке Nginx для работы в режиме обратного прокси-сервера фронтенда.

      Предварительные требования

      Перед началом прохождения этого обучающего модуля вам потребуется следующее:

      • Сервер с установленной операционной системой Ubuntu 20.04 и пользователь без привилегий root и с привилегиями sudo. Следуйте указаниям нашего руководства по начальной настройке сервера.
      • Веб-сервер Nginx, установленный в соответствии с шагами 1 и 2 модуля Установка Nginx в Ubuntu 20.04.
      • Доменное имя, настроенное так, чтобы указывать на ваш сервер. Вы можете приобрести его на Namecheap или получить бесплатно на Freenom. Вы можете узнать, как указывать домены на DigitalOcean, из соответствующей документации по доменам и DNS. Обязательно создайте следующие записи DNS:

        • Запись A, где your_domain указывает на публичный IP-адрес вашего сервера.
        • Запись A, где www.your_domain указывает на публичный IP-адрес вашего сервера.
      • Знакомство со спецификацией WSGI, которую сервер Gunicorn будет использовать для взаимодействия с вашими приложениями Flask. В этом обсуждении более подробно рассказывается о WSGI.

      Шаг 1 — Установка компонентов из хранилищ Ubuntu

      Первым шагом будет установка всех необходимых нам элементов из хранилищ Ubuntu. Она включает установку pip, диспетчера пакетов Python, который будет управлять нашими компонентами Python. Также мы получим файлы разработки Python, необходимые для создания некоторых компонентов Gunicorn.

      Вначале обновим локальный индекс пакетов и установим пакеты, которые позволят нам создать нашу среду Python. Мы установим пакет python3-pip, а также еще несколько пакетов и средств разработки, необходимых для создания надежной среды программирования:

      • sudo apt update
      • sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools

      С этими пакетами мы перейдем к созданию виртуальной среды для нашего проекта.

      Шаг 2 — Создание виртуальной среды Python

      Теперь мы настроим виртуальную среду, чтобы изолировать наше приложение Flask от других файлов Python в системе.

      Для начала установим пакет python3-vevv, который установит модуль venv:

      • sudo apt install python3-venv

      Затем создадим родительский каталог для нашего проекта Flask. Перейдите в каталог после его создания:

      • mkdir ~/myproject
      • cd ~/myproject

      Создайте виртуальную среду для хранения требований Python для вашего проекта Flask, введя следующую команду:

      • python3 -m venv myprojectenv

      Локальные копии Python и pip будут установлены в каталог myprojectenv в каталоге вашего проекта.

      Прежде чем устанавливать приложения в виртуальной среде, ее нужно активировать. Для этого нужно ввести следующую команду:

      • source myprojectenv/bin/activate

      Командная строка изменится, показывая, что теперь вы работаете в виртуальной среде. Она будет выглядеть примерно так: (myprojectenv)user@host:~/myproject$.

      Шаг 3 — Настройка приложения Flask

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

      Вначале мы установим wheel с локальным экземпляром pip, чтобы убедиться, что наши пакеты будут устанавливаться даже при отсутствии архивов wheel:

      Примечание


      Если виртуальная среда активна, то вне зависимости от того, какую версию Python вы используете, вы должны использовать команду pip (а не pip3).

      Затем установим Flask и Gunicorn:

      • pip install gunicorn flask

      Создание образца приложения

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

      Хотя ваше приложение может быть более сложным, мы создадим наше приложение Flask в одном файле с именем myproject.py:

      • nano ~/myproject/myproject.py

      Код приложения будет находиться в этом файле. Он импортирует Flask и создаст экземпляр объекта Flask. Вы можете использовать его для определения функций, которые запускаться при запросе конкретного маршрута:

      ~/myproject/myproject.py

      from flask import Flask
      app = Flask(__name__)
      
      @app.route("/")
      def hello():
          return "<h1 style="color:blue">Hello There!</h1>"
      
      if __name__ == "__main__":
          app.run(host="0.0.0.0")
      

      Здесь определяется, какой контент должен выводиться при доступе к корневому домену. Сохраните файл и закройте его после завершения.

      Если вы следовали указаниям модуля по начальной настройке сервера, у вас должен быть включен брандмауэр UFW. Чтобы протестировать приложение, вам нужно разрешить доступ к порту 5000:

      Теперь вы можете протестировать приложение Flask с помощью следующей команды:

      Вы увидите следующий результат, в том числе полезное предупреждение, напоминающее не использовать эти настройки сервера в реальной работе:

      Output

      * Serving Flask app "myproject" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

      Откройте в браузере IP-адрес вашего сервера с суффиксом :5000:

      http://your_server_ip:5000
      

      Вы увидите примерно следующее:

      Образец приложения Flask

      Когда вы закончите, нажмите CTRL+C в окне терминала, чтобы остановить сервер разработки Flask.

      Создание точки входа WSGI

      Теперь создадим файл, который будет служить точкой входа в наше приложение. Это покажет серверу Gunicorn, как взаимодействовать с приложением.

      Мы назовем этот файл wsgi.py:

      Сейчас мы импортируем экземпляр Flask из нашего приложения в этот файл и запустим его:

      ~/myproject/wsgi.py

      from myproject import app
      
      if __name__ == "__main__":
          app.run()
      

      Сохраните файл и закройте его после завершения.

      Шаг 4 — Настройка Gunicorn

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

      Прежде чем продолжить, нужно убедиться, что Gunicorn может правильно обслуживать приложение.

      Для этого нужно просто передать имя нашей точки входа. Оно составляется из имени модуля (без расширения .py) и имени вызываемого элемента приложения. В нашем случае это wsgi:app.

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

      • cd ~/myproject
      • gunicorn --bind 0.0.0.0:5000 wsgi:app

      Результат будет выглядеть следующим образом:

      Output

      [2020-05-20 14:13:00 +0000] [46419] [INFO] Starting gunicorn 20.0.4 [2020-05-20 14:13:00 +0000] [46419] [INFO] Listening at: http://0.0.0.0:5000 (46419) [2020-05-20 14:13:00 +0000] [46419] [INFO] Using worker: sync [2020-05-20 14:13:00 +0000] [46421] [INFO] Booting worker with pid: 46421

      Откройте в браузере IP-адрес вашего сервера с суффиксом :5000.

      http://your_server_ip:5000
      

      Вы увидите результат выполнения вашего приложения:

      Образец приложения Flask

      Убедившись в его нормальной работе, нажмите CTRL+C в окне терминала.

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

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

      Далее мы созадим файл служебных элементов systemd. Создание файла элементов systemd позволит системе инициализации Ubuntu автоматически запускать Gunicorn и обслуживать приложение Flask при загрузке сервера.

      Для начала создайте файл элементов с расширением .service в каталоге /etc/systemd/system:

      • sudo nano /etc/systemd/system/myproject.service

      Мы начнем с раздела [Unit] этого файла, где указываются метаданные и зависимости. Здесь мы разместим описание службы и предпишем системе инициализации запускать ее только после достижения сетевой цели:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=Gunicorn instance to serve myproject
      After=network.target
      

      Теперь откроем раздел [Service]. Здесь указывается пользователь и группа, от имени которых мы хотим запустить данный процесс. Сделаем владельцем процесса учетную запись обычного пользователя, поскольку этот пользователь является владельцем всех соответствующих файлов. Также назначим владельцем группу www-data, чтобы упростить коммуникацию Nginx с процессом Gunicorn. Не забудьте заменить приведенное имя пользователя своим именем пользователя:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=Gunicorn instance to serve myproject
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      

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

      • Запуск 3 рабочих процессов (хотя вы можете изменить это при необходимости)
      • Создание и привязвка к файлу сокетов Unix myproject.sock в каталоге нашего проекта. Мы зададим значение umask 007, чтобы при создании файла сокета предоставлялся доступ для владельца и для группы, а любой другой доступ ограничивался
      • Укажите имя файла точки входа WSGI, а также вызываемый элемент Python в этом файле (wsgi:app)

      Systemd требует, чтобы мы указывали полный путь исполняемого файла Gunicorn, установленного в нашей виртуальной среде.

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

      /etc/systemd/system/myproject.service

      [Unit]
      Description=Gunicorn instance to serve myproject
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      WorkingDirectory=/home/sammy/myproject
      Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
      ExecStart=/home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
      

      Наконец, добавим раздел [Install]. Это покажет systemd, куда привязывать эту службу, если мы активируем ее запуск при загрузке. Нам нужно, чтобы эта служба запускалась во время работы обычной многопользовательской системы:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=Gunicorn instance to serve myproject
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      WorkingDirectory=/home/sammy/myproject
      Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
      ExecStart=/home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
      
      [Install]
      WantedBy=multi-user.target
      

      Теперь служебный файл systemd готов. Сохраните и закройте его.

      Теперь мы запустим созданную службу Gunicorn и активируем ее запуск при загрузке системы:

      • sudo systemctl start myproject
      • sudo systemctl enable myproject

      Теперь проверим состояние:

      • sudo systemctl status myproject

      Результат должен выглядеть следующим образом:

      Output

      ● myproject.service - Gunicorn instance to serve myproject Loaded: loaded (/etc/systemd/system/myproject.service; enabled; vendor preset: enabled) Active: active (running) since Wed 2020-05-20 14:15:18 UTC; 1s ago Main PID: 46430 (gunicorn) Tasks: 4 (limit: 2344) Memory: 51.3M CGroup: /system.slice/myproject.service ├─46430 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app ├─46449 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app ├─46450 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app └─46451 /home/sammy/myproject/myprojectenv/bin/python3 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app

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

      Шаг 5 — Настройка Nginx для работы с запросами прокси-сервера

      Сервер приложений Gunicorn должен быть запущен и ожидать запросы файла сокета в каталоге проекта. Теперь мы настроим Nginx для передачи веб-запросов на этот сокет. Для этого мы сделаем небольшие добавления в его файл конфигурации.

      Вначале мы создадим новый файл конфигурации серверных блоков в каталоге Nginx sites-available. Назовем его myproject для соответствия остальным именам в этом модуле:

      • sudo nano /etc/nginx/sites-available/myproject

      Откройте серверный блок и укажите Nginx прослушивать порт по умолчанию 80. Также укажите использовать этот блок для запросов доменного имени нашего сервера:

      /etc/nginx/sites-available/myproject

      server {
          listen 80;
          server_name your_domain www.your_domain;
      }
      

      Затем добавим блок расположения, соответствующий каждому запросу. В этот блок мы добавим файл proxy_params, определяющий некоторые общие параметры прокси, которые необходимо настроить. После этого запросы будут переданы на сокет, который мы определили с помощью директивы proxy_pass:

      /etc/nginx/sites-available/myproject

      server {
          listen 80;
          server_name your_domain www.your_domain;
      
          location / {
              include proxy_params;
              proxy_pass http://unix:/home/sammy/myproject/myproject.sock;
          }
      }
      

      Сохраните файл и закройте его после завершения.

      Чтобы активировать созданную конфигурацию серверных блоков Nginx, необходимо привязать файл к каталогу sites-enabled:

      • sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled

      Когда файл будет находиться в этом каталоге, можно провести проверку на ошибки синтаксиса:

      Если ошибок обнаружено не будет, перезапустите процесс Nginx для чтения новой конфигурации:

      • sudo systemctl restart nginx

      В заключение снова изменим настройки брандмауэра. Нам больше не потребуется доступ через порт 5000, и мы можем удалить это правило. Затем мы сможем разрешить полный доступ к серверу Nginx:

      • sudo ufw delete allow 5000
      • sudo ufw allow 'Nginx Full'

      Теперь у вас должна быть возможность открыть доменное имя вашего сервера в браузере:

      http://your_domain
      

      Вы увидите результат выполнения вашего приложения:

      Образец приложения Flask

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

      • sudo less /var/log/nginx/error.log: проверяет журналы ошибок Nginx.
      • sudo less /var/log/nginx/access.log: проверяет журналы доступа Nginx.
      • sudo journalctl -u nginx: проверяет журналы процессов Nginx.
      • sudo journalctl -u myproject: проверяет журналы Gunicorn вашего приложения Flask.

      Шаг 6 — Защита приложения

      Чтобы обеспечить защиту трафика вашего сервера, необходимо получить сертификат SSL для вашего домена. Этого можно добиться несколькими способами, в том числе получить бесплатный сертификат от Let’s Encrypt, сгенерировать сертификат с собственной подписью или приобрести сертификат у другого поставщика и настроить Nginx для его использования, для чего потребуется выполнить шаги с 2 по 6 обучающего модуля Создание сертификата SSL с собственной подписью для Nginx в Ubuntu 20.04. Для удобства мы выберем первый вариант.

      Установите пакет Certbot Nginx с apt:

      • sudo apt install python3-certbot-nginx

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

      • sudo certbot --nginx -d your_domain -d www.your_domain

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

      Если это первый запуск certbot, вам будет предложено указать адрес эл. почты и принять условия обслуживания. После этого certbot свяжется с сервером Let’s Encrypt и отправит запрос с целью подтвердить, что вы контролируете домен, для которого запрашиваете сертификат.

      Если это будет подтверждено, certbot запросит у вас предпочитаемый вариант настройки HTTPS:

      Output

      Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. ------------------------------------------------------------------------------- 1: No redirect - Make no further changes to the webserver configuration. 2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration. ------------------------------------------------------------------------------- Select the appropriate number [1-2] then [enter] (press 'c' to cancel):

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

      Output

      IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/your_domain/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/your_domain/privkey.pem Your cert will expire on 2020-08-18. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le

      Если вы следовали инструкциям по установке Nginx из предварительных требований, вам больше не потребуется разрешать профиль HTTP лишний раз:

      • sudo ufw delete allow 'Nginx HTTP'

      Чтобы подтвердить конфигурацию, снова перейдите в свой домен, используя префикс адреса https://:

      https://your_domain
      

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

      Заключение

      В этом обучающем модуле вы научились создавать и защищать простое приложение Flask в виртуальной среде Python. Вы создали точку входа WSGI, с которой может взаимодействовать любой сервер приложений с поддержкой WSGI, а затем настроили сервер приложения Gunicorn для обеспечения этой функции. После этого вы создали служебный файл systemd, который автоматически запускает сервер приложений при загрузке. Также вы создали серверный блок Nginx, который передает трафик веб-клиента на сервер приложений, перенаправляет внешние запросы и защищает трафик вашего сервера с помощью сертификата Let’s Encrypt.

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



      Source link

      Создание приложений Go для различных операционных систем и разной архитектуры


      При разработке программного обеспечения очень важно учитывать операционную систему и архитектуру процессора, для которого вы будете компилировать ваш бинарный файл. Поскольку часто бывает очень трудно или просто невозможно запустить один бинарный файл на разных ОС/архитектурах, довольно распространенной практикой является сборка итогового бинарного файла для набора различных платформ для максимального увеличения аудитории вашей программы. Однако это может оказаться затруднительным, когда платформа, которую вы используете для разработки, отличается от платформы, где вы хотите развернуть вашу программу. В прошлом, например, при разработке программы на Windows и ее последующем развертывании на компьютере с Linux или macOS могла требоваться настройка инструмента для сборки для каждой среды, где вам требуется бинарный файл. Кроме того, вам нужно поддерживать ваш инструментарий в синхронизированном состоянии, а также учитывать другие моменты, что будет приводить к росту издержек и затруднит совместное тестирование и распространение.

      Go решает эту проблему посредством поддержки различных платформ напрямую в инструменте go build, а также в остальной цепочке инструментов Go. Используя переменные среды и теги сборки, вы можете контролировать, для какой ОС и архитектуры предназначен ваш бинарный файл, а также реализовать рабочий процесс, который позволит быстро добавлять предназначенный для конкретной платформы код без внесения изменений в базу вашего кода.

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

      Предварительные требования

      Для выполнения примера из этой статьи вам потребуется следующее:

      Возможные платформы GOOS и GOARCH

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

      Инструменты Go имеют команду, которая может вывести список поддерживаемых Go платформ для сборки. Этот список может меняться с каждым новым релизом Go, так что описанные здесь комбинации могут быть иными в другой версии Go. На момент написания настоящего руководства текущая версия Go — 1.13.

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

      Вы получите примерно следующий результат:

      Output

      aix/ppc64 freebsd/amd64 linux/mipsle openbsd/386 android/386 freebsd/arm linux/ppc64 openbsd/amd64 android/amd64 illumos/amd64 linux/ppc64le openbsd/arm android/arm js/wasm linux/s390x openbsd/arm64 android/arm64 linux/386 nacl/386 plan9/386 darwin/386 linux/amd64 nacl/amd64p32 plan9/amd64 darwin/amd64 linux/arm nacl/arm plan9/arm darwin/arm linux/arm64 netbsd/386 solaris/amd64 darwin/arm64 linux/mips netbsd/amd64 windows/386 dragonfly/amd64 linux/mips64 netbsd/arm windows/amd64 freebsd/386 linux/mips64le netbsd/arm64 windows/arm

      Данный вывод — это набор пар ключ-значение, разделенных /. Первая часть комбинации перед символом / — это операционная система. В Go эти операционные системы — это возможные значения для переменной среды GOOS, произносится “goose”, название которой означает операционная система Go. Вторая часть, идущая после /, — это архитектура. Как и в случае с операционной системой, это все возможные значения для переменной среды: GOARCH. Это сокращение произносится “gore-ch” и означает архитектура Go.

      Давайте разберем одну из этих комбинаций, чтобы понять, что это означает и как работает, воспользовавшись linux/386 в качестве примера. Пара ключ-значение начинается с переменной GOOS, которая в этом случае будет linux, то есть ОС Linux. Для GOARCH здесь указано значение 386, что означает микропроцессор Intel 80386.

      Существует множество доступных для команды go build платформ, но в большинстве случаев вы будете использовать linux, windows или darwin в качестве значения для GOOS. Это позволяет покрыть три важнейшие платформы OS: Linux, Windows и macOS, которая основана на операционной системе Darwin и поэтому называется darwin. Однако Go позволяет охватить и менее популярные платформы, например nacl, т. е. Native Client от Google.

      Когда вы запускаете команду, например go build​​​, Go использует GOOS и GOARCH для определения способа сборки бинарного файла. Чтобы узнать, какая конфигурация у вашей платформы, вы можете использовать команду go env и передать GOOS и GOARCH в качестве аргументов:

      При тестировании этого примера мы запускаем эту команду в macOS на компьютере с архитектурой AMD64, поэтому мы получим следующее:

      Output

      darwin amd64

      Здесь вывод команды указывает нам, что в нашей системе GOOS=darwin и GOARCH=amd64.

      Теперь вы знаете, что такое GOOS и GOARCH в Go, а также их возможные значения. Далее вы выполните сборку программы для использования в качестве примера использования этих переменных среды и меток сборки для получения бинарных файлов для других платформ.

      Написание платформо-зависимой программы с помощью filepath.Join()

      Прежде чем начать сборку бинарных файлов для других платформ, мы создадим пример программы. Для этой задачи отлично подходит функция Join в пакете path/filepath в стандартной библиотеке Go. Эта функция получает ряд строк и возвращает одну строку, к которой добавлен соответствующий разделитель пути файла.

      Это отличный пример программы, поскольку работа программы зависит от того, в какой ОС она запущена. Для Windows разделитель пути файла — это обратный слэш ​​​, а в системах Unix используется прямой слэш /.

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

      Во-первых, создайте папку в директории src с именем вашего приложения:

      Перейдите в эту директорию:

      Далее создайте новый файл в текстовом редакторе по вашему выбору с именем main.go. В этом обучающем руководстве мы будем использовать Nano:

      После открытия файла добавьте следующий код:

      src/app/main.go

      package main
      
      import (
        "fmt"
        "path/filepath"
      )
      
      func main() {
        s := filepath.Join("a", "b", "c")
        fmt.Println(s)
      }
      

      Функция main() в этом файле использует filepath.Join() для конкатенации трех строк вместе с правильным платформо-зависимым разделителем пути.

      Сохраните и закройте файл, а затем запустите программу:

      При запуске этой программы вы получите разный вывод в зависимости от того, какую платформу вы используете. В Windows вы увидите, что строки будут разделены :

      Output

      abc

      В системах Unix, в том числе macOS и Linux, вы получите следующее:

      Output

      a/b/c

      Это означает, что из-за разных протоколов файловой системы, используемых в этих операционных системах, программа должна будет выполнять разный код для различных платформ. Однако поскольку в ней уже используется разный разделитель пути файла в зависимости от операционной системы, мы знаем, что filepath.Join() уже учитывает различие в платформе. Это вызвано тем, что цепь инструментов Go автоматически определяет GOOS и GOARCH на вашем компьютере и использует эту информацию для применения сниппета кода с правильными маркерами сборки и разделителем файла.

      Давайте рассмотрим, откуда функция filepath.Join() получает нужный разделитель. Запустите следующую команду для просмотра соответствующего сниппета из стандартной библиотеки Go:

      • less /usr/local/go/src/os/path_unix.go

      В результате вы увидите содержимое path_unix.go. Найдите следующую часть файла:

      /usr/local/go/os/path_unix.go

      . . .
      // +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
      
      package os
      
      const (
        PathSeparator     = '/' // OS-specific path separator
        PathListSeparator = ':' // OS-specific path list separator
      )
      . . .
      

      В этом разделе определяется PathSeparator для всех разновидностей Unix-систем, которые поддерживает Go. Обратите внимание на все маркеры сборки сверху, каждый из которых является одним из возможных значений платформ Unix для GOOS. Когда GOOS соответствует этим терминам, ваша программа будет выводить разделитель файла в стиле Unix.

      Нажмите q для возврата к командной строке.

      Далее откройте файл, который определяет поведение filepath.Join() при использовании в Windows:

      • less /usr/local/go/src/os/path_windows.go

      Вы увидите следующее:

      /usr/local/go/os/path_unix.go

      . . .
      package os
      
      const (
              PathSeparator     = '\' // OS-specific path separator
              PathListSeparator = ';'  // OS-specific path list separator
      )
      . . .
      

      Хотя здесь значение PathSeparator будет \, код будет отображать отдельный обратный слэш (), необходимый для путей файлов в Windows, поскольку первый обратный слэш требуется только в качестве символа перехода.

      Обратите внимание, что в отличие от файла Unix, сверху нет меток сборки. Это объясняется тем, что GOOS и GOARCH также могут быть переданы команде go build, добавив нижнее подчеркивание (_) и значение переменной среды в качестве суффикса для имени файла. Мы обсудим это подробнее в разделе Использование суффиксов имен файлов GOOS и GOARCH. Здесь часть _windows названия имени path_windows.go заставляет файл вести себя так, будто у него есть маркер сборки // +build windows в верхней части файла. В связи с этим, когда ваша программа запускается в Windows, она будет использовать константы PathSeparator и PathListSeparator из сниппета кода path_windows.go.

      Чтобы вернуться к командной строке, выйдите из less, нажав q.

      На этом шаге вы создали программу, показывающую, как Go автоматически преобразовывает GOOS и GOARCH в маркеры сборки. Держа это в уме, вы можете обновить вашу программу и написать свою собственную реализацию filepath.Join(), используя маркеры сборки, чтобы вручную задать правильный PathSeparator для платформ Windows и Unix.

      Реализация платформенно-зависимой функции

      Теперь, когда вы знаете, как стандартная библиотека Go имплементирует специфичный для платформы код, вы можете использовать маркеры сборки, чтобы сделать это в вашей собственной программе app. Для этого вам нужно будет написать свою собственную реализацию filepath.Join().

      Откройте ваш файл main.go:

      Замените содержимое main.go на следующее, используя свою собственную функцию с именем Join():

      src/app/main.go

      package main
      
      import (
        "fmt"
        "strings"
      )
      
      func Join(parts ...string) string {
        return strings.Join(parts, PathSeparator)
      }
      
      func main() {
        s := Join("a", "b", "c")
        fmt.Println(s)
      }
      

      Функция Join получает ряд частей и объединяет их вместе с помощью метода strings.Join() из пакета strings для конкатенации частей, используя PathSeparator.

      Вы еще не определили PathSeparator, поэтому давайте сделаем это в другом файле. Сохраните и выйдите из main.go, откройте ваш любимый редактор и создайте новый файл с именем path.go:

      nano path.go
      

      Определите PathSeparator и установите его равным разделителю пути файла Unix /:

      src/app/path.go

      package main
      
      const PathSeparator = "/"
      

      Скомпилируйте и запустите приложение:

      Вывод должен выглядеть следующим образом:

      Output

      a/b/c

      Это значит, что программа успешно запускается для получения пути файла в стиле Unix. Однако это еще не то, что нам нужно: вывод всегда будет a/b/c, вне зависимости от того, на какой платформе программа запущена. Чтобы добавить функционал создания пути файла в стиле Windows, вам нужно добавить версию PathSeparator для Windows и указать команде go build, какую версию следует использовать. В следующем разделе вы сможете воспользоваться маркерами сборки, чтобы выполнить эту задачу.

      Использование маркеров GOOS или GOARCH

      Чтобы учитывать платформы Windows, вы создадите альтернативный файл для path.go и будете использовать маркеры сборки, чтобы убедиться, что сниппеты кода запускаются, только когда GOOS и GOARCH принадлежат к соответствующей платформе.

      Однако сначала нужно добавить маркер сборки в path.go, чтобы указать ему на необходимость выполнять сборку для любых платформ, кроме Windows. Откройте файл:

      Добавьте в файл выделенный маркер сборки:

      src/app/path.go

      // +build !windows
      
      package main
      
      const PathSeparator = "/"
      

      Маркеры сборки Go позволяют использовать инвертирование, что означает, что вы можете указать Go выполнить сборку этого файла для любой платформы, кроме Windows. Чтобы инвертировать маркер доступа, добавьте ! перед маркером.

      Сохраните и закройте файл.

      Теперь, если бы вы захотели запустить эту программу в Windows, то получили бы следующую ошибку:

      Output

      ./main.go:9:29: undefined: PathSeparator

      В данном случае Go не сможет включить path.go для определения переменной PathSeparator.

      Теперь, когда вы убедились, что path.go не будет запускаться, когда для GOOS используется значение Windows, добавьте новый файл, windows.go:

      В файле windows.go необходимо определить PathSeparator для Windows, а также маркер сборки, чтобы команда go build смогла понимать, что это реализация для Windows:

      src/app/windows.go

      // +build windows
      
      package main
      
      const PathSeparator = "\"
      

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

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

      Использование ваших локальных переменных среды GOOS и GOARCH

      Ранее вы запускали команду go env GOOS GOARCH для получения информации о том, в какой ОС и с какой архитектурой вы работаете. При запуске команды go env выполняется поиск двух переменных среды GOOS и GOARCH; если их удалось найти, будут использоваться их значения, но если они не были найдены, Go будет использовать для них информацию для текущей платформы. Это означает, что вы можете изменить значения GOOS или GOARCH, чтобы они не совпадали по умолчанию с данными локальной операционной системы и архитектуры.

      Команда go build ведет себя примерно так же, как и команда go env. Вы можете задать для переменных среды GOOS или GOARCH значение для получения сборки для другой платформы с помощью команды go build.

      Если вы не используете систему Windows, создайте бинарный файл windows для app, установив для переменной среды GOOS значение windows при запуске команды go build:

      Теперь вы можете вывести файлы в вашей текущей директории:

      Вывод списка файлов в директории теперь показывает, что в директории проекта есть исполняемый файл app.exe для Windows:

      Output

      app app.exe main.go path.go windows.go

      Используя команду file, вы можете получить дополнительную информацию об этом файле, подтвердив его сборку:

      Вы получите следующий результат:

      Output

      app.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

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

      • GOOS=linux GOARCH=ppc64 go build

      Теперь ваш исполняемый файл app будет заменен файлом для другой архитектуры. Запустите команду file для этого бинарного файла:

      Вывод будет выглядеть следующим образом:

      app: ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, not stripped
      

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

      Использование суффиксов файлов имен GOOS и GOARCH

      Как вы уже видели ранее, в стандартной библиотеке Go активно используются маркеры сборки для упрощения кода, что позволяет разделять реализации для разных платформ в разных файлах. Когда вы открывали файл os/path_unix.go, там был маркер сборки, который содержал список всех возможных комбинаций, рассматриваемых в качестве Unix-платформ. Однако в файле os/path_windows.go отсутствуют маркеры для сборки, поскольку суффикса в имени файла достаточно, чтобы Go мог понять, для какой платформы предназначен этот файл.

      Давайте рассмотрим синтаксис этого элемента. При присвоении имени файла .go вы можете добавить GOOS и GOARCH в качестве суффиксов к имени файла в этом порядке, отделяя значения нижним подчеркиванием (_). Если у вас есть файл Go с именем filename.go, вы можете указать ОС и архитектуру, изменив имя файла на filename_GOOS_GOARCH.go. Например, если вы хотите скомпилировать его для Windows с 64-битной архитектурой ARM, вы должны использовать имя файла filename_windows_arm64.go. Такое соглашение о наименованиях помогает поддерживать код в организованном виде.

      Обновите вашу программу для использования суффиксов имени файла вместо маркеров сборки. Во-первых, переименуйте файл path.go и windows.go для использования соглашения, используемого в пакете os:

      • mv path.go path_unix.go
      • mv windows.go path_windows.go

      После изменения имен двух файлов вы можете удалить маркер сборки, который вы добавили в path_windows.go:

      Удалите // +build windows, чтобы ваш файл выглядел следующим образом:

      path_windows.go

      package main
      
      const PathSeparator = "\"
      

      Сохраните и закройте файл.

      Поскольку unix — это недействительное значение для GOOS, суффикс _unix.go не будет иметь значение для компилятора Go. Однако он передает предполагаемое назначение файла. Как и в файле os/path_unix.go, ваш файл path_unix.go все еще требует использования маркеров для сборки, поэтому этот файл будет сохранен без изменений.

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

      Заключение

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

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



      Source link

      Отправка push-уведомлений из приложений Django


      Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.

      Введение

      Интернет постоянно меняется, и теперь он может получить функциональные возможности, которые ранее были доступны только непосредственно на мобильных устройствах. Появление в JavaScript инструмента service worker дает вебу такие новые возможности, как выполнение фоновой синхронизации, кеширование оффлайн и отправка push-уведомлений.

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

      В этом обучающем руководстве вы настроите приложение Django в Ubuntu 18.04, которое отправляет push-уведомления в случае любой активности, которая требует от пользователя посещения приложения. Для создания этих уведомлений вы будете использовать пакет Django-Webpush и должны будете настроить и зарегистрировать service worker для отображения уведомлений для клиента. Работающее приложение с уведомлениями будет выглядеть следующим образом:

      Итоговый вид push-уведомления

      Предварительные требования

      Для прохождения этого обучающего руководства вам потребуется следующее:

      Шаг 1 — Установка Django-Webpush и получение VAPID ключей

      Django-Webpush — это пакет, позволяющий разработчикам интегрировать и отправлять push-уведомления в приложения Django. Мы будем использовать этот пакет для запуска и отправки push-уведомлений из нашего приложения. На этом шаге вы установите Django-Webpush и получите ключи добровольной идентификации сервера приложения (Voluntary Application Server Identification, VAPID), которые необходимы для идентификации вашего сервера и обеспечения уникальности каждого запроса.

      Вы обязательно должны находиться в директории проекта ~/djangopush, которая была создана на этапе выполнения предварительных требований:

      Активируйте вашу виртуальную среду:

      • source my_env/bin/activate

      Обновите версию pip для гарантии ее актуальности:

      • pip install --upgrade pip

      Установите Django-Webpush:

      • pip install django-webpush

      После установки пакета добавьте его в список приложений в файле settings.py. Откройте файл settings.py:

      • nano ~/djangopush/djangopush/settings.py

      Добавьте webpush в список INSTALLED_APPS:

      ~/djangopush/djangopush/settings.py

      ...
      
      INSTALLED_APPS = [
          ...,
          'webpush',
      ]
      ...
      

      Сохраните файл и закройте редактор.

      Запустите миграцию в приложении для применения изменений, которые вы внесли в схему базы данных:

      Вывод будет выглядеть следующим образом при условии успешного выполнения миграции:

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, webpush Running migrations: Applying webpush.0001_initial... OK

      Следующий шаг по настройке уведомлений — получение ключей VAPID. Эти ключи используются для идентификации сервера приложения и могут применяться для снижения секретности для URL-адресов подписки, поскольку они ограничивают подписку определенным сервером.

      Чтобы получить ключи VAPID, перейдите к веб-приложению wep-push-codelab. Здесь вы получите автоматически сгенерированные ключи. Скопируйте закрытые и открытые ключи.

      Далее создайте новую запись в файле settings.py для ваших данных о VAPID. Откройте файл:

      • nano ~/djangopush/djangopush/settings.py

      Далее добавьте новую директиву с именем WEBPUSH_SETTINGS с публичными и частными VAPID ключами и ваш адрес электронной почты под AUTH_PASSWORD_VALIDATORS:

      ~/djangopush/djangopush/settings.py

      ...
      
      AUTH_PASSWORD_VALIDATORS = [
          ...
      ]
      
      WEBPUSH_SETTINGS = {
         "VAPID_PUBLIC_KEY": "your_vapid_public_key",
         "VAPID_PRIVATE_KEY": "your_vapid_private_key",
         "VAPID_ADMIN_EMAIL": "[email protected]"
      }
      
      # Internationalization
      # https://docs.djangoproject.com/en/2.0/topics/i18n/
      
      ...
      

      Не забудьте заменить значения your_vapid_public_key, your_vapid_private_key и [email protected] на ваши данные. Ваш адрес электронной почты будет использоваться для отправки вам уведомлений при наличии проблем на сервере push-уведомлений.

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

      Шаг 2 — Настройка представлений

      На этом шаге мы настроим базовое представление home с объектом-ответом HttpResponse для нашей домашней страницы, а также представление send_push. Представления — это функции, которые будут возвращать для веб-запросов. Представление send_push будет использовать библиотеку Django-Webpush для отправки push-уведомлений, которые будут содержать данные, введенные пользователем на домашней странице.

      Перейдите в папку ~/djangopush/djangopush:

      • cd ~/djangopush/djangopush

      Запуск ls внутри папки будет отображать основные файлы проекта:

      Output

      /__init__.py /settings.py /urls.py /wsgi.py

      Файлы в этой папке генерируются автоматически утилитой django-admin, которую вы использовали для создания вашего проекта в предварительных требованиях. Файл settings.py содержит конфигурации для всего проекта, такие как установленные приложения и статичный корневой каталог. Файл urls.py содержит конфигурацию URL для проекта. Здесь вы будете настраивать маршруты согласно созданным вами представлениям.

      Создайте в директории ~/djangopush/djangopush с новый файл именем views.py, который будет хранить представления для вашего проекта:

      • nano ~/djangopush/djangopush/views.py

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

      ~/djangopush/djangopush/views.py

      from django.http.response import HttpResponse
      from django.views.decorators.http import require_GET
      
      @require_GET
      def home(request):
          return HttpResponse('<h1>Home Page<h1>')
      

      Представление home оформляется с помощью декоратора require_GET, ограничивающего представление только для запросов GET. Как правило, представление возвращает ответ для каждого поступающего запроса. Это представление возвращает простой HTML тег в качестве ответа.

      Далее мы создадим представление send_push, которое будет обрабатывать отправленные уведомления с помощью пакета django-webpush. Оно будет ограничено только запросами POST и будет выведено из-под защиты от *межсайтовой подделки запроса *(CSRF). Это позволит протестировать представление с помощью Postman или любой другой службы RESTful. Однако в реальном рабочем проекте вы должны удалить этот декоратор, чтобы защитить ваши представления от CSRF.

      Чтобы создать представление send_push, нужно добавить следующие импорты, чтобы активировать ответы JSON и получить доступ к функции send_user_notification в библиотеке webpush:

      ~/djangopush/djangopush/views.py

      from django.http.response import JsonResponse, HttpResponse
      from django.views.decorators.http import require_GET, require_POST
      from django.shortcuts import get_object_or_404
      from django.contrib.auth.models import User
      from django.views.decorators.csrf import csrf_exempt
      from webpush import send_user_notification
      import json
      

      Далее добавьте декоратор require_POST, который будет использовать тело запроса, отправленного пользователем, для создания и отправки push-уведомления:

      ~/djangopush/djangopush/views.py

      @require_GET
      def home(request):
          ...
      
      
      @require_POST
      @csrf_exempt
      def send_push(request):
          try:
              body = request.body
              data = json.loads(body)
      
              if 'head' not in data or 'body' not in data or 'id' not in data:
                  return JsonResponse(status=400, data={"message": "Invalid data format"})
      
              user_id = data['id']
              user = get_object_or_404(User, pk=user_id)
              payload = {'head': data['head'], 'body': data['body']}
              send_user_notification(user=user, payload=payload, ttl=1000)
      
              return JsonResponse(status=200, data={"message": "Web push successful"})
          except TypeError:
              return JsonResponse(status=500, data={"message": "An error occurred"})
      

      Мы будем использовать два декоратора для представления send_push: декоратор require_POST, ограничивающий представление только для запросов POST, и декоратор csrf_exempt, выводящий представление из-под защиты от CSRF.

      Это представление ожидает данные POST и делает следующее: получает body запроса и с помощью пакета json десериализует документ JSON и получает объект Python, используя json.loads. json.loads получает структурированный документ JSON и преобразовывает его в объект Python.

      Представление ожидает, что у поля объекта запроса будут три свойства:

      • head: заголовок push-уведомления.
      • body: тело уведомления.
      • id: id отправившего запрос пользователя.

      Если какое-либо из требуемых свойств отсутствует, представление будет возвращать JSONResponse со статусом 404 “Not Found”. Если пользователь с данным основным ключом существует, представление будет возвращать user с соответствующим основным ключом, используя функцию get_object_or_404 из библиотеки django.shortcuts. Если пользователь не существует, функция будет возвращать ошибку 404.

      Также представление использует функцию send_user_notification из библиотеки webpush. Эта функция принимает три параметра:

      • User: получатель push-уведомления.
      • payload: информация уведомления, которая включает head и body уведомления.
      • ttl: максимальное время в секундах, в течение которого уведомление следует хранить, если пользователь находится оффлайн.

      При отсутствии ошибок представление возвращает JSONResponse со статусом 200 “Success” и объектом данных. При возникновении ошибки KeyError представление будет возвращать статус 500 “Internal Server Error”. Ошибка KeyError возникает при отсутствии запрошенного ключа объекта.

      На следующем шаге мы создадим соответствующие маршруты URL для представлений, которые мы создали.

      Шаг 3 — Разметка URL-адресов для представлений

      Django позволяет создавать URL-адреса, которые будут подключаться к представлениям с помощью модуля Python с именем URLconf. Этот модуль размечает выражения маршрута URL для функций Python (ваших представлений). Обычно файл конфигурации URL генерируется автоматически при создании проекта. На этом шаге вы будете обновлять этот файл для включения новых маршрутов для представлений, созданных на предыдущем шаге, а также URL-адресов для приложения django-webpush, которые будут предоставлять конечные точки для подписанных пользователей для push-уведомлений.

      Дополнительную информацию о представлениях см. в руководстве Создание представлений Django.

      Откройте urls.py:

      • nano ~/djangopush/djangopush/urls.py

      Файл будет выглядеть примерно так:

      ~/djangopush/djangopush/urls.py

      
      """untitled URL Configuration
      
      The `urlpatterns` list routes URLs to views. For more information please see:
          https://docs.djangoproject.com/en/2.1/topics/http/urls/
      Examples:
      Function views
          1. Add an import:  from my_app import views
          2. Add a URL to urlpatterns:  path('', views.home, name='home')
      Class-based views
          1. Add an import:  from other_app.views import Home
          2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
      Including another URLconf
          1. Import the include() function: from django.urls import include, path
          2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
      """
      from django.contrib import admin
      from django.urls import path
      
      urlpatterns = [
          path('admin/', admin.site.urls),
      ]
      

      Следующим шагом будет разметка представлений с созданными вами URL-адресами. Во-первых, добавьте импорт include, чтобы гарантировать, что все маршруты для библиотеки Django-Webpush будут добавляться в ваш проект:

      ~/djangopush/djangopush/urls.py

      
      """webpushdjango URL Configuration
      ...
      """
      from django.contrib import admin
      from django.urls import path, include
      

      Далее импортируйте представления, которые вы создали на последнем шаге, и обновите список urlpatterns для разметки представлений:

      ~/djangopush/djangopush/urls.py

      
      """webpushdjango URL Configuration
      ...
      """
      from django.contrib import admin
      from django.urls import path, include
      
      from .views import home, send_push
      
      urlpatterns = [
                        path('admin/', admin.site.urls),
                        path('', home),
                        path('send_push', send_push),
                        path('webpush/', include('webpush.urls')),
                    ]
      

      Здесь список urlpatterns регистрирует URL-адреса для пакета django-webpush и сопоставляет представления с URL-адресами /send_push и /home.

      Давайте проверим представление /home, чтобы убедиться, что оно работает надлежащим образом. Убедитесь, что вы находитесь в корневой директории проекта:

      Запустите ваш сервер с помощью следующей команды:

      • python manage.py runserver your_server_ip:8000

      Перейдите по адресу http://your_server_ip:8000. Вы должны увидеть следующую домашнюю страницу:

      Первоначальное представление домашней страницы

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

      Шаг 4 — Создание шаблонов

      Движок шаблонов Django позволяет определять отображаемые пользователям слои приложения с помощью шаблонов, которые аналогичны файлам HTML. На этом шаге вы создадите и отобразите шаблон для представления home.

      Создайте папку с именем templates в корневой директории вашего проекта:

      • mkdir ~/djangopush/templates

      Если вы запустите команду ls в корневой папке вашего проекта в текущий момент, вывод будет выглядеть примерно так:

      Output

      /djangopush /templates db.sqlite3 manage.py /my_env

      Создайте файл home.html в папке templates:

      • nano ~/djangopush/templates/home.html

      Добавьте следующий в файл для создания формы, в которую пользователи смогут вводить информацию для создания push-уведомлений:

      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <meta name="vapid-key" content="{{ vapid_key }}">
          {% if user.id %}
              <meta name="user_id" content="{{ user.id }}">
          {% endif %}
          <title>Web Push</title>
          <link href="https://fonts.googleapis.com/css?family=PT+Sans:400,700" rel="stylesheet">
      </head>
      
      <body>
      <div>
          <form id="send-push__form">
              <h3 class="header">Send a push notification</h3>
              <p class="error"></p>
              <input type="text" name="head" placeholder="Header: Your favorite airline 😍">
              <textarea name="body" id="" cols="30" rows="10" placeholder="Body: Your flight has been cancelled 😱😱😱"></textarea>
              <button>Send Me</button>
          </form>
      </div>
      </body>
      </html>
      

      body файла включает в себя форму с двумя полями: элемент input будет хранить заголовок/название уведомления, а элемент textarea будет хранить тело уведомления.

      В разделе head в файле есть два тега meta, которые будут хранить публичный ключ VAPID и идентификатор пользователя. Эти две переменные требуются для регистрации пользователя и отправки ему push-уведомления. Здесь требуется идентификатор пользователя, поскольку вы будете направлять запросы AJAX на сервер, а id будет использоваться для идентификации пользователя. Если текущий пользователь является зарегистрированным пользователем, шаблон будет создавать тег meta с его id в качестве контента.

      Следующим шагом нужно указать Django, где нужно хранить ваши шаблоны. Для этого нужно отредактировать файл settings.py и обновить список TEMPLATES.

      Откройте файл settings.py:

      • nano ~/djangopush/djangopush/settings.py

      Добавьте следующие данные в список DIRS для указания пути к директории шаблонов:

      ~/djangopush/djangopush/settings.py

      ...
      TEMPLATES = [
          {
              'BACKEND': 'django.template.backends.django.DjangoTemplates',
              'DIRS': [os.path.join(BASE_DIR, 'templates')],
              'APP_DIRS': True,
              'OPTIONS': {
                  'context_processors': [
                      ...
                  ],
              },
          },
      ]
      ...
      

      Затем в файле views.py обновите представление home для отображения шаблона home.html. Откройте файл:

      • nano ~/djangpush/djangopush/views.py

      Во-первых, добавьте ряд импортов, включая конфигурацию settings, которая содержит все параметры проекта из файла settings.py и функцию render из django.shortcuts:

      ~/djangopush/djangopush/views.py

      ...
      from django.shortcuts import render, get_object_or_404
      ...
      import json
      from django.conf import settings
      
      ...
      

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

      ~/djangopush/djangopush/views.py

      ...
      
      @require_GET
      def home(request):
         webpush_settings = getattr(settings, 'WEBPUSH_SETTINGS', {})
         vapid_key = webpush_settings.get('VAPID_PUBLIC_KEY')
         user = request.user
         return render(request, 'home.html', {user: user, 'vapid_key': vapid_key})
      

      Код присваивает значения для следующих переменных:

      • webpush_settings: данный параметр присваивает значение атрибута WEBPUSH_SETTINGS из конфигурации settings.
      • vapid_key: этот элемент получает значение VAPID_PUBLIC_KEY из объекта webpush_settings для отправки клиенту. Данный публичный ключ сравнивается с закрытым ключом, чтобы убедиться, что клиент с публичным ключом может получать push-сообщения от сервера.
      • user: эта переменка поступает из входящего запроса. Когда пользователь отправляет запрос на сервер, данные этого пользователя сохраняются в поле user.

      Функция render будет возвращать файл HTML и объект context, содержащий текущего пользователя и публичный ключ VAPID. Здесь требуются три параметра: запрос, шаблон для отображения и объект, который содержит переменные, используемые в шаблоне.

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

      Шаг 5 — Обслуживание статичных файлов

      Веб-приложения, включая CSS, JavaScript и другие файлы образа, которые Django воспринимает в качестве “статичных файлов”. Django позволяет собирать все статичные файлы из каждого приложения в вашем проекте в одном месте, из которого они будут обслуживаться. Это решение называется django.contrib.staticfiles. На этом шаге мы обновим наши настройки, чтобы указать Django, где наши статичные файлы будут храниться.

      Откройте файл settings.py:

      • nano ~/djangopush/djangopush/settings.py

      В файле settings.py нужно убедиться, что значение STATIC_URL было определено:

      ~/djangopush/djangopush/settings.py

      ...
      STATIC_URL = '/static/'
      

      Далее добавьте список директорий с названием STATICFILES_DIRS, где Django будет искать статичные файлы:

      ~/djangopush/djangopush/settings.py

      ...
      STATIC_URL = '/static/'
      STATICFILES_DIRS = [
          os.path.join(BASE_DIR, "static"),
      ]
      

      Теперь вы можете добавить STATIC_URL в список путей, определенных в файле urls.py.

      Откройте файл:

      • nano ~/djangopush/djangopush/urls.py

      Добавьте следующий код, который будет импортировать конфигурацию static URL-адресов и обновлять список urlpatterns. Вспомогательная функция здесь использует свойства STATIC_URL и STATIC_ROOT, которые мы предоставили в файле settings.py для обслуживания статичных файлов проекта:

      ~/djangopush/djangopush/urls.py

      
      ...
      from django.conf import settings
      from django.conf.urls.static import static
      
      urlpatterns = [
          ...
      ]  + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
      

      После настройки параметров статичных файлов мы можем перейти к определению стиля домашней страницы приложения.

      Шаг 6 — Определение стиля домашней страницы

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

      Создайте папку static, а внутри папки static создайте папку css:

      • mkdir -p ~/djangopush/static/css

      Откройте файл css с именем styles.css в папке css:

      • nano ~/djangopush/static/css/styles.css

      Добавьте следующие стили для домашней страницы:

      ~/djangopush/static/css/styles.css

      
      body {
          height: 100%;
          background: rgba(0, 0, 0, 0.87);
          font-family: 'PT Sans', sans-serif;
      }
      
      div {
          height: 100%;
          display: flex;
          align-items: center;
          justify-content: center;
      }
      
      form {
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          width: 35%;
          margin: 10% auto;
      }
      
      form > h3 {
          font-size: 17px;
          font-weight: bold;
          margin: 15px 0;
          color: orangered;
          text-transform: uppercase;
      }
      
      form > .error {
          margin: 0;
          font-size: 15px;
          font-weight: normal;
          color: orange;
          opacity: 0.7;
      }
      
      form > input, form > textarea {
          border: 3px solid orangered;
          box-shadow: unset;
          padding: 13px 12px;
          margin: 12px auto;
          width: 80%;
          font-size: 13px;
          font-weight: 500;
      }
      
      form > input:focus, form > textarea:focus {
          border: 3px solid orangered;
          box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.2);
          outline: unset;
      }
      
      form > button {
          justify-self: center;
          padding: 12px 25px;
          border-radius: 0;
          text-transform: uppercase;
          font-weight: 600;
          background: orangered;
          color: white;
          border: none;
          font-size: 14px;
          letter-spacing: -0.1px;
          cursor: pointer;
      }
      
      form > button:disabled {
          background: dimgrey;
          cursor: not-allowed;
      }
      

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

      • nano ~/djangopush/templates/home.html

      Обновите раздел head для включения в него ссылки на внешнюю таблицу стилей:

      ~/djangopush/templates/home.html

      
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          ...
          <link href="https://www.digitalocean.com/{% static"/css/styles.css' %}" rel="stylesheet">
      </head>
      <body>
          ...
      </body>
      </html>
      

      Убедитесь, что вы находитесь в директории основного проекта, и снова запустите ваш сервер для проверки работы:

      • cd ~/djangopush
      • python manage.py runserver your_server_ip:8000

      При посещении http://your_server_ip:8080 страница должна выглядеть следующим образом:

      Представление домашней страницы Снова используйте сочетание клавиш CTRL+C для остановки сервера.

      Теперь, когда вы успешно создали страницу home.html и добавили для нее таблицу стилей, вы можете оформить для пользователей подписку на push-уведомления, когда бы они ни посетили домашнюю страницу.

      Шаг 7 — Регистрация service worker и подписка пользователей на push-уведомления

      Push-уведомления в веб могут уведомлять пользователей о наличии обновлений для приложений, на которые они подписаны, или для напоминания о возможности вспомнить приложение, которое они использовали в прошлом. Они опираются на две технологии, API push и API notifications. Обеим технологиям необходимо наличие service worker.

      Push-уведомление отправляется, когда сервер предоставляет информацию для service worker, а service worker использует API уведомлений для отображения этой информации.

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

      В директории static создайте папку с именем js:

      • mkdir ~/djangopush/static/js

      Создайте файл с именем registerSw.js:

      • nano ~/djangopush/static/js/registerSw.js

      Добавьте следующий код, который проверяет, поддерживает ли service worker'ы в браузере пользователя, прежде чем пытаться регистрировать service worker:

      ~/djangopush/static/js/registerSw.js

      
      const registerSw = async () => {
          if ('serviceWorker' in navigator) {
              const reg = await navigator.serviceWorker.register('sw.js');
              initialiseState(reg)
      
          } else {
              showNotAllowed("You can't send push notifications ☹️😢")
          }
      };
      

      Во-первых, функция registerSw проверяет, поддерживает ли браузер service worker'ы, прежде чем регистрировать их. После регистрации она вызывает функцию initializeState с данными регистрации. Если service worker'ы не поддерживаются в браузере, вызывается функция showNotAllowed.

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

      ~/djangopush/static/js/registerSw.js

      
      ...
      
      const initialiseState = (reg) => {
          if (!reg.showNotification) {
              showNotAllowed('Showing notifications isn't supported ☹️😢');
              return
          }
          if (Notification.permission === 'denied') {
              showNotAllowed('You prevented us from showing notifications ☹️🤔');
              return
          }
          if (!'PushManager' in window) {
              showNotAllowed("Push isn't allowed in your browser 🤔");
              return
          }
          subscribe(reg);
      }
      
      const showNotAllowed = (message) => {
          const button = document.querySelector('form>button');
          button.innerHTML = `${message}`;
          button.setAttribute('disabled', 'true');
      };
      

      Функция initializeState проверяет следующее:

      • Активировал ли пользователь уведомления или нет, использование значения reg.showNotification.
      • Предоставил ли пользователь приложению разрешение на отображение уведомлений или нет.
      • Поддерживает ли браузер API PushManager или нет. Если какая-либо из этих проверок не будет пройдена, функция showNotAllowed вызывается, а подписка отменяется.

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

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

      ~/djangopush/static/js/registerSw.js

      
      ...
      
      function urlB64ToUint8Array(base64String) {
          const padding = '='.repeat((4 - base64String.length % 4) % 4);
          const base64 = (base64String + padding)
              .replace(/-/g, '+')
              .replace(/_/g, '/');
      
          const rawData = window.atob(base64);
          const outputArray = new Uint8Array(rawData.length);
          const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
      
          return outputData;
      }
      
      const subscribe = async (reg) => {
          const subscription = await reg.pushManager.getSubscription();
          if (subscription) {
              sendSubData(subscription);
              return;
          }
      
          const vapidMeta = document.querySelector('meta[name="vapid-key"]');
          const key = vapidMeta.content;
          const options = {
              userVisibleOnly: true,
              // if key exists, create applicationServerKey property
              ...(key && {applicationServerKey: urlB64ToUint8Array(key)})
          };
      
          const sub = await reg.pushManager.subscribe(options);
          sendSubData(sub)
      };
      

      Вызов функции pushManager.getSubscription возвращает данные для активной подписки. При наличии активной подписки функция sendSubData вызывается, а информация подписки передается в качестве параметра.

      При отсутствии активной подписки публичный ключ VAPID, который шифруется с помощью алгоритма Base64, преобразовывается в Uint8Array с помощью функции urlB64TUint8Array. Затем вызывается функция pushManager.subscribe с публичным ключом VAPID и значением userVisible в качестве опции. Вы можете ознакомиться с доступными опциями здесь.

      После успешной подписки пользователя следующим шагом будет отправка данных подписки на сервер. Эти данные будут направляться на конечную точку webpush/save_information, предоставленную пакетом django-webpush. Добавьте следующий код под функцией subscribe:

      ~/djangopush/static/js/registerSw.js

      
      ...
      
      const sendSubData = async (subscription) => {
          const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
          const data = {
              status_type: 'subscribe',
              subscription: subscription.toJSON(),
              browser: browser,
          };
      
          const res = await fetch('/webpush/save_information', {
              method: 'POST',
              body: JSON.stringify(data),
              headers: {
                  'content-type': 'application/json'
              },
              credentials: "include"
          });
      
          handleResponse(res);
      };
      
      const handleResponse = (res) => {
          console.log(res.status);
      };
      
      registerSw();
      

      Конечной точке save_information требуется информация о состоянии подписки (subscribe и unsubscribe), данные подписки и браузер. Наконец, мы вызываем функцию registerSw() для запуска процесса подписки пользователя.

      Завершенный файл выглядит следующим образом:

      ~/djangopush/static/js/registerSw.js

      
      const registerSw = async () => {
          if ('serviceWorker' in navigator) {
              const reg = await navigator.serviceWorker.register('sw.js');
              initialiseState(reg)
      
          } else {
              showNotAllowed("You can't send push notifications ☹️😢")
          }
      };
      
      const initialiseState = (reg) => {
          if (!reg.showNotification) {
              showNotAllowed('Showing notifications isn't supported ☹️😢');
              return
          }
          if (Notification.permission === 'denied') {
              showNotAllowed('You prevented us from showing notifications ☹️🤔');
              return
          }
          if (!'PushManager' in window) {
              showNotAllowed("Push isn't allowed in your browser 🤔");
              return
          }
          subscribe(reg);
      }
      
      const showNotAllowed = (message) => {
          const button = document.querySelector('form>button');
          button.innerHTML = `${message}`;
          button.setAttribute('disabled', 'true');
      };
      
      function urlB64ToUint8Array(base64String) {
          const padding = '='.repeat((4 - base64String.length % 4) % 4);
          const base64 = (base64String + padding)
              .replace(/-/g, '+')
              .replace(/_/g, '/');
      
          const rawData = window.atob(base64);
          const outputArray = new Uint8Array(rawData.length);
          const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
      
          return outputData;
      }
      
      const subscribe = async (reg) => {
          const subscription = await reg.pushManager.getSubscription();
          if (subscription) {
              sendSubData(subscription);
              return;
          }
      
          const vapidMeta = document.querySelector('meta[name="vapid-key"]');
          const key = vapidMeta.content;
          const options = {
              userVisibleOnly: true,
              // if key exists, create applicationServerKey property
              ...(key && {applicationServerKey: urlB64ToUint8Array(key)})
          };
      
          const sub = await reg.pushManager.subscribe(options);
          sendSubData(sub)
      };
      
      const sendSubData = async (subscription) => {
          const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
          const data = {
              status_type: 'subscribe',
              subscription: subscription.toJSON(),
              browser: browser,
          };
      
          const res = await fetch('/webpush/save_information', {
              method: 'POST',
              body: JSON.stringify(data),
              headers: {
                  'content-type': 'application/json'
              },
              credentials: "include"
          });
      
          handleResponse(res);
      };
      
      const handleResponse = (res) => {
          console.log(res.status);
      };
      
      registerSw();
      

      Далее добавьте тег script для файла registerSw.js в home.html. Откройте файл:

      • nano ~/djangopush/templates/home.html

      Добавьте тег script перед закрывающим тегом элемента body:

      ~/djangopush/templates/home.html

      
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
         ...
      </head>
      <body>
         ...
         <script src="https://www.digitalocean.com/{% static"/js/registerSw.js' %}"></script>
      </body>
      </html>
      

      Поскольку service worker еще не существует, если бы вы оставили приложение запущенным или попытались запустить его снова, то увидели бы сообщение об ошибке. Давайте устраним эту проблему с помощью service worker.

      Шаг 8 — Создание service worker

      Для отображения push-уведомления вам потребуется активный service worker, установленный на домашней странице приложения. Мы создадим service worker, который прослушивает события push и отображает сообщения при готовности.

      Поскольку мы хотим, чтобы service worker покрывал весь домен, нам нужно установить его в корневой директории приложения. В статье о регистрации service worker вы можете подробнее ознакомиться с процессом. Согласно нашему подходу будет создан файл sw.js в папке templates, который мы позднее зарегистрируем в качестве представления.

      Создайте файл:

      • nano ~/djangopush/templates/sw.js

      Добавьте следующий код, который указывает service worker на необходимость прослушивания push событий:

      ~/djangopush/templates/sw.js

      
      // Register event listener for the 'push' event.
      self.addEventListener('push', function (event) {
          // Retrieve the textual payload from event.data (a PushMessageData object).
          // Other formats are supported (ArrayBuffer, Blob, JSON), check out the documentation
          // on https://developer.mozilla.org/en-US/docs/Web/API/PushMessageData.
          const eventInfo = event.data.text();
          const data = JSON.parse(eventInfo);
          const head = data.head || 'New Notification 🕺🕺';
          const body = data.body || 'This is default content. Your notification didn't have one 🙄🙄';
      
          // Keep the service worker alive until the notification is created.
          event.waitUntil(
              self.registration.showNotification(head, {
                  body: body,
                  icon: 'https://i.imgur.com/MZM3K5w.png'
              })
          );
      });
      

      Service worker следит за наличием push события. В функции обратного вызова данные события конвертируются в текст. Мы используем строки title и body, если в данных события они отсутствуют. Функция showNotification получает название уведомления, заголовок уведомления для отображения и объект options в качестве параметров. Объект options содержит несколько свойств для настройки визуальных параметров уведомления.

      Чтобы service worker мог работать для всего домена, вам потребуется выполнить его установки в корневой директории приложения. Мы будем использовать TemplateView для предоставления service worker доступа ко всему домену.

      Откройте файл urls.py:

      • nano ~/djangopush/djangopush/urls.py

      Добавьте новое объявление импорта и путь в список urlpatterns для создания представления на основе классов:

      ~/djangopush/djangopush/urls.py

      ...
      from django.views.generic import TemplateView
      
      urlpatterns = [
                        ...,
                        path('sw.js', TemplateView.as_view(template_name='sw.js', content_type='application/x-javascript'))
                    ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
      

      Представления на основе классов, такие как TemplateView, позволяют создавать гибкие и доступные для повторного использования представления. В этом случае метод TemplateView.as_view создает путь для service worker, передав недавно созданный service worker в качестве шаблона и application/x-javascript в качестве параметра content_type для шаблона.

      Вы уже создали service worker и зарегистрировали его в качестве маршрута. Далее вы настроите форму на домашней странице для отправки push-уведомлений.

      Шаг 9 — Отправка push-уведомлений

      Используя форму на домашней странице, пользователи смогут отправлять push-уведомления, пока ваш сервер запущен. Также вы можете отправить push-уведомления с помощью любой службы RESTful, например, Postman. Когда пользователь отправляет push-уведомления из формы на домашней странице, данные будут включать head и body, а также id получателя. Данные должны быть структурированы следующим образом:

      {
          head: "Title of the notification",
          body: "Notification body",
          id: "User's id"
      }
      

      Для прослушивания события submit формы и отправки данных, которые вводит пользователь, на сервер, мы создадим файл site.js в директории ~/djangopush/static/js.

      Откройте файл:

      • nano ~/djangopush/static/js/site.js

      Во-первых, добавьте прослушивателя событий submit в форму, что позволит получить значения для вводимых данных в форме и идентификатор пользователя, который хранится в теге meta вашего шаблона:

      ~/djangopush/static/js/site.js

      
      const pushForm = document.getElementById('send-push__form');
      const errorMsg = document.querySelector('.error');
      
      pushForm.addEventListener('submit', async function (e) {
          e.preventDefault();
          const input = this[0];
          const textarea = this[1];
          const button = this[2];
          errorMsg.innerText = '';
      
          const head = input.value;
          const body = textarea.value;
          const meta = document.querySelector('meta[name="user_id"]');
          const id = meta ? meta.content : null;
          ...
          // TODO: make an AJAX request to send notification
      });
      

      Функция pushForm получает input, textarea и button внутри формы. Также она получает информацию из тега meta, включая атрибут user_id и идентификатор пользователя, который хранится в атрибуте content тега. Получив эту информацию, она может отправить запрос POST на конечную точку /send_push на сервере.

      Для отправки запросов на сервер мы будем использовать нативную API Fetch. Мы используем Fetch здесь, поскольку API поддерживается большинством браузеров и не требует для работы внешних библиотек. Под добавленным ранее кодом обновите функцию pushForm для включения кода для отправки запросов AJAX:

      ~/djangopush/static/js/site.js

      const pushForm = document.getElementById('send-push__form');
      const errorMsg = document.querySelector('.error');
      
      pushForm.addEventListener('submit', async function (e) {
           ...
          const id = meta ? meta.content : null;
      
           if (head && body && id) {
              button.innerText = 'Sending...';
              button.disabled = true;
      
              const res = await fetch('/send_push', {
                  method: 'POST',
                  body: JSON.stringify({head, body, id}),
                  headers: {
                      'content-type': 'application/json'
                  }
              });
              if (res.status === 200) {
                  button.innerText = 'Send another 😃!';
                  button.disabled = false;
                  input.value = '';
                  textarea.value = '';
              } else {
                  errorMsg.innerText = res.message;
                  button.innerText = 'Something broke 😢..  Try again?';
                  button.disabled = false;
              }
          }
          else {
              let error;
              if (!head || !body){
                  error = 'Please ensure you complete the form 🙏🏾'
              }
              else if (!id){
                  error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
              }
              errorMsg.innerText = error;
          }
      });
      

      Если три обязательных параметра head, body и id присутствуют, мы отправим запрос и временно отключим кнопку submit.

      Завершенный файл выглядит следующим образом:

      ~/djangopush/static/js/site.js

      const pushForm = document.getElementById('send-push__form');
      const errorMsg = document.querySelector('.error');
      
      pushForm.addEventListener('submit', async function (e) {
          e.preventDefault();
          const input = this[0];
          const textarea = this[1];
          const button = this[2];
          errorMsg.innerText = '';
      
          const head = input.value;
          const body = textarea.value;
          const meta = document.querySelector('meta[name="user_id"]');
          const id = meta ? meta.content : null;
      
          if (head && body && id) {
              button.innerText = 'Sending...';
              button.disabled = true;
      
              const res = await fetch('/send_push', {
                  method: 'POST',
                  body: JSON.stringify({head, body, id}),
                  headers: {
                      'content-type': 'application/json'
                  }
              });
              if (res.status === 200) {
                  button.innerText = 'Send another 😃!';
                  button.disabled = false;
                  input.value = '';
                  textarea.value = '';
              } else {
                  errorMsg.innerText = res.message;
                  button.innerText = 'Something broke 😢..  Try again?';
                  button.disabled = false;
              }
          }
          else {
              let error;
              if (!head || !body){
                  error = 'Please ensure you complete the form 🙏🏾'
              }
              else if (!id){
                  error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
              }
              errorMsg.innerText = error;
          }    
      });
      

      После этого остается добавить файл site.js в файл home.html:

      • nano ~/djangopush/templates/home.html

      Добавьте тег script:

      ~/djangopush/templates/home.html

      
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
         ...
      </head>
      <body>
         ...
         <script src="https://www.digitalocean.com/{% static"/js/site.js' %}"></script>
      </body>
      </html>
      

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

      Шаг 10 — Создание защищенного туннеля для тестирования приложения

      Service worker'ы требуют наличия защищенных соединений для работы на любом сайте, кроме localhost, поскольку они не защищены от взлома для последующей фильтрации и подмены ответов. По этой причине мы создадим защищенный туннель для нашего сервера с помощью ngrok.

      Откройте второе окно командной строки и убедитесь, что вы находитесь в домашней директории:

      Если вы начали работу с чистым сервером на базе Ubuntu 18.04, вам потребуется выполнить установку unzip:

      • sudo apt update && sudo apt install unzip

      Загрузите ngrok:

      • wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
      • unzip ngrok-stable-linux-amd64.zip

      Переместите ngrok в /usr/local/bin, чтобы получить доступ к команде ngrok из командной строки:

      • sudo mv ngrok /usr/local/bin

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

      • cd ~/djangopush
      • python manage.py runserver your_server_ip:8000

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

      Во втором окне командной строки перейдите к папке проекта и активируйте виртуальную среду:

      • cd ~/djangopush
      • source my_env/bin/activate

      Создайте защищенный туннель для вашего приложения:

      • ngrok http your_server_ip:8000

      Вы увидите следующий вывод, который включает информацию о защищенном URL-адресе ngrok:

      Output

      ngrok by @inconshreveable (Ctrl+C to quit) Session Status online Session Expires 7 hours, 59 minutes Version 2.2.8 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://ngrok_secure_url -> 203.0.113.0:8000 Forwarding https://ngrok_secure_url -> 203.0.113.0:8000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00

      Скопируйте ngrok_secure_url из вывода консоли. Вы должны будете добавить его в список ALLOWED_HOSTS в файле settings.py.

      Откройте другое окно командной строки перейдите в папку проекта и активируйте виртуальную среду:

      • cd ~/djangopush
      • source my_env/bin/activate

      Откройте файл settings.py:

      • nano ~/djangopush/djangopush/settings.py

      Обновите список ALLOWED_HOSTS с защищенным туннелем ngrok:

      ~/djangopush/djangopush/settings.py

      ...
      
      ALLOWED_HOSTS = ['your_server_ip', 'ngrok_secure_url']
      ...
      
      

      Перейдите на защищенную страницу администратора для входа: https://ngrok_secure_url/admin/. Вы увидите экран, который будет выглядеть примерно так:

      Вход для администратора ngrok

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

      Введите https://ngrok_secure_url в адресной строке браузера. Вы увидите запрос разрешения на отображение уведомлений. Нажмите кнопку Allow (Разрешить), чтобы разрешить отображение push-уведомлений в браузере.

      запрос push-уведомлений

      Отправка заполненной формы будет отображать уведомлений примерно следующего вида:

      скриншот уведомления

      Примечание: убедитесь, что ваш сервер запущен, прежде чем пытаться отправить уведомления.

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

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

      Заключение

      В этом обучающем руководстве вы научились оформлять подписку пользователя для получения push-уведомлений, устанавливать service worker'ы и отображать push-уведомления с помощью API уведомлений.

      Вы можете пойти дальше и настроить уведомления для конкретных областей вашего приложения при нажатии. Исходный код для данного руководства можно найти здесь.



      Source link