网络不断发展,现在可以实现以前只能在本机移动设备上使用的功能。JavaScript 服务工作者的引入为Web提供了新的功能,可以执行后台同步,脱机缓存和发送推送通知等功能。
推送通知允许用户选择接收移动和Web应用程序的更新。它们还使用户能够使用自定义和相关内容重新使用现有应用程序。
在本教程中,您将在Ubuntu 18.04上设置一个Django应用程序,只要有需要用户访问应用程序的活动,就会发送推送通知。要创建这些通知,您将使用Django-Webpush包并设置和注册服务工作者以向客户端显示通知。带通知的工作应用程序如下所示:
在开始本指南之前,您需要以下内容:
pip
和venv
。djangopush
的项目,按照这些关于在Ubuntu上创建示例Django项目的指南进行设置。务必将服务器的IP地址添加到settings.py
文件中ALLOWED_HOSTS
的指令中。Django-Webpush是一个允许开发人员在Django应用程序中集成和发送Web推送通知的软件包。我们将使用此包来触发和发送来自我们应用程序的推送通知。在此步骤中,您将安装Django-Webpush并获取识别服务器所需的自愿应用程序服务器标识(VAPID)密钥,并确保每个请求的唯一性。
确保您位于先决条件中创建的~/djangopush
项目目录中:
cd ~/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
列表中:
...
INSTALLED_APPS = [
...,
'webpush',
]
...
保存文件并退出编辑器。
在应用程序上运行迁移以应用您对数据库模式所做的更改:
python manage.py migrate
输出将如下所示,表示迁移成功:
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信息创建一个新条目。首先,打开文件:
nano ~/djangopush/djangopush/settings.py
接下来,使用您的VAPID公钥和私钥创建一个名为WEBPUSH_SETTINGS
的新指令以及在下AUTH_PASSWORD_VALIDATORS
面的电子邮件:
...
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_key
,your_vapid_private_key
以及admin@example.com
的占位符。如果推送服务器遇到任何问题,您的电子邮件地址就是通知您的方式。
接下来,我们将设置视图,以显示应用程序的主页并向订阅用户触发推送通知。
在此步骤中,我们将使用HttpResponse
响应对象来设置基本的home
视图和send_push
视图。视图是从Web请求返回响应对象的函数。该 send_push
视图将使用Django-Webpush库发送包含用户在主页上输入的数据的推送通知。
导航到该~/djangopush/djangopush
文件夹:
cd ~/djangopush/djangopush
在文件夹内运行ls
将显示项目的主文件:
/__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
视图,它将显示用户可以发送推送通知的主页。将以下代码添加到文件中:
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
函数:
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
装饰器,它将使用用户发送的请求主体来创建并触发推送通知:
@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
:通知的正文。id
:id
请求用户的。如果缺少任何必需的属性,视图将返回JSONResponse
并且呈现404“未找到”的状态。如果与给定的主密钥的用户存在,该视图将使用所述匹配的主键来返回user
,该主键使用来自django.shortcuts
库的get_object_or_404
函数。如果用户不存在,该函数将返回404错误。
该视图还使用了webpush
库中的send_user_notification
函数。该函数有三个参数:
User
:推送通知的收件人。payload
:通知信息,包括通知head
和body
。ttl
:用户脱机时应存储通知的最长时间(以秒为单位)。如果没有错误发生,视图将返回JSONResponse
并且呈现200“成功”的状态和一个数据对象。如果KeyError
发生,则视图将返回500“内部服务器错误”状态。当对象的请求键不存在时发生KeyError
.
在下一步中,我们将创建相应的URL路由以匹配我们创建的视图。
Django可以创建使用名为URLconf
的Python模块连接到视图的URL。此模块将URL路径表达式映射到Python函数(您的视图)。通常,在创建项目时会自动生成URL配置文件。在此步骤中,您将更新此文件以包含您在上一步中创建的视图的新路由以及django-webpush
应用程序的URL ,这将为订阅用户提供推送通知的端点。
开放urls.py
:
nano ~/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库的所有路由添加到项目中:
"""webpushdjango URL Configuration
...
"""
from django.contrib import admin
from django.urls import path, include
接下来,导入您在上一步中创建的视图,并更新urlpatterns
列表以映射您的视图:
"""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
视图以确保它按预期工作。确保您位于项目的根目录中:
cd ~/djangopush
运行以下命令启动服务器:
python manage.py runserver your_server_ip:8000
导航到http://your_server_ip:8000
。您应该看到以下主页:
此时,您可以使用该CTRL+C
命令终止服务器,我们将继续创建模板并使用该render
功能在视图中呈现它们。
Django的模板引擎允许您使用与HTML文件类似的模板定义应用程序的面向用户层。在此步骤中,您将为home
视图创建和呈现模板。
在项目的根目录中创建一个名为templates
的文件夹:
mkdir ~/djangopush/templates
如果此时在项目的根文件夹中运行ls
,输出将如下所示:
/djangopush
/templates
db.sqlite3
manage.py
/my_env
在templates
文件夹中创建一个名为home.html
的文件:
nano ~/djangopush/templates/home.html
将以下代码添加到文件中以创建一个表单,用户可以在其中输入信息以创建推送通知:
{% 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
文件:
nano ~/djangopush/djangopush/settings.py
将以下内容添加到DIRS
列表中以指定templates目录的路径:
...
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
文件中的所有项目设置,以及以下django.shortcuts
中的render
函数:
...
from django.shortcuts import render, get_object_or_404
...
import json
from django.conf import settings
...
接下来,删除您添加到home
视图中的初始代码并添加以下内容,指定您刚创建的模板的呈现方式:
...
@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来提供静态文件。
Web应用程序包括CSS,JavaScript和Django称为“静态文件”的其他图像文件。Django允许您将项目中每个应用程序的所有静态文件收集到一个位置,从中提供服务。这个解决方案被称为django.contrib.staticfiles
。在这一步中,我们将更新我们的设置,告诉Django我们的静态文件将存储在哪里。
开放settings.py
:
nano ~/djangopush/djangopush/settings.py
在settings.py
中,首先确保STATIC_URL
已被定义:
...
STATIC_URL = '/static/'
接下来,添加一个名为STATICFILES_DIRS
的目录列表,Django将在该列表中查找静态文件:
...
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
您现在可以将STATIC_URL
添加到urls.py
文件中定义的路径列表中。
打开文件:
nano ~/djangopush/djangopush/urls.py
添加以下代码,该代码将导入static
url配置并更新urlpatterns
列表。这里的辅助函数使用了我们在settings.py
的文件中提供的STATIC_URL
和STATIC_ROOT
的属性来服务该项目的静态文件:
...
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
通过配置我们的静态文件设置,我们可以继续设置应用程序主页的样式。
在设置应用程序以提供静态文件后,您可以创建外部样式表并将其链接到home.html
文件以设置主页样式。所有静态文件都将存储在项目根文件夹的static
目录中。
创建一个static
文件夹,并在其中再创建一个css
文件夹:
mkdir -p ~/djangopush/static/css
在css
的文件夹里面打开一个名为styles.css
的css文件:
nano ~/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
部分以包含指向外部样式表的链接:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
<link href="{% static '/css/styles.css' %}" rel="stylesheet">
</head>
<body>
...
</body>
</html>
确保您位于主项目目录中并再次启动服务器以检查您的工作:
cd ~/djangopush
python manage.py runserver your_server_ip:8000
当您访问http://your_server_ip:8000
时,它应如下所示:
再一次,你可以键入CTRL+C
来停止服务器。
现在您已成功创建home.html
页面并对其进行样式设置,您可以订阅用户在访问主页时推送通知。
Web推送通知可以在订阅了应用程序的更新时通知用户,或者提示他们重新使用他们过去使用过的应用程序。它们依赖于两种技术,即推送 API和通知 API。这两种技术都依赖于服务工作者的存在。
当服务器向服务工作者提供信息并且服务工作者使用通知API显示此信息时,将调用推送。
我们将订阅我们的用户推送,然后我们将订阅的信息发送到服务器进行注册。
在static
目录中,创建一个名为js
的文件夹:
mkdir ~/djangopush/static/js
创建一个名为registerSw.js
的文件:
nano ~/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
函数在注册之前检查浏览器是否支持服务工作者。注册后,它会使用注册数据调用该initializeState
函数。如果浏览器不支持服务工作者,则调用该showNotAllowed
函数。
接下来,在registerSw
函数下方添加以下代码,以检查用户是否有资格在尝试订阅之前接收推送通知:
...
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
的值。PushManager
API。如果这些检查中的任何一个失败,showNotAllowed
则调用该函数并中止订阅。该showNotAllowed
函数在按钮上显示一条消息,如果用户没有资格接收通知,则禁用该消息。如果用户限制应用程序显示通知或浏览器不支持推送通知,它还会显示相应的消息。
一旦我们确保用户有资格接收推送通知,下一步就是使用pushManager
订阅它们。在showNotAllowed
函数下面添加以下代码:
...
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
函数下面添加以下代码:
...
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()
函数开始订阅用户的过程。
完成的文件如下所示:
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
标记。打开文件:
nano ~/djangopush/templates/home.html
在body
元素的结束标记之前添加script
标记:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
<script src="{% static '/js/registerSw.js' %}"></script>
</body>
</html>
由于服务工作者尚不存在,如果您使应用程序继续运行或尝试再次启动它,您将看到错误消息。让我们通过创建服务工作者来解决这个问题。
要显示推送通知,您需要在应用程序主页上安装活动服务工作程序。我们将创建一个服务工作者来监听push
事件并在准备好后显示消息。
因为我们希望服务工作者的范围是整个域,所以我们需要将其安装在应用程序的根目录中。您可以在本文中详细了解如何注册服务工作者的过程。我们的方法是在templates
文件夹中创建一个sw.js
文件,然后我们将其注册为视图。
创建文件:
nano ~/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'
})
);
});
服务工作者监听推送事件。在回调函数中,event
数据将转换为文本。如果事件数据没有,我们使用默认值title
和body
字符串。该showNotification
函数将通知标题,要显示的通知的标题和选项对象作为参数。options对象包含几个属性,用于配置通知的可视选项。
要使您的服务工作者能够在整个域中工作,您需要将其安装在应用程序的根目录中。我们将使用TemplateView
允许服务工作者访问整个域。
打开urls.py
文件:
nano ~/djangopush/djangopush/urls.py
在urlpatterns
列表中添加新的import语句和路径以创建基于类的视图:
...
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的
模板,来为服务人员建立路径。
您现在已经创建了一个服务工作者并将其注册为路由。接下来,您将在主页上设置表单以发送推送通知。
使用主页上的表单,用户应该能够在服务器运行时发送推送通知。您还可以使用Postman等任何RESTful服务发送推送通知。当用户从主页上的表单发送推送通知时,数据将包括head
和body
以及接收用户的id
。数据应按以下方式构建:
{
head: "Title of the notification",
body: "Notification body",
id: "User's id"
}
要监听submit
表单事件并将用户输入的数据发送到服务器,我们将在~/djangopush/static/js
目录中创建一个名为site.js
的文件。
打开文件:
nano ~/djangopush/static/js/site.js
首先,向表单添加一个submit
事件监听器,使您能够获取表单输入的值和存储在模板meta
标记中的用户标识:
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
标记中获取信息,包括name属性user_id
和存储在content
标记属性中的用户id 。有了这些信息,它就可以向/send_push
服务器端点发送POST请求。
要将请求发送到服务器,我们将使用本机Fetch API。我们在这里使用Fetch是因为大多数浏览器都支持它,并且不需要外部库来运行。在您添加的代码下方,更新pushForm
函数以包含发送AJAX请求的代码:
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
存在,我们发送请求并暂时禁用提交按钮。
完成的文件如下所示:
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
标签:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
<script src="{% static '/js/site.js' %}"></script>
</body>
</html>
此时,如果您让应用程序继续运行或尝试再次启动它,您将看到错误,因为服务工作者只能在安全域或localhost
中运行。在下一步中,我们将使用ngrok创建到我们的Web服务器的安全隧道。
服务工作者需要安全连接才能在任何站点上运行除了localhost
因为他们可以允许连接被劫持并且响应被过滤和制作。出于这个原因,我们将使用ngrok为我们的服务器创建一个安全隧道。
打开第二个终端窗口,确保您在主目录中:
cd ~
如果您在先决条件中使用干净的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
您将看到以下输出,其中包含有关您的安全ngrok URL的信息:
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
列表中。
打开另一个终端窗口,导航到项目文件夹,然后激活虚拟环境:
cd ~/djangopush
source my_env/bin/activate
打开settings.py
文件:
nano ~/djangopush/djangopush/settings.py
使用ngrok安全隧道更新ALLOWED_HOSTS
列表:
...
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 删除。