无锡市转运信息网

ajax请求携带自定义请求头header(跨域和同域)案例实战教程

2026-03-30 13:45:02 浏览次数:0
详细信息

AJAX 请求携带自定义请求头实战教程

目录

基础知识 同域请求案例 跨域请求案例 完整实战示例 常见问题与解决方案

基础知识

自定义请求头的作用

跨域与同域的区别

同域请求案例

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预检请求

当发送跨域请求且包含以下情况时,浏览器会发送预检请求:

// 客户端代码
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. 请求头被浏览器过滤

问题: 某些请求头可能被浏览器过滤

解决方案:

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头传递 错误处理非常重要,特别是对于认证和授权失败 拦截器模式可以提供更好的代码组织和复用性

通过本文的实战示例,你应该能够掌握在各种场景下使用自定义请求头的方法,并能够处理常见的跨域和认证问题。

相关推荐