-
-
Notifications
You must be signed in to change notification settings - Fork 11.4k
Fixing #385 - Keep defaults local to instance #1395
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
Conversation
ebd36a6 to
5bfd2ea
Compare
|
I'm a fan of this -- I also think that creating an instance should freeze its defaults. I'm not sure if this will get a push into 0.19 (I'd like to focus on getting the security issues fixed in the least difficult way possible) but I would definitely want this in 1.0.0. (As a side note -- 1.0.0 has ditched most of the custom utils in favor of lodash, which could make things easier as well) |
It's a breaking change so that's definitely understandable. If this would only ship in 1.0.0, then a deprecation notice in the next minor version wouldn't make sense either. What is the leadership on this project at the moment? Who decides what goes into which version?
Is there any other way to fix the security issue other than by losing all references between global defaults and/or separate instances?
Does it make sense for me to work on cleaning up this PR at all at this time? I don't mind closing it if it would be redundant work. I would still love to help out in some way if possible. Also another thing just occurred to me. Some config-properties are arrays. Should they always lose reference between instances? const instance1 = axios.create()
const instance2 = axios.create()
// Should this never affect instance2?
instance1.defaults.transformRequest.push(anotherTransformer)Obviously that could result in some accidents. One could also argue that there is a use case for keeping the reference: const commonTransformers = [...]
const instance1 = axios.create({ transformRequest: commonTransformers })
const instance2 = axios.create({ transformRequest: commonTransformers })
// Add new transformer to all instances, yay!
commonTransformers.push(anotherTransformer) |
lib/utils.js
Outdated
|
|
||
| function mergeConfig(defaults, instanceConfig) { | ||
| var config = {}; | ||
| forEach(['url', 'method', 'params', 'data'], function(prop) { |
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.
modify this line to
forEach(['url', 'method', 'params', 'data'], function assignProp(prop) {to pass eslint warnings in CI tests (or some other function name of your choosing)
|
@emilyemorehouse When you have time, could you check out the questions I posted above, so I know if I should close this PR or not? @heisian By freezing the object I did not mean making the configuration-object immutable, which is what |
|
@codeclown deleted. I think the terminology you are looking for is to clone rather than to freeze.
The above statement strongly suggests that you want to freeze the defaults. When you want one instance to not affect the other instance that is called passing by value - not freezing. |
|
@emilyemorehouse I went ahead and cleaned up this PR. There are a few questions above that I'd appreciate an answer to. I would also like to know if this PR is worth pursuing or if I should close it. Currently Travis fails due to some network error, I can spend time on resolving it if this PR has a future. The functionality in this PR is similar to what has been agreed to a long time ago in the comments: #370 |
|
@codeclown I'd say keep it open -- I think this approach would be preferred over #1391, since it takes into account many of the issues and proposed solutions from the previous discussions around this. Since this is an incredibly important issue, I'd love to have approval from at least @nickuraltsev and myself since he's been working on the project longer than I have. Here's answers to your previous questions, let me know if you have more!
I believe that Matt and Ruben have stepped back, and Nick is helping out when he can. I stepped up recently and am still getting my footing (hence wanting another owner's approval for larger issues like this).
We could deem certain attributes of the config "critical", like the headers, and ensure that just those parts aren't shared between global config and instance configs (#1391's original approach). But I feel like that isn't ideal and ignores the bigger picture.
Oo, I do like the use case for references being kept for I've been interested in digging into how AWS handles this in their JS SDK- they have a similar idea of global configs and "instances" for each service, I've just been bogged down the past couple of weeks. |
|
@codeclown @emilyemorehouse agreed after reading the threads referenced by @nickuraltsev This PR seems like the best route to go, or at least is most in-line with what was discussed in those threads. If we can get tests to pass we may be in good shape. Personally I am not a fan of having different behaviours between different properties within the I definitely agree on |
|
@codeclown First of all, thank you so much for working on this! Here are some comments:
@emilyemorehouse We don't have to wait for the |
|
Still alive? |
|
@nickuraltsev Could you just implement this going off of what's here? |
|
@codeclown Hey, are you still interested in working on this PR? |
|
Yes, I'll try to address the comments some evening this week. Of course I have no issue if someone has the time and wants to do it before me.
… On 21 Mar 2018, at 17.57, Nick Uraltsev ***@***.***> wrote:
@codeclown Hey, are you still interested in working on this PR?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
|
@nickuraltsev @emilyemorehouse Sorry for the delay on this. Changes in latest commit:
Please let me know if there's any refactoring or other changes you'd like to see. |
|
@codeclown Thank you so much! I'll review it over the weekend. |
lib/utils.js
Outdated
| var result = {}; | ||
| function assignValue(val, key) { | ||
| if (typeof result[key] === 'object' && typeof val === 'object') { | ||
| result[key] = merge(result[key], val); |
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.
Show we use merge or deepMerge here?
lib/utils.js
Outdated
| if (typeof result[key] === 'object' && typeof val === 'object') { | ||
| result[key] = merge(result[key], val); | ||
| } else if (typeof val === 'object') { | ||
| result[key] = merge({}, val); |
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.
deepMerge?
lib/core/mergeConfig.js
Outdated
| * @param {Object} instanceConfig Instance-specific config | ||
| * @returns {Object} New object resulting from merging instanceConfig to defaults | ||
| */ | ||
| module.exports = function mergeConfig(defaults, instanceConfig) { |
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.
Nitpicking: I'm not sure about the parameter names (defaults and instanceConfig). mergeConfig is also used to merge the request config with the instance config in the request method so I would suggest to rename them to config1 and config2.
lib/core/mergeConfig.js
Outdated
| }); | ||
|
|
||
| utils.forEach(['headers', 'auth', 'proxy'], function mergeInstanceConfigWithDefaults(prop) { | ||
| if (typeof instanceConfig[prop] !== '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.
As far as I can see, it's safe to pass undefined or null to utils.deepMerge (correct me if I'm wrong), so we can probably simplify this block as follows:
if (defaults[prop] || instanceConfig[prop]) {
config[prop] = utils.deepMerge(defaults[prop], instanceConfig[prop])
}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.
I actually was not able to simplify this at all. If I got rid of explicitly checking for undefined and just did ||, the tests would fail. I assume something somewhere relies on null values and that broke it. If absolutely necessary, I can try to dig deeper into this later. I do agree that the if-statements are nasty.
lib/core/mergeConfig.js
Outdated
| var config = {}; | ||
|
|
||
| utils.forEach(['url', 'method', 'params', 'data'], function valueFromInstanceConfig(prop) { | ||
| config[prop] = instanceConfig[prop]; |
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.
I would suggest to add if (typeof instanceConfig[prop] !== 'undefined') check here so we don't create entries like data => undefined, etc.
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.
Also, please see my comment about parameter names.
| * @param {Object} obj1 Object to merge | ||
| * @returns {Object} Result of all merge properties | ||
| */ | ||
| function deepMerge(/* obj1, obj2, obj3, ... */) { |
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.
It would be great to add some tests for deepMerge.
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.
I didn't notice that unit tests were in specs folder. I've now copied the merge tests and added two additional ones.
| } | ||
| }); | ||
|
|
||
| utils.forEach([ |
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.
Instead of listing all these properties here, we could apply this function to all properties that don't belong to the first two groups. Or maybe, it's actually a great idea to specify all properties explicitly so that every time a new property is added, we will have to think whether its values should be merged or not. What do you think?
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.
I would keep this explicit listing, that was also partly the idea behind this config-specific merge-function. The idea is that it's no longer a mystery where config-options originate from, and as you said, when adding new properties it forces to consider how they should be merged (or not).
|
@codeclown Sorry for delay! I've just submitted my review. The PR looks great. There are just a few minor issues / questions. |
|
@nickuraltsev No problem. Thank you for the review, much appreciated. I've addressed your comments. Please let me know if there's anything to change still. I also don't mind squashing these commits into one before merging if that's preferred. I still don't know why the tests are failing in CI, because I haven't unfortunately had time to dive into the test setup. Apparently some local server instance is not responding as expected. I will save tinkering with that to last, after the PR is otherwise OK to go. If you have an idea about it, would be cool to hear. |
lib/core/mergeConfig.js
Outdated
|
|
||
| /** | ||
| * Config-specific merge-function which creates a new config-object | ||
| * based on given defaults and instance config. |
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.
Could you please update the comment so it doesn't suggest that this function can be used only to merge defaults with an instance config?
| var config = {}; | ||
|
|
||
| utils.forEach(['url', 'method', 'params', 'data'], function valueFromInstanceConfig(prop) { | ||
| if (typeof config2[prop] !== '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.
I've just realized that we always want to use url, method, params, and data values from the request config (not from defaults or the instance config). It means that we should probably remove the if (typeof config2[prop] !== 'undefined') check. Sorry for the back and forth!
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.
valueFromInstanceConfig name is misleading. valueFromConfig2?
lib/core/mergeConfig.js
Outdated
| }); | ||
|
|
||
| utils.forEach(['headers', 'auth', 'proxy'], function mergeInstanceConfigWithDefaults(prop) { | ||
| if (typeof config2[prop] !== '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.
Can we simplify it like this?
function mergeValues(prop) {
if (config1[prop] !== 'undefined' || config2[prop] !== 'undefined') {
config[prop] = utils.deepMerge(config1[prop], config2[prop]);
}
}It should be ok to pass null or undefined to deepMerge so I would just check if at least one of the config objects contains the prop.
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.
I would also change the name to smth like mergeValues
lib/core/mergeConfig.js
Outdated
| 'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName', | ||
| 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'maxContentLength', | ||
| 'validateStatus', 'maxRedirects', 'httpAgent', 'httpsAgent', 'cancelToken' | ||
| ], function defaultToInstanceConfig(prop) { |
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.
Could you please rename it to something like defaultToConfig2?
|
@codeclown Thank you for this PR! |
|
This is amazing news. Thanks for fixing this. What is the plan for releasing this to npm? |
|
ATM merging this caused tests to fail on Travis (see https://travis-ci.org/axios/axios/branches). We'll be able to release as soon as that's fixed. |
|
has this been shipped? we have to use a ghetto XMLHTTPRequest atm instead 😿 |
|
@Dakkers the current build is still failing (even with cutting older IE versions) so I haven't released it yet 😔 |
|
Released as part of 0.19.0-beta.1. This can be installed using |
Alternative solution to some other PR's addressing this issue, such as #1391, addressing the same issues (#385, #812, #1170, #1117, #1387, ...).
Issue
When creating Axios instances, some references are kept to global
defaultsobject.Proposed solution
I propose that Axios gets rid of the legacy requirement (existing test) of changes to
axios.defaultspropagating to instance defaults even when that instance was created before the change toaxios.defaults. I think that when creating a new Axios instance, it should freeze its own defaults in time and not be affected by changes to global defaults.I don't know why that behaviour has been intended originally, but IMO it doesn't make sense.
Not to mention that it is a security hazard in server-side environments. Someone might be setting authentication keys in the defaults and that would affect requests made by different end users because other instances would possibly/probably share those keys.
Implementation
I implemented a specific
utils.mergeConfig(defaults, instanceConfig)function which very explicitly creates the configuration-object for a new Axios instance. This function is used when a new instance is created, and it leaves no reference to any objects in the basedefaults.At the time of writing this implementation is quite messy because I just focused on getting the tests to pass. I will try to clean it up a bit, suggestions also welcome.
As this changes behaviour, I assume it would be a breaking change.
I hope for comments on this proposal/implementation. Also thanks to everyone who's been working on this issue in various threads already.
cc @nickuraltsev @heisian @emilyemorehouse