前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何从Django应用程序发送Web推送通知

如何从Django应用程序发送Web推送通知

原创
作者头像
姚啊姚
修改2018-11-15 15:32:14
9.8K0
修改2018-11-15 15:32:14
举报
文章被收录于专栏:云计算教程系列

介绍

网络不断发展,现在可以实现以前只能在本机移动设备上使用的功能。JavaScript 服务工作者的引入为Web提供了新的功能,可以执行后台同步,脱机缓存和发送推送通知等功能

推送通知允许用户选择接收移动和Web应用程序的更新。它们还使用户能够使用自定义和相关内容重新使用现有应用程序。

在本教程中,您将在Ubuntu 18.04上设置一个Django应用程序,只要有需要用户访问应用程序的活动,就会发送推送通知。要创建这些通知,您将使用Django-Webpush包并设置和注册服务工作者以向客户端显示通知。带通知的工作应用程序如下所示:

先决条件

在开始本指南之前,您需要以下内容:

  • 一个Ubuntu 18.04服务器,具有非root用户和活动防火墙。没有服务器的同学可以在这里购买,不过我个人更推荐您使用免费的腾讯云开发者实验室进行试验,学会安装后再购买服务器
  • 遵循这些准则安装pipvenv
  • 在您的主目录中创建一个名为djangopush的项目,按照这些关于在Ubuntu上创建示例Django项目的指南进行设置。务必将服务器的IP地址添加到settings.py文件中ALLOWED_HOSTS的指令中。

第1步 - 安装Django-Webpush并获取Vapid密钥

Django-Webpush是一个允许开发人员在Django应用程序中集成和发送Web推送通知的软件包。我们将使用此包来触发和发送来自我们应用程序的推送通知。在此步骤中,您将安装Django-Webpush并获取识别服务器所需的自愿应用程序服务器标识(VAPID)密钥,并确保每个请求的唯一性。

确保您位于先决条件中创建的~/djangopush项目目录中:

代码语言:javascript
复制
cd ~/djangopush

激活您的虚拟环境:

代码语言:javascript
复制
source my_env/bin/activate

升级您的pip版本以确保它是最新的:

代码语言:javascript
复制
pip install --upgrade pip

安装Django-Webpush:

代码语言:javascript
复制
pip install django-webpush

安装软件包后,将其添加到settings.py文件中的应用程序列表中。首先打开settings.py

代码语言:javascript
复制
nano ~/djangopush/djangopush/settings.py

添加webpush到以下INSTALLED_APPS列表中:

代码语言:javascript
复制
...
​
INSTALLED_APPS = [
    ...,
    'webpush',
]
...

保存文件并退出编辑器。

在应用程序上运行迁移以应用您对数据库模式所做的更改:

代码语言:javascript
复制
python manage.py migrate

输出将如下所示,表示迁移成功:

代码语言:javascript
复制
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, webpush
Running migrations:
  Applying webpush.0001_initial... OK

设置Web推送通知的下一步是获取VAPID密钥。这些密钥标识应用程序服务器,可用于减少推送订阅URL的保密性,因为它们限制对特定服务器的订阅。

要获取VAPID密钥,请导航到wep-push-codelab Web应用程序。在这里,您将获得自动生成的密钥。复制私钥和公钥。

接下来,在settings.py中为您的VAPID信息创建一个新条目。首先,打开文件:

代码语言:javascript
复制
nano ~/djangopush/djangopush/settings.py

接下来,使用您的VAPID公钥和私钥创建一个名为WEBPUSH_SETTINGS的新指令以及在下AUTH_PASSWORD_VALIDATORS面的电子邮件:

代码语言:javascript
复制
...
​
AUTH_PASSWORD_VALIDATORS = [
    ...
]
​
WEBPUSH_SETTINGS = {
   "VAPID_PUBLIC_KEY": "your_vapid_public_key",
   "VAPID_PRIVATE_KEY": "your_vapid_private_key",
   "VAPID_ADMIN_EMAIL": "admin@example.com"
}
​
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
​
...

