fix: dns interceptor with Pool
This relates to...
Rationale
The DNS interceptor didn't work with Pool/Client since it didn't get the origin unless included with each request.
Changes
Features
Bug Fixes
- fix dns interceptor with Client/Pool
Breaking Changes and Deprecations
Status
- [x] I have read and agreed to the Developer's Certificate of Origin
- [x] Tested
- [ ] Benchmarked (optional)
- [ ] Documented
- [ ] Review ready
- [ ] In review
- [ ] Merge ready
The origin should always be in
origDispatchOpts. This seems to be a hack.
I kinda agree, but didn't find where else to put it?
I made an alternative solution. It currently breaks a test for test/interceptors/retry.js and I'm unsure if it is an actual error or not? Should I just change the expected value?
This still looks weird... where/how does the origin get lost?
The interceptor does only have access to the options called by .request(). The request method has access to all variables in this (e.g Pool) since it is bound to this here: https://github.com/nodejs/undici/blob/c2933ef5dc4ff289d19883377ca0f83e4b048555/lib/dispatcher/dispatcher.js#L23
It could probably be solved in the same manner for the interceptor by changing: https://github.com/nodejs/undici/blob/c2933ef5dc4ff289d19883377ca0f83e4b048555/lib/dispatcher/dispatcher.js#L34
Into:
dispatch = interceptor.bind(this)(dispatch)
or something like this (which is a bit like my original approach):
dispatch = interceptor(dispatch, this)
The last method has the benefit of allowing the interceptors to return arrow functions as well as ordinary functions.
THis now conflicts, can you rebase?
Let me try to check it during the weekend; if there's a workaround, let's take it, otherwise this should be ok for now and we can revisit later so dns is unblocked
diff --git a/lib/interceptor/dns.js b/lib/interceptor/dns.js
index c8d56c2c..c6a1a480 100644
--- a/lib/interceptor/dns.js
+++ b/lib/interceptor/dns.js
@@ -342,6 +342,10 @@ module.exports = interceptorOpts => {
return dispatch => {
return function dnsInterceptor (origDispatchOpts, handler) {
+ if (origDispatchOpts.origin == null) {
+ return dispatch(origDispatchOpts, handler)
+ }
+
const origin =
The problem here is the assumption that you don't have to pass
originin the request options if used withClientorPool, which IMHO is incorrect and it's working as intended.
@ronag That was my assumption. Just to be clear, do you think it should be like this?
const client = new Pool('https://google.com', {
connections: 10
})
await client.request({
origin: 'https://google.com', // origin must be included in each request
method: 'GET',
path: '/'
})
If that is the case, then I think there are a lot of inconsistencies and contradictions in both the code and documentation.
If that is the case, then I think there are a lot of inconsistencies and contradictions in both the code and documentation.
Possibly. Then let's just make the dns interceptor a noop when origin is missing as I suggested with the patch.
I do agree that the documentation has some statements that are either not fully true or confusing for the reader that can lead to a wrong assumption; but that's at an overall take.
The last being said; I'm not 100% in sync with the statement that passing the origin should be mandated on every request call. I'm in sync when use through dynamic dispatchers like Agent or the BalancedPool, but not fully in sync for Client and Pool which are tight to a single origin.
For the latter use-cases, they should have the origin already stick to their state and do not ask the caller to pass it as its goal is to be tight to a single downstream.
I'm ok with the dns interceptor doing a noop if no origin is passed or if it is already an IP address; but the fact that Pool and Client does not forward the origin seems a bit of miss alignment with its purpose
I think that Client and Pool actually should throw an error when origin is provided to request(), so that the caller notices that it is wrong.
Below is an example of how it works today when different origin is provided to Client during the constructor and request(). The caller might think that the request will be made to http://localhost:2000, which it won't. In this case a thrown error would be much clearer and avoid mistakes. It works similarly for Pool.
import { createServer } from 'node:http'
import { Client } from 'undici'
const server = createServer()
server.on('request', (req, res) => {
res.end('hello')
})
server.listen(0)
const client = new Client(`http://localhost:${server.address().port}`)
const response = await client.request({
method: 'GET',
origin: 'http://localhost:2000',
path: '/'
})
console.log(await response.body.text()) // will print 'hello'
await server.close()
I'm ok with the
dnsinterceptor doing anoopif no origin is passed or if it is already an IP address; but the fact that Pool and Client does not forward the origin seems a bit of miss alignment with its purpose
I think that it should throw an error, since it should never be the case that origin is missing. Unless it can get origin directly from Client or Pool via one of the solutions I provided earlier.
I think that Client and Pool actually should throw an error when origin is provided to request(), so that the caller notices that it is wrong.
It should throw when a different origin is provided. Yes.
It should throw when a different origin is provided. Yes.
I can understand from the viewpoint of backwards-compatibility, but are there other benefits for not throwing every time? I'm afraid that it encourages the wrong behavior to allow an origin, which isn't used,. I have however made a commit which does that.
Still blocking. Please don't override request and instead ignore requests without origin in the dna interceptor
@ronag
- Why is it better to ignore requests without origin in the dns interceptor instead of throwing an error?
- Do you mean that I should remove all code I placed in
ClientandPool, or just that I shouldn't modify the request options if origin was provided?
The only change here should be a condition in the dns interceptor and a test.
The only change here should be a condition in the dns interceptor and a test.
So basically remove all changes so far, apply the patch your provided earlier and then write a test?
IMHO that doesn't solve anything. I as a user would not expect the dns interceptor to be silently bypassed when origin wasn't provided again in my request.
Everything points to origin not being necessary with Client and Pool and it is also not possible to switch origin, so why not override request and make sure that the interceptors get it? Even the examples in the documentation https://undici.nodejs.org/#/docs/api/Client?id=example-client-connect-event shows that request can be called without providing origin again.
Gentle ping
Gentle ping
I don't know how to move forward since none of my proposed solutions have been accepted and I don't think the alternatives are any good. Do you have any ideas?
cc: @ronag
I'm not sure what's wrong with my proposal?
Expect origin to be passed to request, if it is passed, make sure it's same as the Pool/Client and if not passed then it's a noop for the dns interceptor.
I'm not sure what's wrong with my proposal?
Expect origin to be passed to request, if it is passed, make sure it's same as the Pool/Client and if not passed then it's a noop for the dns interceptor.
I think it is wrong that:
- Client and Pool forces the user to pass origin for each request. It isn't documentet and wouldn't work for different origins. https://github.com/nodejs/undici/pull/3957#issuecomment-2563583182
- the dns interceptor should be silently bypassed if a different origin is passed. I much rather it throws an error so that the user is informed that there is something wrong.