I have an API built on Google Cloud Endpoints and am having trouble loading the endpoints through gapi.client using the API's discovery doc. The gapi.client library loads without issue, but my API fails to initialize because gapi.client is attempting to load https://[PROJECT_ID].appspot.com/static/proxy.html
rather than https://[PROJECT_ID].appspot.com/_ah/api/static/proxy.html
.
I've tested the following code with both https://apis.google.com/js/api.js
and https://apis.google.com/js/client.js
and both produce the same result. If anyone could explain the difference between these two files that'd also be appreciated.
Here is the JavaScript used to load the API (executed after https://apis.google.com/js/api.js
has loaded):
gapi.load('client', function() {
// executes.
console.log("client ready");
gapi.client.init({
discoveryDocs: ["url-path-to-discovery-doc-see-below"]
}).then(function(response) {
// never executed
console.log("load response", response);
if (listeners != null && 0 < listeners.length) {
for (let obj in listeners) {
const callback = obj.callback;
if (typeof callback == 'function') {
callback.call(obj.context);
}
}
}
}).catch(function(response) {
// never executed
console.log("init failed", response);
});
});
The then
and catch
callbacks never execute and the script throws a 404 error trying to access https://[PROJECT_ID].appspot.com/static/proxy.html?inconsequential-query-string
.
Accessing https://[PROJECT_ID].appspot.com/_ah/api/static/proxy.html
in a browser loads as expect.
Here's my discovery doc (generated using endpoints-framework-maven-plugin
), instances of [PROJECT_ID]
replaced with the actual Google Cloud Project ID:
{
"auth": {
"oauth2": {
"scopes": {
"https://www.googleapis.com/auth/userinfo.email": {
"description": "View your email address"
}
}
}
},
"basePath": "/_ah/api/myapi/v1/",
"baseUrl": "https://[PROJECT_ID].appspot.com/_ah/api/myapi/v1/",
"batchPath": "batch",
"description": "This is an API",
"discoveryVersion": "v1",
"icons": {
"x16": "https://www.gstatic.com/images/branding/product/1x/googleg_16dp.png",
"x32": "https://www.gstatic.com/images/branding/product/1x/googleg_32dp.png"
},
"id": "auth:v1",
"kind": "discovery#restDescription",
"name": "myapi",
"parameters": {
"alt": {
"default": "json",
"description": "Data format for the response.",
"enum": [
"json"
],
"enumDescriptions": [
"Responses with Content-Type of application/json"
],
"location": "query",
"type": "string"
},
"fields": {
"description": "Selector specifying which fields to include in a partial response.",
"location": "query",
"type": "string"
},
"key": {
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
"location": "query",
"type": "string"
},
"oauth_token": {
"description": "OAuth 2.0 token for the current user.",
"location": "query",
"type": "string"
},
"prettyPrint": {
"default": "true",
"description": "Returns response with indentations and line breaks.",
"location": "query",
"type": "boolean"
},
"quotaUser": {
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.",
"location": "query",
"type": "string"
},
"userIp": {
"description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.",
"location": "query",
"type": "string"
}
},
"protocol": "rest",
"resources": {
"test": {
"methods": {
"get": {
"description": "Test endpoint GET method.",
"httpMethod": "GET",
"id": "myapi.test.get",
"parameterOrder": [
"message"
],
"parameters": {
"message": {
"location": "query",
"required": true,
"type": "string"
}
},
"path": "test.get",
"response": {
"$ref": "Response"
},
"scopes": [
"https://www.googleapis.com/auth/userinfo.email"
]
},
"post": {
"description": "Test endpoint POST method.",
"httpMethod": "POST",
"id": "myapi.test.post",
"parameterOrder": [
"message"
],
"parameters": {
"message": {
"location": "query",
"required": true,
"type": "string"
}
},
"path": "test.post",
"response": {
"$ref": "Response"
},
"scopes": [
"https://www.googleapis.com/auth/userinfo.email"
]
}
}
}
},
"rootUrl": "https://[PROJECT_ID].appspot.com/_ah/api/",
"schemas": {
"Response": {
"id": "Response",
"properties": {
"message": {
"description": "",
"type": "string"
},
"status": {
"description": "",
"type": "string"
}
},
"type": "object"
}
},
"servicePath": "myapi/v1/",
"title": "My Endpoints",
"version": "v1"
}
Additional thoughts and notes:
It would appear the API "root" (/_ah/api) needs to be set somewhere, but all the documentation I've found seems to show support for setting the root is deprecated.
The API is built in Java and utilizes Google's API annotations from [Maven: com.google.endpoints:endpoints-framework:2.2.1]. The @Api annotation does allow for root
and backendRoot
fields, but both are deprecated.
The API loads as expected when loaded using gapi.client.load, but this load function is deprecated according to the documentation.
gapi.client.load('myapi', 'v1', function() {
console.log("myapi ready");
gapi.client.myapi.test.post({
message: "Hello, API!"
}).execute(function(response) {
// expected response from API.
console.log(response);
});
}, 'https://[PROJECT_ID].appspot.com/_ah/api');
Many thanks.