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 `