feat: cache system proxy to avoid redundant lookups (#6990)

- bruno-cli: fetch system proxy once before request loop and store in options
- bruno-electron: initialize system proxy cache at app startup
- Add refresh button in preferences to manually update cached system proxy
- Replace per-request getSystemProxy() calls with cached values

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
lohit
2026-01-30 16:45:36 +00:00
committed by GitHub
parent 559946bcce
commit 3112380289
8 changed files with 116 additions and 21 deletions

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { IconLoader2 } from '@tabler/icons';
import { getSystemProxyVariables } from 'providers/ReduxStore/slices/app';
import { IconLoader2, IconRefresh } from '@tabler/icons';
import { getSystemProxyVariables, refreshSystemProxy } from 'providers/ReduxStore/slices/app';
import StyledWrapper from '../StyledWrapper';
const SystemProxy = () => {
@@ -11,21 +11,32 @@ const SystemProxy = () => {
const [isFetching, setIsFetching] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
dispatch(getSystemProxyVariables())
const fetchProxy = (forceRefresh = false) => {
setIsFetching(true);
setError(null);
const action = forceRefresh ? refreshSystemProxy : getSystemProxyVariables;
dispatch(action())
.then(() => setError(null))
.catch((err) => setError(err.message || String(err)))
.finally(() => setIsFetching(false));
};
useEffect(() => {
fetchProxy(false);
}, [dispatch]);
const handleRefresh = () => {
fetchProxy(true);
};
return (
<StyledWrapper>
<div className="mb-3 text-muted system-proxy-settings space-y-4">
<div className="flex items-start justify-start flex-col gap-2 mt-2">
<div className="flex flex-row items-center gap-2">
<div>
<h2 className="text-xs system-proxy-title">
System Proxy {isFetching ? <IconLoader2 className="animate-spin ml-1" size={18} strokeWidth={1.5} /> : null}
<h2 className="text-xs system-proxy-title flex flex-row">
System Proxy {isFetching ? <IconLoader2 className="animate-spin ml-1" size={16} strokeWidth={1.5} /> : null}
</h2>
<small className="system-proxy-description">
Below values are sourced from your system proxy settings.
@@ -75,6 +86,13 @@ const SystemProxy = () => {
<div className="system-proxy-value">{no_proxy || '-'}</div>
</div>
</div>
<span
className="text-link cursor-pointer hover:underline default-collection-location-browse flex flex-row items-center"
onClick={handleRefresh}
>
<IconRefresh size={14} strokeWidth={1.5} className="mr-1" />
Refresh
</span>
</div>
</StyledWrapper>
);

View File

@@ -248,4 +248,16 @@ export const getSystemProxyVariables = () => (dispatch, getState) => {
});
};
export const refreshSystemProxy = () => (dispatch, getState) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer.invoke('renderer:refresh-system-proxy')
.then((variables) => {
dispatch(updateSystemProxyVariables(variables));
return variables;
})
.then(resolve).catch(reject);
});
};
export default appSlice.reducer;

View File

