-
Notifications
You must be signed in to change notification settings - Fork 553
/
graphql.go
128 lines (112 loc) · 3.58 KB
/
graphql.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// SPDX-License-Identifier: Apache-2.0
package proxy
import (
"bytes"
"context"
"fmt"
"io"
"net/url"
"strconv"
"strings"
"github.com/luraproject/lura/v2/config"
"github.com/luraproject/lura/v2/logging"
"github.com/luraproject/lura/v2/transport/http/client/graphql"
)
// NewGraphQLMiddleware returns a middleware with or without the GraphQL
// proxy wrapping the next element (depending on the configuration).
// It supports both queries and mutations.
// For queries, it completes the variables object using the request params.
// For mutations, it overides the defined variables with the request body.
// The resulting request will have a proper graphql body with the query and the
// variables
func NewGraphQLMiddleware(logger logging.Logger, remote *config.Backend) Middleware {
opt, err := graphql.GetOptions(remote.ExtraConfig)
if err != nil {
if err != graphql.ErrNoConfigFound {
logger.Warning(
fmt.Sprintf("[BACKEND: %s %s -> %s][GraphQL] %s", remote.ParentEndpoint, remote.ParentEndpoint, remote.URLPattern, err.Error()))
}
return emptyMiddlewareFallback(logger)
}
extractor := graphql.New(*opt)
var generateBodyFn func(*Request) ([]byte, error)
var generateQueryFn func(*Request) (url.Values, error)
switch opt.Type {
case graphql.OperationMutation:
generateBodyFn = func(req *Request) ([]byte, error) {
if req.Body == nil {
return extractor.BodyFromBody(strings.NewReader(""))
}
defer req.Body.Close()
return extractor.BodyFromBody(req.Body)
}
generateQueryFn = func(req *Request) (url.Values, error) {
if req.Body == nil {
return extractor.QueryFromBody(strings.NewReader(""))
}
defer req.Body.Close()
return extractor.QueryFromBody(req.Body)
}
case graphql.OperationQuery:
generateBodyFn = func(req *Request) ([]byte, error) {
return extractor.BodyFromParams(req.Params)
}
generateQueryFn = func(req *Request) (url.Values, error) {
return extractor.QueryFromParams(req.Params)
}
default:
return emptyMiddlewareFallback(logger)
}
return func(next ...Proxy) Proxy {
if len(next) > 1 {
logger.Fatal("too many proxies for this %s %s -> %s proxy middleware: NewGraphQLMiddleware only accepts 1 proxy, got %d",
remote.ParentEndpointMethod, remote.ParentEndpoint, remote.URLPattern, len(next))
return nil
}
logger.Debug(
fmt.Sprintf(
"[BACKEND: %s %s -> %s][GraphQL] Operation: %s, Method: %s",
remote.ParentEndpointMethod,
remote.ParentEndpoint,
remote.URLPattern,
opt.Type,
opt.Method,
),
)
if opt.Method == graphql.MethodGet {
return func(ctx context.Context, req *Request) (*Response, error) {
q, err := generateQueryFn(req)
if err != nil {
return nil, err
}
req.Body = io.NopCloser(bytes.NewReader([]byte{}))
req.Method = string(opt.Method)
req.Headers["Content-Length"] = []string{"0"}
// even when there is no content, we just set the content-type
// header to be safe if the server side checks it:
req.Headers["Content-Type"] = []string{"application/json"}
if req.Query != nil {
for k, vs := range q {
for _, v := range vs {
req.Query.Add(k, v)
}
}
} else {
req.Query = q
}
return next[0](ctx, req)
}
}
return func(ctx context.Context, req *Request) (*Response, error) {
b, err := generateBodyFn(req)
if err != nil {
return nil, err
}
req.Body = io.NopCloser(bytes.NewReader(b))
req.Method = string(opt.Method)
req.Headers["Content-Length"] = []string{strconv.Itoa(len(b))}
req.Headers["Content-Type"] = []string{"application/json"}
return next[0](ctx, req)
}
}
}