🐛 fix(driver-manager): 修复驱动管理网络告警重复并强化代理引导

- 新增下载链路域名探测,区分“GitHub可达但驱动下载链路不可达”
- 网络不可达场景仅保留红色强提醒,移除重复二级告警
- 强提醒增加“打开全局代理设置”入口,优先引导使用 GoNavi 全局代理
- 统一网络检测与目录说明提示图标尺寸,修复加载期视觉不一致
- refs #141
This commit is contained in:
Syngnat
2026-03-02 15:58:58 +08:00
parent e3b142053f
commit 78c5351399
3 changed files with 366 additions and 74 deletions

View File

@@ -979,6 +979,7 @@ function App() {
<DriverManagerModal
open={isDriverModalOpen}
onClose={() => setIsDriverModalOpen(false)}
onOpenGlobalProxySettings={() => setIsProxyModalOpen(true)}
/>
<Modal
title="关于 GoNavi"

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Alert, Button, Collapse, Input, Modal, Progress, Select, Space, Switch, Table, Tag, Typography, message } from 'antd';
import { DeleteOutlined, DownloadOutlined, FileSearchOutlined, FolderOpenOutlined, ReloadOutlined } from '@ant-design/icons';
import { DeleteOutlined, DownloadOutlined, FileSearchOutlined, FolderOpenOutlined, InfoCircleFilled, ReloadOutlined } from '@ant-design/icons';
import { EventsOn } from '../../wailsjs/runtime/runtime';
import { useStore } from '../store';
import { normalizeOpacityForPlatform } from '../utils/appearance';
@@ -63,6 +63,9 @@ type DriverNetworkProbe = {
reachable: boolean;
httpStatus?: number;
latencyMs?: number;
tcpLatencyMs?: number;
httpLatencyMs?: number;
method?: string;
error?: string;
};
@@ -71,12 +74,22 @@ type DriverNetworkStatus = {
summary: string;
recommendedProxy: boolean;
proxyConfigured: boolean;
downloadChainReachable?: boolean;
downloadRequiredHosts?: string[];
proxyEnv?: Record<string, string>;
checks: DriverNetworkProbe[];
checkedAt?: string;
logPath?: string;
};
const parseOptionalLatency = (value: unknown): number | undefined => {
const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed < 0) return undefined;
return parsed;
};
const sharedInfoAlertIcon = <InfoCircleFilled style={{ fontSize: 24 }} />;
type DriverVersionOption = {
version: string;
downloadUrl: string;
@@ -90,8 +103,15 @@ type DriverVersionOption = {
const buildVersionOptionKey = (option: DriverVersionOption) => `${option.version}@@${option.downloadUrl}`;
const buildVersionSizeLoadingKey = (driverType: string, optionKey: string) => `${driverType}@@${optionKey}`;
const DRIVER_TABLE_SCROLL_X = 1450;
const DRIVER_STATUS_CACHE_TTL_MS = 60 * 1000;
const DRIVER_NETWORK_CACHE_TTL_MS = 5 * 60 * 1000;
const normalizeDriverSearchText = (value: string) => String(value || '').trim().toLowerCase();
let driverStatusSnapshotCache: { rows: DriverStatusRow[]; downloadDir: string; cachedAt: number } | null = null;
let driverNetworkSnapshotCache: { status: DriverNetworkStatus; cachedAt: number } | null = null;
const isFreshCache = (cachedAt: number, ttlMs: number): boolean => Date.now() - cachedAt <= ttlMs;
const buildVersionSelectOptions = (options: DriverVersionOption[]) => {
type SelectOption = { value: string; label: string };
type SelectGroup = { label: string; options: SelectOption[] };
@@ -138,7 +158,11 @@ const buildVersionSelectOptions = (options: DriverVersionOption[]) => {
return grouped;
};
const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({ open, onClose }) => {
const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenGlobalProxySettings?: () => void }> = ({
open,
onClose,
onOpenGlobalProxySettings,
}) => {
const theme = useStore((state) => state.theme);
const appearance = useStore((state) => state.appearance);
const darkMode = theme === 'dark';
@@ -166,6 +190,11 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
const [versionLoadingMap, setVersionLoadingMap] = useState<Record<string, boolean>>({});
const [versionSizeLoadingMap, setVersionSizeLoadingMap] = useState<Record<string, boolean>>({});
const [horizontalScrollWidth, setHorizontalScrollWidth] = useState(DRIVER_TABLE_SCROLL_X);
const downloadDirRef = useRef(downloadDir);
useEffect(() => {
downloadDirRef.current = downloadDir;
}, [downloadDir]);
const appendOperationLog = useCallback((
driverType: string,
@@ -283,10 +312,16 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
horizontalSyncSourceRef.current = '';
}, []);
const refreshStatus = useCallback(async (toastOnError = true) => {
setLoading(true);
const refreshStatus = useCallback(async (
toastOnError = true,
options?: { showLoading?: boolean },
) => {
const showLoading = options?.showLoading ?? true;
if (showLoading) {
setLoading(true);
}
try {
const res = await GetDriverStatusList(downloadDir, '');
const res = await GetDriverStatusList(downloadDirRef.current, '');
if (!res?.success) {
if (toastOnError) {
message.error(res?.message || '拉取驱动状态失败');
@@ -298,6 +333,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
const resolvedDir = String(data.downloadDir || '').trim();
const drivers = Array.isArray(data.drivers) ? data.drivers : [];
const effectiveDownloadDir = resolvedDir || downloadDirRef.current;
if (resolvedDir) {
setDownloadDir(resolvedDir);
}
@@ -320,17 +356,30 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
message: String(item.message || '').trim() || undefined,
}));
setRows(nextRows);
driverStatusSnapshotCache = {
rows: nextRows,
downloadDir: effectiveDownloadDir,
cachedAt: Date.now(),
};
} catch (err: any) {
if (toastOnError) {
message.error(`拉取驱动状态失败:${err?.message || String(err)}`);
}
} finally {
setLoading(false);
if (showLoading) {
setLoading(false);
}
}
}, [downloadDir]);
}, []);
const checkNetworkStatus = useCallback(async (toastOnError = false) => {
setNetworkChecking(true);
const checkNetworkStatus = useCallback(async (
toastOnError = false,
options?: { showLoading?: boolean },
) => {
const showLoading = options?.showLoading ?? true;
if (showLoading) {
setNetworkChecking(true);
}
try {
const res = await CheckDriverNetworkStatus();
if (!res?.success) {
@@ -345,26 +394,40 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
name: String(item.name || '').trim(),
url: String(item.url || '').trim(),
reachable: !!item.reachable,
httpStatus: Number(item.httpStatus || 0) || undefined,
latencyMs: Number(item.latencyMs || 0) || undefined,
httpStatus: parseOptionalLatency(item.httpStatus),
latencyMs: parseOptionalLatency(item.latencyMs),
tcpLatencyMs: parseOptionalLatency(item.tcpLatencyMs),
httpLatencyMs: parseOptionalLatency(item.httpLatencyMs),
method: String(item.method || '').trim().toUpperCase() || undefined,
error: String(item.error || '').trim() || undefined,
}));
setNetworkStatus({
const nextStatus: DriverNetworkStatus = {
reachable: !!data.reachable,
summary: String(data.summary || '').trim() || '驱动网络检测已完成',
recommendedProxy: !!data.recommendedProxy,
proxyConfigured: !!data.proxyConfigured,
downloadChainReachable: typeof data.downloadChainReachable === 'boolean' ? data.downloadChainReachable : undefined,
downloadRequiredHosts: Array.isArray(data.downloadRequiredHosts)
? data.downloadRequiredHosts.map((item: unknown) => String(item || '').trim()).filter(Boolean)
: undefined,
proxyEnv: (data.proxyEnv || {}) as Record<string, string>,
checkedAt: String(data.checkedAt || '').trim() || undefined,
checks: normalizedChecks,
logPath: String(data.logPath || '').trim() || undefined,
});
};
setNetworkStatus(nextStatus);
driverNetworkSnapshotCache = {
status: nextStatus,
cachedAt: Date.now(),
};
} catch (err: any) {
if (toastOnError) {
message.error(`驱动网络检测失败:${err?.message || String(err)}`);
}
} finally {
setNetworkChecking(false);
if (showLoading) {
setNetworkChecking(false);
}
}
}, []);
@@ -523,8 +586,29 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
tableScrollTargetsRef.current = [];
return;
}
refreshStatus(false);
checkNetworkStatus(false);
const cachedStatus = driverStatusSnapshotCache;
const hasCachedStatus = !!cachedStatus;
if (cachedStatus) {
setRows(cachedStatus.rows);
if (cachedStatus.downloadDir) {
setDownloadDir(cachedStatus.downloadDir);
}
}
const shouldRefreshStatus = !cachedStatus || !isFreshCache(cachedStatus.cachedAt, DRIVER_STATUS_CACHE_TTL_MS);
if (shouldRefreshStatus) {
void refreshStatus(false, { showLoading: !hasCachedStatus });
}
const cachedNetwork = driverNetworkSnapshotCache;
const hasCachedNetwork = !!cachedNetwork;
if (cachedNetwork) {
setNetworkStatus(cachedNetwork.status);
}
const shouldRefreshNetwork = !cachedNetwork || !isFreshCache(cachedNetwork.cachedAt, DRIVER_NETWORK_CACHE_TTL_MS);
if (shouldRefreshNetwork) {
void checkNetworkStatus(false, { showLoading: !hasCachedNetwork });
}
}, [checkNetworkStatus, open, refreshStatus]);
useEffect(() => {
@@ -1106,6 +1190,18 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
const activeDriverLogs = operationLogMap[logDriverType] || [];
const activeDriverLogLines = activeDriverLogs.map((item) => `[${item.time}] ${item.text}`);
const proxyEnvEntries = Object.entries(networkStatus?.proxyEnv || {});
const downloadRequiredHosts = (networkStatus?.downloadRequiredHosts || []).filter(Boolean);
const showDownloadChainAlert = networkStatus?.downloadChainReachable === false;
const networkUnreachable = networkStatus?.reachable === false;
const downloadRequiredHostText = (downloadRequiredHosts.length > 0
? downloadRequiredHosts
: ['github.com', 'api.github.com', 'release-assets.githubusercontent.com', 'objects.githubusercontent.com', 'raw.githubusercontent.com']).join('、');
const githubConnectivityProbe = networkStatus?.checks.find((item) => item.name === 'GitHub API')
|| networkStatus?.checks.find((item) => item.name === 'GitHub 驱动发布')
|| null;
const githubConnectivityLatencyMs = githubConnectivityProbe
? (githubConnectivityProbe.httpLatencyMs ?? githubConnectivityProbe.latencyMs ?? githubConnectivityProbe.tcpLatencyMs)
: undefined;
const logBlockBackground = darkMode
? `rgba(28, 28, 28, ${Math.max(opacity, 0.82)})`
: `rgba(255, 255, 255, ${Math.max(opacity, 0.92)})`;
@@ -1156,15 +1252,43 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Text type="secondary"> MySQL / Redis / Oracle / PostgreSQL </Text>
{networkStatus ? (
<Alert
type={networkStatus.reachable ? 'success' : 'warning'}
showIcon
message={networkStatus.summary}
description={(
<Space direction="vertical" size={6} style={{ width: '100%' }}>
<Text type="secondary">
GitHub Go HTTP/HTTPS/SOCKS5
</Text>
networkUnreachable ? (
<Alert
type="error"
showIcon
message={showDownloadChainAlert ? '重要提醒:驱动下载链路域名不可达' : '重要提醒:驱动下载网络不可达'}
description={(
<Space direction="vertical" size={8} style={{ width: '100%' }}>
{showDownloadChainAlert ? (
<>
<Text>
访 GitHub
GoNavi
</Text>
{onOpenGlobalProxySettings ? (
<Button size="small" onClick={onOpenGlobalProxySettings}></Button>
) : null}
<Text>
{downloadRequiredHostText} TUN
</Text>
</>
) : (
<Text>{networkStatus.summary}</Text>
)}
{proxyEnvEntries.length > 0 ? (
<Text type="secondary">
{proxyEnvEntries.map(([key]) => key).join('、')}
</Text>
) : null}
</Space>
)}
/>
) : (
<Alert
type="success"
showIcon
message={networkStatus.summary}
description={(
<Collapse
size="small"
items={[
@@ -1173,11 +1297,11 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
label: '查看网络检测明细',
children: (
<Space direction="vertical" size={4} style={{ width: '100%' }}>
{networkStatus.checks.map((item) => (
<Text key={`${item.name}-${item.url}`} type={item.reachable ? 'secondary' : 'danger'}>
{item.name}{item.reachable ? '可达' : '不可达'}{item.httpStatus ? `HTTP ${item.httpStatus}` : ''}{item.latencyMs ? `${item.latencyMs}ms` : ''}{item.error ? `${item.error}` : ''}
</Text>
))}
<Text type="secondary">
GitHub {githubConnectivityProbe ? (githubConnectivityProbe.reachable ? '可达' : '不可达') : '暂无结果'}
{githubConnectivityLatencyMs !== undefined ? `${githubConnectivityLatencyMs}ms` : ''}
{githubConnectivityProbe?.error ? `${githubConnectivityProbe.error}` : ''}
</Text>
{proxyEnvEntries.length > 0 ? (
<Text type="secondary">
{proxyEnvEntries.map(([key]) => key).join('、')}
@@ -1190,30 +1314,47 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void }> = ({
},
]}
/>
</Space>
)}
/>
)}
/>
)
) : (
<Alert type="info" showIcon message={networkChecking ? '正在检测驱动下载网络...' : '尚未完成网络检测'} />
<Alert
type="info"
showIcon
icon={sharedInfoAlertIcon}
message={networkChecking ? '正在检测驱动下载网络...' : '尚未完成网络检测'}
/>
)}
<Alert
type="info"
showIcon
icon={sharedInfoAlertIcon}
message="驱动目录与复用说明"
description={(
<Space direction="vertical" size={6} style={{ width: '100%' }}>
<Text type="secondary"></Text>
<Text type="secondary">/ `mariadb-driver-agent``mariadb-driver-agent.exe``GoNavi-DriverAgents.zip`使</Text>
<Paragraph copyable={{ text: downloadDir || '-' }} style={{ marginBottom: 0 }}>
{downloadDir || '-'}
</Paragraph>
{networkStatus?.logPath ? (
<Paragraph copyable={{ text: networkStatus.logPath }} style={{ marginBottom: 0 }}>
{networkStatus.logPath}
</Paragraph>
) : null}
</Space>
<Collapse
size="small"
items={[
{
key: 'driver-directory',
label: '查看驱动目录与复用说明',
children: (
<Space direction="vertical" size={6} style={{ width: '100%' }}>
<Text type="secondary"></Text>
<Text type="secondary">/ `mariadb-driver-agent``mariadb-driver-agent.exe``GoNavi-DriverAgents.zip`使</Text>
<Paragraph copyable={{ text: downloadDir || '-' }} style={{ marginBottom: 0 }}>
{downloadDir || '-'}
</Paragraph>
{networkStatus?.logPath ? (
<Paragraph copyable={{ text: networkStatus.logPath }} style={{ marginBottom: 0 }}>
{networkStatus.logPath}
</Paragraph>
) : null}
</Space>
),
},
]}
/>
)}
/>

View File

@@ -83,12 +83,15 @@ type driverDownloadProgressPayload struct {
}
type driverNetworkProbeItem struct {
Name string `json:"name"`
URL string `json:"url"`
Reachable bool `json:"reachable"`
HTTPStatus int `json:"httpStatus,omitempty"`
LatencyMs int64 `json:"latencyMs,omitempty"`
Error string `json:"error,omitempty"`
Name string `json:"name"`
URL string `json:"url"`
Reachable bool `json:"reachable"`
HTTPStatus int `json:"httpStatus,omitempty"`
LatencyMs int64 `json:"latencyMs,omitempty"`
TCPLatency int64 `json:"tcpLatencyMs,omitempty"`
HTTPLatency int64 `json:"httpLatencyMs,omitempty"`
Method string `json:"method,omitempty"`
Error string `json:"error,omitempty"`
}
type pinnedDriverPackage struct {
@@ -201,6 +204,7 @@ const (
driverBundleIndexMaxSize = 1 << 20
driverManifestMaxSize = 2 << 20
driverNetworkProbeTimeout = 4 * time.Second
driverNetworkProbeTCPTimeout = 3 * time.Second
localDriverDirectoryScanMaxEntries = 20000
driverChecksumPolicyStrict = "strict"
driverChecksumPolicyWarn = "warn"
@@ -647,24 +651,43 @@ func (a *App) CheckDriverNetworkStatus() connection.QueryResult {
Name: "GitHub 驱动发布",
URL: fmt.Sprintf("https://github.com/%s/releases/latest/download/%s", updateRepo, optionalDriverBundleAssetName),
},
{
Name: "GitHub Release 资产域名",
URL: "https://release-assets.githubusercontent.com/",
},
{
Name: "Go 模块代理",
URL: "https://proxy.golang.org/github.com/go-sql-driver/mysql/@v/list",
},
}
client := newHTTPClientWithGlobalProxy(driverNetworkProbeTimeout)
allReachable := true
for index := range checks {
checks[index] = probeDriverNetworkEndpoint(checks[index])
checks[index] = probeDriverNetworkEndpoint(client, checks[index])
if !checks[index].Reachable {
allReachable = false
}
}
findProbe := func(name string) (driverNetworkProbeItem, bool) {
for _, item := range checks {
if strings.EqualFold(strings.TrimSpace(item.Name), strings.TrimSpace(name)) {
return item, true
}
}
return driverNetworkProbeItem{}, false
}
githubAPICheck, _ := findProbe("GitHub API")
githubReleaseCheck, _ := findProbe("GitHub 驱动发布")
releaseAssetsCheck, _ := findProbe("GitHub Release 资产域名")
downloadChainReachable := githubReleaseCheck.Reachable && releaseAssetsCheck.Reachable
proxyEnv := collectDriverProxyEnv()
proxyConfigured := len(proxyEnv) > 0
summary := "驱动下载网络检测通过,可直接安装驱动。"
if !allReachable {
if githubAPICheck.Reachable && !downloadChainReachable {
summary = "重要提醒GitHub API 可达,但驱动下载链路不可达。请优先在 GoNavi 启用全局代理(填写代理应用本地地址和端口),并在代理规则中放行 github.com、api.github.com、release-assets.githubusercontent.com、objects.githubusercontent.com、raw.githubusercontent.com若仍失败再考虑开启 TUN 模式。"
} else if !allReachable {
if proxyConfigured {
summary = "检测到部分驱动下载地址不可达,请确认系统代理配置有效后重试。"
} else {
@@ -678,6 +701,14 @@ func (a *App) CheckDriverNetworkStatus() connection.QueryResult {
"recommendedProxy": !allReachable,
"proxyConfigured": proxyConfigured,
"proxyEnv": proxyEnv,
"downloadChainReachable": downloadChainReachable,
"downloadRequiredHosts": []string{
"github.com",
"api.github.com",
"release-assets.githubusercontent.com",
"objects.githubusercontent.com",
"raw.githubusercontent.com",
},
"checkedAt": time.Now().Format(time.RFC3339),
"checks": checks,
}
@@ -890,12 +921,15 @@ func (a *App) emitDriverDownloadProgress(driverType string, status string, downl
runtime.EventsEmit(a.ctx, driverDownloadProgressEvent, payload)
}
func probeDriverNetworkEndpoint(item driverNetworkProbeItem) driverNetworkProbeItem {
func probeDriverNetworkEndpoint(client *http.Client, item driverNetworkProbeItem) driverNetworkProbeItem {
probed := item
probed.Reachable = false
probed.HTTPStatus = 0
probed.Error = ""
probed.LatencyMs = 0
probed.TCPLatency = 0
probed.HTTPLatency = 0
probed.Method = ""
urlText := strings.TrimSpace(item.URL)
if urlText == "" {
@@ -903,33 +937,34 @@ func probeDriverNetworkEndpoint(item driverNetworkProbeItem) driverNetworkProbeI
return probed
}
client := newHTTPClientWithGlobalProxy(driverNetworkProbeTimeout)
start := time.Now()
req, err := http.NewRequest(http.MethodHead, urlText, nil)
if err != nil {
probed.Error = normalizeErrorMessage(err)
return probed
if tcpLatency, tcpErr := probeDriverTCPLatency(urlText); tcpErr == nil {
probed.TCPLatency = tcpLatency
probed.LatencyMs = tcpLatency
}
req.Header.Set("User-Agent", "GoNavi-DriverManager")
resp, err := client.Do(req)
if err != nil {
// 某些网关不支持 HEAD请回退为 GET不读取正文
reqGet, reqErr := http.NewRequest(http.MethodGet, urlText, nil)
if reqErr != nil {
probed.Error = normalizeErrorMessage(reqErr)
probed.LatencyMs = time.Since(start).Milliseconds()
return probed
}
reqGet.Header.Set("User-Agent", "GoNavi-DriverManager")
resp, err = client.Do(reqGet)
if client == nil {
client = newHTTPClientWithGlobalProxy(driverNetworkProbeTimeout)
}
start := time.Now()
resp, method, err := doDriverProbeRequest(client, urlText, http.MethodGet)
if err != nil || shouldFallbackHeadProbe(resp) {
if resp != nil {
_ = resp.Body.Close()
}
// 回退到 HEAD 时重置计时,避免把失败重试耗时累计到最终延迟指标里。
start = time.Now()
resp, method, err = doDriverProbeRequest(client, urlText, http.MethodHead)
}
probed.HTTPLatency = time.Since(start).Milliseconds()
if probed.LatencyMs <= 0 {
probed.LatencyMs = probed.HTTPLatency
}
probed.LatencyMs = time.Since(start).Milliseconds()
if err != nil {
probed.Error = normalizeDriverNetworkError(err)
return probed
}
defer resp.Body.Close()
probed.Method = method
probed.HTTPStatus = resp.StatusCode
if resp.StatusCode >= 500 {
@@ -940,6 +975,121 @@ func probeDriverNetworkEndpoint(item driverNetworkProbeItem) driverNetworkProbeI
return probed
}
func probeDriverTCPLatency(rawURL string) (int64, error) {
dialAddr, err := resolveDriverProbeDialAddress(rawURL)
if err != nil {
return 0, err
}
start := time.Now()
conn, err := net.DialTimeout("tcp", dialAddr, driverNetworkProbeTCPTimeout)
elapsed := time.Since(start)
latency := elapsed.Milliseconds()
if elapsed > 0 && latency <= 0 {
latency = 1
}
if err != nil {
return latency, err
}
_ = conn.Close()
return latency, nil
}
func resolveDriverProbeDialAddress(rawURL string) (string, error) {
urlText := strings.TrimSpace(rawURL)
if urlText == "" {
return "", fmt.Errorf("检测地址为空")
}
parsed, err := url.Parse(urlText)
if err != nil {
return "", err
}
targetHost := strings.TrimSpace(parsed.Hostname())
if targetHost == "" {
return "", fmt.Errorf("检测地址缺少主机")
}
targetPort := strings.TrimSpace(parsed.Port())
if targetPort == "" {
if strings.EqualFold(parsed.Scheme, "http") {
targetPort = "80"
} else {
targetPort = "443"
}
}
if proxyURL := resolveDriverProbeProxyURL(parsed); proxyURL != nil {
proxyHost := strings.TrimSpace(proxyURL.Hostname())
if proxyHost == "" {
return net.JoinHostPort(targetHost, targetPort), nil
}
proxyPort := strings.TrimSpace(proxyURL.Port())
if proxyPort == "" {
proxyPort = defaultPortForScheme(proxyURL.Scheme)
}
return net.JoinHostPort(proxyHost, proxyPort), nil
}
return net.JoinHostPort(targetHost, targetPort), nil
}
func resolveDriverProbeProxyURL(target *url.URL) *url.URL {
if target == nil {
return nil
}
snapshot := currentGlobalProxyConfig()
if snapshot.Enabled {
proxyURL, err := buildProxyURLFromConfig(snapshot.Proxy)
if err == nil {
return proxyURL
}
}
req := &http.Request{URL: target}
proxyURL, err := http.ProxyFromEnvironment(req)
if err != nil {
return nil
}
return proxyURL
}
func defaultPortForScheme(scheme string) string {
switch strings.ToLower(strings.TrimSpace(scheme)) {
case "https":
return "443"
case "socks5", "socks5h":
return "1080"
case "http":
fallthrough
default:
return "80"
}
}
func doDriverProbeRequest(client *http.Client, urlText string, method string) (*http.Response, string, error) {
req, err := http.NewRequest(method, urlText, nil)
if err != nil {
return nil, "", err
}
req.Header.Set("User-Agent", "GoNavi-DriverManager")
// 用 GET+Range 探测可更接近真实下载链路,同时避免下载正文。
if strings.EqualFold(method, http.MethodGet) {
req.Header.Set("Range", "bytes=0-0")
}
resp, err := client.Do(req)
if err != nil {
return nil, method, err
}
return resp, method, nil
}
func shouldFallbackHeadProbe(resp *http.Response) bool {
if resp == nil {
return false
}
return resp.StatusCode == http.StatusMethodNotAllowed || resp.StatusCode == http.StatusNotImplemented
}
func normalizeDriverNetworkError(err error) string {
if err == nil {
return ""