@@ -18,6 +18,7 @@ const constants = require('../constants');
const { findItemInCollection, createCollectionJsonFromPathname, getCallStack, FORMAT_CONFIG } = require('../utils/collection');
const { hasExecutableTestInScript } = require('../utils/request');
const { createSkippedFileResults } = require('../utils/run');
const { getSystemProxy } = require('@usebruno/requests');
const command = 'run [paths...]';
const desc = 'Run one or more requests/folders';
@@ -608,6 +609,15 @@ const handler = async function (argv) {
const runtime = getJsSandboxRuntime(sandbox);
// Fetch system proxy once for all requests (skip if --noproxy flag is set)
if (!noproxy) {
try {
options['cachedSystemProxy'] = await getSystemProxy();
} catch (error) {
console.warn(chalk.yellow('Failed to detect system proxy, continuing without system proxy'));
}
}
const runSingleRequestByPathname = async (relativeItemPathname) => {
const ext = FORMAT_CONFIG[collection.format].ext;
return new Promise(async (resolve, reject) => {

View File

@@ -15,7 +15,6 @@ const { SocksProxyAgent } = require('socks-proxy-agent');
const { makeAxiosInstance } = require('../utils/axios-instance');
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
const { getSystemProxy } = require('@usebruno/requests');
const path = require('path');
const { parseDataFromResponse } = require('../utils/common');
const { getCookieStringForUrl, saveCookies } = require('../utils/cookies');
@@ -241,6 +240,7 @@ const runSingleRequest = async function (
const options = getOptions();
const insecure = get(options, 'insecure', false);
const noproxy = get(options, 'noproxy', false);
const cachedSystemProxy = get(options, 'cachedSystemProxy', null);
const httpsAgentRequestFields = {};
if (insecure) {
@@ -310,10 +310,11 @@ const runSingleRequest = async function (
proxyMode = 'on';
} else if (!collectionProxyDisabled && collectionProxyInherit) {
// Inherit from system proxy
const systemProxy = await getSystemProxy();
const { http_proxy, https_proxy } = systemProxy;
if (http_proxy?.length || https_proxy?.length) {
proxyMode = 'system';
if (cachedSystemProxy) {
const { http_proxy, https_proxy } = cachedSystemProxy;
if (http_proxy?.length || https_proxy?.length) {
proxyMode = 'system';
}
}
// else: no system proxy available, proxyMode stays 'off'
}
@@ -357,8 +358,7 @@ const runSingleRequest = async function (
}
} else if (proxyMode === 'system') {
try {
const systemProxy = await getSystemProxy();
const { http_proxy, https_proxy, no_proxy } = systemProxy;
const { http_proxy, https_proxy, no_proxy } = cachedSystemProxy || {};
const shouldUseSystemProxy = shouldUseProxy(request.url, no_proxy || '');
const parsedUrl = new URL(request.url);
const isHttpsRequest = parsedUrl.protocol === 'https:';
@@ -505,7 +505,7 @@ const runSingleRequest = async function (
const proxyConfig = get(brunoConfig, 'proxy');
const interpolatedClientCertificates = clientCertificates ? interpolateObject(clientCertificates, oauth2InterpolationOptions) : undefined;
const interpolatedProxyConfig = proxyConfig ? interpolateObject(proxyConfig, oauth2InterpolationOptions) : undefined;
const systemProxyConfig = await getSystemProxy();
const systemProxyConfig = cachedSystemProxy;
const { httpAgent: oauth2HttpAgent, httpsAgent: oauth2HttpsAgent } = await getHttpHttpsAgents({
requestUrl: oauth2RequestUrl,

View File

@@ -171,6 +171,12 @@ app.on('ready', async () => {
}
}
// Initialize system proxy cache early (non-blocking)
const { initializeSystemProxy } = require('./store/system-proxy');
initializeSystemProxy().catch((err) => {
console.warn('Failed to initialize system proxy cache:', err);
});
Menu.setApplicationMenu(menu);
const { maximized, x, y, width, height } = loadWindowState();

View File

@@ -1,9 +1,10 @@
const fs = require('node:fs');
const path = require('path');
const { get } = require('lodash');
const { getCACertificates, getSystemProxy } = require('@usebruno/requests');
const { getCACertificates } = require('@usebruno/requests');
const { preferencesUtil } = require('../../store/preferences');
const { getBrunoConfig } = require('../../store/bruno-config');
const { getCachedSystemProxy } = require('../../store/system-proxy');
const { interpolateString } = require('./interpolate-string');
/**
@@ -142,10 +143,10 @@ const getCertsAndProxyConfig = async ({
proxyConfig = globalProxyConfigData;
proxyMode = 'on';
} else if (!globalDisabled && globalInherit) {
// Use system proxy
// Use system proxy (cached at app startup)
proxyMode = 'system';
const systemProxyConfig = await getSystemProxy();
proxyConfig = systemProxyConfig;
const systemProxyConfig = getCachedSystemProxy();
proxyConfig = systemProxyConfig || { http_proxy: null, https_proxy: null, no_proxy: null, source: 'cache-miss' };
}
// else: global proxy is disabled, proxyMode stays 'off'
}

View File

@@ -1,7 +1,7 @@
const { ipcMain, nativeTheme } = require('electron');
const { getPreferences, savePreferences } = require('../store/preferences');
const { globalEnvironmentsStore } = require('../store/global-environments');
const { getSystemProxy } = require('@usebruno/requests');
const { getCachedSystemProxy, refreshSystemProxy } = require('../store/system-proxy');
const registerPreferencesIpc = (mainWindow) => {
ipcMain.handle('renderer:ready', async (event) => {
@@ -40,8 +40,17 @@ const registerPreferencesIpc = (mainWindow) => {
});
ipcMain.handle('renderer:get-system-proxy-variables', async () => {
const systemProxyConfig = await getSystemProxy();
return systemProxyConfig;
// Return cached value (initialized at app startup)
const cachedProxy = getCachedSystemProxy();
if (cachedProxy) {
return cachedProxy;
}
// Fallback: refresh if cache is empty (shouldn't happen normally)
return await refreshSystemProxy();
});
ipcMain.handle('renderer:refresh-system-proxy', async () => {
return await refreshSystemProxy();
});
};

View File

@@ -0,0 +1,39 @@
const { getSystemProxy } = require('@usebruno/requests');
let cachedSystemProxy = null;
const initializeSystemProxy = async () => {
try {
cachedSystemProxy = await getSystemProxy();
return cachedSystemProxy;
} catch (error) {
console.error('Failed to initialize system proxy:', error);
cachedSystemProxy = {
http_proxy: null,
https_proxy: null,
no_proxy: null,
source: 'error'
};
return cachedSystemProxy;
}
};
const refreshSystemProxy = async () => {
try {
cachedSystemProxy = await getSystemProxy();
return cachedSystemProxy;
} catch (error) {
console.error('Failed to refresh system proxy:', error);
throw error;
}
};
const getCachedSystemProxy = () => {
return cachedSystemProxy;
};
module.exports = {
initializeSystemProxy,
refreshSystemProxy,
getCachedSystemProxy
};