diff --git a/package-lock.json b/package-lock.json index fa915ce..08ebe7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "polyapi", - "version": "0.23.13", + "version": "0.23.14", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 4428a7e..4dbd91e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "polyapi", - "version": "0.23.13", + "version": "0.23.14", "description": "Poly is a CLI tool to help create and manage your Poly definitions.", "license": "MIT", "repository": { diff --git a/src/commands/generate/index.ts b/src/commands/generate/index.ts index 6c037b4..bd3d2e7 100644 --- a/src/commands/generate/index.ts +++ b/src/commands/generate/index.ts @@ -66,6 +66,16 @@ const prepareDir = async (polyPath: string) => { } }; +const getExecutionConfig = () => ({ + directExecute: process.env.API_FUNCTION_DIRECT_EXECUTE === 'true', + mtls: { + certPath: process.env.MTLS_CERT_PATH, + keyPath: process.env.MTLS_KEY_PATH, + caPath: process.env.MTLS_CA_PATH, + rejectUnauthorized: process.env.NODE_ENV !== 'development', + }, +}); + const generateRedirectIndexFiles = async (polyPath: string) => { const defaultPolyLib = getPolyLibPath(DEFAULT_POLY_PATH); @@ -156,6 +166,7 @@ const generateApiFunctionJSFiles = async (libPath: string, specifications: ApiFu `${libPath}/api/index.js`, template({ specifications, + executionConfig: getExecutionConfig(), }), ); }; diff --git a/templates/api-index.js.hbs b/templates/api-index.js.hbs index 752bf93..52d270f 100644 --- a/templates/api-index.js.hbs +++ b/templates/api-index.js.hbs @@ -1,6 +1,19 @@ const axios = require('../axios'); const set = require('lodash/set'); const merge = require('lodash/merge'); +const https = require('https'); +const fs = require('fs'); + +// Environment variables injected during generation +const env = { + directExecute: {{executionConfig.directExecute}}, + mtls: { + certPath: '{{executionConfig.mtls.certPath}}', + keyPath: '{{executionConfig.mtls.keyPath}}', + caPath: '{{executionConfig.mtls.caPath}}', + rejectUnauthorized: {{executionConfig.mtls.rejectUnauthorized}}, + } +}; const functions = [ {{#each specifications}} @@ -8,6 +21,28 @@ const functions = [ {{/each}} ]; +// Create MTLS agent if paths are provided +let httpsAgent = undefined; +const getHttpsAgent = () => { + if (httpsAgent) { + return httpsAgent; + } + + const { mtls } = env; + if (!mtls.certPath || !mtls.keyPath || !mtls.caPath) { + return undefined; + } + + httpsAgent = new https.Agent({ + cert: fs.readFileSync(mtls.certPath), + key: fs.readFileSync(mtls.keyPath), + ca: fs.readFileSync(mtls.caPath), + rejectUnauthorized: mtls.rejectUnauthorized, + }); + + return httpsAgent; +}; + module.exports = (clientID, polyCustom) => merge( {}, functions.reduce( @@ -15,8 +50,66 @@ module.exports = (clientID, polyCustom) => merge( acc, path, (...args) => { - const requestStartTime = Date.now(); + const requestServerStartTime = Date.now(); const requestArgs = argKeys.reduce((acc, key, index) => ({ ...acc, [key]: args[index] }), {}); + + // Check if direct execution is enabled + const { directExecute } = env; + + if (directExecute === true) { + // Make direct API call + + let polyHeaders; + let serverPreperationTimeMs; + let roundTripServerNetworkLatencyMs; + let requestApiStartTime; + + return axios.post( + `/functions/api/${id}/direct-execute?clientId=${clientID}`, + requestArgs, + { + headers: { + 'x-poly-execution-id': polyCustom.executionId, + } + } + ).then(({ headers, data }) => { + polyHeaders = headers; + if (data && (data.status < 200 || data.status >= 300)) { + console.error('Error getting direct execution data for api function with id:', id, 'Status code:', data.status, 'Request data:', requestArgs, 'Response data:', data.data); + } + + serverPreperationTimeMs = Number(polyHeaders['x-poly-execution-duration']); + roundTripServerNetworkLatencyMs = Date.now() - requestServerStartTime - serverPreperationTimeMs; + + requestApiStartTime = Date.now(); + const httpsAgent = getHttpsAgent(); + + return axios({ + ...data, + headers: { + ...data.headers, + }, + httpsAgent, + }) + }).then(({ headers, data, status, ...args }) => { + if (status && (status < 200 || status >= 300)) { + console.error('Error direct executing api function with id:', id, 'Status code:', status, 'Request data:', requestArgs, 'Response data:', data.data); + } + const apiExecutionTimeMs = Date.now() - requestApiStartTime; + return { + data: data, + status: status, + headers: { ...headers }, + metrics: { + roundTripServerNetworkLatencyMs, + serverPreperationTimeMs, + apiExecutionTimeMs, + } + }; + }); + } + + // default indirect execution return axios.post( `/functions/api/${id}/execute?clientId=${clientID}`, { @@ -36,7 +129,7 @@ module.exports = (clientID, polyCustom) => merge( console.error('Error executing api function with id:', id, 'Status code:', data.status, 'Request data:', requestArgs, 'Response data:', responseData); } const serverExecutionTimeMs = Number(headers['x-poly-execution-duration']); - const roundTripNetworkLatencyMs = Date.now() - requestStartTime - serverExecutionTimeMs; + const roundTripNetworkLatencyMs = Date.now() - requestServerStartTime - serverExecutionTimeMs; return { ...data, metrics: {