mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-26 14:15:52 +00:00
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:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
39
packages/bruno-electron/src/store/system-proxy.js
Normal file
39
packages/bruno-electron/src/store/system-proxy.js
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user