AJAX 请求携带自定义请求头实战教程
目录
基础知识
同域请求案例
跨域请求案例
完整实战示例
常见问题与解决方案
基础知识
自定义请求头的作用
- 身份验证 (Authentication)
- 会话管理 (Session Management)
- 传递额外信息 (Additional Information)
- API版本控制 (API Versioning)
跨域与同域的区别
- 同域:协议、域名、端口完全相同
- 跨域:协议、域名、端口任意一个不同
同域请求案例
1. 基础实现
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>同域请求示例</title>
</head>
<body>
<div id="result">点击按钮发送请求</div>
<button onclick="sendRequest()">发送请求</button>
<script>
function sendRequest() {
// 创建XMLHttpRequest对象
const xhr = new XMLHttpRequest();
// 配置请求
xhr.open('POST', '/api/data', true);
// 设置自定义请求头
xhr.setRequestHeader('X-Custom-Header', 'my-custom-value');
xhr.setRequestHeader('X-API-Version', '2.0');
xhr.setRequestHeader('Authorization', 'Bearer token123');
// 设置内容类型
xhr.setRequestHeader('Content-Type', 'application/json');
// 处理响应
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
document.getElementById('result').textContent =
'响应: ' + xhr.responseText;
} else {
document.getElementById('result').textContent =
'错误: ' + xhr.status;
}
}
};
// 发送请求
xhr.send(JSON.stringify({ name: '张三', age: 25 }));
}
</script>
</body>
</html>
2. 使用Fetch API的同域请求
async function fetchWithCustomHeaders() {
try {
const response = await fetch('/api/data', {
method: 'POST',
headers: {
'X-Custom-Header': 'custom-value',
'X-API-Version': '2.0',
'Authorization': 'Bearer your-token-here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '李四',
email: 'lisi@example.com'
})
});
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
console.log('响应数据:', data);
return data;
} catch (error) {
console.error('请求失败:', error);
}
}
3. 使用Axios的同域请求
// 安装: npm install axios
import axios from 'axios';
// 创建带默认请求头的实例
const apiClient = axios.create({
baseURL: '/api',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-API-Key': 'your-api-key'
}
});
// 发送请求
async function sendRequest() {
try {
const response = await apiClient.post('/data', {
user: '王五',
action: 'create'
}, {
headers: {
'X-Custom-Header': 'additional-header'
}
});
console.log('响应:', response.data);
} catch (error) {
console.error('请求失败:', error);
}
}
跨域请求案例
1. CORS预检请求
当发送跨域请求且包含以下情况时,浏览器会发送预检请求:
- 使用非简单方法 (GET, POST, HEAD 以外的)
- 自定义请求头
- Content-Type不是简单类型
// 客户端代码
async function sendCrossOriginRequest() {
const response = await fetch('https://api.example.com/data', {
method: 'PUT',
headers: {
'X-Custom-Header': 'custom-value',
'X-Auth-Token': 'token123',
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: 1, status: 'active' }),
mode: 'cors' // 重要:明确指定CORS模式
});
return await response.json();
}
2. 服务端CORS配置示例
Node.js/Express 服务器配置:
const express = require('express');
const cors = require('cors');
const app = express();
// 基础CORS配置(允许所有来源)
app.use(cors());
// 或更精确的配置
app.use(cors({
origin: 'http://localhost:3000', // 允许的源
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: [
'Content-Type',
'Authorization',
'X-Custom-Header',
'X-API-Version'
],
exposedHeaders: ['X-Custom-Response-Header'],
credentials: true, // 允许发送cookies
maxAge: 86400 // 预检请求缓存时间(秒)
}));
// 处理OPTIONS预检请求
app.options('/api/data', cors());
// API路由
app.post('/api/data', (req, res) => {
// 读取自定义请求头
const customHeader = req.headers['x-custom-header'];
const authToken = req.headers['authorization'];
console.log('接收到的自定义头:', {
customHeader,
authToken
});
// 设置自定义响应头
res.setHeader('X-Custom-Response-Header', 'response-value');
res.json({
success: true,
message: '请求成功',
headersReceived: {
customHeader,
authToken
}
});
});
app.listen(3001, () => {
console.log('服务器运行在 http://localhost:3001');
});
3. 跨域请求的Fetch完整示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>跨域请求示例</title>
</head>
<body>
<h1>跨域请求测试</h1>
<button onclick="testCORS()">发送跨域请求</button>
<div id="output"></div>
<script>
const API_URL = 'http://localhost:3001/api/data';
async function testCORS() {
const output = document.getElementById('output');
try {
// 发送带有自定义头的跨域请求
const response = await fetch(API_URL, {
method: 'POST',
mode: 'cors', // 重要:必须指定cors模式
credentials: 'include', // 是否发送cookies
headers: {
'Content-Type': 'application/json',
'X-API-Key': '123456',
'X-Custom-Header': '跨域测试',
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
},
body: JSON.stringify({
userId: 101,
action: 'getProfile'
})
});
// 读取响应头
const customResponseHeader = response.headers.get('X-Custom-Response-Header');
console.log('响应头:', customResponseHeader);
if (response.ok) {
const data = await response.json();
output.innerHTML = `
<h3>请求成功</h3>
<p>状态码: ${response.status}</p>
<p>响应数据: ${JSON.stringify(data, null, 2)}</p>
`;
} else {
output.innerHTML = `
<h3>请求失败</h3>
<p>状态码: ${response.status}</p>
<p>状态文本: ${response.statusText}</p>
`;
}
} catch (error) {
output.innerHTML = `
<h3>请求错误</h3>
<p>${error.message}</p>
`;
console.error('请求错误:', error);
}
}
</script>
</body>
</html>
完整实战示例
1. 带JWT认证的API请求封装
// api-client.js - 通用API客户端
class APIClient {
constructor(baseURL, options = {}) {
this.baseURL = baseURL;
this.defaultHeaders = {
'Content-Type': 'application/json',
'Accept': 'application/json',
...options.headers
};
// 从localStorage获取token
this.token = localStorage.getItem('auth_token');
// 请求拦截器
this.requestInterceptors = [];
this.responseInterceptors = [];
}
// 设置认证token
setAuthToken(token) {
this.token = token;
if (token) {
localStorage.setItem('auth_token', token);
} else {
localStorage.removeItem('auth_token');
}
}
// 添加请求拦截器
addRequestInterceptor(interceptor) {
this.requestInterceptors.push(interceptor);
}
// 添加响应拦截器
addResponseInterceptor(interceptor) {
this.responseInterceptors.push(interceptor);
}
// 通用请求方法
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
// 构建headers
const headers = {
...this.defaultHeaders,
...options.headers
};
// 添加认证头
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
// 应用请求拦截器
let requestConfig = {
method: options.method || 'GET',
headers,
body: options.body,
mode: options.mode || 'cors',
credentials: options.credentials || 'same-origin'
};
this.requestInterceptors.forEach(interceptor => {
requestConfig = interceptor(requestConfig);
});
try {
const response = await fetch(url, requestConfig);
// 应用响应拦截器
let processedResponse = response;
this.responseInterceptors.forEach(interceptor => {
processedResponse = interceptor(processedResponse);
});
if (!response.ok) {
// 处理HTTP错误
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// 根据Content-Type解析响应
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else {
return await response.text();
}
} catch (error) {
console.error('API请求失败:', error);
throw error;
}
}
// 快捷方法
get(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'GET' });
}
post(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'POST',
body: JSON.stringify(data)
});
}
put(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
}
// 使用示例
const api = new APIClient('https://api.example.com', {
headers: {
'X-API-Version': '1.0',
'X-Client-ID': 'web-app-v1'
}
});
// 添加请求日志拦截器
api.addRequestInterceptor(config => {
console.log('发送请求:', config.method, config.url);
return config;
});
// 添加认证失败处理拦截器
api.addResponseInterceptor(response => {
if (response.status === 401) {
// token过期,跳转到登录页
window.location.href = '/login';
}
return response;
});
// 使用API
async function getUserProfile(userId) {
try {
const profile = await api.get(`/users/${userId}`, {
headers: {
'X-Request-ID': generateRequestId(),
'X-Timezone': Intl.DateTimeFormat().resolvedOptions().timeZone
}
});
return profile;
} catch (error) {
console.error('获取用户信息失败:', error);
throw error;
}
}
function generateRequestId() {
return 'req_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
2. 完整前后端示例
前端应用 (HTML + JavaScript):
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>实战API请求示例</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; padding: 20px; }
.container { max-width: 800px; margin: 0 auto; }
.panel { background: #f5f5f5; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
.btn { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
.btn:hover { background: #0056b3; }
.output { background: white; padding: 15px; border-radius: 4px; margin-top: 10px; font-family: monospace; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
</style>
</head>
<body>
<div class="container">
<h1>API请求实战演示</h1>
<!-- 登录面板 -->
<div class="panel">
<h2>1. 用户认证</h2>
<div class="form-group">
<label>用户名:</label>
<input type="text" id="username" placeholder="输入用户名">
</div>
<div class="form-group">
<label>密码:</label>
<input type="password" id="password" placeholder="输入密码">
</div>
<button class="btn" onclick="login()">登录</button>
<div class="output" id="loginOutput"></div>
</div>
<!-- API请求面板 -->
<div class="panel">
<h2>2. 发送API请求</h2>
<div class="form-group">
<label>自定义请求头:</label>
<input type="text" id="customHeader" placeholder="X-Custom-Data" value="test-data">
</div>
<button class="btn" onclick="getPublicData()">获取公开数据</button>
<button class="btn" onclick="getProtectedData()">获取保护数据</button>
<div class="output" id="apiOutput"></div>
</div>
<!-- 请求日志 -->
<div class="panel">
<h2>3. 请求日志</h2>
<div class="output" id="logOutput"></div>
</div>
</div>
<script src="api-client.js"></script>
<script>
// 初始化API客户端
const api = new APIClient('http://localhost:3001', {
headers: {
'X-Client-Type': 'WebBrowser',
'X-Client-Version': '1.0.0'
}
});
let logOutput = document.getElementById('logOutput');
// 添加请求日志拦截器
api.addRequestInterceptor(config => {
const logEntry = `[${new Date().toLocaleTimeString()}] ${config.method} ${config.url}`;
logOutput.innerHTML = logEntry + '<br>' + logOutput.innerHTML;
console.log('请求:', config);
return config;
});
// 登录函数
async function login() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const output = document.getElementById('loginOutput');
if (!username || !password) {
output.innerHTML = '请填写用户名和密码';
return;
}
try {
const result = await api.post('/auth/login', {
username,
password
});
if (result.token) {
api.setAuthToken(result.token);
output.innerHTML = '登录成功!Token已保存';
}
} catch (error) {
output.innerHTML = `登录失败: ${error.message}`;
}
}
// 获取公开数据
async function getPublicData() {
const output = document.getElementById('apiOutput');
const customHeader = document.getElementById('customHeader').value;
try {
const data = await api.get('/public/data', {
headers: {
'X-Custom-Data': customHeader,
'X-Request-Source': 'DemoPage'
}
});
output.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
} catch (error) {
output.innerHTML = `请求失败: ${error.message}`;
}
}
// 获取保护数据
async function getProtectedData() {
const output = document.getElementById('apiOutput');
try {
const data = await api.get('/protected/data', {
headers: {
'X-Time-Zone': Intl.DateTimeFormat().resolvedOptions().timeZone
}
});
output.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
} catch (error) {
if (error.message.includes('401')) {
output.innerHTML = '未授权!请先登录';
} else {
output.innerHTML = `请求失败: ${error.message}`;
}
}
}
</script>
</body>
</html>
后端服务器 (Node.js + Express):
// server.js
const express = require('express');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const app = express();
const SECRET_KEY = 'your-secret-key-here';
// CORS配置
app.use(cors({
origin: 'http://localhost:8080', // 前端服务器地址
credentials: true,
allowedHeaders: [
'Content-Type',
'Authorization',
'X-API-Key',
'X-Custom-Data',
'X-Request-Source',
'X-Client-Type',
'X-Client-Version',
'X-Time-Zone'
]
}));
app.use(express.json());
// 登录端点
app.post('/auth/login', (req, res) => {
const { username, password } = req.body;
// 简单验证
if (username === 'admin' && password === 'password') {
// 生成JWT token
const token = jwt.sign(
{ userId: 1, username, role: 'admin' },
SECRET_KEY,
{ expiresIn: '1h' }
);
res.json({
success: true,
message: '登录成功',
token,
user: { id: 1, username, role: 'admin' }
});
} else {
res.status(401).json({
success: false,
message: '用户名或密码错误'
});
}
});
// 公开API端点
app.get('/public/data', (req, res) => {
// 读取自定义请求头
const customData = req.headers['x-custom-data'];
const requestSource = req.headers['x-request-source'];
res.json({
success: true,
message: '公开数据',
serverTime: new Date().toISOString(),
receivedHeaders: {
customData,
requestSource,
clientType: req.headers['x-client-type'],
clientVersion: req.headers['x-client-version']
},
data: [
{ id: 1, name: '公开项目1' },
{ id: 2, name: '公开项目2' }
]
});
});
// JWT验证中间件
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({
success: false,
message: '未提供认证token'
});
}
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) {
return res.status(403).json({
success: false,
message: 'token无效或已过期'
});
}
req.user = user;
next();
});
}
// 保护API端点
app.get('/protected/data', authenticateToken, (req, res) => {
// 访问用户信息
const user = req.user;
const timezone = req.headers['x-time-zone'];
res.json({
success: true,
message: '保护数据',
user,
timezone,
sensitiveData: [
{ id: 1, name: '机密文档1', access: '授权用户' },
{ id: 2, name: '机密文档2', access: '授权用户' }
]
});
});
// 处理OPTIONS预检请求
app.options('*', cors());
const PORT = 3001;
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
console.log(`测试页面: http://localhost:8080/index.html`);
});
常见问题与解决方案
1. 跨域请求被阻止
问题: 跨域请求失败,控制台显示CORS错误
解决方案:
// 前端确保设置正确的请求模式
fetch('https://api.example.com/data', {
method: 'POST',
mode: 'cors', // 明确指定cors模式
headers: {
'Content-Type': 'application/json'
}
});
// 后端正确配置CORS
app.use(cors({
origin: 'http://your-frontend-domain.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header']
}));
2. 自定义请求头不被允许
问题: 预检请求失败,自定义头被阻止
解决方案:
// 后端需要明确允许自定义头
app.use(cors({
allowedHeaders: [
'Content-Type',
'Authorization',
'X-Custom-Header', // 添加你的自定义头
'X-API-Version'
]
}));
// 或者针对特定路由
app.options('/api/data', cors({
allowedHeaders: ['X-Custom-Header']
}));
3. 认证Token过期处理
解决方案:
// 使用响应拦截器处理401错误
api.addResponseInterceptor(async (response) => {
if (response.status === 401) {
// 尝试刷新token
const newToken = await refreshToken();
if (newToken) {
// 保存新token
api.setAuthToken(newToken);
// 重新发送原始请求
return api.request(originalRequest);
} else {
// 跳转到登录页
window.location.href = '/login';
}
}
return response;
});
4. 请求头被浏览器过滤
问题: 某些请求头可能被浏览器过滤
解决方案:
- 避免使用保留的请求头(如:
Proxy-, Sec-, Accept-Charset等)
- 使用标准化的自定义头格式(如:
X-前缀)
- 在服务器端验证并处理这些头
5. 性能优化
// 使用请求缓存
const requestCache = new Map();
async function cachedRequest(url, options) {
const cacheKey = JSON.stringify({ url, options });
if (requestCache.has(cacheKey)) {
return requestCache.get(cacheKey);
}
const response = await fetch(url, options);
const data = await response.json();
// 设置缓存(5分钟过期)
requestCache.set(cacheKey, data);
setTimeout(() => requestCache.delete(cacheKey), 5 * 60 * 1000);
return data;
}
总结
同域请求相对简单,可以直接设置自定义请求头
跨域请求需要服务器正确配置CORS,允许自定义请求头
预检请求会自动触发,不需要手动处理
认证信息通常通过Authorization头传递
错误处理非常重要,特别是对于认证和授权失败
拦截器模式可以提供更好的代码组织和复用性
通过本文的实战示例,你应该能够掌握在各种场景下使用自定义请求头的方法,并能够处理常见的跨域和认证问题。