Hello Ocean! ๐ŸŒผ

[gqlgen] gin.Context ์ด์šฉํ•˜๊ธฐ ๋ณธ๋ฌธ

Go/GO-GraphQL

[gqlgen] gin.Context ์ด์šฉํ•˜๊ธฐ

bba_dda 2023. 8. 9. 23:43
๋ฐ˜์‘ํ˜•

๋ฌธ์ œ

gin์„ graphql(gqlgen)์˜ router๋กœ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•  ๋•Œ
gin.Context์— ๋‹ด์•„๋†“์€ ๊ฐ’๋“ค์ด, graphql resolver์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ctx(context.Context)์— ๋‹ด๊ฒจ์žˆ์ง€ ์•Š์•˜๋‹ค. 

 

gin.Context์— key-value๋กœ ์ €์žฅํ•œ ๊ฐ’์„

resolver์˜ ctx.Value("key")๋กœ ์ ‘๊ทผํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ์ƒํ™ฉ

 

 

gqlgen ๊ณต์‹ ๋ฌธ์„œ์—์„œ ์ œ๊ณตํ•˜๋Š” example์— gin.Context๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์•ˆ๋‚ด๋˜์–ด ์žˆ๋‹ค. ๋งํฌ

ํ•˜์ง€๋งŒ, ์ด ๋ฐฉ๋ฒ•์€ context.Context์— gin.Context๋ฅผ ๊ทธ๋Œ€๋กœ ๋‹ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ๊ฐ์˜ resolver์—์„œ ๋งค๋ฒˆ ์•„๋ž˜์˜ ์ž‘์—…์„ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

1. context.Context์—์„œ gin.Context ๊บผ๋‚ด๊ธฐ

2. gin.Context์—์„œ ์›ํ•˜๋Š” ๊ฐ’ ๊บผ๋‚ด๊ธฐ

func (r *resolver) Todo(ctx context.Context) (*Todo, error) {
	gc, err := GinContextFromContext(ctx) // ๋งค๋ฒˆ gin.Context๋ฅผ ๊บผ๋‚ด๊ณ 
	if err != nil {
		return nil, err
	}

	// gc์—์„œ ํ•„์š”ํ•œ ๊ฐ’์„ ๊บผ๋‚ด์•ผ ํ•œ๋‹ค
	// ...
}

๊ธธ์ง€ ์•Š์€ ์ฝ”๋“œ๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋งค resolver์— ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ค‘๋ณต๋œ ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.

 

์›์ธ

๊ทธ๋ ‡๋‹ค๋ฉด ์™œ, gqlgen resolver์˜ ctx์— gin.Context์˜ ๊ฐ’์ด ๋“ค์–ด์žˆ์ง€ ์•Š์€๊ฑธ๊นŒ?

 

gqlgen์—์„œ ์ œ๊ณตํ•˜๋Š” gin์„ ์ด์šฉํ•œ example์„ ๋ณด๋ฉด, graphql handler์— gin.Context.Request๋ฅผ ๋„˜๊ธฐ๊ฒŒ ๋˜๊ณ 

 

...
// Defining the Graphql handler
func graphqlHandler() gin.HandlerFunc {
	// NewExecutableSchema and Config are in the generated.go file
	// Resolver is in the resolver.go file
	h := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))

	return func(c *gin.Context) {
		h.ServeHTTP(c.Writer, c.Request) // ์—ฌ๊ธฐ!
	}
}
...

h.ServeHTTP()์—์„œ๋Š”, ๋„˜๊ฒจ๋ฐ›์€ Request.Context() (==Request.ctx) ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer func() {
		if err := recover(); err != nil {
			err := s.exec.PresentRecoveredError(r.Context(), err)
			gqlErr, _ := err.(*gqlerror.Error)
			resp := &graphql.Response{Errors: []*gqlerror.Error{gqlErr}}
			b, _ := json.Marshal(resp)
			w.WriteHeader(http.StatusUnprocessableEntity)
			w.Write(b)
		}
	}()

	r = r.WithContext(graphql.StartOperationTrace(r.Context())) // ์—ฌ๊ธฐ!

	transport := s.getTransport(r)
	if transport == nil {
		sendErrorf(w, http.StatusBadRequest, "transport not supported")
		return
	}

	transport.Do(w, r, s.exec)
}

 

๊ฒฐ๊ณผ์ ์œผ๋กœ,

 

gin.Context ์˜ ํ•˜์œ„์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ํ•„๋“œ๋“ค์ด ์กด์žฌํ•˜๊ณ 

// gin.Context
type Context struct {
	...
	Request   *http.Request
	Keys      map[string]any
	...
}

 

gin.Context์— Set()์„ ํ†ตํ•ด ์ €์žฅํ•˜๋Š” ๊ฐ’๋“ค์€ gin.Context.Keys ์— ์ €์žฅ๋˜๋Š”๋ฐ

