I want in a Node.js project to perform a simple fetch request with proxy by specifically using ProxyAgent from undici and native fetch but i can't get it to work. The request never resolves or rejects and the first console.log of the proxy code never runs. Other ways like using axios or http.get or even postman do work. Am i doing something wrong? Does undici proxy for some reason works only with certain proxies?
index.js
import { ProxyAgent } from "undici";
const client = new ProxyAgent("http://127.0.0.1:8000");
fetch("http://ifconfig.io/ip", {
dispatcher: client,
method: "GET",
})
.then((response) => {
console.log("Got response");
if (!response.ok) {
throw Error("response not ok");
}
return response.text();
})
.then((data) => {
console.log("Response Data = ", data);
})
.catch((error) => {
console.error("Error occurred:", error.message);
});
Code of a simple proxy with Node.js (which works fine with postman and axios) proxy.js
const http = require('http');
const httpProxy = require('http-proxy');
const url = require('url');
// Create a proxy server
const proxy = httpProxy.createProxyServer({});
// Create an HTTP server that will use the proxy
const server = http.createServer((req, res) => {
console.log("Original request URL:", req.url);
const parsedUrl = url.parse(req.url, true);
// The target is set directly to the desired backend service
const target = `${parsedUrl.protocol}//${parsedUrl.host}`;
// Proxy the request to the target server
proxy.web(req, res, { target });
// Log the proxied request
console.log(`Proxied request to: ${target}`);
});
// Listen on a specific port (e.g., 8000)
const PORT = 8000;
server.listen(PORT, () => {
console.log(`Dynamic proxy server is running on http://localhost:${PORT}`);
});
Tried to also import fetch from undici but same results. In case it helps i run this in Node.js LTS version 20.17.0 (also tried in 18.18.2) and this my package.json
{
"name": "nodejs-proj",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type":"module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.7.4",
"http-proxy": "^1.18.1",
"http-proxy-agent": "^7.0.2",
"undici": "^6.19.8"
}
}
Working example with axios. indexAxios.js
import axios from "axios";
axios.get('https://ifconfig.io/ip', {
proxy: {
protocol: "http",
host: '127.0.0.1',
port: 8000,
}
})
.then(response => {
console.log('Response data:', response.data);
})
.catch(error => {
console.error('Error occurred:', error.message);
});
Also tried this proxy same results
import http from "http";
import request from "request";
const PORT = 8000;
http.createServer(function (req, res) {
console.log(`Incoming request to ${req.url}`);
req.pipe(request(req.url)).pipe(res);
}).listen(PORT);
Also tried the following proxy which again works just fine with axios but when i use it with the fetch example it just bypasses it and gets a response. (fetched data from an http mock api because it didn't like https)
import * as http from 'http';
import { createProxy } from 'proxy';
const server = createProxy(http.createServer());
// Log each incoming request
server.on('request', (req, res) => {
console.log(`Incoming request: ${req.method} ${req.url}`);
});
server.listen(3128, () => {
var port = server.address().port;
console.log('HTTP(s) proxy server listening on port %d', port);
});
Do you want to make an HTTP request (like in index.js
) or an HTTPS request (like in indexAxios.js
)? The following answer is about HTTPS requests.
HTTP and HTTPS requests made by fetch
are much different when proxies are involved.
If the fetch
statement in your index.js
makes an HTTPS request through a proxy, it first sends a CONNECT request
CONNECT ifconfig.io:443 HTTP/1.1
Host: ifconfig.io:443
Proxy-Connection: Keep-Alive
to http://localhost:8000.
But your proxy.js
server is not prepared to handle such CONNECT requests. It must contain an event handler like
server.on("connect", function(request, socket, head) {
// request.url = "ifconfig.io:443"
var s = net.createConnection({host: "ifconfig.io", port: 443}, function() {
socket.write("HTTP/1.1 200\r\n\r\n");
s.pipe(socket);
s.write(head);
});
socket.once("data", function(data) {
s.write(data);
socket.pipe(s);
});
});
The handler establishes a connection with the target host and forwards all subsequent (encrypted) traffic that goes over the same connection ("SSL tunneling").
The other proxies you tried seem to lack such a CONNECT handler as well (what you call "it didn't like https").
axios
does not use SSL tunneling. Instead of a CONNECT request it makes the following request (unencrypted) to http://localhost:8080:
GET https://ifconfig.io/ip HTTP/1.1
host: ifconfig.io
Connection: keep-alive
Your proxy.js
server then forwards this as a separate HTTPS request, but since the request from browser to proxy is HTTP only, the request made by axios
is not end-to-end encrypted. For example, the "path portion" of the URL, (/ip
) is transmitted unencrypted between browser and proxy and so is the response to this request.
(By contrast, the unencrypted CONNECT request does not contain the entire URL, only the host.)