Add support for client that uses GET as transport mechanism (#186)
Current implementation always uses POST as the transport mechanism. Adding GET support enables usage of GET queries for caching simply via URL. Some notes: - I left the existing API for creating a new client as is, but the implementation could be much cleaner by introducing some sort of configuration struct when creating a new client - The construction of the query parameters follows the logic from Apollo's client implementation, which can be found here https://github.com/apollographql/apollo-client/blob/8beb4820edc6352996e08f7f73bde3573f1eb666/src/link/http/rewriteURIForGET.ts - Updated integration tests to use both sets of clients. Updating the tests to use a test suite would be cleaner
This commit is contained in:
+93
-8
@@ -4,9 +4,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
)
|
||||
@@ -53,10 +56,30 @@ type client struct {
|
||||
// The typical method of adding authentication headers is to wrap the client's
|
||||
// Transport to add those headers. See example/caller.go for an example.
|
||||
func NewClient(endpoint string, httpClient Doer) Client {
|
||||
return newClient(endpoint, httpClient, http.MethodPost)
|
||||
}
|
||||
|
||||
// NewClientUsingGet returns a Client which makes requests to the given endpoint,
|
||||
// suitable for most users.
|
||||
//
|
||||
// The client makes GET requests to the given GraphQL endpoint using a GET query,
|
||||
// with the query, operation name and variables encoded as URL parameters.
|
||||
// It will use the given http client, or http.DefaultClient if a nil client is passed.
|
||||
//
|
||||
// The client does not support mutations, and will return an error if passed a request
|
||||
// that attempts one.
|
||||
//
|
||||
// The typical method of adding authentication headers is to wrap the client's
|
||||
// Transport to add those headers. See example/caller.go for an example.
|
||||
func NewClientUsingGet(endpoint string, httpClient Doer) Client {
|
||||
return newClient(endpoint, httpClient, http.MethodGet)
|
||||
}
|
||||
|
||||
func newClient(endpoint string, httpClient Doer, method string) Client {
|
||||
if httpClient == nil || httpClient == (*http.Client)(nil) {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
return &client{httpClient, endpoint, http.MethodPost}
|
||||
return &client{httpClient, endpoint, method}
|
||||
}
|
||||
|
||||
// Doer encapsulates the methods from *http.Client needed by Client.
|
||||
@@ -100,15 +123,14 @@ type Response struct {
|
||||
}
|
||||
|
||||
func (c *client) MakeRequest(ctx context.Context, req *Request, resp *Response) error {
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
var httpReq *http.Request
|
||||
var err error
|
||||
if c.method == http.MethodGet {
|
||||
httpReq, err = c.createGetRequest(req)
|
||||
} else {
|
||||
httpReq, err = c.createPostRequest(req)
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest(
|
||||
c.method,
|
||||
c.endpoint,
|
||||
bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,3 +164,66 @@ func (c *client) MakeRequest(ctx context.Context, req *Request, resp *Response)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) createPostRequest(req *Request) (*http.Request, error) {
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest(
|
||||
c.method,
|
||||
c.endpoint,
|
||||
bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return httpReq, nil
|
||||
}
|
||||
|
||||
func (c *client) createGetRequest(req *Request) (*http.Request, error) {
|
||||
parsedURL, err := url.Parse(c.endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queryParams := parsedURL.Query()
|
||||
queryUpdated := false
|
||||
|
||||
if req.Query != "" {
|
||||
if strings.HasPrefix(strings.TrimSpace(req.Query), "mutation") {
|
||||
return nil, errors.New("client does not support mutations")
|
||||
}
|
||||
queryParams.Set("query", req.Query)
|
||||
queryUpdated = true
|
||||
}
|
||||
|
||||
if req.OpName != "" {
|
||||
queryParams.Set("operationName", req.OpName)
|
||||
queryUpdated = true
|
||||
}
|
||||
|
||||
if req.Variables != nil {
|
||||
variables, variablesErr := json.Marshal(req.Variables)
|
||||
if variablesErr != nil {
|
||||
return nil, variablesErr
|
||||
}
|
||||
queryParams.Set("variables", string(variables))
|
||||
queryUpdated = true
|
||||
}
|
||||
|
||||
if queryUpdated {
|
||||
parsedURL.RawQuery = queryParams.Encode()
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest(
|
||||
c.method,
|
||||
parsedURL.String(),
|
||||
http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return httpReq, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user