gqlgen resolver์— ๋„˜์–ด๊ฐ€๋Š” context๋Š” gin.Context.Request.ctx ๋ผ์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์ด๋‹ค.

 

ํ•ด๊ฒฐ๋ฐฉ์•ˆ

๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ์›ํ•˜๋Š”๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ?

gin.Context์— key-value๋กœ ์ €์žฅํ•œ ๊ฐ’์„

resolver์˜ ctx.Value("key")๋กœ ์ ‘๊ทผํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ์ƒํ™ฉ

 

1. ์‚ฌ์šฉํ•  ๊ฐ’๋“ค์„ gin.Context.Keys๊ฐ€ ์•„๋‹Œ gin.Context.Request.ctx์— ์ €์žฅํ•˜๊ธฐ

์ด๋ ‡๊ฒŒ ๋˜๋ฉด, ์›ํ•˜๋Š” ์ƒํ™ฉ์€ ์–ป์–ด๋‚ผ ์ˆ˜ ์žˆ์ง€๋งŒ ๋‘ ๊ฐ€์ง€ ๋‹จ์ ์ด ์ƒ๊ธด๋‹ค.

1) gin middleware์—์„œ context์— ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๋กœ์ง์ด ๋ณต์žกํ•ด์ง€๊ณ 

2) ์—ฌ๋Ÿฌ ๊ฐœ๋ฐœ์ž๊ฐ€ ํ•จ๊ป˜ ์ž‘์—…ํ•  ๋•Œ gin.Context.Keys๊ฐ€ ์•„๋‹ˆ๋ผ Request๋‚ด๋ถ€ ctx์— ๊ฐ’์„ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์ด ์ถฉ๋ถ„ํžˆ ๊ณต์œ ๊ฐ€ ๋˜์–ด์•ผ ํ•œ๋‹ค.

-> gqlgen ๊ณต์‹๋ฌธ์„œ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ๋น„๊ตํ•ด์„œ ์ด์ ์ด ์—†์Œ

func ginMiddleware(c *gin.Context) {
    ctx := c.Request.Context()
    ctx = context.WithValue(ctx, key, value)
    c.Request = c.Request.WithContext(ctx)
}

 

2. h.ServeHTTP() ์ด์ „์—, gin.Context.Keys์— ์กด์žฌํ•˜๋Š” ๊ฐ’๋“ค์„ ํ•œ ๋ฒˆ์— gin.Context.Request.ctx๋กœ ๋„ฃ์–ด์ฃผ๊ธฐ

h.ServeHTTP()๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ง์ „์— gin.Context.Keys์— ์กด์žฌํ•˜๋Š” ๊ฐ’๋“ค์„

ํ•œ ๋ฒˆ์— gin.Context.Request.ctx๋กœ ๋„ฃ์–ด์ฃผ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

...
// Defining the Graphql handler
func graphqlHandler() gin.HandlerFunc {
	// NewExecutableSchema and Config are in the generated.go file
	// Resolver is in the resolver.go file
	h := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))

	return func(c *gin.Context) {
		ctx := c.Request.Context()
		for k, v := range c.Keys {
			ctx = context.WithValue(ctx, k, v)
		}
		req := c.Request.WithContext(ctx)
		h.ServeHTTP(c.Writer, req)
	}
}
...

 

์ด์ „ ๋ฐฉ๋ฒ•๋“ค๊ณผ ํฌ๊ฒŒ ๋‹ค๋ฅธ์ ์ด ์—†๋‹ค๊ณ  ๋Š๊ปด์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ ์œ ์ง€๋ณด์ˆ˜ ์ธก๋ฉด์—์„œ ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

ํ›„์— ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž ํ˜น์€ ์ด์ „ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์žŠ์–ด๋ฒ„๋ฆฐ ๋‹น์‚ฌ์ž๊ฐ€ context์— ๊ฐ’์„ ์ถ”๊ฐ€๋กœ ์ €์žฅํ•ด์•ผํ•  ์ƒํ™ฉ์—์„œ ์‹ ๊ฒฝ์“ธ ํฌ์ธํŠธ๊ฐ€ ์ค„์–ด๋“ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

 

๊ต‰์žฅํžˆ ์‚ฌ์†Œํ•œ ๋ฌธ์ œ ๊ฐ™์ง€๋งŒ,

- ์ค‘๋ณต ์ฝ”๋“œ ์ค„์ด๊ธฐ

- ์‰ฌ์šด ์œ ์ง€๋ณด์ˆ˜

์ธก๋ฉด์—์„œ ๊ณ ๋ฏผํ•ด๋ณผ ์ˆ˜ ์žˆ์—ˆ๋˜ ๋ฌธ์ œ์˜€๋‹ค.

๋ฐ˜์‘ํ˜•