github.com/weaviate/weaviate@v1.24.6/adapters/handlers/graphql/graphiql/graphiql.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  // Based on `graphiql.go` from https://github.com/graphql-go/handler
    13  // only made RenderGraphiQL a public function.
    14  package graphiql
    15  
    16  import (
    17  	"encoding/json"
    18  	"html/template"
    19  	"net/http"
    20  	"strings"
    21  )
    22  
    23  // graphiqlVersion is the current version of GraphiQL
    24  const graphiqlVersion = "0.11.11"
    25  
    26  // graphiqlData is the page data structure of the rendered GraphiQL page
    27  type graphiqlData struct {
    28  	GraphiqlVersion string
    29  	QueryString     string
    30  	Variables       string
    31  	OperationName   string
    32  	AuthKey         string
    33  	AuthToken       string
    34  }
    35  
    36  func AddMiddleware(next http.Handler) http.Handler {
    37  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    38  		if strings.HasPrefix(r.URL.Path, "/v1/graphql") && r.Method == http.MethodGet {
    39  			renderGraphiQL(w, r)
    40  		} else {
    41  			next.ServeHTTP(w, r)
    42  		}
    43  	})
    44  }
    45  
    46  // renderGraphiQL renders the GraphiQL GUI
    47  func renderGraphiQL(w http.ResponseWriter, r *http.Request) {
    48  	w.Header().Set("WWW-Authenticate", `Basic realm="Provide your key and token (as username as password respectively)"`)
    49  
    50  	user, password, authOk := r.BasicAuth()
    51  	if !authOk {
    52  		http.Error(w, "Not authorized", 401)
    53  		return
    54  	}
    55  
    56  	queryParams := r.URL.Query()
    57  
    58  	t := template.New("GraphiQL")
    59  	t, err := t.Parse(graphiqlTemplate)
    60  	if err != nil {
    61  		http.Error(w, err.Error(), http.StatusInternalServerError)
    62  		return
    63  	}
    64  
    65  	// Attempt to deserialize the 'variables' query key to something reasonable.
    66  	var queryVars interface{}
    67  	err = json.Unmarshal([]byte(queryParams.Get("variables")), &queryVars)
    68  
    69  	var varsString string
    70  	if err == nil {
    71  		vars, err := json.MarshalIndent(queryVars, "", "  ")
    72  		if err != nil {
    73  			http.Error(w, err.Error(), http.StatusInternalServerError)
    74  			return
    75  		}
    76  		varsString = string(vars)
    77  		if varsString == "null" {
    78  			varsString = ""
    79  		}
    80  	}
    81  
    82  	// Create result string
    83  	d := graphiqlData{
    84  		GraphiqlVersion: graphiqlVersion,
    85  		QueryString:     queryParams.Get("query"),
    86  		Variables:       varsString,
    87  		OperationName:   queryParams.Get("operationName"),
    88  		AuthKey:         user,
    89  		AuthToken:       password,
    90  	}
    91  	err = t.ExecuteTemplate(w, "index", d)
    92  	if err != nil {
    93  		http.Error(w, err.Error(), http.StatusInternalServerError)
    94  	}
    95  }
    96  
    97  // tmpl is the page template to render GraphiQL
    98  const graphiqlTemplate = `
    99  {{ define "index" }}
   100  <!--
   101  The request to this GraphQL server provided the header "Accept: text/html"
   102  and as a result has been presented GraphiQL - an in-browser IDE for
   103  exploring GraphQL.
   104  
   105  If you wish to receive JSON, provide the header "Accept: application/json" or
   106  add "&raw" to the end of the URL within a browser.
   107  -->
   108  <!DOCTYPE html>
   109  <html>
   110  <head>
   111    <meta charset="utf-8" />
   112    <title>GraphiQL</title>
   113    <meta name="robots" content="noindex" />
   114    <meta name="referrer" content="origin">
   115    <style>
   116      body {
   117        height: 100%;
   118        margin: 0;
   119        overflow: hidden;
   120        width: 100%;
   121      }
   122      #graphiql {
   123        height: 100vh;
   124      }
   125    </style>
   126    <link href="//cdn.jsdelivr.net/npm/graphiql@{{ .GraphiqlVersion }}/graphiql.css" rel="stylesheet" />
   127    <script src="//cdn.jsdelivr.net/es6-promise/4.0.5/es6-promise.auto.min.js"></script>
   128    <script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
   129    <script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
   130    <script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
   131    <script src="//cdn.jsdelivr.net/npm/graphiql@{{ .GraphiqlVersion }}/graphiql.min.js"></script>
   132  </head>
   133  <body>
   134    <div id="graphiql">Loading...</div>
   135    <script>
   136      // Collect the URL parameters
   137      var parameters = {};
   138      window.location.search.substr(1).split('&').forEach(function (entry) {
   139        var eq = entry.indexOf('=');
   140        if (eq >= 0) {
   141          parameters[decodeURIComponent(entry.slice(0, eq))] =
   142            decodeURIComponent(entry.slice(eq + 1));
   143        }
   144      });
   145  
   146      // Produce a Location query string from a parameter object.
   147      function locationQuery(params) {
   148        return '?' + Object.keys(params).filter(function (key) {
   149          return Boolean(params[key]);
   150        }).map(function (key) {
   151          return encodeURIComponent(key) + '=' +
   152            encodeURIComponent(params[key]);
   153        }).join('&');
   154      }
   155  
   156      // Derive a fetch URL from the current URL, sans the GraphQL parameters.
   157      var graphqlParamNames = {
   158        query: true,
   159        variables: true,
   160        operationName: true
   161      };
   162  
   163      var otherParams = {};
   164      for (var k in parameters) {
   165        if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
   166          otherParams[k] = parameters[k];
   167        }
   168      }
   169      var fetchURL = locationQuery(otherParams);
   170  
   171      // Defines a GraphQL fetcher using the fetch API.
   172      function graphQLFetcher(graphQLParams) {
   173        return fetch(fetchURL, {
   174          method: 'post',
   175          headers: {
   176            'Accept': 'application/json',
   177            'Content-Type': 'application/json',
   178            'X-API-KEY': {{ .AuthKey }},
   179            'X-API-TOKEN': {{ .AuthToken }}
   180          },
   181          body: JSON.stringify(graphQLParams),
   182          credentials: 'include',
   183        }).then(function (response) {
   184          return response.text();
   185        }).then(function (responseBody) {
   186          try {
   187            return JSON.parse(responseBody);
   188          } catch (error) {
   189            return responseBody;
   190          }
   191        });
   192      }
   193  
   194      // When the query and variables string is edited, update the URL bar so
   195      // that it can be easily shared.
   196      function onEditQuery(newQuery) {
   197        parameters.query = newQuery;
   198        updateURL();
   199      }
   200  
   201      function onEditVariables(newVariables) {
   202        parameters.variables = newVariables;
   203        updateURL();
   204      }
   205  
   206      function onEditOperationName(newOperationName) {
   207        parameters.operationName = newOperationName;
   208        updateURL();
   209      }
   210  
   211      function updateURL() {
   212        history.replaceState(null, null, locationQuery(parameters));
   213      }
   214  
   215      // Render <GraphiQL /> into the body.
   216      ReactDOM.render(
   217        React.createElement(GraphiQL, {
   218          fetcher: graphQLFetcher,
   219          onEditQuery: onEditQuery,
   220          onEditVariables: onEditVariables,
   221          onEditOperationName: onEditOperationName,
   222          query: {{ .QueryString }},
   223          response: null,
   224          variables: {{ .Variables }},
   225          operationName: {{ .OperationName }},
   226        }),
   227        document.getElementById('graphiql')
   228      );
   229    </script>
   230  </body>
   231  </html>
   232  {{ end }}
   233  `