Inspecting Raw Sockets in Axios for Node.js

Axios abstracts low-level network communication in Node.js, but certain tasks—such as measuring precise network latency, checking TLS certificate details, or diagnosing connection issues—require access to the underlying network socket. In Node.js, Axios relies on the built-in http and https modules, which provide access to net.Socket and tls.TLSSocket instances. This article covers the primary methods to capture and inspect the raw socket used by Axios.

Method 1: Accessing the Socket via the Response Object

When an HTTP request completes successfully, Axios attaches the underlying http.ClientRequest object to response.request. You can access the socket directly from this object.

const axios = require('axios');

async function inspectSocketFromResponse() {
  const response = await axios.get('https://example.com');
  const socket = response.request.socket;

  console.log('Local Port:', socket.localPort);
  console.log('Remote Address:', socket.remoteAddress);
  console.log('Encrypted:', socket.encrypted);

  if (socket.encrypted) {
    console.log('TLS Protocol:', socket.getProtocol());
    console.log('Peer Certificate:', socket.getPeerCertificate());
  }
}

inspectSocketFromResponse();

If the request fails, the same request object is attached to error.request, allowing you to inspect the socket during network errors.

Method 2: Listening to the 'socket' Event on the Request

If you need to inspect or attach listeners to the socket before the request finishes (for example, to track DNS resolution or connection handshakes), you can capture the http.ClientRequest instance early using the socket event.

Axios exposes the underlying request instance in request interceptors or through transport hooks:

const axios = require('axios');

const instance = axios.create();

instance.interceptors.request.use((config) => {
  // Axios creates the request internally, but you can hook into
  // the transport request creation via custom adapters or agent events.
  return config;
});

To directly listen to the native 'socket' event, handle the request instance as soon as it is assigned:

const https = require('https');
const axios = require('axios');

const agent = new https.Agent();

axios.get('https://example.com', { httpsAgent: agent, transport: {
  request: (options, callback) => {
    const req = https.request(options, callback);
    req.on('socket', (socket) => {
      console.log('Socket assigned to request');
      socket.on('lookup', () => console.log('DNS lookup complete'));
      socket.on('connect', () => console.log('TCP connect complete'));
      socket.on('secureConnect', () => console.log('TLS handshake complete'));
    });
    return req;
  }
}});

Method 3: Using a Custom HTTP/HTTPS Agent

Overriding the createConnection method of an http.Agent or https.Agent gives you full visibility into raw socket creation before it is handed off to the HTTP parser.

const axios = require('axios');
const https = require('https');
const tls = require('tls');

class CustomAgent extends https.Agent {
  createConnection(options, callback) {
    const socket = tls.connect(options, () => {
      console.log('Connected to:', options.host);
      callback(null, socket);
    });

    socket.on('data', (chunk) => {
      console.log('Raw Bytes Received:', chunk.length);
    });

    return socket;
  }
}

async function run() {
  const httpsAgent = new CustomAgent();
  await axios.get('https://example.com', { httpsAgent });
}

run();

This method is ideal for logging raw binary data, tracking byte counts, or injecting customized socket-level timeouts and keep-alive settings.