99designs / 99designs/gqlgen

Unexpected OperationMiddleware execution

Đang mở
#1,166 4 bình luận 0 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
Go
Star
10.8k
Fork
1.3k
Merge trung bình
2 ngày 36 phút
Pull request đã merge (30 ngày)
26

Mô tả

### What happened?

This might just be a matter of documentation clarification, but we were using to wrap operations to record timings and other metadata and found that the `next OperationHandler` that is passed in returns before the operation is actually completed.

Is this expected behavior?

This was the output when I just added log statements to a handler extension that implements:

```
graphql.ResponseInterceptor
graphql.OperationInterceptor
graphql.FieldInterceptor
graphql.HandlerExtension
```

```
2020/04/24 11:23:25 1 before operation
2020/04/24 11:23:25 1 after operation
2020/04/24 11:23:25 1 before response
2020/04/24 11:23:25 1 before field Query.todos
2020/04/24 11:23:25 in resolver
2020/04/24 11:23:25 1 after field Query.todos
2020/04/24 11:23:25 1 before field Todo.id
2020/04/24 11:23:25 1 after field Todo.id
2020/04/24 11:23:25 1 before field Query.todos
2020/04/24 11:23:25 in resolver
2020/04/24 11:23:25 1 after field Query.todos
2020/04/24 11:23:25 1 before field Todo.id
2020/04/24 11:23:25 1 after field Todo.id
2020/04/24 11:23:25 1 before field Todo.id
2020/04/24 11:23:25 1 after field Todo.id
2020/04/24 11:23:25 1 before field Todo.id
2020/04/24 11:23:25 1 after field Todo.id
2020/04/24 11:23:25 1 after response
```

For a query:

```
query {
todos {
id
}
foo: todos {
id
}
}
```

We can see here that the operation middleware is called and returns before the response middleware starts.

### What did you expect?

We expected the handler passed to our operation middleware to not return until the operation finished execution and the response was resolved.

### Minimal graphql.schema and models to reproduce

I used `gqlgen init` to create the default models. This is my `server.go` file:

```
package main

import (
"context"
"log"
"net/http"
"os"
"strconv"
"test/graph"
"test/graph/generated"

"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
)

const defaultPort = "8080"

func main() {
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}

srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
srv.Use(testExtension(1))

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))
}

// Tracer is an opencensus tracer that instruments GraphQL requests with opencensus spans
type testExtension int

var _ interface {
graphql.ResponseInterceptor
graphql.OperationInterceptor
graphql.FieldInterceptor
graphql.HandlerExtension
} = testExtension(0)

func (t testExtension) ExtensionName() string {
return strconv.FormatInt(int64(t), 0)
}

func (t testExtension) Validate(schema graphql.ExecutableSchema) error {
return nil
}

func (t testExtension) InterceptOperation(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
opctx := graphql.GetOperationContext(ctx)
log.Printf("%d before operation %s\n", t, opctx.OperationName)
defer func() {
log.Printf("%d after operation %s\n", t, opctx.OperationName)
}()
return next(ctx)
}

func (t testExtension) InterceptField(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
fctx := graphql.GetFieldContext(ctx)
log.Printf("%d before field %s.%s\n", t, fctx.Object, fctx.Field.Name)
defer func() {
log.Printf("%d after field %s.%s\n", t, fctx.Object, fctx.Field.Name)
}()
return next(ctx)
}

func (t testExtension) InterceptResponse(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
log.Printf("%d before response\n", t)
defer func() {
log.Printf("%d after response\n", t)
}()
return next(ctx)
}
```

And the `schema.resolvers.go`:

```go
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
package graph

import (
"context"
"fmt"
"log"
"test/graph/generated"
"test/graph/model"
)

func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
panic(fmt.Errorf("not implemented"))
}

func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
log.Printf("in resolver")

return []*model.Todo{
{
ID: "1234",
Text: "todo",
Done: true,
User: &model.User{
ID: "4567",
Name: "Joe",
},
},
{
ID: "9827",
Text: "todo",
Done: true,
User: &model.User{
ID: "4567",
Name: "Joe",
},
},
}, nil
}

func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
```

### versions
- `gqlgen version`? `v0.11.0`
- `go version`? `go version go1.14.2 linux/amd64`
- dep or go modules? go modules

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.