不要忘了使用自己的信息替换值为your_vapid_public_keyyour_vapid_private_key以及admin@example.com的占位符。如果推送服务器遇到任何问题,您的电子邮件地址就是通知您的方式。

接下来,我们将设置视图,以显示应用程序的主页并向订阅用户触发推送通知。

第2步 - 设置视图

在此步骤中,我们将使用HttpResponse响应对象来设置基本的home 视图send_push视图。视图是从Web请求返回响应对象的函数。该 send_push 视图将使用Django-Webpush库发送包含用户在主页上输入的数据的推送通知。

导航到该~/djangopush/djangopush文件夹:

代码语言:javascript
复制
cd ~/djangopush/djangopush

在文件夹内运行ls将显示项目的主文件:

代码语言:javascript
复制
/__init__.py
/settings.py
/urls.py
/wsgi.py

此文件夹中的文件由您用于在先决条件中创建项目的django-admin实用程序自动生成。该settings.py文件包含项目范围的配置,如已安装的应用程序和静态根文件夹。该urls.py文件包含项目的URL配置。您可以在此处设置路线以匹配您创建的视图。

~/djangopush/djangopush目录中创建一个名为 views.py的新文件,该文件将包含项目的视图:

代码语言:javascript
复制
nano ~/djangopush/djangopush/views.py

我们要做的第一个视图是home视图,它将显示用户可以发送推送通知的主页。将以下代码添加到文件中:

代码语言:javascript
复制
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响应并访问webpush库中的send_user_notification函数:

代码语言:javascript
复制
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装饰器,它将使用用户发送的请求主体来创建并触发推送通知:

代码语言:javascript
复制
@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文档反序列化为使用json.loads的Python对象。json.loads获取结构化JSON文档并将其转换为Python对象。

视图期望请求主体对象具有三个属性:

  • head:推送通知的标题。
  • body:通知的正文。
  • idid请求用户的。

如果缺少任何必需的属性,视图将返回JSONResponse并且呈现404“未找到”的状态。如果与给定的主密钥的用户存在,该视图将使用所述匹配的主键来返回user,该主键使用来自django.shortcuts库的get_object_or_404函数。如果用户不存在,该函数将返回404错误。

该视图还使用了webpush库中的send_user_notification函数。该函数有三个参数:

  • User:推送通知的收件人。
  • payload:通知信息,包括通知headbody
  • ttl:用户脱机时应存储通知的最长时间(以秒为单位)。

如果没有错误发生,视图将返回JSONResponse并且呈现200“成功”的状态和一个数据对象。如果KeyError发生,则视图将返回500“内部服务器错误”状态。当对象的请求键不存在时发生KeyError.

在下一步中,我们将创建相应的URL路由以匹配我们创建的视图。

第3步 - 将URL映射到视图

Django可以创建使用名为URLconf的Python模块连接到视图的URL。此模块将URL路径表达式映射到Python函数(您的视图)。通常,在创建项目时会自动生成URL配置文件。在此步骤中,您将更新此文件以包含您在上一步中创建的视图的新路由以及django-webpush应用程序的URL ,这将为订阅用户提供推送通知的端点。

开放urls.py

代码语言:javascript
复制
nano ~/djangopush/djangopush/urls.py

该文件将如下所示:

代码语言:javascript
复制
"""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库的所有路由添加到项目中:

代码语言:javascript
复制
"""webpushdjango URL Configuration
...
"""
from django.contrib import admin
from django.urls import path, include

接下来,导入您在上一步中创建的视图,并更新urlpatterns列表以映射您的视图:

代码语言:javascript
复制
"""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列表会注册django-webpush包的URL,并将您的视图映射到URL /send_push/home

让我们测试/home视图以确保它按预期工作。确保您位于项目的根目录中:

