Skip to content
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

More flexible ways to customize adapters #2808

Closed
chinesedfan opened this issue Mar 7, 2020 · 14 comments
Closed

More flexible ways to customize adapters #2808

chinesedfan opened this issue Mar 7, 2020 · 14 comments

Comments

@chinesedfan
Copy link
Collaborator

chinesedfan commented Mar 7, 2020

Is your feature request related to a problem? Please describe.

Users may want to set different options for native http module, or do something with the request object. axios only supports some of them, but doesn't want to add more and more options. For example,

Describe the solution you'd like

Abstract the adapter as more detailed lifecycles, like beforeSend, afterSend. I just have the basic idea, but no detailed solution.

Describe alternatives you've considered

If only consider the options, we can add an extra config called adapterOptions. Otherwise, users must implement their own adapters.

Additional context

Should consider both xhr and http adapters to form a fixed pattern.

@rhorning
Copy link

@chinesedfan, I don't really have a suggestion, but I also need #2233 (or an alternative to set the local address).

@seahindeniz
Copy link

seahindeniz commented Mar 24, 2020

What about having something like beforeSend and transportOptions to avoid creating new and different options for each library?

Something like this usage:

/**
 * @param {import("http").ClientRequest | XMLHttpRequest} req 
 */
let beforeSend = (req) => {
  if (req instanceof XMLHttpRequest) {
    req.setRequestHeader("foo", "bar");
    req.onprogress = () => {
      // ... I know about axios has onUploadProgress but this is just an
      // example of how it can be used
    };
  } else {
    req.setNoDelay(true);
    req.setHeader("foo", "bar");
  }
};

/**
 * @type {import("http").RequestOptions}
 */
let transportOptions = {
  localAddress: "127.0.0.1",
};

let instance = axios.create({
  // ...
  beforeSend,
  transportOptions, // Node only
});

beforeSend will be called before these lines:

axios/lib/adapters/xhr.js

Lines 177 to 178 in 885ada6

// Send the request
request.send(requestData);

or this

axios/lib/adapters/http.js

Lines 275 to 282 in 885ada6

// Send the request
if (utils.isStream(data)) {
data.on('error', function handleStreamError(err) {
reject(enhanceError(err, config, null, req));
}).pipe(req);
} else {
req.end(data);
}

Since transportOptions can only be applied on Node side, it can be destructed in here

var options = {
path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''),
method: config.method.toUpperCase(),
headers: headers,
agent: agent,
agents: { http: config.httpAgent, https: config.httpsAgent },
auth: auth
};

Wdyt @chinesedfan?

@chinesedfan
Copy link
Collaborator Author

@seahindeniz Yes, you shared most of idea with me. Maybe we can learn something from other libraries first, like got or request. It's not easy to design an ideal solution.

@Dramex
Copy link

Dramex commented Apr 20, 2020

so right now we can't use localAddress with axios ? o.o

@github-actions
Copy link
Contributor

Hello! 👋 \n\nThis issue is being automatically marked as stale because it has not been updated in a while. Please confirm that the issue is still present and reproducible. If no updates or new comments are received the issue will be closed in a few days. \n\nThanks

@Poyoman39
Copy link

As a workaround for options, waiting for #2808 to be implemented you may do something like :

  const wrappedHttp = {
    ...http,
    request: (options, callback) => http.request({
      ...options,
      localAddress: 'some.ip.add.ress',
    }, callback),
  };

  const instance = axios.create({
    transport: wrappedHttp,
  });

@carlchan1994
Copy link

@Poyoman39 Poyoman39 I have tried your sample code to set up the localAddress but it does not work.
I have tried 'eth1' or private ip for the localAddress field.

@carlchan1994
Copy link

@Poyoman39 Sorry I forgot this feature is not launched yet.

@Poyoman39
Copy link

Poyoman39 commented Oct 19, 2020

@carlchan1994 my sample should work with an IP. I tried it on a local server, and it seemed to work well. Be sure your outbound IP address is available => ip addr
Also be sure to use http or https based on what type of request you make

@carlchan1994
Copy link

carlchan1994 commented Oct 20, 2020

@Poyoman39 I tried again and success this time. Thanks a lot. Here is my code:

const axios = require('axios').default
const http = require('http')

function create_axios(ip){
  const wrappedHttp = {
    ...http,
    request: (options, callback) => http.request({
      ...options,
      localAddress: ip,
    }, callback),
  }

  return axios.create({
    transport: wrappedHttp,
  })
}

const eth0 = create_axios('10.180.60.82')
const eth1 = create_axios('10.180.60.83')
const eth2 = create_axios('10.180.60.84')
const eth3 = create_axios('10.180.60.85')

eth0.get('https://ifconfig.co/ip').then(res => { console.info(res.data) })
eth1.get('https://ifconfig.co/ip').then(res => { console.info(res.data) })
eth2.get('https://ifconfig.co/ip').then(res => { console.info(res.data) })
eth3.get('https://ifconfig.co/ip').then(res => { console.info(res.data) })

@carlchan1994
Copy link

carlchan1994 commented Dec 15, 2020

In case of accessing webpage with HTTPS, you may need to use "wrappedHttps" instead of "wrappedHttp"

const axios = require('axios').default
const https = require('https')

function create_axios(ip){
  let wrappedHttps = {
    ...https,
    request: (options, callback) => https.request({
      ...options,
      localAddress: ip,
    }, callback),
  }

  return axios.create({
    transport: wrappedHttps,
  })
}

@Turbine1991
Copy link

Any solution for IPv6 addresses? The above workaround gives me Error: bind EINVAL 2307:1000:3c83:ca00:2000::

@Poyoman39
Copy link

Poyoman39 commented Mar 16, 2021

Here is my solution working both with https and IPV6

const https = require('https');

const getWrappedHttpsTransport = ({
  localAddress,
  ...forceOptions
} = {}) => ({
  ...https,
  request: (options, callback) => https.request({
    ...options,
    ...forceOptions,
    ...localAddress && {
      localAddress,
      family: localAddress.includes(':') ? 6 : 4,
    },
  }, callback),
});

Use it like that :

  axios.create({
    transport: getWrappedHttpsTransport({
      localAddress,
    }),
  });

@jasonsaayman
Copy link
Member

Closing this issue due to it's age, stale state or due to a loss of momentum surrounding the issue. Please open a new issue should this problem still be relevant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants