0

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.

dzimney
  • 565
  • 1
  • 5
  • 15
  • are you getting any visible error on the app engine logs? in case you are getting, could you share them? – Soni Sol Nov 07 '20 at 00:04
  • @JoséSoní No. There's nothing outside the 404. The resource exists at /_ah/api/static/proxy.html. The issue is really in how to adjust the root api path used by the client. – dzimney Nov 07 '20 at 01:34
  • I [opened an issue](https://github.com/google/google-api-javascript-client/issues/697) to the gapi.client project and it seems that this is not a support usage of the client library or Cloud Endpoints. The client library is only intended to be used with Google's API, which formerly lived on /_ah/api paths. It seems Google is moving away from this standard and thus the change in paths. I could update my server configuration to redirect to the correct proxy.html, but considering Google seems to have no intention of supporting this usage, I think I'll resolve to find a different approach. – dzimney Nov 07 '20 at 01:40

0 Answers0