Respect TTL of a DNS record for proxy config#5960
Conversation
|
Hi, @chickenchickenlove! Thanks for submitting this PR. There is a limitation in the current patch:
To address this, we need to ensure TTL is respected and refresh the address once TTL expires. final ProxyConfig proxyConfig = factory.proxyConfigSelector().select(protocol, endpoint);
final Future<InetSocketAddress> resolveFuture = addressResolverGroup.getResolver(
ctx.eventLoop().withoutContext()).resolve(proxyConfig.proxyAddress());
if (resolveFuture.isSuccess()) {
...
} else {
resolveFuture.addListener(...);
}Because we resolve the address internally, we can now accept unresolved addresses as well: Please, let me know if you need any further information. 😉 |
This reverts commit 417bf3b.
…kenlove/armeria into 241027-proxy-config
| } | ||
|
|
||
| private ProxyConfig getProxyConfig(SessionProtocol protocol, Endpoint endpoint) { | ||
| private ProxyConfig getProxyConfig(SessionProtocol protocol, Endpoint endpoint, ClientRequestContext ctx) { |
There was a problem hiding this comment.
We have to recreate ProxyConfig with the new proxy address because the address is resolved:
if (proxyConfig.proxyAddress() != null) {
final Future<InetSocketAddress> resolveFuture = addressResolverGroup.getResolver(
ctx.eventLoop().withoutContext()).resolve(proxyConfig.proxyAddress());
resolveFuture.addListener(future -> {
if (future.isSuccess()) {
final ProxyConfig newProxyConfig = proxyConfig.withNewProxyAddress(
(InetSocketAddress) future.get());
...
}
}
}Because getting the ProxyConfig is now asynchronous, we have to change the signature of this method.
We have two options:
- Returning
CompletableFuture<ProxyConfig>instead ofProxyConfig// in HttpClientDelegate.execute() try { final CompletableFuture<ProxyConfig> future = getProxyConfig(protocol, endpoint, ctx); future.handle(....) } catch (Throwable t) { return earlyFailedResponse(t, ctx); }
- Using a callback that is executed after resolution like we do for resolving address
private void resolveProxyConfig(..., BiConsumer<@Nullable ProxyConig, @Nullable Throwable> onComplete) { final ProxyConfig proxyConfig = factory.proxyConfigSelector().select(protocol, endpoint); requireNonNull(proxyConfig, "proxyConfig"); ... }
Please let me know if you need further information. 😉
There was a problem hiding this comment.
@minwoox , Thanks for your comments 🙇♂️
I made a new commit with following second option!
Because ProxyConfig will be resolved in other thread, so the method maybeHAProxy() will get capturedProxyAddress instead of ServiceRequestContext.
Also, res instance will be returned right away even if future don't completed.
So I used the method earlyFailed(...) instead of earlyfailedResponse(...).
When you have time, Please take another look 🙇♂️
jrhee17
left a comment
There was a problem hiding this comment.
Looks good overall, left some minor comments 🙇
| final ProxyConfig newProxyConfig = proxyConfig.withNewProxyAddress(resolvedAddress); | ||
| onComplete.accept(maybeHAProxy(newProxyConfig, capturedProxiedAddresses), null); | ||
| } else { | ||
| logger.warn("Resolved address is invalid or unresolved: {}. " + |
There was a problem hiding this comment.
I didn't understand this case - are you concerned that the resolution succeeds but the InetAddress does not exist?
Would it make sense to just align error handling with dns resolution for the normal code path (not using proxy)?
There was a problem hiding this comment.
Thanks for your comments!
After take a look RefreshingAddressResolver, RefreshingAddressResolver returns InetSocketAddress when succeed.
As you mentioned before, it is unnecessary codes.
Therefore, I delete this codes.
|
|
||
| @Override | ||
| public ProxyConfig withNewProxyAddress(InetSocketAddress newProxyAddress) { | ||
| return new DirectProxyConfig(); |
There was a problem hiding this comment.
nit; it may make more sense to throw an exception instead
| * Returns a new proxy address instance that respects DNS TTL. | ||
| * @param newProxyAddress the inet socket address | ||
| */ | ||
| public abstract ProxyConfig withNewProxyAddress(InetSocketAddress newProxyAddress); |
There was a problem hiding this comment.
nit; I believe that currently only resolved InetSocketAddress can be used to create a ProxyConfig.
Can you add some integration tests to confirm the current code works correctly?
There was a problem hiding this comment.
@jrhee17 Thanks for your comments. 🙇♂️
I added two integration test codes for this feature.
Due to restrictions on mocking, narrow Access specifier, I couldn't use a spy instance to validate it or inject DNSResolver as well.
That's why I made a new class called DNSResolverFacadeUtils class 😅 .
As @minwoox mentioned, we don't need to check if InetSocketAddress is resolved, since it will be resolved when HttpClientDelegate calls execute(). After removing checkArgument(...), I was finally able to write some integration test cases... 😅
When you have time, please take another look. 🙇♂️
| return this.sourceAddress == null ? new HAProxyConfig(proxyAddress) | ||
| : new HAProxyConfig(proxyAddress, this.sourceAddress); |
There was a problem hiding this comment.
The input is ignored.
| return this.sourceAddress == null ? new HAProxyConfig(proxyAddress) | |
| : new HAProxyConfig(proxyAddress, this.sourceAddress); | |
| requireNonNull(newProxyAddress, "newProxyAddress"); | |
| return this.sourceAddress == null ? new HAProxyConfig(newProxyAddress) | |
| : new HAProxyConfig(newProxyAddress, this.sourceAddress); |
There was a problem hiding this comment.
This comment wasn't addressed. Was it intended? Let me know if I'm missing something or misunderstood.
There was a problem hiding this comment.
I'm really sorry about this. 🙇♂️
I missed it.
Now, I fixed it!
| } | ||
|
|
||
| @Test | ||
| void testUnresolvedProxyAddress() { |
There was a problem hiding this comment.
Should we revive this test and fix it to check if ProxyConfig is successfully created with unresolved addresses?
There was a problem hiding this comment.
Thanks for your comments 🙇♂️
Good point, I didn't expect it needed to be kept at all. 😓
I revived this test and fixed it!
|
@ikhoon nim, thanks for your comments! 🙇♂️ |
| private ProxyConfig getProxyConfig(SessionProtocol protocol, Endpoint endpoint) { | ||
| private void resolveProxyConfig(SessionProtocol protocol, Endpoint endpoint, ClientRequestContext ctx, | ||
| BiConsumer<@Nullable ProxyConfig, @Nullable Throwable> onComplete) { | ||
| final ProxyConfig proxyConfig = factory.proxyConfigSelector().select(protocol, endpoint); |
There was a problem hiding this comment.
It would be efficient if we could skip the resolution process if proxyConfig.proxyAddress() was created with IP addresses.
| return Objects.hash(proxyAddress, sourceAddress); | ||
| return hash(proxyAddress, sourceAddress); |
There was a problem hiding this comment.
Have been reverted!
|
@ikhoon nim, thanks for your clean up commit. The one is fixed. When you have time, Please take another look. 🙇♂️ |
ikhoon
left a comment
There was a problem hiding this comment.
Thanks, @chickenchickenlove!
| checkArgument(sourceAddress.getAddress().getClass() == proxyAddress.getAddress().getClass(), | ||
| "sourceAddress and proxyAddress should be the same type"); | ||
| } else { | ||
| logger.warn("Either the source or proxy address could not be resolved. " + |
There was a problem hiding this comment.
What do you think of removing this warning? This message doesn't seem useful, and I'm unsure if warn is a proper level.
There was a problem hiding this comment.
I agree. I removed it!
minwoox
left a comment
There was a problem hiding this comment.
Sorry about the late review.
It looks great now. 👍
…ate.java Co-authored-by: minux <[email protected]>
…nfig.java Co-authored-by: minux <[email protected]>
|
@jrhee17 Please, review this PR when you have time. 😉 |
| DecodedHttpResponse response) { | ||
| final UnprocessedRequestException cause = UnprocessedRequestException.of(t); | ||
| ctx.cancel(cause); | ||
| response.close(cause); |
There was a problem hiding this comment.
nit; ctx.cancel calls response.close since the cancellation task is updated. The current implementation is fine though.
|
Great job, @chickenchickenlove! 👏 👏 👏 |
|
Thanks a lot for reviewing my PR. 🙇♂️ |
Motivation:
From now on, armeria client ignores DNS TTL.
Thus the client will keep sending the request to the old proxy.
Modifications:
InetSocketAddressmutable to support a feature which update DNS.xxxProxyConfig(lastUpdatedTime, Schedulers)refreshIntervalJVM doesn't consider DNS TTL as default.
Therefore, client has a intent to DNS update,
IMHO, Client should configure JVM options.
Result: