From 857ce0bed50671cb2b7d921c2b15b9d2478c69c8 Mon Sep 17 00:00:00 2001 From: kvmw Date: Mon, 4 Aug 2025 11:11:11 +0200 Subject: [PATCH 1/2] refactor: remove simple-oauth2 library An HTTP call with built-in fetch library is used to get access token, in order to reduce external dependencies. Signed-off-by: kvmw --- README.md | 4 +- package-lock.json | 108 +--------------------------------- packages/shared/package.json | 3 +- packages/shared/src/oauth2.js | 47 +++++++++------ 4 files changed, 32 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index 41833c0..a859c25 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ For information on the Service Registry product in Tanzu Platform for Cloud Foun - Push the `greeter-messages` application: ``` - cd greeter-messages && cf push + cd packages/greeter-messages && cf push ``` - Push the `greeter` application: ``` - cd greeter && cf push + cd packages/greeter && cf push ``` ## Trying It Out diff --git a/package-lock.json b/package-lock.json index 1c172e9..c636359 100644 --- a/package-lock.json +++ b/package-lock.json @@ -166,53 +166,6 @@ "resolved": "packages/shared", "link": true }, - "node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/bourne": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", - "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/hoek": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", - "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@hapi/topo/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/wreck": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.0.tgz", - "integrity": "sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/boom": "^10.0.1", - "@hapi/bourne": "^3.0.0", - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -292,33 +245,6 @@ "url": "https://opencollective.com/pkgr" } }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/address/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1573,25 +1499,6 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "license": "MIT" }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/joi/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2324,18 +2231,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/simple-oauth2": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/simple-oauth2/-/simple-oauth2-5.1.0.tgz", - "integrity": "sha512-gWDa38Ccm4MwlG5U7AlcJxPv3lvr80dU7ARJWrGdgvOKyzSj1gr3GBPN1rABTedAYvC/LsGYoFuFxwDBPtGEbw==", - "license": "Apache-2.0", - "dependencies": { - "@hapi/hoek": "^11.0.4", - "@hapi/wreck": "^18.0.0", - "debug": "^4.3.4", - "joi": "^17.6.4" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -2603,8 +2498,7 @@ "license": "Apache-2.0", "dependencies": { "dotenv": "^17.2.0", - "eureka-js-client": "^4.5.0", - "simple-oauth2": "^5.1.0" + "eureka-js-client": "^4.5.0" } } } diff --git a/packages/shared/package.json b/packages/shared/package.json index b98bcd3..59c3334 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -7,7 +7,6 @@ "license": "Apache-2.0", "dependencies": { "dotenv": "^17.2.0", - "eureka-js-client": "^4.5.0", - "simple-oauth2": "^5.1.0" + "eureka-js-client": "^4.5.0" } } diff --git a/packages/shared/src/oauth2.js b/packages/shared/src/oauth2.js index 51dffad..abe7924 100644 --- a/packages/shared/src/oauth2.js +++ b/packages/shared/src/oauth2.js @@ -1,34 +1,43 @@ -import { ClientCredentials } from 'simple-oauth2'; import services from './vcap-services.js'; const credentials = services.getCredentials('p.service-registry'); -const client = new ClientCredentials({ - client: { - id: credentials.client_id, - secret: credentials.client_secret, - }, - auth: { - tokenHost: credentials.access_token_uri.replace('/oauth/token', ''), - tokenPath: '/oauth/token', - }, -}); - let token = null; +// Checks if token is expired or will be expired in given window. +const expired = (token, expirationWindowSeconds = 0) => { + return ( + !token || + token.expires_at - (Date.now() + expirationWindowSeconds * 1000) <= 0 + ); +}; + // Requests an access token from the UAA server using client credentials. const getAccessToken = async () => { // If token is not set or expired, request a new one. - if (!token || token.expired(3)) { - try { - token = await client.getToken(); - } catch (error) { - console.error('Failed to get access token', error.message); - throw error; + if (!token || expired(token, 10)) { + const response = await fetch(credentials.access_token_uri, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Basic ${Buffer.from(`${credentials.client_id}:${credentials.client_secret}`).toString('base64')}`, + }, + body: new URLSearchParams({ grant_type: 'client_credentials' }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); } + + const data = await response.json(); + + token = { + access_token: data.access_token, + expires_at: Date.now() + data.expires_in * 1000, + }; } - return token.token.access_token; + return token.access_token; }; export default { From db3f28d80508591084400a5a42e6a34390bee3a4 Mon Sep 17 00:00:00 2001 From: kvmw Date: Mon, 4 Aug 2025 11:15:45 +0200 Subject: [PATCH 2/2] feat: simple round-robin solution for load-balancing Signed-off-by: kvmw --- packages/greeter/server.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/greeter/server.js b/packages/greeter/server.js index c8b0cb8..c536777 100644 --- a/packages/greeter/server.js +++ b/packages/greeter/server.js @@ -4,7 +4,10 @@ import { env, eureka } from '@greeting/shared'; const app = express(); const eurekaClient = await eureka.getClient(env.port); -const serviceHost = () => { +// Counter for round-robin load balancing +let instanceCounter = 0; + +const getServiceInstance = () => { const instances = eurekaClient .getInstancesByAppId('GREETER-MESSAGES') @@ -14,14 +17,17 @@ const serviceHost = () => { throw new Error('No instances of GREETER-MESSAGES available'); } - return instances[0].hostName; + const selectedInstance = instances[instanceCounter % instances.length]; + instanceCounter = (instanceCounter + 1) % instances.length; + + return selectedInstance.hostName; }; app.get('/hello', async (req, res) => { const salutation = req.query.salutation || 'Hello'; const name = req.query.name || 'Bob'; - const url = `https://${serviceHost()}/greeting?salutation=${salutation}&name=${name}`; + const url = `https://${getServiceInstance()}/greeting?salutation=${salutation}&name=${name}`; const response = await fetch(`${url}`, { headers: {