Files
jisuxiang1/src/app/tools/http_tester/utils.ts
T
2025-04-10 18:06:45 +08:00

349 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { HttpMethod, HttpResponse, RequestHeader, NetworkType, ResponseData } from './types';
import { apiClient } from '@/lib/api-client';
// 代理响应接口
interface ProxyResponse {
status: number;
statusText: string;
headers: Record<string, string>;
data: ResponseData;
time?: number;
size?: number;
}
/**
* 发送HTTP请求
* @param url 请求URL
* @param method 请求方法
* @param headers 请求头
* @param body 请求体
* @param bodyFormat 请求体格式
* @param formFields 表单字段
* @param networkType 网络类型(公网/本地)
*/
export const sendHttpRequest = async (
url: string,
method: HttpMethod,
headers: RequestHeader[],
body: string,
bodyFormat: 'json' | 'text' | 'form',
formFields: RequestHeader[],
networkType: NetworkType = 'public'
): Promise<{ response: HttpResponse | null; error: string | null }> => {
try {
if (!url.trim()) {
return { response: null, error: 'URL不能为空' };
}
// 准备请求头
const headerObj: Record<string, string> = {};
headers.forEach(h => {
if (h.key.trim() && h.value.trim()) {
headerObj[h.key] = h.value;
}
});
// 准备请求体
let requestBody: string | FormData | undefined;
if (['POST', 'PUT', 'PATCH'].includes(method)) {
if (bodyFormat === 'json') {
try {
// 尝试验证JSON格式
if (body.trim()) {
JSON.parse(body);
}
requestBody = body;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// 忽略具体错误,只返回格式无效的消息
return { response: null, error: '请求体不是有效的JSON格式' };
}
} else if (bodyFormat === 'form') {
try {
// 使用表单字段构建请求体
const formData = new URLSearchParams();
formFields.forEach(field => {
if (field.key.trim() && field.value.trim()) {
formData.append(field.key, field.value);
}
});
if ([...formData.keys()].length === 0) {
return { response: null, error: '表单至少需要一个有效的字段' };
}
requestBody = formData.toString();
// 设置适当的Content-Type
headerObj['Content-Type'] = 'application/x-www-form-urlencoded';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// 忽略具体错误,只返回处理失败的消息
return { response: null, error: '表单数据处理失败,请检查输入的字段值' };
}
} else {
requestBody = body;
}
}
const startTime = performance.now();
// 根据网络类型决定发送请求的方式
let responseData: ProxyResponse;
let clientTime: number;
// 如果是本地/局域网请求,直接发送请求(不通过代理)
if (networkType === 'local') {
try {
// 直接使用fetch API发送请求到目标地址
const response = await fetch(url, {
method,
headers: headerObj,
body: ['POST', 'PUT', 'PATCH'].includes(method) ? requestBody : undefined,
});
const endTime = performance.now();
clientTime = Math.round(endTime - startTime);
// 获取响应头
const headers: Record<string, string> = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
// 尝试解析响应体
let data: ResponseData;
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
data = await response.json();
} else {
data = await response.text();
}
// 计算响应大小
const bodyText = typeof data === 'string' ? data : JSON.stringify(data);
const size = new Blob([bodyText]).size;
responseData = {
status: response.status,
statusText: response.statusText,
headers,
data,
time: clientTime,
size,
};
} catch (error) {
return {
response: null,
error: `本地请求失败: ${(error as Error).message}。请确保目标服务器已配置CORS,允许跨域请求。`
};
}
} else {
// 通过后端代理发送请求,避免跨域问题
const proxyResponse = await apiClient.post<ProxyResponse>('/api/proxy', {
url,
method,
headers: headerObj,
body: requestBody,
});
const endTime = performance.now();
clientTime = Math.round(endTime - startTime);
responseData = proxyResponse;
}
return {
response: {
status: responseData.status,
statusText: responseData.statusText,
headers: responseData.headers,
data: responseData.data,
// 使用服务器计算的响应时间或客户端时间
time: responseData.time || clientTime,
size: responseData.size || 0,
},
error: null
};
} catch (error) {
console.error('请求错误', error);
return {
response: null,
error: (error as Error).message || '请求失败'
};
}
};
/**
* 生成Markdown格式的接口文档
* @param url 请求URL
* @param method 请求方法
* @param headers 请求头
* @param requestBody 请求体
* @param bodyFormat 请求体格式
* @param formFields 表单字段
* @param response 响应数据
* @param networkType 网络类型(公网/本地)
* @returns Markdown格式的接口文档
*/
export const generateMarkdownDoc = (
url: string,
method: HttpMethod,
headers: RequestHeader[],
requestBody: string,
bodyFormat: 'json' | 'text' | 'form',
formFields: RequestHeader[],
response: HttpResponse | null,
networkType: NetworkType = 'public'
): string => {
// 准备请求体展示
let requestBodyContent = '';
if (['POST', 'PUT', 'PATCH'].includes(method)) {
if (bodyFormat === 'json' && requestBody.trim()) {
try {
// 美化JSON
const parsedJson = JSON.parse(requestBody);
requestBodyContent = JSON.stringify(parsedJson, null, 2);
} catch {
requestBodyContent = requestBody;
}
} else if (bodyFormat === 'form') {
// 构建表单内容,但不需要在这里生成请求体内容
// 因为我们将在下面的请求参数部分直接使用表格展示
const formData = new URLSearchParams();
formFields.forEach(field => {
if (field.key.trim() && field.value.trim()) {
formData.append(field.key, field.value);
}
});
// 在表单模式下,请求体内容为空,因为我们会直接使用表格展示
requestBodyContent = '';
} else {
requestBodyContent = requestBody;
}
}
// 过滤有效的请求头
const validHeaders = headers.filter(h => h.key.trim() && h.value.trim());
// 构建Markdown文档 - 使用ShowDoc风格
let markdown = ``;
// 简要描述部分
markdown += `**简要描述:** \n\n`;
markdown += `- 自动生成的API接口文档\n\n`;
// 请求模式部分(新增)
markdown += `**请求模式:** \n\n`;
markdown += `- ${networkType === 'local' ? '本地/局域网' : '公网代理'} \n\n`;
// 请求URL部分
markdown += `**请求URL** \n\n`;
markdown += `- \`${url}\`\n\n`;
// 请求方式部分
markdown += `**请求方式:**\n\n`;
markdown += `- ${method} \n\n`;
// 请求头部分(如果有)
if (validHeaders.length > 0) {
markdown += `**请求头:** \n\n`;
markdown += `| 参数名 | 必选 | 参数值 | 说明 |\n`;
markdown += `| ------ | ---- | ------ | ---- |\n`;
validHeaders.forEach(header => {
const isContent = header.key.toLowerCase() === 'content-type';
markdown += `| ${header.key} | ${isContent ? '是' : '否'} | ${header.value} | ${isContent ? '请求数据类型' : '-'} |\n`;
});
markdown += `\n`;
}
// 请求参数部分(针对POST、PUT等方法)
if (['POST', 'PUT', 'PATCH'].includes(method) && (requestBodyContent || bodyFormat === 'form')) {
markdown += `**请求参数:** \n\n`;
// 针对表单格式单独处理
if (bodyFormat === 'form') {
// 直接生成表单参数表格
markdown += `| 参数名 | 必选 | 类型 | 说明 |\n`;
markdown += `| ------ | ---- | ---- | ---- |\n`;
const validFormFields = formFields.filter(field => field.key.trim() && field.value.trim());
if (validFormFields.length > 0) {
validFormFields.forEach(field => {
markdown += `| ${field.key} | 是 | string | - |\n`;
});
markdown += `\n`;
// 添加URL编码格式的说明
const formData = new URLSearchParams();
validFormFields.forEach(field => {
formData.append(field.key, field.value);
});
markdown += `**表单URL编码格式:** \n\n`;
markdown += `\`${formData.toString()}\`\n\n`;
} else {
markdown += `| - | - | - | 无参数 |\n\n`;
}
}
// JSON或文本格式的处理
else {
// 尝试解析参数并构建表格
try {
if (bodyFormat === 'json') {
const parsedBody = JSON.parse(requestBodyContent);
if (typeof parsedBody === 'object' && parsedBody !== null) {
markdown += `| 参数名 | 必选 | 类型 | 说明 |\n`;
markdown += `| ------ | ---- | ---- | ---- |\n`;
Object.entries(parsedBody).forEach(([key, value]) => {
const type = Array.isArray(value) ? 'array' : typeof value;
markdown += `| ${key} | 是 | ${type} | - |\n`;
});
markdown += `\n`;
} else {
markdown += `\`\`\`json\n${requestBodyContent}\n\`\`\`\n\n`;
}
} else if (requestBodyContent.trim()) {
markdown += `\`\`\`\n${requestBodyContent}\n\`\`\`\n\n`;
}
} catch {
if (requestBodyContent.trim()) {
markdown += `\`\`\`\n${requestBodyContent}\n\`\`\`\n\n`;
}
}
}
}
// 响应结果部分
if (response) {
// 返回示例
markdown += `**返回示例**\n\n`;
if (typeof response.data === 'object') {
// 直接使用JSON.stringify的缩进参数格式化JSON
markdown += `\`\`\`json\n${JSON.stringify(response.data, null, 2)}\n\`\`\`\n\n`;
} else if (typeof response.data === 'string') {
// 尝试检测是否为JSON字符串
try {
const parsedJson = JSON.parse(response.data);
markdown += `\`\`\`json\n${JSON.stringify(parsedJson, null, 2)}\n\`\`\`\n\n`;
} catch {
// 不是JSON字符串,直接显示
markdown += `\`\`\`\n${response.data}\n\`\`\`\n\n`;
}
} else {
markdown += `\`\`\`\n${response.data}\n\`\`\`\n\n`;
}
}
// 备注
markdown += `**备注** \n\n`;
markdown += `- 此文档由HTTP测试工具自动生成\n`;
markdown += `- 响应时间: ${response?.time || '-'}ms\n`;
markdown += `- 响应大小: ${response?.size || '-'} bytes\n`;
return markdown;
};