Skip to content

Commit 859fc4a

Browse files
quiet-nodeNana-ECebadiere
authored
chore: Cherry pick PR2504 (#2541)
feat: Add Koa Server Request Timeout (#2504) * Add Koa Server Request Timeout * fix: Added test and documentation to the server request timeout setting. fix: Added doc updated. fix: Bumped timeout to 60 seconds. fix: removed only. fix: Added empty line. Update packages/server/src/index.ts fix: Applied feedback fix: Added back the chai. fix: Restored the chai exclude fix: Removed the async in the sendJsonRpcRequestWithDelay fix: Reduced the timeout to allow tests to finish. fix: Moved timeout test to rateLimiter. fix: Removed comma after LIMIT_DURATION fix: Bumped up the timeout delay to ensure it works in CI. fix: Testing in CI. Removed the .only fix: Adjusted the test delay. fix: Added error details. fix: Added more debug info. fix: Added delay for linux. Debugging in CI. fix: Debugging in CI. Setting test timeout fix: Debugging in CI. Nested promise. fix: Including delay in testfile. Debugging CI. fix: Debugging in CIfix: Debugging in CI. Updated package-log. fix: Extracted setting logic to util function to be used in server startup and test framework fix: Bumped up the timeout for tests on local node. * fix: Moved into its own CI job. * fix: Cleanup * fix: Clean up. --------- Signed-off-by: Nana Essilfie-Conduah <[email protected]> Signed-off-by: ebadiere <[email protected]> Signed-off-by: Logan Nguyen <[email protected]> Co-authored-by: Nana Essilfie-Conduah <[email protected]> Co-authored-by: ebadiere <[email protected]>
1 parent d0a8ceb commit 859fc4a

File tree

13 files changed

+148
-2
lines changed

13 files changed

+148
-2
lines changed

.github/workflows/acceptance.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ jobs:
100100
with:
101101
testfilter: cache-service
102102

103+
server-config:
104+
name: Server Config
105+
uses: ./.github/workflows/acceptance-workflow.yml
106+
with:
107+
testfilter: serverconfig
108+
103109
publish_results:
104110
name: Publish Results
105111
if: ${{ !cancelled() }}

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Unless you need to set a non-default value, it is recommended to only populate o
3939
| `RATE_LIMIT_DISABLED` | "false" | Flag to disable IP based rate limiting. |
4040
| `REQUEST_ID_IS_OPTIONAL` | "" | Flag to set it the JSON RPC request id field in the body should be optional. Note, this breaks the API spec and is not advised and is provided for test purposes only where some wallets may be non compliant |
4141
| `SERVER_PORT` | "7546" | The RPC server port number to listen for requests on. Currently a static value defaulting to 7546. See [#955](https://github.com/hashgraph/hedera-json-rpc-relay/issues/955) |
42+
| `SERVER_REQUEST_TIMEOUT_MS`| "60000" | The time of inactivity allowed before a timeout is triggered and the socket is closed. See [NodeJs Server Timeout](https://nodejs.org/api/http.html#serversettimeoutmsecs-callback) |
4243

4344
## Relay
4445

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"acceptancetest:precompile-calls": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@precompile-calls' --exit",
4747
"acceptancetest:cache-service": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@cache-service' --exit",
4848
"acceptancetest:rpc_api_schema_conformity": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@api-conformity' --exit",
49+
"acceptancetest:serverconfig": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@server-config' --exit",
4950
"build": "npx lerna run build",
5051
"build-and-test": "npx lerna run build && npx lerna run test",
5152
"build:docker": "docker build . -t ${npm_package_name}",

packages/server/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@
1919
*/
2020

2121
import app from './server';
22+
import { setServerTimeout } from './koaJsonRpc/lib/utils'; // Import the 'setServerTimeout' function from the correct location
2223

2324
async function main() {
24-
await app.listen({ port: process.env.SERVER_PORT || 7546 });
25+
const server = await app.listen({ port: process.env.SERVER_PORT || 7546 });
26+
27+
// set request timeout to ensure sockets are closed after specified time of inactivity
28+
setServerTimeout(server);
2529
}
2630

2731
main();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*-
2+
*
3+
* Hedera JSON RPC Relay
4+
*
5+
* Copyright (C) 2024 Hedera Hashgraph, LLC
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
import type { Server } from 'http';
22+
23+
export function setServerTimeout(server: Server): void {
24+
const requestTimeoutMs = parseInt(process.env.SERVER_REQUEST_TIMEOUT_MS ?? '60000');
25+
server.setTimeout(requestTimeoutMs);
26+
}

packages/server/tests/acceptance/index.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import constants from '@hashgraph/json-rpc-relay/dist/lib/constants';
4545
// Utils and types
4646
import { Utils } from '../helpers/utils';
4747
import { AliasAccount } from '../types/AliasAccount';
48+
import { setServerTimeout } from '../../src/koaJsonRpc/lib/utils';
4849

4950
chai.use(chaiAsPromised);
5051
dotenv.config({ path: path.resolve(__dirname, '../../../.env') });
@@ -185,6 +186,7 @@ describe('RPC Server Acceptance Tests', function () {
185186

186187
logger.info(`Start relay on port ${constants.RELAY_PORT}`);
187188
relayServer = app.listen({ port: constants.RELAY_PORT });
189+
setServerTimeout(relayServer);
188190

189191
if (process.env.TEST_WS_SERVER === 'true') {
190192
logger.info(`Start ws-server on port ${constants.WEB_SOCKET_PORT}`);

packages/server/tests/acceptance/rateLimiter.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
*
33
* Hedera JSON RPC Relay
44
*
5-
* Copyright (C) 2023 Hedera Hashgraph, LLC
5+
* Copyright (C) 2023-2024 Hedera Hashgraph, LLC
66
*
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*-
2+
*
3+
* Hedera JSON RPC Relay
4+
*
5+
* Copyright (C) 2024 Hedera Hashgraph, LLC
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
import { expect } from 'chai';
21+
import { Utils } from '../helpers/utils';
22+
23+
describe('@server-config Server Configuration Options Coverage', function () {
24+
describe('Koa Server Timeout', () => {
25+
it('should timeout a request after the specified time', async () => {
26+
const requestTimeoutMs: number = parseInt(process.env.SERVER_REQUEST_TIMEOUT_MS || '3000');
27+
const host = 'localhost';
28+
const port = parseInt(process.env.SERVER_PORT || '7546');
29+
const method = 'eth_blockNumber';
30+
const params: any[] = [];
31+
32+
try {
33+
await Utils.sendJsonRpcRequestWithDelay(host, port, method, params, requestTimeoutMs + 1000);
34+
throw new Error('Request did not timeout as expected'); // Force the test to fail if the request does not time out
35+
} catch (err) {
36+
expect(err.code).to.equal('ECONNRESET');
37+
expect(err.message).to.equal('socket hang up');
38+
}
39+
});
40+
});
41+
});

packages/server/tests/helpers/utils.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import RelayCall from '../../tests/helpers/constants';
2727
import { AccountId, KeyList, PrivateKey } from '@hashgraph/sdk';
2828
import { AliasAccount } from '../types/AliasAccount';
2929
import ServicesClient from '../clients/servicesClient';
30+
import http from 'http';
3031

3132
export class Utils {
3233
/**
@@ -308,4 +309,63 @@ export class Utils {
308309
}
309310
return accounts;
310311
}
312+
313+
static sendJsonRpcRequestWithDelay(
314+
host: string,
315+
port: number,
316+
method: string,
317+
params: any[],
318+
delayMs: number,
319+
): Promise<any> {
320+
const requestData = JSON.stringify({
321+
jsonrpc: '2.0',
322+
method: method,
323+
params: params,
324+
id: 1,
325+
});
326+
327+
const options = {
328+
hostname: host,
329+
port: port,
330+
path: '/',
331+
method: 'POST',
332+
headers: {
333+
'Content-Type': 'application/json',
334+
'Content-Length': Buffer.byteLength(requestData),
335+
},
336+
timeout: delayMs,
337+
};
338+
339+
return new Promise((resolve, reject) => {
340+
// setup the request
341+
const req = http.request(options, (res) => {
342+
let data = '';
343+
344+
res.on('data', (chunk) => {
345+
data += chunk;
346+
});
347+
348+
res.on('end', () => {
349+
resolve(JSON.parse(data));
350+
});
351+
});
352+
353+
// handle request errors for testing purposes
354+
req.on('timeout', () => {
355+
req.destroy();
356+
reject(new Error(`Request timed out after ${delayMs}ms`));
357+
});
358+
359+
req.on('error', (err) => {
360+
reject(err);
361+
});
362+
363+
// Introduce a delay with inactivity, before sending the request
364+
setTimeout(async () => {
365+
req.write(requestData);
366+
req.end();
367+
await new Promise((r) => setTimeout(r, delayMs + 1000));
368+
}, delayMs);
369+
});
370+
}
311371
}

0 commit comments

Comments
 (0)