-
Notifications
You must be signed in to change notification settings - Fork 81
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
Agent
instance itself encapsulates the certificate validation logic.- If
terminalCertificatePath
is"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
terminalCertificatePath
points to a certificate file, the agent is configured with thatca
and thecheckServerIdentity
function. When this agent makes a new connection (including for a redirect), thecheckServerIdentity
function is invoked to verify the server's certificate against the new hostname.
- If
-
checkServerIdentity
's Role: ThecheckServerIdentity
function (fromsrc/helpers/checkServerIdentity.ts
) is specifically designed to verify if the server's certificate is valid for the connected hostname. Ifadyen.com
uses wildcard certificates (e.g.,*.adyen.com
) or if thecheckServerIdentity
function 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 theca
doesn't trust it),checkServerIdentity
should throw an error, preventing the connection. -
verifyLocation
as a Safeguard: The existingverifyLocation
check already ensures that redirects are only followed to*.adyen.com
domains. 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.agent
property: TheClientRequest
object (whichconnectionRequest
is an instance of) does have anagent
property in Node.js'shttp
andhttps
modules. While it might not always be explicitly typed in all environments or older TypeScript versions, it is a standard property of theClientRequest
object at runtime. My previous suggestion used(connectionRequest as any).agent
to ensure type compatibility, but the property itself exists and holds theAgent
instance used for that request. -
Defaulting to
globalAgent
: You are correct that if noagent
is explicitly passed in therequestOptions
, Node.js will default tohttp.globalAgent
orhttps.globalAgent
. However, the core purpose of this pull request is to avoid recreating agents for each request and to benefit fromkeepAlive
by reusing specific agents managed bythis.agents
(based onterminalCertificatePath
).When a redirect occurs, a new
ClientRequest
is created. If this new request defaults toglobalAgent
, it means it's not reusing the specifichttps.Agent
instance that was created and configured (e.g., withca
orrejectUnauthorized: false
) by yourselectAgent
method 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.Agent
instance from the originalconnectionRequest
(which came from yourthis.agents
pool) 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
checkServerIdentity
function (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
:
Theagent
property is indeed part of thehttp.ClientRequest
andhttps.ClientRequest
objects in Node.js. You can find this documented in the official Node.js API documentation:Under the
http.ClientRequest
properties, it explicitly listsrequest.agent <http.Agent | undefined>
. My previous suggestion used(connectionRequest as any).agent
primarily 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
agent
from the originalconnectionRequest
to 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
terminalCertificatePath
is"unencrypted"
, the agent configured withrejectUnauthorized: false
is reused, maintaining the intended behavior of skipping certificate validation for that specific configuration. - If
terminalCertificatePath
points to a certificate file, the agent configured withca
andcheckServerIdentity
is reused. In this scenario, thecheckServerIdentity
function (fromsrc/helpers/checkServerIdentity.ts
) is designed to validate the server's certificate against the new hostname of the redirected request. As long ascheckServerIdentity
is correctly implemented to handle wildcard certificates (like*.adyen.com
) or other valid certificate chains for Adyen domains, it will ensure security.
The existing
verifyLocation
check already acts as a safeguard, ensuring that redirects are only followed to*.adyen.com
domains. This, combined with a robustcheckServerIdentity
implementation, 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 test
and it passedFixed issue: