prisma反向代理
概要
接触 prisma 有段时间了, 期间也使用过其他几种 graphql 接口自动生成的框架. 总的来说, 还是 prisma 生成的接口比较丰富, 使用上也比较方便, 和数据库之间耦合也低.
prisma 文档: (写本文时是 1.34 版)
为什么要做 prisma 的反向代理
prisma 服务虽然自动生成了接口, 但是这些接口其实不建议直接暴露给前端来用, 因为实际项目中, 最基本的要对接口进行认证和权限控制. 甚至还有其他需求, 不可能只用自动生成的接口就能完成所有的功能.
所以, 一般在使用 prisma 服务的时候, 一般都会再封装一层(可以称为 gateway), 在 gateway 上做认证, 权限等等, 只有合法的请求才会最终转发到 prisma 服务上. prisma 服务本身可以导出 client sdk, 用来方便 gateway 的编写, 目前支持 4 种格式 (javascript, typescript, golang, flow), javascript 和 typescript 的是 client sdk 功能比较全, golang 功能弱一些, flow 没有尝试过.
我在使用 golang client sdk 写 gateway 的时候, 发现 golang 的 graphql server 相关的库没有 js/ts 那么完善. 于是, 就想用反向代理的方式, 拦截前端的 graphql 请求, 做了相应操作之后直接再将请求内容转发给 prisma 服务. 这种方式不使用 prisma 生成的 client sdk, 也突破语言的限制, 除了 golang, java, c# 等其他语言也可以作为 prisma 的 gateway
反向代理示例(by golang)
采用 golang 的 作为 gateway 的 web 服务框架. 认证部分使用 中间件. 反向代理和权限部分没有使用现成的框架.
整个 gateway 的示例包含:
- prisma 服务(prisma + mysql): 这部分有现成的 docker image, 只要配置示例的表和字段即可
- gateway (golang gin): golang gin 的 api 服务
prisma 服务
-
prisma.yml
endpoint: http://${env:prisma_host}:${env:prisma_port}/illuminant/${env:prisma_stage} datamodel: datamodel.prisma secret: ${env:prisma_management_api_secret} generate: - generator: go-client output: ./
-
.env
prisma_host=localhost prisma_port=4466 prisma_stage=dev prisma_management_api_secret=secret-key
-
datamodel.prisma
type user { id: id! @id name: string! @unique realname: string! password: string! createdat: datetime! @createdat updatedat: datetime! @updatedat }
-
docker-compose.yml
version: '3' services: illuminant: image: prismagraphql/prisma:1.34 # restart: always ports: - "4466:4466" environment: prisma_config: | port: 4466 managementapisecret: secret-key databases: default: connector: mysql host: mysql-db user: root password: prisma # rawaccess: true port: 3306 migrations: true mysql-db: image: mysql:5.7 # restart: always environment: mysql_root_password: prisma volumes: - mysql:/var/lib/mysql volumes: mysql: ~
以上文件放在同一个目录即可, 包含了所有 prisma 服务和 mysql 服务所需要的文件
gateway 服务
gateway 服务是关键, 也是今后扩展的部分. 采用 golang gin 框架来编写.
整体流程
- http 请求
- route 路由
- 认证 check
- 权限 check
- 请求转发 prisma 服务(这一步一般都是转发到 prisma, 如果有上传/下载, 或者统计之类的需求, 需要另外写 api)
- 返回 response
认证
authmiddleware := controller.jwtmiddleware() apiv1 := r.group("/api/v1") // no auth routes apiv1.post("/login", authmiddleware.loginhandler) // auth routes authroute := apiv1.group("/") authroute.get("/refresh_token", authmiddleware.refreshhandler) authroute.use(authmiddleware.middlewarefunc()) { // proxy prisma graphql authroute.post("/graphql", reverseproxy()) }
/api/v1/graphql 在满足 jwt 认证的情况下才可以访问.
反向代理
func reverseproxy() gin.handlerfunc { return func(c *gin.context) { director := func(req *http.request) { req.url.scheme = "http" req.url.host = primsa-host req.url.path = primsa-endpoint delete(req.header, "authorization") req.header["authorization"] = []string{"bearer " + primsa-token} } // 解析出 body 中的内容, 进行权限检查 body, err := c.getrawdata() if err != nil { fmt.println(err) } // 对 body 进行权限 check // 权限 check, 解析出 graphql 中请求的函数, 然后判断是否有权限 // 目前的方式是根据请求中函数的名称来判断权限, 也就是只能对表的 curd 权限进行判断, 对于表中的字段权限还无法检查 // 如果权限检查没有通过, 直接返回, 不要再进行下面的请求转发 // 将 body 反序列化回请求中, 转发给 prisma 服务 c.request.body = ioutil.nopcloser(bytes.newbuffer(body)) proxy := &httputil.reverseproxy{director: director} proxy.modifyresponse = controller.rewritebody proxy.servehttp(c.writer, c.request) } }
权限
// 检查权限 func checkauthority(body []byte, userid string) bool { var bodyjson struct { query string `json:"query"` } log := logger.getlogger() if err := json.unmarshal(body, &bodyjson); err != nil { log.error("body convert to json error: %s", err.error()) return false } graphqlfunc := regrexgraphqlfunc(bodyjson.query) if graphqlfunc == "" { return false } // 这里的 userid 是从 jwt 中解析出来的, 然后再判断用户是否有权限 if graphqlfunc == "users" { return false } return true } // 匹配 graphql 请求的函数 func regrexgraphqlfunc(graphqlreq string) string { graphqlreq = strings.trimspace(graphqlreq) // reg examples: // { users {id} } // { users(where: {}) {id} } // mutation{ user(data: {}) {id} } var regstrs = []string{ `^\{\s*(\w+)\s*\{.*\}\s*\}$`, `^\{\s*(\w+)\s*\(.*\)\s*\{.*\}\s*\}$`, `^mutation\s*\{\s*(\w+)\s*\(.*\)\s*\{.*\}\s*\}$`, } for _, regstr := range regstrs { r := regexp.mustcompile(regstr) matches := r.findstringsubmatch(graphqlreq) if matches != nil && len(matches) > 1 { return matches[1] } } return "" }
这里的权限检查是个实现思路, 不是最终的代码.
其中用正则表达式的方式来匹配请求中的函数只是临时的方案, 不是最好的方式,
最好的方式应该用 golang 对应的 graphql 解析库来解析出请求的结构, 然后再判断解析出的函数时候有权限
总结
采用反向代理的方式, 是为了突破 prisma client sdk 的限制, 如果以后 client sdk 完善之后, 还是基于 client sdk 来开发 gateway 更加可靠.
上一篇: 运营出现事故怎么办?