代码语言:javascript
复制
cd ~/djangopush

运行以下命令启动服务器:

代码语言:javascript
复制
python manage.py runserver your_server_ip:8000

导航到http://your_server_ip:8000。您应该看到以下主页:

此时,您可以使用该CTRL+C命令终止服务器,我们将继续创建模板并使用该render功能在视图中呈现它们。

第4步 - 创建模板

Django的模板引擎允许您使用与HTML文件类似的模板定义应用程序的面向用户层。在此步骤中,您将为home视图创建和呈现模板。

在项目的根目录中创建一个名为templates的文件夹:

代码语言:javascript
复制
mkdir ~/djangopush/templates

如果此时在项目的根文件夹中运行ls,输出将如下所示:

代码语言:javascript
复制
/djangopush
/templates
db.sqlite3
manage.py
/my_env

templates文件夹中创建一个名为home.html的文件:

代码语言:javascript
复制
nano ~/djangopush/templates/home.html

将以下代码添加到文件中以创建一个表单,用户可以在其中输入信息以创建推送通知:

代码语言:javascript
复制
{% 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公钥和用户的id。注册用户并向其发送推送通知需要这两个变量。此处需要用户的ID,因为您将向服务器发送AJAX请求,并将id用于标识用户。如果当前用户是注册用户,则模板将创建一个meta标签,并将其id作为内容。

下一步是告诉Django在哪里找到你的模板。为此,您将编辑settings.py和更新TEMPLATES列表。

打开settings.py文件:

代码语言:javascript
复制
nano ~/djangopush/djangopush/settings.py

将以下内容添加到DIRS列表中以指定templates目录的路径:

代码语言:javascript
复制
...
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模板。打开文件:

代码语言:javascript
复制
nano ~/djangpush/djangopush/views.py

首先,添加一些其他导入,包括settings配置,其中包含settings.py文件中的所有项目设置,以及以下django.shortcuts中的render函数:

代码语言:javascript
复制
...
from django.shortcuts import render, get_object_or_404
...
import json
from django.conf import settings
​
...

接下来,删除您添加到home视图中的初始代码并添加以下内容,指定您刚创建的模板的呈现方式:

代码语言:javascript
复制
...
​
@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:这将从settings配置中分配WEBPUSH_SETTINGS属性的值。
  • vapid_key:这将从要发送到客户端的webpush_settings对象获取VAPID_PUBLIC_KEY值。根据私钥检查此公钥,以确保允许具有公钥的客户端从服务器接收推送消息。
  • user:此变量来自传入请求。每当用户向服务器发出请求时,该用户的详细信息都存储在该user字段中。

render函数将返回一个HTML文件和一个包含当前用户和服务器的vapid公钥的上下文对象。在这里有三个参数:request,将被提出的template,并且对象包含将在模板中使用的变量。

通过创建模板并更新home视图,我们可以继续配置Django来提供静态文件。

第5步 - 提供静态文件

Web应用程序包括CSS,JavaScript和Django称为“静态文件”的其他图像文件。Django允许您将项目中每个应用程序的所有静态文件收集到一个位置,从中提供服务。这个解决方案被称为django.contrib.staticfiles。在这一步中,我们将更新我们的设置,告诉Django我们的静态文件将存储在哪里。

开放settings.py

代码语言:javascript
复制
nano ~/djangopush/djangopush/settings.py

settings.py中,首先确保STATIC_URL已被定义:

代码语言:javascript
复制
...
STATIC_URL = '/static/'

接下来,添加一个名为STATICFILES_DIRS的目录列表,Django将在该列表中查找静态文件:

代码语言:javascript
复制
...
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
]

您现在可以将STATIC_URL添加到urls.py文件中定义的路径列表中。

打开文件:

代码语言:javascript
复制
nano ~/djangopush/djangopush/urls.py

添加以下代码,该代码将导入staticurl配置并更新urlpatterns列表。这里的辅助函数使用了我们在settings.py的文件中提供的STATIC_URLSTATIC_ROOT的属性来服务该项目的静态文件:

代码语言:javascript
复制
...
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文件夹,并在其中再创建一个css文件夹:

代码语言:javascript
复制
mkdir -p ~/djangopush/static/css

css的文件夹里面打开一个名为styles.css的css文件:

代码语言:javascript
复制
nano ~/djangopush/static/css/styles.css

为主页添加以下样式:

代码语言:javascript
复制
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文件:

代码语言:javascript
复制
nano ~/djangopush/templates/home.html

更新该head部分以包含指向外部样式表的链接:

代码语言:javascript
复制
{% load static %}
<!DOCTYPE html>
<html lang="en">
​
<head>
    ...
    <link href="{% static '/css/styles.css' %}" rel="stylesheet">
</head>
<body>
    ...
</body>
</html>

确保您位于主项目目录中并再次启动服务器以检查您的工作:

代码语言:javascript
复制
cd ~/djangopush
python manage.py runserver your_server_ip:8000

当您访问http://your_server_ip:8000时,它应如下所示:

再一次,你可以键入CTRL+C来停止服务器。

现在您已成功创建home.html页面并对其进行样式设置,您可以订阅用户在访问主页时推送通知。

步骤7 - 注册服务工作者和订阅用户以推送通知

Web推送通知可以在订阅了应用程序的更新时通知用户,或者提示他们重新使用他们过去使用过的应用程序。它们依赖于两种技术,即推送 API和通知 API。这两种技术都依赖于服务工作者的存在。

当服务器向服务工作者提供信息并且服务工作者使用通知API显示此信息时,将调用推送。

我们将订阅我们的用户推送,然后我们将订阅的信息发送到服务器进行注册。

static目录中,创建一个名为js的文件夹:

代码语言:javascript
复制
mkdir ~/djangopush/static/js 

创建一个名为registerSw.js的文件:

代码语言:javascript
复制
nano ~/djangopush/static/js/registerSw.js

添加以下代码,在尝试注册服务工作者之前检查用户浏览器是否支持服务工作者:

代码语言:javascript
复制
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函数在注册之前检查浏览器是否支持服务工作者。注册后,它会使用注册数据调用该initializeState函数。如果浏览器不支持服务工作者,则调用该showNotAllowed函数。

接下来,在registerSw函数下方添加以下代码,以检查用户是否有资格在尝试订阅之前接收推送通知:

代码语言:javascript
复制
...
​
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的值。
  • 用户是否已授予显示通知的应用程序权限。
  • 浏览器是否支持PushManagerAPI。如果这些检查中的任何一个失败,showNotAllowed则调用该函数并中止订阅。

showNotAllowed函数在按钮上显示一条消息,如果用户没有资格接收通知,则禁用该消息。如果用户限制应用程序显示通知或浏览器不支持推送通知,它还会显示相应的消息。

一旦我们确保用户有资格接收推送通知,下一步就是使用pushManager订阅它们。在showNotAllowed函数下面添加以下代码:

代码语言:javascript
复制
...
​
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函数,并将订阅信息作为参数传入。

如果不存在活动订阅,则使用该urlB64ToUint8Array函数将V64ID公钥(Base64 URL安全编码)转换为Uint8Array 。然后使用VAPID公钥和userVisible值作为选项调用pushManager.subscribe。您可以在此处阅读有关可用选项的更多信息。

成功订阅用户后,下一步是将订阅数据发送到服务器。数据将被发送到django-webpush包提供的webpush/save_information端点。在subscribe函数下面添加以下代码:

代码语言:javascript
复制
...
​
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端点需要订阅的状态信息(subscribeunsubscribe),预订数据,以及浏览器。最后,我们调用该registerSw()函数开始订阅用户的过程。

完成的文件如下所示:

代码语言:javascript
复制
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();

接下来,为registerSw.js文件在home.html中添加script标记。打开文件:

代码语言:javascript
复制
nano ~/djangopush/templates/home.html

body元素的结束标记之前添加script标记:

代码语言:javascript
复制
{% load static %}
<!DOCTYPE html>
<html lang="en">
​
<head>
   ...
</head>
<body>
   ...
   <script src="{% static '/js/registerSw.js' %}"></script>
</body>
</html>

由于服务工作者尚不存在,如果您使应用程序继续运行或尝试再次启动它,您将看到错误消息。让我们通过创建服务工作者来解决这个问题。

第8步 - 创建服务工作者

要显示推送通知,您需要在应用程序主页上安装活动服务工作程序。我们将创建一个服务工作者来监听push事件并在准备好后显示消息。

因为我们希望服务工作者的范围是整个域,所以我们需要将其安装在应用程序的根目录中。您可以在本文中详细了解如何注册服务工作者的过程。我们的方法是在templates文件夹中创建一个sw.js文件,然后我们将其注册为视图。

创建文件:

代码语言:javascript
复制
nano ~/djangopush/templates/sw.js

添加以下代码,告诉服务工作者监听推送事件:

代码语言:javascript
复制
// 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'
        })
    );
});

服务工作者监听推送事件。在回调函数中,event数据将转换为文本。如果事件数据没有,我们使用默认值titlebody字符串。该showNotification函数将通知标题,要显示的通知的标题和选项对象作为参数。options对象包含几个属性,用于配置通知的可视选项。

要使您的服务工作者能够在整个域中工作,您需要将其安装在应用程序的根目录中。我们将使用TemplateView允许服务工作者访问整个域。

打开urls.py文件:

代码语言:javascript
复制
nano ~/djangopush/djangopush/urls.py

urlpatterns列表中添加新的import语句和路径以创建基于类的视图:

代码语言:javascript
复制
...
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方法通过将最近创建的服务人员为模板,将application/x-javascript建立为content_type的模板,来为服务人员建立路径。

您现在已经创建了一个服务工作者并将其注册为路由。接下来,您将在主页上设置表单以发送推送通知。

第9步 - 发送推送通知

使用主页上的表单,用户应该能够在服务器运行时发送推送通知。您还可以使用Postman等任何RESTful服务发送推送通知。当用户从主页上的表单发送推送通知时,数据将包括headbody以及接收用户的id。数据应按以下方式构建:

代码语言:javascript
复制
{
    head: "Title of the notification",
    body: "Notification body",
    id: "User's id"
}

要监听submit表单事件并将用户输入的数据发送到服务器,我们将在~/djangopush/static/js目录中创建一个名为site.js的文件。

打开文件:

代码语言:javascript
复制
nano ~/djangopush/static/js/site.js 

首先,向表单添加一个submit事件监听器,使您能够获取表单输入的值和存储在模板meta标记中的用户标识:

代码语言:javascript
复制
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函数在表单内获取inputtextarea以及button。它还从meta标记中获取信息,包括name属性user_id和存储在content标记属性中的用户id 。有了这些信息,它就可以向/send_push服务器端点发送POST请求。

要将请求发送到服务器,我们将使用本机Fetch API。我们在这里使用Fetch是因为大多数浏览器都支持它,并且不需要外部库来运行。在您添加的代码下方,更新pushForm函数以包含发送AJAX请求的代码:

代码语言:javascript
复制
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;
    }
});

如果需要三个参数headbody以及id存在,我们发送请求并暂时禁用提交按钮。

完成的文件如下所示:

代码语言:javascript
复制
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

代码语言:javascript
复制
nano ~/djangopush/templates/home.html

添加script标签:

代码语言:javascript
复制
{% load static %}
<!DOCTYPE html>
<html lang="en">
​
<head>
   ...
</head>
<body>
   ...
   <script src="{% static '/js/site.js' %}"></script>
