使用Go语言学习GraphQL服务器端(第10课)- GraphQL独有的中间件

你好。

我們將談論有關「GraphQL獨特的中間件」的話題。

关于这一章

在之前的章节中,我们使用了类似中间件的扩展(Extension)功能来根据查询复杂度来决定是否接受请求。然而,GraphQL 还有其他一些中间件,可以在解析器的前后插入逻辑处理。在本章中,我们将介绍它们。

可以应用于GraphQL服务器的中间件

在 github.com/99designs/gqlgen/graphql 中定义的中间件总共有四种类型。

type OperationMiddleware func(ctx context.Context, next OperationHandler) ResponseHandler
type ResponseMiddleware func(ctx context.Context, next ResponseHandler) *Response
type RootFieldMiddleware func(ctx context.Context, next RootResolver) Marshaler
type FieldMiddleware func(ctx context.Context, next Resolver) (res interface{}, err error)

中文翻译选项:
– 中间件的引入方法 de
– 引入中间件的方法 de
– 中间件的安装指南 de
– 安装中间件的方法 de

为了将这些中间件引入到GraphQL服务器中,需要使用以下提供在Server结构体中的方法。

func (s *Server) AroundOperations(f graphql.OperationMiddleware)
func (s *Server) AroundResponses(f graphql.ResponseMiddleware)
func (s *Server) AroundRootFields(f graphql.RootFieldMiddleware)
func (s *Server) AroundFields(f graphql.FieldMiddleware)
func main() {
	// (中略)

	srv := handler.NewDefaultServer(internal.NewExecutableSchema(internal.Config{
		Resolvers: &graph.Resolver{
			Srv:     service,
			Loaders: graph.NewLoaders(service),
		},
		Complexity: graph.ComplexityConfig(),
	}))
+	srv.AroundRootFields(func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler {
+		// (処理内容)
+	})
+	srv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
+		// (処理内容)
+	})
+	srv.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
+		// (処理内容)
+	})
+	srv.AroundFields(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
+		// (処理内容)
+	})

	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)

	log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

不同种类的中间件功能

从这里开始,我们将介绍四种中间件每种的功能是什么。

操作中间件

在接收到客户端请求时,操作中间件是首先被调用的中间件。
在经过该中间件的处理之后,进入解释实际发送过来的查询的步骤。

type OperationMiddleware func(ctx context.Context, next OperationHandler) ResponseHandler

以下是一些使用实例。

srv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
	log.Println("before OperationHandler")
	res := next(ctx)
	defer log.Println("after OperationHandler")
	return res
})
2023/12/12 23:36:46 connect to http://localhost:8080/ for GraphQL playground
2023/12/12 23:37:02 before OperationHandler
2023/12/12 23:37:02 after OperationHandler

响应中间件

ResponseMiddleware负责在经过OperationMiddleware的前后处理之后,创建要返回给客户端的响应的前后处理阶段。

type ResponseMiddleware func(ctx context.Context, next ResponseHandler) *Response

以下是一些使用示例。

srv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
	log.Println("before OperationHandler")
	res := next(ctx)
	defer log.Println("after OperationHandler")
	return res
})
+srv.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
+	log.Println("before ResponseHandler")
+	res := next(ctx)
+	defer log.Println("after ResponseHandler")
+	return res
+})
2023/12/12 23:47:27 connect to http://localhost:8080/ for GraphQL playground
2023/12/12 23:47:42 before OperationHandler
2023/12/12 23:47:42 after OperationHandler
2023/12/12 23:47:42 before ResponseHandler
2023/12/12 23:47:42 after ResponseHandler

可以看出,OperationMiddleware的后续处理在ResponseMiddleware的前序处理之前进行。

根领域中间件

RootFieldMiddleware是一个中间件,可以在执行根解析器创建整个响应数据之前和之后插入处理过程。

type RootFieldMiddleware func(ctx context.Context, next RootResolver) Marshaler

以下是利用示例。

srv.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
	log.Println("before ResponseHandler")
	res := next(ctx)
	defer log.Println("after ResponseHandler")
	return res
})
+srv.AroundRootFields(func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler {
+	log.Println("before RootResolver")
+	res := next(ctx)
+	defer func() {
+		var b bytes.Buffer
+		res.MarshalGQL(&b)
+		log.Println("after RootResolver", b.String())
+	}()
+	return res
+})
2023/12/13 00:02:16 connect to http://localhost:8080/ for GraphQL playground
2023/12/13 00:02:21 before ResponseHandler
2023/12/13 00:02:21 before RootResolver
2023/12/13 00:02:21 after RootResolver {"id":"PJ_1","title":"My Project","url":"http://example.com/project/1"}
2023/12/13 00:02:21 after ResponseHandler

与ResponseMiddleware的关系如下。

    1. 在返回给客户端之前创建响应(通过ResponseMiddleware进行前处理)

 

    1. 创建响应

 

    1. 在执行根解析器之前执行(通过RootFieldMiddleware进行前处理)

 

    1. 执行根解析器并收集响应所需的数据

 

    1. 在执行根解析器之后执行(通过RootFieldMiddleware进行后处理)

 

    1. 将执行结果编码为JSON并作为响应数据

 

    在创建响应之后执行(通过ResponseMiddleware进行后处理)

领域中间件

Graphql的响应体是json格式的,正如您所知,json由许多键值对的字段组成。
FieldMiddleware是一种中间件,用于在创建包含在响应中的json字段之前和之后添加逻辑处理。

