阿多尼斯JS 传送
AdonisJS的本地服务器-Sent-Event(SSE)模块.
AdonisJS Transmit是AdonisJS的本地服务器-Sent-Event (SSE)模块. 它为发送事件给客户端提供了简单的API. 它还支持 重写 作为向多个服务器或实例广播事件的传输层.
在使用这个模块之前,你应该先知道一些事情.
👉 单向通信: 数据传输只发生在服务器到客户端之间,而不是相反.
👉 文本数据 仅限: SSE只支持文本数据的传输,不能发送二进制数据.
👉 HTTP 协议 : 使用的基本协议是常规的HTTP,而不是任何特殊或专有的协议.
目录
安装
从 npm 登记册中安装软件包如下:
node ace add @adonisjs/transmit
使用量
模块显示 transmit 实例,用于向客户端发送事件.
import transmit from '@adonisjs/transmit/services/main'
// Anywhere in your code
transmit.broadcast('channelName', { username: 'lanz' })
频道
频道是组织事件的一种方式. 例如,您可以有一个频道用于 users 和另一个用于 posts。客户端可以订阅一个或多个频道接收事件.
频道名称
频道名称必须是一个字符串,除此之外不得包含任何特殊字符 /。以下是有效的频道名称.
transmit.broadcast('users', { username: 'lanz' })
transmit.broadcast('users/1', { username: 'lanz' })
transmit.broadcast('users/1/posts', { username: 'lanz' })
频道授权
您可以将频道标为私人频道,然后授权客户端订阅. 授权使用回调函数进行.
// start/transmit.ts
import type { HttpContext } from '@adonisjs/core/http'
transmit.authorize<{ id: string }>('users/:id', (ctx: HttpContext, { id }) => {
return ctx.auth.user?.id === +id
})
[注] 别忘了加上你
start/transmit.ts文件在preloads数组adonisrc.ts文档.
当客户端试图订阅一个私人频道时,会以频道参数和HTTP上下文来引用回调函数. 回调函数必须返回布尔值以允许或不允许订阅 .
这个 ctx 当订阅请求通过您认证的中间软件时, 才会带一个认证的用户 。 默认情况下, 用于订阅和取消订阅的路由( X)__transmit/subscribe 财务报告和审定财务报表 __transmit/unsubscribe)是公开的,所以 ctx.auth.user 实值 undefined启用 transmit.registerRoutes 保护他们:
// start/routes.ts
import transmit from '@adonisjs/transmit/services/main'
import { middleware } from '#start/kernel'
transmit.registerRoutes((route) => {
// Authenticate the client before it can subscribe to a channel
if (route.getPattern() === '__transmit/subscribe') {
route.middleware(middleware.auth())
return
}
})
[注] 一旦订阅路线被认证,
ctx.auth.user在您内部authorize调用,允许您将私有频道范围限制到当前用户.
客户端
这个 @adonisjs/transmit-client 软件包允许您连接到服务器,订阅频道,并对收到的信息作出反应.
npm i @adonisjs/transmit-client
通过指向您的服务器创建客户端 。 当您的订阅路由被认证时( 请参见 频道授权),使用 beforeSubscribe 勾勾在每次订阅请求中附上您的证书 。 钩子接收出局 Request,可以直接改变其标题.
import { Transmit } from '@adonisjs/transmit-client'
export const transmit = new Transmit({
baseUrl: window.location.origin,
beforeSubscribe: (request) => {
request.headers.set('Authorization', `Bearer ${getToken()}`)
},
})
然后订阅频道并收听消息:
const subscription = transmit.subscription('users/1')
await subscription.create()
subscription.onMessage((message) => {
console.log(message)
})
// Stop receiving messages
await subscription.delete()
[注] 如果您也保护未订阅的路由, 请使用匹配
beforeUnsubscribe要认证的钩__transmit/unsubscribe请求同样的方式.
同步中
传输支持跨多个服务器同步事件或使用传输层的事件. 您可以通过改变配置和引用驱动程序来启用同步( 目前只有 Redis 可用) .
// config/transmit.ts
import env from '#start/env'
import { defineConfig } from '@adonisjs/transmit'
import { redis } from '@adonisjs/transmit/transports'
export default defineConfig({
transport: {
driver: redis({
host: env.get('REDIS_HOST'),
port: env.get('REDIS_PORT'),
password: env.get('REDIS_PASSWORD'),
}),
},
})
[注] 确保有
ioredis使用时安装redis驾驶员.
Ing
传输支持拨打客户端以保持连接. 您可以通过改变配置来启用 ping.
// config/transmit.ts
import { defineConfig } from '@adonisjs/transmit'
import { redis } from '@adonisjs/transmit/transports'
export default defineConfig({
pingInterval: '1m',
})
活动
传输用途 娱乐 释放任何生命周期事件。 您可以使用 on 方法.
transmit.on('broadcast', ({ channel, payload }) => {
logger.debug('TRANSMIT broadcasted')
logger.debug(`─ channel: ${channel}`)
logger.debug(`─ payload: ${JSON.stringify(payload)}`)
})
transmit.on('connect', ({ uid }) => {
logger.debug('TRANSMIT connected')
logger.debug(`─ uid: ${uid}`)
})
transmit.on('disconnect', ({ uid }) => {
logger.debug('TRANSMIT disconnected')
logger.debug(`─ uid: ${uid}`)
})
transmit.on('subscribe', ({ channel, uid }) => {
logger.debug('TRANSMIT subscribed')
logger.debug(`─ channel: ${channel}`)
logger.debug(`─ uid: ${uid}`)
})
transmit.on('unsubscribe', ({ channel, uid }) => {
logger.debug('TRANSMIT unsubscribed')
logger.debug(`─ channel: ${channel}`)
logger.debug(`─ uid: ${uid}`)
})
这个 connect 财务报告和审定财务报表 disconnect 事件暴露了 uid 客户。 与 频道授权您可以绘制地图 uid 到经认证的用户,以跟踪当前连接的人.
测试
因为 transmit.on 返回一个停止监听的函数,您可以在击中您的端点时收集所发射的广播,并对此进行断言。 以下示例使用了 日本兵团 与 @japa/api-client 插件 :
import transmit from '@adonisjs/transmit/services/main'
import { test } from '@japa/runner'
test.group('Posts', () => {
test('it broadcasts the created post', async ({ client, assert }) => {
const broadcasts: { channel: string; payload: unknown }[] = []
const stopListening = transmit.on('broadcast', (event) => broadcasts.push(event))
const response = await client.post('/posts').loginAs(user)
stopListening()
assert.equal(response.status(), 201)
assert.lengthOf(broadcasts, 1)
assert.equal(broadcasts[0].channel, `users/${user.id}`)
})
test('it rejects an unauthenticated subscription', async ({ client, assert }) => {
const response = await client.post('/__transmit/subscribe').json({ channel: 'users/1' })
assert.equal(response.status(), 401)
})
})
避免GZIP干扰
部署使用的程序时 @adonisjs/transmit,必须确保GZip压缩不会干扰 text/event-stream server-Sent Events(SSE)使用的内容类型. 压缩应用到 text/event-stream 可以引起连接问题,导致频繁断开或SSE故障.
如果您的部署使用了反向代理( 如 Traefik 或 Nginx) 或其他应用 GZip 的中间软件, 请确保 压缩被禁用 text/event-stream 内容类型.
特拉菲克的示例配置
traefik.http.middlewares.gzip.compress=true
traefik.http.middlewares.gzip.compress.excludedcontenttypes=text/event-stream
traefik.http.routers.my-router.middlewares=gzip