基本概念
(1)webapp
网络应用,是指使用web技术实现的应用程序,在web浏览器或其它web运行环境的上下文中执行。
(2)application server
网络应用的服务器,是指webapp的服务器端。
(3)push message
推送消息,是指application server发送给webapp的消息。application server在推送消息给推送服务(例如,GCM)时,会带上push subscription,推送服务会找到与push subscription相关联的active worker ,然后把消息推送给它,并触发它的onpush。
(4)push subscription
推送订阅,是代表webapp在用户代理(例如,浏览器)和推送服务(例如,GCM)之间建立的消息传递上下文。每个push subscription都与一个service worker registration相关联,而一个service worker registration最多只有一个push subscription。每个push subscription都有一个push endpoint和一个公钥,公钥是用于给application server加密要推送的消息数据。
(5)push endpoint
push endpoint,在ServiceWorker向push service注册时,由push service生成。在Chrome 45版本之前,push endpoint是指推送服务的地址,例如,GCM服务器的地址。在Chrome 45及之后版本,push endpoint = push server address + subscriptionId,其中subscriptionId可以唯一标识push subscription。在浏览器客户端,通过subscriptionId可以找到相应的app_id和sender_id。app_id包含作用域和workerId,例如,push#http://example.com/test/#1。
(6)sender_id
sender_id,是创建Google API项目时生成的项目ID。ServiceWorker通过浏览器向GCM注册订阅消息时,需要上传sender_id,才能订阅成功。GCM会使用sender_id,通过Instance ID API到Instance ID Service去注册生成Instance ID,此步骤最终生成的注册信息,也就是前面提到的 subscriptionId。注:Chrome52之前版本对应的GCM/FCM还不支持标准的Web Push Protocol,所以才需要通过gcm_sender_id去找到相应的浏览器客户端,Chrome52版本及其对应的GCM/FCM已支持标准的Web Push Protocol,不再需要sender_id了,即支持标准的Web Push Protocol的push服务器都是不需要sender_id的。
(7)Subscription Refreshes
浏览器或推送服务,可以在任何时间更新push subscription。更新成功后,必须触发与push subscription相关联的service worker registration的pushsubscriptionchange事件,如果更新失败,浏览器应定期触发更新,直至成功。
(8)Subscription Deactivation
push subscription停止使用时,浏览器和推送服务必须删除已保存的副本,相关消息不再推送。service worker registration在注销或清除注册信息时,相关联的push subscription会被停用。
(9)Push service
推送服务,是指一个推送服务系统,允许application server通过它推送消息给webapp。
Push流程
(1)页面向浏览器注册一个ServiceWorker,浏览器生成对应的ServiceWorkerRegistration对象。
(2)页面通过ServiceWorker的PushManager.subscribe方法向push service(GCM)订阅消息,GCM 返回push subscription (endpoint+subscriptionId)给浏览器,浏览器找到对应的ServiceWorker,并把push subscription传递给ServiceWorker。
(3)页面通过PushManager.getSubscription获取到push subscription,并将它发送给页面服务器。注,可以使用ajax请求发送数据给页面服务器。
(4)页面服务器需要推送消息时,将push message和push subscription发送给push service(例如,GCM)。GCM根据push subscription找到对应的浏览器客户端并把消息推送给它,浏览器通过app_id找到相应的ServiceWorker,激活和把消息传递给它并触发它的onpush。
(5)页面JS根据实际需要去处理onpush事件,比如,弹出消息通知,存储数据到本地,提前到服务器去fetch资源,等等。
(6)页面在不再需要订阅消息时,可以发送ajax请求通知页面服务器去清除push subscription。然后使用PushSubscription.unsubscribe向push service去取消订阅。
页面实现消息推送的详细流程,可以参考官方文档 Your First Web Push Notification 和 Push Notifications on the Open Web。
基本流程的示例代码:
Push服务
(1)GCM/FCM
注1:页面服务器(app server)使用HTTP/XMPP协议发送消息给Google GCM Connection Servers,GCM Connection Server 推送消息给相应的浏览器客户端。
注2:页面服务器(app server)发送消息的格式如下,
curl --header "Authorization: key=<YOUR_API_KEY>" --header "Content-Type: application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"<YOUR_REGISTRATION_ID>\"]}"
其中,API_KEY 是创建Google API项目时生成的Server Key,GCM可通过它找到对应的sender_id。YOUR_REGISTRATION_ID 是订阅消息时生成的subscriptionId。
注3:Google Chrome是第一个支持Push API的浏览器,最先支持的版本为Chrome 42,这个时候Web Push Protocol还未完稿,所以其push service使用的GCM/FCM服务器并不支持标准Web Push Protocol。Chrome 52版本实现了对标准Web Push Protocol的支持,此时,GCM/FCM也已支持标准Web Push Protocol。
Mozilla推送服务使用autopush作为它的push服务器。autopush是支持标准Web Push Protocol,它使用websockets与FireFox浏览器进行交互。
Mozilla推送服务的架构如下,
(3)第三方Push服务
Push API标准是允许替换endpoint的,即任何实现了Web Push Protocol的服务器都可作为endpoint,并且对使用者是透明的。
那么需要做哪些工作来实现我们浏览器自己的push服务呢?
我们先看看Web Push Protocol定义了那些内容:
+-------+ +--------------+ +-------------+ | UA | | Push Service | | Application | +-------+ +--------------+ | Server | | | +-------------+ | Subscribe | | |--------------------->| | | Monitor | | |<====================>| | | | | | Distribute Push Resource | |-------------------------------------------->| | | | : : : | | Push Message | | Push Message |<---------------------| |<---------------------| | | | | Figure 1: WebPush Architecture
- 浏览器客户端连接push service
需要使用https。 - 浏览器客户端订阅push message
浏览器需要发送POST请求,到push service去订阅消息。比如,
POST /subscribe HTTP/1.1
Host: push.example.net
订阅成功后push service的响应如下,
HTTP/1.1 201 Created
Date: Thu, 11 Dec 2014 23:56:52 GMT
Link: </push/JzLQ3raZJfFBR0aqvOMsLrt54w4rJUsV>;
rel="urn:ietf:params:push"
Link: </subscription-set/4UXwi2Rd7jGS7gp5cuutF8ZldnEuvbOy>;
rel="urn:ietf:params:push:set"
Location: https://push.example.net/subscription/LBhhw0OohO-Wl4Oi971UG
这个步骤中,push service需要能够根据不同情况返回不同的响应码,能够识别出同一客户端的请求。 - 页面服务器发送推送消息
application server发送POST请求给push service,请求它将消息推送给浏览器客户端。请求如下,
POST /push/JzLQ3raZJfFBR0aqvOMsLrt54w4rJUsV HTTP/1.1
Host: push.example.net
TTL: 15
Content-Type: text/plain;charset=utf8
Content-Length: 36
iChYuI3jMzt3ir20P8r_jgRR-dSuN182x7iB
push service的响应如下,
HTTP/1.1 201 Created
Date: Thu, 11 Dec 2014 23:56:55 GMT
Location: https://push.example.net/message/qDIYHNcfAIPP_5ITvURr-d6BGt
这个步骤中,push service需要能够根据不同情况返回不同的响应码,能够根据TTL保存推送消息一段时间并且含有Topic
头部的新消息能替换旧消息,能够根据Urgency头部决定推送的优先级。 - 浏览器客户端接收订阅的推送消息
浏览器客户端发GET请求请求接收推送消息,请求信息如下,
HEADERS [stream 7] +END_STREAM +END_HEADERS
:method = GET
:path = /subscription/LBhhw0OohO-Wl4Oi971UG
:authority = push.example.net
push service允许保持请求,在application server发送推送消息之后,它将消息通过HTTP/2 server push将消息推送给浏览器客户端,响应信息如下,
PUSH_PROMISE [stream 7; promised stream 4] +END_HEADERS
:method = GET
:path = /message/qDIYHNcfAIPP_5ITvURr-d6BGt
:authority = push.example.net
HEADERS [stream 4] +END_HEADERS
:status = 200
date = Thu, 11 Dec 2014 23:56:56 GMT
last-modified = Thu, 11 Dec 2014 23:56:55 GMT
cache-control = private
link = </push/JzLQ3raZJfFBR0aqvOMsLrt54w4rJUsV>;
rel="urn:ietf:params:push"
content-type = text/plain;charset=utf8
content-length = 36
DATA [stream 4] +END_STREAM
iChYuI3jMzt3ir20P8r_jgRR-dSuN182x7iB
HEADERS [stream 7] +END_STREAM +END_HEADERS
:status = 200
浏览器客户端接收到消息后,必须回应一个HTTP DELETE给push service,
DELETE /message/qDIYHNcfAIPP_5ITvURr-d6BGt HTTP/1.1
Host: push.example.net
push service在收到浏览器客户端的回应后,必须返回一个204的响应给application server。如果没有收到回应,则会认为消息还未派发到浏览器客户端,
那么push service应该继续重试,直至消息过期。 - 安全隐私方面的考虑
push服务需要考虑安全相关的一些问题,比如推送的消息需要进行加密,用户设备信息不应包含在消息推送中,过多异常请求时能够拒绝服务,
服务器日志不应暴露用户信息,等等。
从上面可以看看,push service至少需要具备的一些能力,比如,支持https,支持http/2 server push,支持POST,GET,DELETE请求,能够根据请求生成订阅信息,能够根据请求头部控制响应的优先级,能够存储消息一段时间。即服务器端有一定的实现成本。
Chrome 52已支持标准Web Push Protocol,第三方浏览器可参考其实现,即浏览器端的实现成本会变低。
浏览器支持
在国外,Google Chrome,Firefox,以及一些厂商的系统浏览器,比如三星,都已支持Push API。预计后面会越来越多的浏览器厂商支持。
在国内,还未有浏览器厂商支持Push API,估计这种情况会持续很长一段时间,门槛在于push service的缺失,风险在于内容监管。
从上面我们可以看到,Push API提供了一种页面服务器与页面交互的方法,是web page能成长为web app所需的关键能力。同时,我们也可看到,国内是不能使用GCM/FCM作为push service的,因为需要翻墙。如果我们自己搭建push service,服务器的软硬件都需要不小的成本,估计还未有浏览器厂商愿意承担这个成本。这对一些现有的推送服务商倒是一个十分难得的机遇,支持标准的Web Push Protocol,提供给所有浏览器厂商使用,做成国内最有影响力的推送服务,类似Google的GCM/FCM。
如果Web Push一直得不到支持,对于浏览器SDK来说,可以提供一套非标准的API,允许客户端(比如,手淘)推送消息给浏览器的ServiceWorker,push service则由内置浏览器SDK的客户端自行去实现。
参考文档
Your First Web Push Notification
Push Notifications on the Open Web