type FieldMiddleware func(ctx context.Context, next Resolver) (res interface{}, err error)

我会在下面列举一些利用的例子。

srv.AroundRootFields(func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler {
	log.Println("before RootResolver")
	res := next(ctx)
	defer func() {
		var b bytes.Buffer
		res.MarshalGQL(&b)
		log.Println("after RootResolver", b.String())
	}()
	return res
})
+srv.AroundFields(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
+	res, err = next(ctx)
+	log.Println(res)
+	return
+})

在添加了FieldMiddleware之后,按照以下的方式发送请求。

query {
  node(id: "PJ_1") {
    id
    ... on ProjectV2 {
      title
      url
    }
  }
}

对于这个查询,响应包含四个 JSON 字段,分别是“node”、 “id”、 “title” 和“url”。

{
  "data": {
    "node": {
      "id": "PJ_1",
      "title": "My Project",
      "url": "http://example.com/project/1"
    }
  }
}

因此,调用FieldMiddleware的预处理和后处理集合将会发生4次。

2023/12/12 23:55:35 connect to http://localhost:8080/ for GraphQL playground
2023/12/12 23:55:38 before RootResolver
2023/12/12 23:55:38 before Resolver
2023/12/12 23:55:38 after Resolver &{PJ_1 My Project {http   example.com /project/1  false false   } 1 <nil> 0xc000283a40}
2023/12/12 23:55:38 before Resolver
2023/12/12 23:55:38 after Resolver PJ_1
2023/12/12 23:55:38 before Resolver
2023/12/12 23:55:38 after Resolver My Project
2023/12/12 23:55:38 before Resolver
2023/12/12 23:55:38 after Resolver {http   example.com /project/1  false false   }
2023/12/12 23:55:38 after RootResolver {"id":"PJ_1","title":"My Project","url":"http://example.com/project/1"}

此外,与RootFieldMiddleware的关系如下所示。

    1. 执行根解析器之前(=根字段中间件的前处理)

 

    1. 执行根解析器

 

    1. 在创建字段之前(=字段中间件的前处理)

 

    1. 收集需要的数据并创建响应字段

 

    1. 在创建字段之后(=字段中间件的后处理)

 

    1. 回到步骤1,直到所有必要的字段都被创建

 

    执行根解析器(=根字段中间件的后处理)

总结 – 各中间件之间的关系

到目前为止,我已经介绍了GraphQL所提供的四种中间件。

OperationMiddleware: クライアントからリクエストを受け取ったときに最初に呼ばれる

ResponseMiddleware: クライアントに返すレスポンスを作成するという段階の前後処理を担う

RootFieldMiddleware: レスポンスデータ全体を作成するルートリゾルバの実行前後に処理を挿入するミドルウェア

FieldMiddleware: レスポンスに含めるjsonフィールドを1つ作る処理の前後にロジックを組み込むためのミドルウェア

最后,我们将总结并确认在同时使用这四个的情况下,将会产生什么样的执行顺序。

srv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
	log.Println("before OperationHandler")
	res := next(ctx)
	defer log.Println("after OperationHandler")
	return res
})
srv.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
	log.Println("before ResponseHandler")
	res := next(ctx)
	defer log.Println("after ResponseHandler")
	return res
})
srv.AroundRootFields(func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler {
	log.Println("before RootResolver")
	res := next(ctx)
	defer func() {
		var b bytes.Buffer
		res.MarshalGQL(&b)
		log.Println("after RootResolver", b.String())
	}()
	return res
})
srv.AroundFields(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
	log.Println("before Resolver")
	res, err = next(ctx)
	defer log.Println("after Resolver", res)
	return
})
2023/12/12 23:57:59 connect to http://localhost:8080/ for GraphQL playground
2023/12/12 23:58:02 before OperationHandler
2023/12/12 23:58:02 after OperationHandler
2023/12/12 23:58:02 before ResponseHandler
2023/12/12 23:58:02 before RootResolver
2023/12/12 23:58:02 before Resolver
2023/12/12 23:58:02 after Resolver {PJ_1 My Project http://example.com/project/1 1 <nil> 0xc000183830}
2023/12/12 23:58:02 before Resolver
2023/12/12 23:58:02 after Resolver PJ_1
2023/12/12 23:58:02 before Resolver
2023/12/12 23:58:02 after Resolver My Project
2023/12/12 23:58:02 before Resolver
2023/12/12 23:58:02 after Resolver http://example.com/project/1
2023/12/12 23:58:02 after RootResolver {"id":"PJ_1","title":"My Project","url":"http://example.com/project/1"}
2023/12/12 23:58:02 after ResponseHandler

从这里可以看出,流程如下。

    1. 接收客户端的请求

执行OperationMiddleware的前处理
创建ResponseHandler来生成客户端看到的完整响应数据,包括data、errors和extensions

执行OperationMiddleware的后处理

执行ResponseMiddleware的前处理

执行ResponseHandler

执行RootFieldMiddleware的前处理
运行根解析器,获取要放入响应的data字段的数据

执行FieldMiddleware的前处理
收集响应所需的数据,创建响应字段

执行FieldMiddleware的后处理
直到所有必要的字段都被创建完毕,回到步骤1重复执行

执行RootFieldMiddleware的后处理
将根解析器收集到的数据进行JSON编码,存储在响应的data字段中

执行ResponseMiddleware的后处理

接下来,我想要介绍“通过指令添加认证机制”。

今天就到这里。
谢谢你。
请多关照。

广告
将在 10 秒后关闭
bannerAds