Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added subscriptions option to handler #74

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
132 changes: 94 additions & 38 deletions graphiql.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@ import (
"encoding/json"
"html/template"
"net/http"
"strings"

"github.com/graphql-go/graphql"
)

// graphiqlData is the page data structure of the rendered GraphiQL page
// data is the page data structure of the rendered GraphiQL page
type graphiqlData struct {
GraphiqlVersion string
QueryString string
VariablesString string
OperationName string
ResultString string
GraphiqlVersion string
SubscriptionTransportVersion string
QueryString string
ResultString string
VariablesString string
OperationName string
Endpoint template.URL
SubscriptionEndpoint template.URL
UsingHTTP bool
UsingWS bool
}

// renderGraphiQL renders the GraphiQL GUI
func renderGraphiQL(w http.ResponseWriter, params graphql.Params) {
func renderGraphiQL(w http.ResponseWriter, params graphql.Params, handler Handler) {
t := template.New("GraphiQL")
t, err := t.Parse(graphiqlTemplate)
if err != nil {
Expand Down Expand Up @@ -50,12 +56,29 @@ func renderGraphiQL(w http.ResponseWriter, params graphql.Params) {
resString = string(result)
}

isEndpointUsingWS := strings.HasPrefix(handler.Endpoint, "ws://")
UsingHTTP := !isEndpointUsingWS
UsingWS := isEndpointUsingWS || handler.SubscriptionsEndpoint != ""
SubscriptionEndpoint := ""
if UsingWS {
if isEndpointUsingWS {
SubscriptionEndpoint = handler.Endpoint
} else {
SubscriptionEndpoint = handler.SubscriptionsEndpoint
}
}

d := graphiqlData{
GraphiqlVersion: graphiqlVersion,
QueryString: params.RequestString,
ResultString: resString,
VariablesString: varsString,
OperationName: params.OperationName,
GraphiqlVersion: graphiqlVersion,
SubscriptionTransportVersion: subscriptionTransportVersion,
QueryString: params.RequestString,
ResultString: resString,
VariablesString: varsString,
OperationName: params.OperationName,
Endpoint: template.URL(handler.Endpoint),
SubscriptionEndpoint: template.URL(SubscriptionEndpoint),
UsingHTTP: UsingHTTP,
UsingWS: UsingWS,
}
err = t.ExecuteTemplate(w, "index", d)
if err != nil {
Expand All @@ -68,6 +91,9 @@ func renderGraphiQL(w http.ResponseWriter, params graphql.Params) {
// graphiqlVersion is the current version of GraphiQL
const graphiqlVersion = "0.11.11"

// subscriptionTransportVersion is the current version of the subscription transport of GraphiQL
const subscriptionTransportVersion = "0.8.2"

// tmpl is the page template to render GraphiQL
const graphiqlTemplate = `
{{ define "index" }}
Expand Down Expand Up @@ -103,6 +129,17 @@ add "&raw" to the end of the URL within a browser.
<script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
<script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/graphiql@{{ .GraphiqlVersion }}/graphiql.min.js"></script>

{{ if .UsingHTTP }}
<script src="//cdn.jsdelivr.net/fetch/2.0.1/fetch.min.js"></script>
{{ end }}
{{ if .UsingWS }}
<script src="//unpkg.com/subscriptions-transport-ws@{{ .SubscriptionTransportVersion }}/browser/client.js"></script>
{{ end }}
{{ if and .UsingWS .UsingHTTP }}
<script src="//unpkg.com/[email protected]/browser/client.js"></script>
{{ end }}

</head>
<body>
<div id="graphiql">Loading...</div>
Expand All @@ -118,10 +155,8 @@ add "&raw" to the end of the URL within a browser.
});

// Produce a Location query string from a parameter object.
function locationQuery(params) {
return '?' + Object.keys(params).filter(function (key) {
return Boolean(params[key]);
}).map(function (key) {
function locationQuery(params, location) {
return (location ? location: '') + '?' + Object.keys(params).map(function (key) {
return encodeURIComponent(key) + '=' +
encodeURIComponent(params[key]);
}).join('&');
Expand All @@ -140,28 +175,49 @@ add "&raw" to the end of the URL within a browser.
otherParams[k] = parameters[k];
}
}
var fetchURL = locationQuery(otherParams);

// Defines a GraphQL fetcher using the fetch API.
function graphQLFetcher(graphQLParams) {
return fetch(fetchURL, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(graphQLParams),
credentials: 'include',
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}

{{ if .UsingWS }}
var subscriptionsClient = new window.SubscriptionsTransportWs.SubscriptionClient({{ .SubscriptionEndpoint }}, {
reconnect: true
});
}
var graphQLWSFetcher = subscriptionsClient.request.bind(subscriptionsClient);
{{ end }}

{{ if .UsingHTTP }}
var fetchURL = locationQuery(otherParams, {{ .Endpoint }});

// Defines a GraphQL fetcher using the fetch API.
function graphQLHttpFetcher(graphQLParams) {
return fetch(fetchURL, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(graphQLParams),
credentials: 'include',
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
{{ end }}

{{ if and .UsingWS .UsingHTTP }}
var fetcher = window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLHttpFetcher);
{{ else }}
{{ if .UsingWS }}
var fetcher = graphQLWSFetcher;
{{ end }}
{{ if .UsingHTTP }}
var fetcher = graphQLHttpFetcher;
{{ end }}
{{ end }}

// When the query and variables string is edited, update the URL bar so
// that it can be easily shared.
Expand All @@ -187,7 +243,7 @@ add "&raw" to the end of the URL within a browser.
// Render <GraphiQL /> into the body.
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: graphQLFetcher,
fetcher: fetcher,
onEditQuery: onEditQuery,
onEditVariables: onEditVariables,
onEditOperationName: onEditOperationName,
Expand Down
60 changes: 34 additions & 26 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ const (
type ResultCallbackFn func(ctx context.Context, params *graphql.Params, result *graphql.Result, responseBody []byte)

type Handler struct {
Schema *graphql.Schema
pretty bool
graphiql bool
playground bool
rootObjectFn RootObjectFn
resultCallbackFn ResultCallbackFn
formatErrorFn func(err error) gqlerrors.FormattedError
Schema *graphql.Schema
pretty bool
graphiql bool
Endpoint string
SubscriptionsEndpoint string
playground bool
rootObjectFn RootObjectFn
resultCallbackFn ResultCallbackFn
formatErrorFn func(err error) gqlerrors.FormattedError
}

type RequestOptions struct {
Expand Down Expand Up @@ -153,7 +155,7 @@ func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *
acceptHeader := r.Header.Get("Accept")
_, raw := r.URL.Query()["raw"]
if !raw && !strings.Contains(acceptHeader, "application/json") && strings.Contains(acceptHeader, "text/html") {
renderGraphiQL(w, params)
renderGraphiQL(w, params, *h)
return
}
}
Expand Down Expand Up @@ -197,21 +199,25 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
type RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{}

type Config struct {
Schema *graphql.Schema
Pretty bool
GraphiQL bool
Playground bool
RootObjectFn RootObjectFn
ResultCallbackFn ResultCallbackFn
FormatErrorFn func(err error) gqlerrors.FormattedError
Schema *graphql.Schema
Pretty bool
GraphiQL bool
Endpoint string
SubscriptionsEndpoint string
Playground bool
RootObjectFn RootObjectFn
ResultCallbackFn ResultCallbackFn
FormatErrorFn func(err error) gqlerrors.FormattedError
}

func NewConfig() *Config {
return &Config{
Schema: nil,
Pretty: true,
GraphiQL: true,
Playground: false,
Schema: nil,
Pretty: true,
GraphiQL: true,
Endpoint: "",
SubscriptionsEndpoint: "",
Playground: false,
}
}

Expand All @@ -225,12 +231,14 @@ func New(p *Config) *Handler {
}

return &Handler{
Schema: p.Schema,
pretty: p.Pretty,
graphiql: p.GraphiQL,
playground: p.Playground,
rootObjectFn: p.RootObjectFn,
resultCallbackFn: p.ResultCallbackFn,
formatErrorFn: p.FormatErrorFn,
Schema: p.Schema,
pretty: p.Pretty,
graphiql: p.GraphiQL,
Endpoint: p.Endpoint,
SubscriptionsEndpoint: p.SubscriptionsEndpoint,
playground: p.Playground,
rootObjectFn: p.RootObjectFn,
resultCallbackFn: p.ResultCallbackFn,
formatErrorFn: p.FormatErrorFn,
}
}