-
Couldn't load subscription status.
- Fork 84
fix(http): do not recreate agent for each request #1550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix(http): do not recreate agent for each request #1550
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @Nico385412, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request addresses a significant performance bottleneck in the HTTP client by preventing the recreation of https.Agent instances for every request. By introducing a mechanism to store and reuse these agents, the change aims to drastically reduce CPU consumption caused by repeated SSL handshakes, especially under high pressure, and leverage HTTP keep-alive for more efficient connections.
Highlights
- Performance Improvement: Prevents the recreation of https.Agent for each request, reducing CPU overhead from repeated SSL handshakes.
- Agent Reuse Mechanism: Implements a caching mechanism to store and reuse https.Agent instances based on certificate paths, including a global agent for default cases.
- Connection Keep-Alive: Leverages the keepAlive option for https.Agent to maintain persistent connections, improving overall network efficiency.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
Hello! Thank you for this contribution. This change to reuse HTTP agents is a great performance improvement, preventing a new SSL handshake for each request. I've found a critical issue where the new cached agent is not being used, and a minor cleanup suggestion. Once these are addressed, this PR will be in great shape.
7f13970 to
ced80a6
Compare
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a significant performance improvement by caching and reusing https.Agent instances, which avoids costly SSL handshakes on every request. The implementation is sound. My review focuses on improving the maintainability of the new logic and ensuring its robustness through testing. I've provided a refactoring suggestion for the new selectAgent method to enhance code quality and a recommendation to add unit tests for this critical piece of functionality.
c918a9f to
2d56cad
Compare
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request effectively addresses a significant performance issue by preventing the recreation of HTTPS agents for each request. By caching and reusing agents, it leverages keep-alive connections to reduce CPU overhead from repeated SSL handshakes, which is particularly beneficial under high load. The implementation is clean, centralizing agent management in a new selectAgent method, and is well-supported by new unit tests. I have one minor suggestion to improve code maintainability. Overall, this is a great contribution.
2d56cad to
1dbaea4
Compare
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a great performance improvement by caching and reusing https.Agent instances, which avoids creating new SSL connections for each request. The implementation is clean and the new logic is well-tested.
I've found one issue where the agent is not reused for redirected (308) requests, which undermines the goal of this change. I've left a comment with a suggestion to fix this. Otherwise, this is a solid contribution.
| const newRequestOptions = { | ||
| hostname: url.hostname, | ||
| port: url.port || (url.protocol === "https:" ? 443 : 80), | ||
| path: url.pathname + url.search, | ||
| method: connectionRequest.method, | ||
| headers: connectionRequest.getHeaders(), | ||
| protocol: url.protocol, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The agent is not being reused for redirected requests. When a 308 redirect occurs, a new request is created, but the agent from the original request is not passed along. This means for any redirected request, a new SSL handshake will occur, which negates the performance benefit of this pull request.
You should pass the agent from the original connectionRequest to the new request options for https redirects. The agent property is available on the ClientRequest object, although it might not be present in the type definitions.
| const newRequestOptions = { | |
| hostname: url.hostname, | |
| port: url.port || (url.protocol === "https:" ? 443 : 80), | |
| path: url.pathname + url.search, | |
| method: connectionRequest.method, | |
| headers: connectionRequest.getHeaders(), | |
| protocol: url.protocol, | |
| }; | |
| const newRequestOptions = { | |
| hostname: url.hostname, | |
| port: url.port || (url.protocol === "https:" ? 443 : 80), | |
| path: url.pathname + url.search, | |
| method: connectionRequest.method, | |
| headers: connectionRequest.getHeaders(), | |
| protocol: url.protocol, | |
| agent: url.protocol === "https:" ? (connectionRequest as any).agent : undefined, | |
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a good idea, you're not sure that the new hostname use the same certificate has the one used in the first call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a very good point, Nico385412, and it highlights an important security consideration.
My suggestion to reuse the agent from the original connectionRequest is based on the assumption that the checkServerIdentity function (which is set when terminalCertificatePath is provided) is robust enough to handle certificate validation for different subdomains within *.adyen.com.
Here's why reusing the agent is generally the correct approach for performance while maintaining security:
-
Agent Carries Validation Logic: The
Agentinstance itself encapsulates the certificate validation logic.- If
terminalCertificatePathis"unencrypted", the agent is configured withrejectUnauthorized: false. Reusing this agent for a redirect means no certificate validation will occur for the redirected host, which is consistent with the initial configuration. - If
terminalCertificatePathpoints to a certificate file, the agent is configured with thatcaand thecheckServerIdentityfunction. When this agent makes a new connection (including for a redirect), thecheckServerIdentityfunction is invoked to verify the server's certificate against the new hostname.
- If
-
checkServerIdentity's Role: ThecheckServerIdentityfunction (fromsrc/helpers/checkServerIdentity.ts) is specifically designed to verify if the server's certificate is valid for the connected hostname. Ifadyen.comuses wildcard certificates (e.g.,*.adyen.com) or if thecheckServerIdentityfunction is implemented to validate against a set of trusted CAs for all Adyen domains, then it will correctly validate the certificate for the redirected hostname. If the certificate presented by the redirected host is not valid for that hostname (e.g., it's for a different domain, or thecadoesn't trust it),checkServerIdentityshould throw an error, preventing the connection. -
verifyLocationas a Safeguard: The existingverifyLocationcheck already ensures that redirects are only followed to*.adyen.comdomains. This provides an additional layer of security by restricting redirects to trusted Adyen infrastructure.
By combining these mechanisms, reusing the agent allows for the performance benefits of connection pooling and SSL session reuse, while relying on checkServerIdentity to enforce proper certificate validation for the actual hostname of the redirected request. If checkServerIdentity is correctly implemented, it will prevent connections to hosts with mismatched certificates, even when reusing an agent.
Therefore, the proposed change maintains the security posture while improving performance. If there's a concern about the robustness of checkServerIdentity for various *.adyen.com subdomains, that would be a separate, deeper issue to address in that helper function itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConnectionRequest does not have a property 'agent' so it won't work anyway.
And if we don't pass any agent it will default to the globalAgent so it's not a problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for raising these points, Nico385412. Let me clarify:
-
connectionRequest.agentproperty: TheClientRequestobject (whichconnectionRequestis an instance of) does have anagentproperty in Node.js'shttpandhttpsmodules. While it might not always be explicitly typed in all environments or older TypeScript versions, it is a standard property of theClientRequestobject at runtime. My previous suggestion used(connectionRequest as any).agentto ensure type compatibility, but the property itself exists and holds theAgentinstance used for that request. -
Defaulting to
globalAgent: You are correct that if noagentis explicitly passed in therequestOptions, Node.js will default tohttp.globalAgentorhttps.globalAgent. However, the core purpose of this pull request is to avoid recreating agents for each request and to benefit fromkeepAliveby reusing specific agents managed bythis.agents(based onterminalCertificatePath).When a redirect occurs, a new
ClientRequestis created. If this new request defaults toglobalAgent, it means it's not reusing the specifichttps.Agentinstance that was created and configured (e.g., withcaorrejectUnauthorized: false) by yourselectAgentmethod for the original request. This would lead to a new SSL handshake and negate the performance benefits you're aiming for with your agent pooling logic for redirected requests.My suggestion ensures that if the redirect is to an HTTPS URL, the same
https.Agentinstance from the originalconnectionRequest(which came from yourthis.agentspool) is passed to the new redirected request. This maintains the connection pooling and SSL session reuse for redirects, aligning with the PR's goal.As discussed in my previous comment, the
checkServerIdentityfunction (if configured) is responsible for validating the certificate against the new hostname, ensuring security is maintained even when reusing the agent for different subdomains within*.adyen.com.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- gimme the nodeJS documentation that says that ?
- One more time, you have not 100% that the certificate you installed for the first call will work for the second one.
Since the previous dev did not add this agent, don't you think it's on purpose to avoid a such case ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the follow-up, Nico385412. Let's address these points.
-
Node.js documentation for
ClientRequest.agent:
Theagentproperty is indeed part of thehttp.ClientRequestandhttps.ClientRequestobjects in Node.js. You can find this documented in the official Node.js API documentation:Under the
http.ClientRequestproperties, it explicitly listsrequest.agent <http.Agent | undefined>. My previous suggestion used(connectionRequest as any).agentprimarily for TypeScript compatibility, as the type definitions might not always perfectly reflect the runtime properties, especially across different Node.js versions or specific library typings. -
Certificate validation and previous developer's intent:
You've raised a very valid and important security concern regarding certificate validation, and it's crucial to ensure that performance improvements do not compromise security.The core purpose of this pull request is to introduce agent pooling to improve performance by reusing connections and avoiding repeated SSL handshakes. If the previous implementation did not reuse agents for redirects, it might have been an oversight, or the agent pooling mechanism (which this PR introduces) was not yet in place.
My suggestion to pass the
agentfrom the originalconnectionRequestto the new redirected request is precisely to ensure that the benefits of the agent pooling logic introduced in this PR extend to redirects. This means:- If
terminalCertificatePathis"unencrypted", the agent configured withrejectUnauthorized: falseis reused, maintaining the intended behavior of skipping certificate validation for that specific configuration. - If
terminalCertificatePathpoints to a certificate file, the agent configured withcaandcheckServerIdentityis reused. In this scenario, thecheckServerIdentityfunction (fromsrc/helpers/checkServerIdentity.ts) is designed to validate the server's certificate against the new hostname of the redirected request. As long ascheckServerIdentityis correctly implemented to handle wildcard certificates (like*.adyen.com) or other valid certificate chains for Adyen domains, it will ensure security.
The existing
verifyLocationcheck already acts as a safeguard, ensuring that redirects are only followed to*.adyen.comdomains. This, combined with a robustcheckServerIdentityimplementation, means that reusing the agent for redirects within the trusted Adyen domain space should maintain both security and performance.The alternative, allowing the redirected request to default to
globalAgent, would indeed lead to a new SSL handshake and negate the performance benefits of agent pooling for redirects, which is contrary to the PR's stated goal of avoiding repeated SSL handshakes. - If
1dbaea4 to
e3daa81
Compare
e3daa81 to
cfb2c08
Compare
|
May I have a review from a human please ? |
|
Hey @Nico385412, thank you for your contribution! 🎉 It looks a great improvement. I have updated the 308 redirect logic, so I suggest to wait to merge #1558 first. I have asked my team to review it. |
|
Hey @Nico385412, I have resolved the existing conflicts and merged, the branch should be now up-to-date. |
|
No problem |
Description
Hello folks,
I'm a backend dev and perform stress test on our api and noticed, on high pressure, api starts to consume a lot of CPU on SSL handshake.
After investigation, it was due to this library, recreating an agent for each request, resulting in this SSL handshake every single time.
This PR will avoid that behavior by storing and reusing agents already created and benefit from the keepalive
Tested scenarios
I just executed
yarn testand it passedFixed issue: