概要

接触 prisma 有段时间了, 期间也使用过其他几种 graphql 接口自动生成的框架. 总的来说, 还是 prisma 生成的接口比较丰富, 使用上也比较方便, 和数据库之间耦合也低.

prisma 文档: https://www.prisma.io/docs (写本文时是 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 的 gin 作为 gateway 的 web 服务框架. 认证部分使用 gin-jwt 中间件. 反向代理和权限部分没有使用现成的框架.

整个 gateway 的示例包含:

  1. prisma 服务(prisma + mysql): 这部分有现成的 docker image, 只要配置示例的表和字段即可
  2. gateway (golang gin): golang gin 的 api 服务

prisma 服务

  1. 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: ./
  2. .env

    PRISMA_HOST=localhost
    PRISMA_PORT=4466
    PRISMA_STAGE=dev
    PRISMA_MANAGEMENT_API_SECRET=secret-key
  3. datamodel.prisma

    type User {
    id: ID! @id
    name: String! @unique
    realName: String!
    password: String! createdAt: DateTime! @createdAt
    updatedAt: DateTime! @updatedAt
    }
  4. 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 框架来编写.

整体流程

  1. HTTP 请求
  2. route 路由
  3. 认证 Check
  4. 权限 Check
  5. 请求转发 prisma 服务(这一步一般都是转发到 prisma, 如果有上传/下载, 或者统计之类的需求, 需要另外写 API)
  6. 返回 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 更加可靠.

prisma反向代理的更多相关文章

  1. hasura的golang反向代理

    概述 反向代理代码 对请求的处理 对返回值的处理 遇到的问题 概述 一直在寻找一个好用的 graphql 服务, 之前使用比较多的是 prisma, 但是 prisma1 很久不再维护了, 而 pri ...

  2. nginx配置反向代理或跳转出现400问题处理记录

    午休完上班后,同事说测试站点访问接口出现400 Bad Request  Request Header Or Cookie Too Large提示,心想还好是测试服务器出现问题,影响不大,不过也赶紧上 ...

  3. 使用python自动生成docker nginx反向代理配置

    由于在测试环境上用docker部署了多个应用,而且他们的端口有的相同,有的又不相同,数量也比较多,在使用jenkins发版本的时候,不好配置,于是想要写一个脚本,能在docker 容器创建.停止的时候 ...

  4. Windos环境用Nginx配置反向代理和负载均衡

    Windos环境用Nginx配置反向代理和负载均衡 引言:在前后端分离架构下,难免会遇到跨域问题.目前的解决方案大致有JSONP,反向代理,CORS这三种方式.JSONP兼容性良好,最大的缺点是只支持 ...

  5. Nginx反向代理,负载均衡,redis session共享,keepalived高可用

    相关知识自行搜索,直接上干货... 使用的资源: nginx主服务器一台,nginx备服务器一台,使用keepalived进行宕机切换. tomcat服务器两台,由nginx进行反向代理和负载均衡,此 ...

  6. 使用Nginx反向代理 让IIS和Tomcat等多个站点一起飞

    使用Nginx 让IIS和Tomcat等多个站点一起飞 前言: 养成一个好习惯,解决一个什么问题之后就记下来,毕竟“好记性不如烂笔头”. 这样也能帮助更多的人 不是吗? 最近闲着没事儿瞎搞,自己在写一 ...

  7. 使用nginx反向代理,一个80端口下,配置多个微信项目

    我们要接入微信公众号平台开发,需要填写服务器配置,然后依据接口文档才能实现业务逻辑.但是微信公众号接口只支持80接口(80端口).我们因业务需求需要在一个公众号域名下面,发布两个需要微信授权的项目,怎 ...

  8. 腾讯云下安装 nodejs + 实现 Nginx 反向代理

    本文将介绍如何给腾讯云上的 Ubuntu Server 12.04 LTS 64位主机安装 node 及 nginx,并简单配置反向代理. 笔者在整个安装过程中遇到不少麻烦(不赘述),如果你希望少踩坑 ...

  9. 简易nginx TCP反向代理设置

    nginx从1.9.0开始支持TCP反向代理,之前只支持HTTP.这是我的系统示意图: 为何需要? 为什么需要反向代理?主要是: 负载均衡 方便管控 比如我现在要更新后端服务器,如果不用负载均衡的话, ...

随机推荐

  1. 使用Runtime的hook技术为tableView实现一个空白缺省页

    一.介绍 UITableView和UICollectionView是iOS开发最常用的控件,也是必不可少的控件,这两个控件基本能实现各种各样的界面样式. 它们都是通过代理模式监测数据源的有无对数据进行 ...

  2. php time() 和 $_SERVER['REQUEST_TIME']

    time() 和 $_SERVER['REQUEST_TIME']效率 结果:(其中之一) 结论: time() : 执行时间在0.10 - 0.30 之间 $_SERVER['REQUEST_TIM ...

  3. python-3.8.0 新特性之赋值表达式

    [python-3.8.0 新特性之赋值表达式] 赋值表达式的语法是这样的“ name := expression ”,形式上看和赋值语句 “ = ” 差不多,就作用上来看也雷同.也就是说 “:=” ...

  4. java高并发系列 - 第11天:线程中断的几种方式

    java高并发系列第11篇文章. 本文主要探讨一下中断线程的几种方式. 通过一个变量控制线程中断 代码: package com.itsoku.chat05; import java.util.con ...

  5. go-爬虫-百度贴吧(并发版)

    爬取百度贴吧的网页 非并发版 package main import ( "fmt" "io" "net/http" "os&qu ...

  6. Meterpreter初探

    Meterpreter Meterpreter号称"黑客瑞士军刀",Meterpreter是Metasploit框架中的一个杀手锏,通常作为漏洞溢出后的攻击载荷使用,攻击载荷在触发 ...

  7. python中动态创建类

    class Foo(Bar): pass Foo中有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象(我说的是类对象,请紧 ...

  8. Apache—给一个站点绑定多个域名

    前提简介: ServerAdmin:Apache服务管理员通知邮箱地址,如果有真实的邮箱地址也可以设置此值.  ServerName:是服务的名字,只能填写一个域名. ServerAlias:serv ...

  9. Shel脚本-初步入门之《06》

    Shel脚本-初步入门-06 Shell 脚本的建立和执行 6.Shell 脚本的建立和执行 6.1 Shell脚本的建立 在 Linux 系统中,Shell 脚本(bash Shell 程序)通常是 ...

  10. mysql从命令行执行sql语句

    mysql -u root -p -e "create database mydb;"