// ==UserScript==
// @name 网络请求分析工具
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description 网络异常分析工具,可以分析网络请求失败的原因并提供专业的排查建议
// @author Wangshiwei
// @match *://*/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=bing.com
// @license MIT
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
'use strict';
const CUSTOM_ICON = `
`
// 获取保存的位置
function getSavedPosition() {
try {
const saved = localStorage.getItem('network-analysis-btn-position');
if (saved) {
const pos = JSON.parse(saved);
return { x: pos.x, y: pos.y };
}
} catch (e) {
// 忽略错误
}
return null;
}
// 保存位置
function savePosition(x, y) {
try {
localStorage.setItem('network-analysis-btn-position', JSON.stringify({ x, y }));
} catch (e) {
// 忽略错误
}
}
// 创建悬浮按钮
function createFloatingButton() {
// 检查是否已经存在,防止重复创建
if (document.getElementById('network-analysis-container')) {
return;
}
const container = document.createElement('div');
container.id = 'network-analysis-container';
// 获取保存的位置或使用默认位置(右侧中间)
const savedPos = getSavedPosition();
const defaultX = window.innerWidth - 92; // 右侧(72px按钮 + 20px边距)
const defaultY = window.innerHeight / 2 - 36; // 中间(72px按钮的一半)
container.style.cssText = `
position: fixed;
right: ${savedPos ? 'auto' : '20px'};
left: ${savedPos ? savedPos.x + 'px' : 'auto'};
top: ${savedPos ? savedPos.y + 'px' : '50%'};
transform: ${savedPos ? 'none' : 'translateY(-50%)'};
width: 72px;
z-index: 10000;
user-select: none;
`;
const button = document.createElement('div');
button.id = 'network-analysis-btn';
// 默认网络图标 SVG(如果用户未提供自定义图标则使用此图标)
const defaultIconSVG = `
`;
// 使用自定义图标(如果提供),否则使用默认图标
const iconHTML = CUSTOM_ICON && CUSTOM_ICON.trim() ? CUSTOM_ICON.trim() : defaultIconSVG;
button.innerHTML = `
`;
button.style.cssText = `
position:fixed;
right:0px;
width: 48px;
height: 72px;
background: #fff;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease, transform 0.2s ease;
user-select: none;
border: 1px solid #f0f0f0;
opacity: 0.6;
`;
// 关闭按钮事件
const closeBtn = button.querySelector('#close-btn');
closeBtn.addEventListener('click', (e) => {
e.stopPropagation();
container.style.display = 'none';
});
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = '#f5f5f5';
closeBtn.style.color = '#333';
closeBtn.style.borderColor = '#d0d0d0';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = '#fff';
closeBtn.style.color = '#999';
closeBtn.style.borderColor = '#e0e0e0';
});
// 点击按钮打开弹窗(排除关闭按钮)
button.addEventListener('click', (e) => {
if (e.target.id !== 'close-btn' && !e.target.closest('#close-btn')) {
toggleModal();
}
});
// 拖动功能
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
// 初始化偏移量
if (savedPos) {
xOffset = savedPos.x;
yOffset = savedPos.y;
} else {
xOffset = defaultX;
yOffset = defaultY;
}
button.addEventListener('mousedown', (e) => {
if (e.target.id === 'close-btn' || e.target.closest('#close-btn')) {
return;
}
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
if (e.button === 0) { // 左键
isDragging = true;
button.style.cursor = 'grabbing';
button.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.25), 0 4px 8px rgba(0, 0, 0, 0.15)';
button.style.transform = 'scale(1.05)';
}
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, container);
}
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
button.style.cursor = 'move';
button.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1)';
button.style.transform = 'scale(1)';
// 保存位置
const rect = container.getBoundingClientRect();
savePosition(rect.left, rect.top);
}
});
// 触摸事件支持
button.addEventListener('touchstart', (e) => {
if (e.target.id === 'close-btn' || e.target.closest('#close-btn')) {
return;
}
const touch = e.touches[0];
initialX = touch.clientX - xOffset;
initialY = touch.clientY - yOffset;
isDragging = true;
});
document.addEventListener('touchmove', (e) => {
if (isDragging) {
e.preventDefault();
const touch = e.touches[0];
currentX = touch.clientX - initialX;
currentY = touch.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, container);
}
});
document.addEventListener('touchend', () => {
if (isDragging) {
isDragging = false;
const rect = container.getBoundingClientRect();
savePosition(rect.left, rect.top);
}
});
function setTranslate(xPos, yPos, el) {
// 限制在可视区域内
const maxX = window.innerWidth - el.offsetWidth;
const maxY = window.innerHeight - el.offsetHeight;
xPos = Math.max(0, Math.min(xPos, maxX));
yPos = Math.max(0, Math.min(yPos, maxY));
el.style.left = xPos + 'px';
el.style.top = yPos + 'px';
el.style.right = 'auto';
el.style.transform = 'none';
}
// 如果已保存位置,应用它;否则使用默认位置
if (savedPos) {
setTranslate(savedPos.x, savedPos.y, container);
} else {
// 延迟设置默认位置,确保窗口尺寸已确定
setTimeout(() => {
const defaultX = window.innerWidth - 92;
const defaultY = window.innerHeight / 2 - 36;
setTranslate(defaultX, defaultY, container);
savePosition(defaultX, defaultY);
}, 100);
}
// 窗口大小改变时调整位置
window.addEventListener('resize', () => {
const rect = container.getBoundingClientRect();
const maxX = window.innerWidth - container.offsetWidth;
const maxY = window.innerHeight - container.offsetHeight;
let newX = rect.left;
let newY = rect.top;
if (newX > maxX) newX = maxX;
if (newY > maxY) newY = maxY;
if (newX < 0) newX = 0;
if (newY < 0) newY = 0;
if (newX !== rect.left || newY !== rect.top) {
setTranslate(newX, newY, container);
savePosition(newX, newY);
}
});
container.appendChild(button);
document.body.appendChild(container);
return container;
}
// 创建弹窗
function createModal() {
// 检查是否已经存在,防止重复创建
if (document.getElementById('network-analysis-modal')) {
return;
}
const modal = document.createElement('div');
modal.id = 'network-analysis-modal';
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 600px;
max-width: 90vw;
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
z-index: 10001;
display: none;
flex-direction: column;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
`;
modal.innerHTML = `
网络请求分析工具
`;
// 添加加载动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#url-input:focus {
border-color: #667eea;
}
#send-request-btn:hover {
opacity: 0.9;
}
#send-request-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#get-from-page-btn:hover, #clear-config-btn:hover {
opacity: 0.9;
}
#result-content::-webkit-scrollbar {
width: 8px;
}
#result-content::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
#result-content::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
#result-content::-webkit-scrollbar-thumb:hover {
background: #555;
}
`;
document.head.appendChild(style);
document.body.appendChild(modal);
// 绑定事件
document.getElementById('close-modal-btn').addEventListener('click', () => {
toggleModal();
});
document.getElementById('send-request-btn').addEventListener('click', () => {
handleRequest();
});
document.getElementById('url-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleRequest();
}
});
// 从当前页面获取请求头和Cookie
document.getElementById('get-from-page-btn').addEventListener('click', () => {
getFromCurrentPage();
});
// 清除配置
document.getElementById('clear-config-btn').addEventListener('click', () => {
document.getElementById('headers-input').value = '';
document.getElementById('cookie-input').value = '';
});
// 点击遮罩层关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) {
toggleModal();
}
});
return modal;
}
// 从当前页面获取请求头和Cookie
function getFromCurrentPage() {
const headersInput = document.getElementById('headers-input');
const cookieInput = document.getElementById('cookie-input');
// 获取当前页面的Cookie
const cookies = document.cookie;
if (cookies) {
cookieInput.value = cookies;
}
const headers = [];
const commonHeaders = {};
// 添加常见的请求头
commonHeaders['Accept'] = 'application/json, text/plain, */*';
commonHeaders['Accept-Language'] = navigator.language || 'zh-CN,zh;q=0.9';
commonHeaders['User-Agent'] = navigator.userAgent;
commonHeaders['Referer'] = window.location.href;
commonHeaders['Origin'] = window.location.origin;
commonHeaders['Connection'] = 'keep-alive';
commonHeaders['Sec-Fetch-Dest'] = 'empty';
commonHeaders['Sec-Fetch-Mode'] = 'cors';
commonHeaders['Sec-Fetch-Site'] = 'same-origin';
// 检查Cookie中是否有token信息
if (cookies) {
const cookiePairs = cookies.split(';');
for (const pair of cookiePairs) {
const trimmedPair = pair.trim();
const equalIndex = trimmedPair.indexOf('=');
if (equalIndex <= 0) continue;
const key = trimmedPair.substring(0, equalIndex).trim();
let value = trimmedPair.substring(equalIndex + 1).trim();
if (key && (key.toLowerCase().includes('token') || key.toLowerCase().includes('auth'))) {
// 尝试URL解码
try {
value = decodeURIComponent(value);
} catch (e) {
// 如果解码失败,尝试部分替换
value = value.replace(/%20/g, ' ').replace(/%2B/g, '+');
}
// 检查是否已经包含 "token " 或 "Bearer "
if (value.startsWith('token ') || value.startsWith('Bearer ')) {
commonHeaders['Authorization'] = value;
break;
} else if (value && value.length > 0 && value.length < 500) {
// 如果值看起来像token(长度合理,不包含特殊字符)
if (!value.includes('{') && !value.includes('[') && !value.includes('"')) {
commonHeaders['Authorization'] = `token ${value}`;
break;
}
}
}
}
}
// 检查localStorage和sessionStorage中的token
try {
const storageKeys = ['token', 'auth', 'authorization', 'access_token', 'api_token', 'bearer_token'];
// 检查localStorage
for (const key of storageKeys) {
const value = localStorage.getItem(key);
if (value && value.trim()) {
if (value.startsWith('token ') || value.startsWith('Bearer ')) {
commonHeaders['Authorization'] = value;
break;
} else if (!value.includes('{') && !value.includes('[') && value.length < 200) {
commonHeaders['Authorization'] = `token ${value}`;
break;
}
}
}
// 如果localStorage没找到,检查sessionStorage
if (!commonHeaders['Authorization']) {
for (const key of storageKeys) {
const value = sessionStorage.getItem(key);
if (value && value.trim()) {
if (value.startsWith('token ') || value.startsWith('Bearer ')) {
commonHeaders['Authorization'] = value;
break;
} else if (!value.includes('{') && !value.includes('[') && value.length < 200) {
commonHeaders['Authorization'] = `token ${value}`;
break;
}
}
}
}
// 检查所有localStorage键,查找包含token的
if (!commonHeaders['Authorization']) {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && (key.toLowerCase().includes('token') || key.toLowerCase().includes('auth'))) {
const value = localStorage.getItem(key);
if (value && value.trim() && !value.includes('{') && !value.includes('[') && value.length < 200) {
if (value.startsWith('token ') || value.startsWith('Bearer ')) {
commonHeaders['Authorization'] = value;
} else {
commonHeaders['Authorization'] = `token ${value}`;
}
break;
}
}
}
}
} catch (e) {
// 忽略错误
}
// 格式化请求头
for (const [key, value] of Object.entries(commonHeaders)) {
headers.push(`${key}: ${value}`);
}
headersInput.value = headers.join('\n');
// 提示用户
const btn = document.getElementById('get-from-page-btn');
const originalText = btn.textContent;
btn.textContent = '已获取 ✓';
btn.style.background = '#27ae60';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '#667eea';
}, 2000);
}
// 切换弹窗显示/隐藏
function toggleModal() {
const modal = document.getElementById('network-analysis-modal');
if (modal.style.display === 'none' || !modal.style.display) {
modal.style.display = 'flex';
// 创建遮罩层
if (!document.getElementById('modal-overlay')) {
const overlay = document.createElement('div');
overlay.id = 'modal-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 10000;
`;
overlay.addEventListener('click', () => {
toggleModal();
});
document.body.appendChild(overlay);
}
} else {
modal.style.display = 'none';
const overlay = document.getElementById('modal-overlay');
if (overlay) {
overlay.remove();
}
}
}
// 解析请求头字符串
function parseHeaders(headersText) {
const headers = {};
if (!headersText || !headersText.trim()) {
return headers;
}
const lines = headersText.split('\n');
lines.forEach(line => {
line = line.trim();
if (!line) return;
const colonIndex = line.indexOf(':');
if (colonIndex > 0) {
const key = line.substring(0, colonIndex).trim();
const value = line.substring(colonIndex + 1).trim();
if (key && value) {
headers[key] = value;
}
}
});
return headers;
}
// 处理请求
function handleRequest() {
const urlInput = document.getElementById('url-input');
const headersInput = document.getElementById('headers-input');
const cookieInput = document.getElementById('cookie-input');
const url = urlInput.value.trim();
const sendBtn = document.getElementById('send-request-btn');
const resultContent = document.getElementById('result-content');
if (!url) {
alert('请输入URL');
return;
}
// 验证URL格式
try {
new URL(url);
} catch (e) {
alert('URL格式不正确,请输入完整的URL(包含协议,如 https://)');
return;
}
// 解析请求头和Cookie
const headers = parseHeaders(headersInput.value);
const cookie = cookieInput.value.trim();
// 显示加载状态(只在结果区域内显示)
sendBtn.disabled = true;
resultContent.innerHTML = '';
// 发送请求
const startTime = Date.now();
// 构建请求配置
const requestConfig = {
method: 'GET',
url: url,
timeout: 30000,
onload: function(response) {
const endTime = Date.now();
const duration = endTime - startTime;
sendBtn.disabled = false;
let analysis = analyzeResponse(response, duration, url);
displayResult(analysis);
},
onerror: function(error) {
const endTime = Date.now();
const duration = endTime - startTime;
sendBtn.disabled = false;
let analysis = analyzeError(error, duration, url);
displayResult(analysis);
},
ontimeout: function() {
sendBtn.disabled = false;
let analysis = analyzeTimeout(url);
displayResult(analysis);
}
};
// 如果有请求头或Cookie,添加到请求中
if (Object.keys(headers).length > 0 || cookie) {
requestConfig.headers = { ...headers };
if (cookie) {
requestConfig.headers['Cookie'] = cookie;
}
}
GM_xmlhttpRequest(requestConfig);
}
// 格式化字节大小
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
// 解析URL结构
function parseUrlStructure(url) {
try {
const urlObj = new URL(url);
return {
protocol: urlObj.protocol,
host: urlObj.hostname,
port: urlObj.port || (urlObj.protocol === 'https:' ? '443' : urlObj.protocol === 'http:' ? '80' : 'Default'),
path: urlObj.pathname,
query: urlObj.search ? urlObj.search.substring(1) : 'None',
hash: urlObj.hash ? urlObj.hash.substring(1) : 'None',
fullUrl: url
};
} catch (e) {
return null;
}
}
// 解析响应头
function parseHeaders(headersString) {
const headers = {};
if (!headersString) return headers;
const lines = headersString.split(/\r?\n/);
lines.forEach(line => {
const colonIndex = line.indexOf(':');
if (colonIndex > 0) {
const key = line.substring(0, colonIndex).trim().toLowerCase();
const value = line.substring(colonIndex + 1).trim();
if (key && value) {
if (headers[key]) {
// 如果已存在,转换为数组
if (Array.isArray(headers[key])) {
headers[key].push(value);
} else {
headers[key] = [headers[key], value];
}
} else {
headers[key] = value;
}
}
}
});
return headers;
}
// 分析响应
function analyzeResponse(response, duration, url) {
const status = response.status;
const statusText = response.statusText;
const headersString = response.responseHeaders || '';
const responseText = response.responseText || '';
const responseLength = responseText ? new Blob([responseText]).size : 0;
// 解析URL结构
const urlStructure = parseUrlStructure(url);
// 解析响应头
const headers = parseHeaders(headersString);
let analysis = {
url: url,
urlStructure: urlStructure,
status: status,
statusText: statusText,
duration: duration,
isError: status >= 400,
issues: [],
suggestions: [],
responseHeaders: headers,
responseHeadersRaw: headersString,
responseLength: responseLength,
responseLengthFormatted: formatBytes(responseLength),
contentType: headers['content-type'] || 'Unknown',
corsConfig: {},
details: {}
};
// 分析HTTP状态码
if (status >= 200 && status < 300) {
analysis.issues.push('请求成功');
analysis.suggestions.push('请求已成功完成,无需修复');
} else if (status >= 300 && status < 400) {
analysis.issues.push(`HTTP ${status} 重定向`);
analysis.suggestions.push('检查重定向目标URL是否正确');
analysis.suggestions.push('确认是否需要跟随重定向');
} else if (status === 400) {
analysis.issues.push('HTTP 400 Bad Request - 请求参数错误');
analysis.suggestions.push('检查请求参数格式是否正确');
analysis.suggestions.push('验证请求体是否符合API要求');
analysis.suggestions.push('检查Content-Type头是否正确');
} else if (status === 401) {
analysis.issues.push('HTTP 401 Unauthorized - 未授权');
analysis.suggestions.push('检查是否需要添加认证信息(Token、API Key等)');
analysis.suggestions.push('验证认证信息是否过期');
analysis.suggestions.push('确认用户是否有访问权限');
} else if (status === 403) {
analysis.issues.push('HTTP 403 Forbidden - 禁止访问');
analysis.suggestions.push('检查用户权限设置');
analysis.suggestions.push('验证IP地址是否被限制');
analysis.suggestions.push('确认资源访问策略(CORS、防火墙等)');
} else if (status === 404) {
analysis.issues.push('HTTP 404 Not Found - 资源不存在');
analysis.suggestions.push('检查URL路径是否正确');
analysis.suggestions.push('验证资源是否已被删除或移动');
analysis.suggestions.push('确认API端点是否存在');
} else if (status === 405) {
analysis.issues.push('HTTP 405 Method Not Allowed - 请求方法不允许');
analysis.suggestions.push('检查HTTP方法(GET、POST、PUT、DELETE等)是否正确');
analysis.suggestions.push('查看API文档确认支持的请求方法');
} else if (status === 408) {
analysis.issues.push('HTTP 408 Request Timeout - 请求超时');
analysis.suggestions.push('检查网络连接是否稳定');
analysis.suggestions.push('增加请求超时时间');
analysis.suggestions.push('优化请求数据大小');
} else if (status === 429) {
analysis.issues.push('HTTP 429 Too Many Requests - 请求过于频繁');
analysis.suggestions.push('实施请求频率限制(Rate Limiting)');
analysis.suggestions.push('检查响应头中的Retry-After字段');
analysis.suggestions.push('使用指数退避策略重试');
} else if (status === 500) {
analysis.issues.push('HTTP 500 Internal Server Error - 服务器内部错误');
analysis.suggestions.push('这是服务器端问题,联系服务器管理员');
analysis.suggestions.push('检查服务器日志获取详细错误信息');
analysis.suggestions.push('验证服务器资源是否充足(内存、CPU等)');
} else if (status === 502) {
analysis.issues.push('HTTP 502 Bad Gateway - 网关错误');
analysis.suggestions.push('检查上游服务器是否正常运行');
analysis.suggestions.push('验证网关配置是否正确');
analysis.suggestions.push('检查负载均衡器状态');
} else if (status === 503) {
analysis.issues.push('HTTP 503 Service Unavailable - 服务不可用');
analysis.suggestions.push('服务器可能正在维护,稍后重试');
analysis.suggestions.push('检查服务器负载是否过高');
analysis.suggestions.push('验证依赖服务是否可用');
} else if (status === 504) {
analysis.issues.push('HTTP 504 Gateway Timeout - 网关超时');
analysis.suggestions.push('检查上游服务器响应时间');
analysis.suggestions.push('增加网关超时配置');
analysis.suggestions.push('优化后端处理性能');
} else {
analysis.issues.push(`HTTP ${status} ${statusText}`);
analysis.suggestions.push('查看HTTP状态码文档了解具体含义');
analysis.suggestions.push('检查服务器返回的错误信息');
}
// 分析CORS配置
analysis.corsConfig = {
allowOrigin: headers['access-control-allow-origin'] || 'Not Set',
allowMethods: headers['access-control-allow-methods'] || 'Not Set',
allowHeaders: headers['access-control-allow-headers'] || 'Not Set',
allowCredentials: headers['access-control-allow-credentials'] || 'Not Set',
maxAge: headers['access-control-max-age'] || 'Not Set'
};
// CORS检查
if (!headers['access-control-allow-origin']) {
if (status >= 200 && status < 300) {
analysis.suggestions.push('注意:响应中未发现CORS头,跨域请求可能被阻止');
}
}
// Content-Type检查
const contentType = headers['content-type'] || '';
if (contentType) {
analysis.details.contentType = contentType;
// 检查内容类型是否匹配URL扩展名
if (urlStructure && urlStructure.path) {
const pathLower = urlStructure.path.toLowerCase();
if (pathLower.endsWith('.png') || pathLower.endsWith('.jpg') || pathLower.endsWith('.jpeg') || pathLower.endsWith('.gif') || pathLower.endsWith('.webp')) {
if (!contentType.includes('image/')) {
analysis.issues.push('⚠️ 内容类型不匹配:URL指向图片但响应不是图片类型,可能是错误页面');
}
} else if (pathLower.endsWith('.json')) {
if (!contentType.includes('application/json')) {
analysis.issues.push('⚠️ 内容类型不匹配:URL指向JSON但响应类型不正确');
}
}
}
}
// 分析响应时间
if (duration > 5000) {
analysis.issues.push(`响应时间较长:${duration}ms`);
analysis.suggestions.push('优化服务器性能');
analysis.suggestions.push('检查数据库查询效率');
analysis.suggestions.push('考虑使用CDN加速');
} else if (duration < 100) {
analysis.details.performance = '响应速度优秀';
} else if (duration < 1000) {
analysis.details.performance = '响应速度良好';
} else if (duration < 3000) {
analysis.details.performance = '响应速度一般';
} else {
analysis.details.performance = '响应速度较慢';
}
// 添加服务器信息
if (headers['server']) {
analysis.details.server = headers['server'];
}
// 添加日期信息
if (headers['date']) {
analysis.details.date = headers['date'];
}
return analysis;
}
// 分析错误
function analyzeError(error, duration, url) {
const urlStructure = parseUrlStructure(url);
let analysis = {
url: url,
urlStructure: urlStructure,
status: 'ERROR',
statusText: '网络错误',
duration: duration,
isError: true,
issues: [],
suggestions: [],
responseHeaders: {},
responseHeadersRaw: '',
responseLength: 0,
responseLengthFormatted: '0 Bytes',
contentType: 'Unknown',
corsConfig: {},
details: {}
};
// 根据错误类型分析
const errorMessage = error.error || '';
const errorString = String(errorMessage).toLowerCase();
if (errorString.includes('network') || errorString.includes('failed to fetch')) {
analysis.issues.push('网络连接失败');
analysis.suggestions.push('检查网络连接是否正常');
analysis.suggestions.push('验证URL是否可访问');
analysis.suggestions.push('检查防火墙或代理设置');
analysis.suggestions.push('尝试使用ping或curl测试连接');
} else if (errorString.includes('cors') || errorString.includes('cross-origin')) {
analysis.issues.push('CORS跨域请求被阻止');
analysis.suggestions.push('服务器需要设置Access-Control-Allow-Origin响应头');
analysis.suggestions.push('检查请求头是否包含不被允许的自定义头');
analysis.suggestions.push('对于预检请求,确保服务器正确处理OPTIONS请求');
analysis.suggestions.push('如果是开发环境,可以考虑使用代理服务器');
} else if (errorString.includes('ssl') || errorString.includes('certificate')) {
analysis.issues.push('SSL/TLS证书错误');
analysis.suggestions.push('检查SSL证书是否有效');
analysis.suggestions.push('验证证书是否过期');
analysis.suggestions.push('确认证书链是否完整');
analysis.suggestions.push('检查系统时间是否正确');
} else if (errorString.includes('dns')) {
analysis.issues.push('DNS解析失败');
analysis.suggestions.push('检查域名是否正确');
analysis.suggestions.push('验证DNS服务器是否可用');
analysis.suggestions.push('尝试使用其他DNS服务器(如8.8.8.8)');
analysis.suggestions.push('检查本地hosts文件');
} else if (errorString.includes('timeout')) {
analysis.issues.push('请求超时');
analysis.suggestions.push('增加请求超时时间');
analysis.suggestions.push('检查网络延迟');
analysis.suggestions.push('优化请求数据大小');
analysis.suggestions.push('检查服务器响应速度');
} else {
analysis.issues.push('未知网络错误');
analysis.suggestions.push('检查浏览器控制台获取详细错误信息');
analysis.suggestions.push('验证URL格式是否正确');
analysis.suggestions.push('尝试使用其他工具(如Postman)测试请求');
analysis.suggestions.push('检查浏览器扩展是否干扰请求');
}
return analysis;
}
// 分析超时
function analyzeTimeout(url) {
const urlStructure = parseUrlStructure(url);
return {
url: url,
urlStructure: urlStructure,
status: 'TIMEOUT',
statusText: '请求超时',
duration: 30000,
isError: true,
issues: ['请求在30秒内未收到响应'],
suggestions: [
'检查网络连接是否稳定',
'验证服务器是否正常运行',
'增加请求超时时间设置',
'检查服务器负载是否过高',
'优化请求数据,减少传输大小',
'考虑使用异步处理或轮询机制',
'检查防火墙或代理是否阻止请求'
],
responseHeaders: {},
responseHeadersRaw: '',
responseLength: 0,
responseLengthFormatted: '0 Bytes',
contentType: 'Unknown',
corsConfig: {},
details: {}
};
}
// 显示结果
function displayResult(analysis) {
const resultContent = document.getElementById('result-content');
let html = '';
// URL结构分析
if (analysis.urlStructure) {
html += ``;
html += `
📋 URL结构分析
`;
html += `
目标URL:${analysis.url || analysis.urlStructure.fullUrl}
`;
html += `
协议:${analysis.urlStructure.protocol}
`;
html += `
主机:${analysis.urlStructure.host}
`;
html += `
端口:${analysis.urlStructure.port}
`;
html += `
路径:${analysis.urlStructure.path}
`;
html += `
查询参数:${analysis.urlStructure.query}
`;
html += `
哈希:${analysis.urlStructure.hash}
`;
html += `
`;
}
// HTTP响应信息
html += ``;
html += `
📡 HTTP响应信息
`;
html += `
状态码:`;
if (analysis.isError) {
html += `${analysis.status} ${analysis.statusText || ''}`;
} else {
html += `${analysis.status} ${analysis.statusText || 'Success'}`;
}
html += `
`;
html += `
状态消息:${analysis.statusText || 'N/A'}
`;
html += `
响应时间:${analysis.duration}ms
`;
html += `
响应数据长度:${analysis.responseLengthFormatted} (${analysis.responseLength} bytes)
`;
html += `
内容类型:${analysis.contentType}`;
if (analysis.contentType && analysis.contentType !== 'Unknown' && analysis.contentType.includes('text/html') && analysis.urlStructure && (analysis.urlStructure.path.endsWith('.png') || analysis.urlStructure.path.endsWith('.jpg') || analysis.urlStructure.path.endsWith('.jpeg'))) {
html += ` ⚠️ 响应不是图片类型,可能是错误页面`;
}
html += `
`;
if (analysis.details.server) {
html += `
服务器:${analysis.details.server}
`;
}
if (analysis.details.date) {
html += `
响应日期:${analysis.details.date}
`;
}
html += `
`;
// 响应头列表
if (analysis.responseHeadersRaw) {
html += ``;
html += `
📋 响应头列表
`;
html += `
`;
const headerLines = analysis.responseHeadersRaw.split(/\r?\n/).filter(line => line.trim());
headerLines.forEach(line => {
html += `
${escapeHtml(line)}
`;
});
html += `
`;
html += `
`;
}
// CORS配置
if (analysis.corsConfig && Object.keys(analysis.corsConfig).length > 0) {
html += ``;
html += `
🔒 CORS配置
`;
html += `
Access-Control-Allow-Origin:${analysis.corsConfig.allowOrigin}
`;
html += `
Access-Control-Allow-Methods:${analysis.corsConfig.allowMethods}
`;
html += `
Access-Control-Allow-Headers:${analysis.corsConfig.allowHeaders}
`;
html += `
Access-Control-Allow-Credentials:${analysis.corsConfig.allowCredentials}
`;
html += `
Access-Control-Max-Age:${analysis.corsConfig.maxAge}
`;
html += `
`;
}
// 问题分析
if (analysis.issues && analysis.issues.length > 0) {
html += ``;
html += `
❌ 问题分析
`;
html += `
`;
analysis.issues.forEach(issue => {
html += `- ${issue}
`;
});
html += `
`;
html += `
`;
}
// 排查建议
if (analysis.suggestions && analysis.suggestions.length > 0) {
html += ``;
html += `
💡 排查建议
`;
html += `
`;
analysis.suggestions.forEach((suggestion, index) => {
html += `- ${suggestion}
`;
});
html += `
`;
html += `
`;
}
// 其他详细信息
if (analysis.details && Object.keys(analysis.details).length > 0) {
const detailKeys = Object.keys(analysis.details).filter(key =>
key !== 'contentType' && key !== 'server' && key !== 'date' && key !== 'performance'
);
if (detailKeys.length > 0) {
html += ``;
html += `
ℹ️ 其他信息
`;
html += `
`;
detailKeys.forEach(key => {
html += `
${key}: ${analysis.details[key]}
`;
});
if (analysis.details.performance) {
html += `
性能评估:${analysis.details.performance}
`;
}
html += `
`;
html += `
`;
}
}
resultContent.innerHTML = html;
}
// HTML转义函数
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 检查是否应该运行脚本
function shouldRunScript() {
// 只在主窗口中运行,不在 iframe 中运行
if (window.self !== window.top) {
return false;
}
// 检查是否已经存在按钮容器
if (document.getElementById('network-analysis-container')) {
return false;
}
// 检查是否已经初始化过(防止重复执行)
if (window.networkAnalysisToolInitialized) {
return false;
}
return true;
}
// 初始化
function init() {
// 检查是否应该运行
if (!shouldRunScript()) {
return;
}
// 标记为已初始化
window.networkAnalysisToolInitialized = true;
createFloatingButton();
createModal();
}
// 等待DOM加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();