</body>
</html>

此时,如果您让应用程序继续运行或尝试再次启动它,您将看到错误,因为服务工作者只能在安全域或localhost中运行。在下一步中,我们将使用ngrok创建到我们的Web服务器的安全隧道。

步骤10 - 创建安全隧道以测试应用程序

服务工作者需要安全连接才能在任何站点上运行除了localhost因为他们可以允许连接被劫持并且响应被过滤和制作。出于这个原因,我们将使用ngrok为我们的服务器创建一个安全隧道。

打开第二个终端窗口,确保您在主目录中:

代码语言:javascript
复制
cd ~

如果您在先决条件中使用干净的18.04服务器,那么您将需要安装unzip

代码语言:javascript
复制
sudo apt update && sudo apt install unzip

下载ngrok:

代码语言:javascript
复制
wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
unzip ngrok-stable-linux-amd64.zip

移动ngrok/usr/local/bin,这样你将有机会从终端命令获得ngrok

代码语言:javascript
复制
sudo mv ngrok /usr/local/bin

在第一个终端窗口中,确保您位于项目目录中并启动服务器:

代码语言:javascript
复制
cd ~/djangopush
python manage.py runserver your_server_ip:8000

在为应用程序创建安全隧道之前,您需要执行此操作。

在第二个终端窗口中,导航到项目文件夹,然后激活虚拟环境:

代码语言:javascript
复制
cd ~/djangopush
source my_env/bin/activate

创建应用程序的安全隧道:

代码语言:javascript
复制
ngrok http your_server_ip:8000

您将看到以下输出,其中包含有关您的安全ngrok URL的信息:

代码语言:javascript
复制
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。您需要在您的settings.py文件中将其添加到ALLOWED_HOSTS列表中。

打开另一个终端窗口,导航到项目文件夹,然后激活虚拟环境:

代码语言:javascript
复制
cd ~/djangopush
source my_env/bin/activate

打开settings.py文件:

代码语言:javascript
复制
nano ~/djangopush/djangopush/settings.py

使用ngrok安全隧道更新ALLOWED_HOSTS列表:

代码语言:javascript
复制
...
​
ALLOWED_HOSTS = ['your_server_ip', 'ngrok_secure_url']
...

导航到安全管理页面以登录https://ngrok_secure_url/admin/:。您将看到如下所示的屏幕:

在此屏幕上输入您的Django管理员用户信息。您现在可以发送推送通知了。

在您的浏览器中访问https://ngrok_secure_url。您将看到一个提示,要求获得显示通知的权限。单击“ 允许”按钮,让浏览器显示推送通知:

提交填写的表单将显示类似于此的通知:

注意:在尝试发送通知之前,请确保您的服务器正在运行。

如果您收到通知,那么您的应用程序正在按预期工作。

您已经创建了一个Web应用程序,可以在服务器上触发推送通知,并在服务工作者的帮助下接收并显示通知。您还完成了获取从应用程序服务器发送推送通知所需的VAPID密钥的步骤。

结论

在本教程中,您学习了如何使用通知API订阅用户以推送通知,安装服务工作者和显示推送通知。

您可以进一步配置通知,以便在单击时打开应用程序的特定区域。可以在此处找到本教程的源代码。

更多Linux教程请前往腾讯云+社区学习更多知识。


参考文献:《 How To Send Web Push Notifications from Django Applications》

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 先决条件
  • 第1步 - 安装Django-Webpush并获取Vapid密钥
  • 第2步 - 设置视图
  • 第3步 - 将URL映射到视图
  • 第4步 - 创建模板
  • 第5步 - 提供静态文件
  • 第6步 - 设置主页样式
  • 步骤7 - 注册服务工作者和订阅用户以推送通知
  • 第8步 - 创建服务工作者
  • 第9步 - 发送推送通知
  • 步骤10 - 创建安全隧道以测试应用程序
  • 结论
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档