漏洞赏金猎人笔记-GraphQL-III
声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。
前言
前面介绍了query、mutation这两种类型的操作,今天将要介绍最后一种类型的操作:subscription
这里对这三种类型的操作做一个简单的总结:
-
query 查询:获取数据,比如查找,CRUD 中的 R -
mutation 变更:对数据进行变更,比如增加、删除、修改,CRUD 中的 CUD -
substription 订阅:当数据发生更改,进行消息推送
此外,这三种类型都需要由解析函数 Resolver 来提供数据,比如
query {
hi
}
那么同名的解析函数resolver(一般也是写在后端)应该是这样的
Query: {
hi (parent, args, context, info) {
return ...
}
}
解析函数接受四个参数,分别为
-
parent:当前上一个解析函数的返回值 -
args:查询中传入的参数 -
context:提供给所有解析器的上下文信息 -
info:一个保存与当前查询相关的字段特定信息以及 schema 详细信息的值
解析函数的返回值可以是一个具体的值,也可以是 Promise 或 Promise 数组。
此外还需要知道Schema这个文件:
它定义了字段的类型、数据的结构,描述了接口数据请求的规则,当我们进行一些错误的查询的时候 GraphQL 引擎会负责告诉我们哪里有问题,和详细的错误信息,对开发调试十分友好。
Schema一般使用一个简单的强类型模式语法,称为模式描述语言(Schema Definition Language, SDL)
# src/schema.graphql
# Query 入口
type Query {
hello: String
users: [User]!
user(id: String): [User]!
}
# Mutation 入口
type Mutation {
createUser(id: ID!, name: String!, email: String!, age: Int,gender: Gender): User!
updateUser(id: ID!, name: String, email: String, age: Int, gender: Gender): User!
deleteUser(id: ID!): User
}
# Subscription 入口
type Subscription {
subsUser(id: ID!): User
}
type User implements UserInterface {
id: ID!
name: String!
age: Int
gender: Gender
email: String!
}
# 枚举类型
enum Gender {
MAN
WOMAN
}
# 接口类型
interface UserInterface {
id: ID!
name: String!
age: Int
gender: Gender
}
这里的 Schema 文件(这个文件一般写在后端)从 Query、Mutation、Subscription 入口开始定义了各个对象类型或标量类型,这些字段的类型也可能是其他的对象类型或标量类型,组成一个树形的结构,而用户在向服务端发送请求的时候,沿着这个树选择一个或多个分支就可以获取多组信息。
正文
这里主要通过一个实例来理解,先看个图来理解大体上的一个逻辑:
通过订阅这样的一个操作在服务端注册了了一个订阅查询,如果服务端有对应的更新,就会触发这个所订阅的查询,最后会在客户端实时更新。
下面通过一个例子来理解一下Subscription:
当前端发起订阅请求之后,如果后端发现数据改变,可以给前端推送实时信息。
Schema文件:
# Subscription 入口
type Subscription {
subsUser(id: ID!): User
}
type User {
id: ID!
name: String!
age: Int
email: String!
}
对应resolver:
import Db from '../db'
const { PubSub, withFilter } = require('apollo-server')
const pubsub = new PubSub()
const USER_UPDATE_CHANNEL = 'USER_UPDATE'
export default {
Mutation: {
updateUser: (parent, { id, name, email, age }) => Db.user({ id })
.then(existUser => {
if (!existUser)
throw new Error('没有这个id的人')
return existUser
})
.then(() => Db.updateUser({ id, name, email, age }))
.then(user => {
pubsub.publish(USER_UPDATE_CHANNEL, { subsUser: user })
return user
})
},
Subscription: {
subsUser: {
subscribe: withFilter(
(parent, { id }) => pubsub.asyncIterator(USER_UPDATE_CHANNEL),
(payload, variables) => payload.subsUser.id === variables.id
),
resolve: (payload, variables) => {
console.log('? 接收到数据:', payload)
}
}
}
}
这里的 pubsub 是 apollo-server 里负责订阅和发布的类,它在接受订阅时提供一个异步迭代器,在后端觉得需要发布订阅的时候向前端发布 payload。withFilter 的作用是过滤掉不需要的订阅消息
我们发布一个订阅请求:
# 请求
subscription subsUser($id: ID!) {
subsUser(id: $id) {
id
name
age
email
}
}
# 参数
{ "id": "2" }
用刚刚的数据更新操作来进行一次数据的更改,然后我们将获取到并打印出 pubsub.publish
发布的 payload,这样就完成了数据订阅。
全文完结。
参考
https://www.apollographql.com/docs/react/data/subscriptions/
https://dgraph.io/blog/post/how-does-graphql-subscription/
https://github.com/apollographql/docs-examples/blob/main/apollo-server/v3/subscriptions-graphql-ws/src/index.ts#L14
原文始发于微信公众号(迪哥讲事):漏洞赏金猎人笔记-GraphQL-III