POST 为什么会发送两次请求?

什么是 HTTP 请求中的 Preflight 请求? 🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『』发一些比较有趣的文章,有兴趣的也可以关注下。』,获取更多有趣内容。 阅读详情

POST 为什么会发送两次请求?

一、预检请求(Preflight Request)详解

1.1 什么是预检请求?

Server (服务器)Client (浏览器)Server (服务器)Client (浏览器)简单请求非简单请求(需要预检)POST /api/dataResponseOPTIONS /api/data (预检请求)204 No Content (预检响应)POST /api/data (实际请求)Response

1.2 触发预检请求的条件

// 以下情况会触发预检请求(CORS 非简单请求)

// 1. 使用了非简单方法
fetch('/api/data', {
    method: 'PUT',  // 或 DELETE、PATCH 等
});

// 2. 自定义请求头
fetch('/api/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',  // 非简单值
        'X-Custom-Header': 'value'          // 自定义头
    }
});

// 3. Content-Type 是非简单值
// 简单值只有三种:
// - application/x-www-form-urlencoded
// - multipart/form-data
// - text/plain

// 4. 携带凭证(credentials)
fetch('/api/data', {
    method: 'POST',
    credentials: 'include'  // 发送 cookies
});

二、HTTP 请求生命周期分析

2.1 完整请求流程

// 客户端发起请求示例
async function makeComplexPostRequest() {
    const response = await fetch('https://api.example.com/data', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer token123',
            'X-Custom-Header': 'custom-value'
        },
        body: JSON.stringify({ key: 'value' }),
        credentials: 'include',
        mode: 'cors'
    });
    
    return response.json();
}

// 实际发生的请求:
// 第一次:OPTIONS 请求(预检)
OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: https://client.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,authorization,x-custom-header

// 服务器响应
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://client.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400

// 第二次:实际的 POST 请求
POST /data HTTP/1.1
Host: api.example.com
Origin: https://client.example.com
Content-Type: application/json
Authorization: Bearer token123
X-Custom-Header: custom-value

{"key":"value"}

// 服务器响应
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://client.example.com
Access-Control-Expose-Headers: X-Custom-Response
X-Custom-Response: custom-data
Content-Type: application/json

{"status":"success"}

2.2 服务器端处理示例

// Node.js/Express 服务器端配置
const express = require('express');
const app = express();

// 1. 全局 CORS 中间件
app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`, req.headers);
    
    // 处理预检请求
    if (req.method === 'OPTIONS') {
        console.log('处理预检请求');
        
        res.header('Access-Control-Allow-Origin', 'https://client.example.com');
        res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
        res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Custom-Header');
        res.header('Access-Control-Allow-Credentials', 'true');
        res.header('Access-Control-Max-Age', '86400'); // 24小时缓存
        
        return res.status(204).send(); // 204 No Content
    }
    
    next();
});

// 2. 处理实际请求
app.post('/api/data', (req, res) => {
    console.log('处理实际 POST 请求');
    
    res.header('Access-Control-Allow-Origin', 'https://client.example.com');
    res.header('Access-Control-Allow-Credentials', 'true');
    
    res.json({ 
        status: 'success',
        message: 'POST 请求处理成功'
    });
});

// 3. 或者使用 cors 中间件
const cors = require('cors');
app.use(cors({
    origin: 'https://client.example.com',
    credentials: true,
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header'],
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    maxAge: 86400
}));

三、不同场景下的请求次数分析

3.1 简单请求 vs 复杂请求

// 场景1:简单请求(只有1次请求)
// 满足所有条件:
// 1. 方法为 GET、HEAD、POST
// 2. Content-Type 为简单值
// 3. 没有自定义头
// 4. 不携带凭证(默认)

fetch('/api/simple', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: 'key=value&name=test'
});
// 实际:1次 POST 请求

// 场景2:复杂请求(2次请求)
fetch('/api/complex', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ key: 'value' })
});
// 实际:1次 OPTIONS + 1次 POST

// 场景3:使用非简单方法(2次请求)
fetch('/api/data', {
    method: 'PUT',
    headers: {
        'Content-Type': 'application/json'
    }
});
// 实际:1次 OPTIONS + 1次 PUT

// 场景4:带自定义头(2次请求)
fetch('/api/data', {
    method: 'POST',
    headers: {
        'X-Api-Key': '123456'
    }
});
// 实际:1次 OPTIONS + 1次 POST

// 场景5:跨域且带凭证(2次请求)
fetch('https://api.other-domain.com/data', {
    method: 'POST',
    credentials: 'include'
});
// 实际:1次 OPTIONS + 1次 POST

3.2 预检请求的缓存机制

// 服务器可以通过 Access-Control-Max-Age 控制预检请求缓存
app.use((req, res, next) => {
    if (req.method === 'OPTIONS') {
        // 设置预检请求缓存时间(单位:秒)
        res.header('Access-Control-Max-Age', '86400'); // 24小时
        
        // 浏览器在24小时内不会再次发送预检请求
        // 适用于同一URL的后续请求
    }
    next();
});

// 示例:缓存的效果
async function makeMultipleRequests() {
    // 第一次调用:发送预检请求
    await fetch('/api/data', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
    });
    
    // 24小时内的后续调用:不发送预检请求
    await fetch('/api/data', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
    });
    
    // 改变方法:会重新发送预检请求
    await fetch('/api/data', {
        method: 'PUT',  // 方法改变,重新预检
        headers: { 'Content-Type': 'application/json' }
    });
}

四、现代浏览器的具体表现

4.1 Chrome DevTools 网络面板分析

网络面板显示示例:

请求1:OPTIONS /api/data
方法: OPTIONS
状态: 204 No Content
耗时: 15ms
请求头:
  Origin: https://example.com
  Access-Control-Request-Method: POST
  Access-Control-Request-Headers: content-type
响应头:
  Access-Control-Allow-Origin: https://example.com
  Access-Control-Allow-Methods: POST
  Access-Control-Allow-Headers: Content-Type
  Access-Control-Max-Age: 86400

请求2:POST /api/data  
方法: POST
状态: 200 OK
耗时: 50ms
请求头:
  Origin: https://example.com
  Content-Type: application/json
响应头:
  Access-Control-Allow-Origin: https://example.com

4.2 不同浏览器的差异

// 测试不同浏览器对预检请求的处理
const testBrowsers = async () => {
    const config = {
        method: 'POST',
        headers: { 
            'Content-Type': 'application/json',
            'X-Test-Header': 'test'
        }
    };
    
    // Chrome/Firefox/Safari: 会发送预检请求
    // Edge (Chromium-based): 同 Chrome
    // 旧版 IE: 行为不同(部分版本不支持 CORS)
    
    try {
        const response = await fetch('/api/test', config);
        console.log('请求成功');
    } catch (error) {
        console.log('请求失败:', error);
    }
};

// 浏览器兼容性检查
if ('fetch' in window) {
    // 现代浏览器支持 fetch
    if ('credentials' in Request.prototype) {
        // 支持 CORS 凭证
    }
}

五、解决方案与优化策略

5.1 避免不必要的预检请求

// 方案1:保持请求为简单请求
async function makeSimpleRequest() {
    // 使用简单 Content-Type
    const formData = new URLSearchParams();
    formData.append('key', 'value');
    
    const response = await fetch('/api/data', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: formData
    });
    // 不会触发预检请求
}

// 方案2:使用 GET 替代 POST(如果安全允许)
async function getInsteadOfPost() {
    const params = new URLSearchParams({
        data: JSON.stringify({ key: 'value' })
    });
    
    const response = await fetch(`/api/data?${params}`);
    // GET 请求不会触发预检
}

// 方案3:服务器端设置合理的缓存时间
// 见上面的 Access-Control-Max-Age 配置

// 方案4:使用代理服务器
// 前端 -> 同域代理 -> 目标API
async function useProxy() {
    // 前端请求同域
    const response = await fetch('/api/proxy/data', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ key: 'value' })
    });
    // 不会触发预检,因为同域
}

// Nginx 代理配置示例
// location /api/proxy/ {
//     proxy_pass https://target-api.com/;
//     proxy_set_header Host $host;
// }

5.2 服务器端最佳实践

// Spring Boot 配置示例
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://client.example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600); // 1小时缓存
    }
}

// 或者使用过滤器
@Component
public class CorsFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        
        // 设置 CORS 头
        response.setHeader("Access-Control-Allow-Origin", "https://client.example.com");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        
        // 处理预检请求
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }
    }
}

5.3 前端优化方案

// 方案1:请求合并与批处理
class RequestBatcher {
    constructor() {
        this.pendingRequests = new Map();
        this.batchTimeout = 100; // 100ms 批处理窗口
    }
    
    async sendRequest(url, config) {
        // 检查是否已经有相同请求
        const key = `${url}_${JSON.stringify(config)}`;
        
        if (this.pendingRequests.has(key)) {
            return this.pendingRequests.get(key);
        }
        
        // 创建新的 Promise
        const promise = this.makeRequest(url, config);
        this.pendingRequests.set(key, promise);
        
        // 清理已完成的请求
        promise.finally(() => {
            this.pendingRequests.delete(key);
        });
        
        return promise;
    }
    
    async makeRequest(url, config) {
        // 实际的请求逻辑
        return fetch(url, config);
    }
}

// 方案2:使用 WebSocket 替代 HTTP
class WebSocketClient {
    constructor(url) {
        this.ws = new WebSocket(url);
        this.callbacks = new Map();
        this.requestId = 0;
        
        this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            const callback = this.callbacks.get(data.requestId);
            if (callback) {
                callback(data);
                this.callbacks.delete(data.requestId);
            }
        };
    }
    
    sendRequest(method, path, data) {
        const requestId = ++this.requestId;
        
        return new Promise((resolve, reject) => {
            this.callbacks.set(requestId, resolve);
            
            this.ws.send(JSON.stringify({
                requestId,
                method,
                path,
                data
            }));
            
            // 设置超时
            setTimeout(() => {
                if (this.callbacks.has(requestId)) {
                    this.callbacks.delete(requestId);
                    reject(new Error('Request timeout'));
                }
            }, 5000);
        });
    }
}

// 使用示例
const wsClient = new WebSocketClient('wss://api.example.com/ws');
wsClient.sendRequest('POST', '/data', { key: 'value' })
    .then(response => console.log(response));

六、实际案例分析

6.1 文件上传场景

// 文件上传会触发预检请求
async function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('metadata', JSON.stringify({
        name: file.name,
        size: file.size
    }));
    
    // 即使使用 FormData,Content-Type 会被自动设置
    // 但如果有自定义头,还是会触发预检
    const response = await fetch('/api/upload', {
        method: 'POST',
        headers: {
            'X-Upload-Token': 'token123'  // 自定义头触发预检
        },
        body: formData
        // 注意:不要手动设置 Content-Type
        // 浏览器会自动设置为 multipart/form-data
    });
    
    return response.json();
}

6.2 带认证的 API 调用

// 认证场景下的多次请求问题
class ApiClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
        this.token = null;
        this.preflightCache = new Map();
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;
        const cacheKey = `${options.method}_${endpoint}`;
        
        // 检查是否需要发送预检请求
        const needsPreflight = this.checkPreflightRequired(options);
        
        if (needsPreflight && !this.preflightCache.has(cacheKey)) {
            // 发送预检请求
            await this.sendPreflightRequest(url, options);
            this.preflightCache.set(cacheKey, true);
        }
        
        // 发送实际请求
        return this.sendActualRequest(url, options);
    }
    
    checkPreflightRequired(options) {
        // 判断是否为简单请求
        const method = options.method || 'GET';
        const contentType = options.headers?.['Content-Type'];
        
        const isSimpleMethod = ['GET', 'POST', 'HEAD'].includes(method.toUpperCase());
        const isSimpleContentType = !contentType || 
            ['application/x-www-form-urlencoded', 
             'multipart/form-data', 
             'text/plain'].includes(contentType);
        
        const hasCustomHeaders = options.headers && 
            Object.keys(options.headers).some(header => 
                !['accept', 'accept-language', 'content-language', 
                  'content-type'].includes(header.toLowerCase()));
        
        return !isSimpleMethod || !isSimpleContentType || hasCustomHeaders;
    }
    
    async sendPreflightRequest(url, options) {
        const preflightResponse = await fetch(url, {
            method: 'OPTIONS',
            headers: {
                'Access-Control-Request-Method': options.method || 'POST',
                'Access-Control-Request-Headers': Object.keys(options.headers || {}).join(',')
            }
        });
        
        if (!preflightResponse.ok) {
            throw new Error('Preflight request failed');
        }
    }
}

七、性能影响与监控

7.1 性能影响分析

// 测量预检请求的性能影响
class PerformanceMonitor {
    constructor() {
        this.metrics = {
            totalRequests: 0,
            preflightRequests: 0,
            totalTime: 0,
            preflightTime: 0
        };
    }
    
    async measureRequest(fetchFn) {
        const startTime = performance.now();
        const networkEntries = performance.getEntriesByType('resource');
        const initialCount = networkEntries.length;
        
        try {
            const response = await fetchFn();
            const endTime = performance.now();
            
            const newNetworkEntries = performance.getEntriesByType('resource');
            const requests = newNetworkEntries.slice(initialCount);
            
            requests.forEach(entry => {
                this.metrics.totalRequests++;
                this.metrics.totalTime += entry.duration;
                
                if (entry.name.includes('OPTIONS')) {
                    this.metrics.preflightRequests++;
                    this.metrics.preflightTime += entry.duration;
                }
            });
            
            return {
                success: true,
                duration: endTime - startTime,
                requestCount: requests.length,
                metrics: { ...this.metrics }
            };
        } catch (error) {
            return {
                success: false,
                error: error.message
            };
        }
    }
    
    getPerformanceReport() {
        const avgTotalTime = this.metrics.totalTime / this.metrics.totalRequests;
        const avgPreflightTime = this.metrics.preflightTime / this.metrics.preflightRequests;
        
        return {
            totalRequests: this.metrics.totalRequests,
            preflightRequests: this.metrics.preflightRequests,
            preflightPercentage: (this.metrics.preflightRequests / this.metrics.totalRequests * 100).toFixed(2) + '%',
            averageTotalTime: avgTotalTime.toFixed(2) + 'ms',
            averagePreflightTime: avgPreflightTime.toFixed(2) + 'ms',
            preflightOverhead: ((avgPreflightTime / avgTotalTime) * 100).toFixed(2) + '%'
        };
    }
}

// 使用示例
const monitor = new PerformanceMonitor();

// 测试不同请求
const testCases = [
    () => fetch('/api/simple', { method: 'POST' }),
    () => fetch('/api/complex', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
    }),
    () => fetch('/api/complex', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' }
    })
];

for (const testCase of testCases) {
    const result = await monitor.measureRequest(testCase);
    console.log('测试结果:', result);
}

console.log('性能报告:', monitor.getPerformanceReport());

7.2 实时监控方案

// 使用 PerformanceObserver 监控请求
class RequestObserver {
    constructor() {
        this.observer = new PerformanceObserver((list) => {
            const entries = list.getEntries();
            entries.forEach(entry => {
                this.analyzeRequest(entry);
            });
        });
        
        this.observer.observe({ entryTypes: ['resource'] });
        this.stats = {
            simple: { count: 0, totalTime: 0 },
            preflight: { count: 0, totalTime: 0 },
            actual: { count: 0, totalTime: 0 }
        };
    }
    
    analyzeRequest(entry) {
        const url = entry.name;
        const method = this.extractMethod(entry);
        const isPreflight = method === 'OPTIONS';
        
        if (isPreflight) {
            this.stats.preflight.count++;
            this.stats.preflight.totalTime += entry.duration;
            
            // 查找对应的实际请求
            this.findActualRequest(url);
        } else {
            this.stats.actual.count++;
            this.stats.actual.totalTime += entry.duration;
        }
    }
    
    extractMethod(entry) {
        // 从 PerformanceResourceTiming 提取请求方法
        if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
            // 可能需要从服务器日志获取
            return 'unknown';
        }
        return 'unknown';
    }
    
    findActualRequest(preflightUrl) {
        // 实现逻辑:找到对应的实际请求
    }
    
    getStats() {
        return this.stats;
    }
}

八、总结与最佳实践

8.1 POST 发送两次请求的原因总结

场景是否发送两次原因
同域请求否不涉及 CORS
简单跨域请求否满足 CORS 简单请求条件
复杂跨域请求是需要预检请求验证
带凭证的跨域请求是需要预检
非简单方法(PUT/DELETE)是需要预检
自定义请求头是需要预检
非简单 Content-Type是需要预检

8.2 优化建议

// 前端最佳实践
const bestPractices = {
    // 1. 尽量保持请求为简单请求
    useSimpleRequest: () => ({
        method: 'POST', // 只使用 GET、POST、HEAD
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    }),
    
    // 2. 合理设置预检缓存
    setPreflightCache: (maxAge = 86400) => {
        // 服务器端设置 Access-Control-Max-Age
    },
    
    // 3. 合并请求头
    combineHeaders: (headers) => {
        // 避免过多的自定义头
        const essentialHeaders = {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer token'
        };
        return { ...essentialHeaders, ...headers };
    },
    
    // 4. 使用代理服务器
    useProxy: (apiUrl) => {
        // 同域代理避免预检
        return `/api/proxy${apiUrl}`;
    },
    
    // 5. 考虑使用 WebSocket
    considerWebSocket: () => {
        // 对于频繁的请求,考虑 WebSocket
    }
};

// 服务器端最佳实践
const serverBestPractices = {
    // 1. 正确配置 CORS
    configureCORS: () => `
        Access-Control-Allow-Origin: https://client.example.com
        Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
        Access-Control-Allow-Headers: Content-Type, Authorization
        Access-Control-Allow-Credentials: true
        Access-Control-Max-Age: 86400
    `,
    
    // 2. 处理 OPTIONS 请求
    handleOptions: (req, res) => {
        if (req.method === 'OPTIONS') {
            res.writeHead(204, headers);
            res.end();
            return true;
        }
        return false;
    },
    
    // 3. 监控预检请求
    monitorPreflight: () => {
        // 记录和监控 OPTIONS 请求
    }
};

8.3 决策流程图

开始请求
    ↓
判断是否跨域?
    ├── 否 → 发送1次请求 → 结束
    ↓ 是
判断是否为简单请求?
    ├── 是 → 发送1次请求 → 结束
    ↓ 否
检查预检缓存是否有效?
    ├── 是 → 跳过预检 → 发送1次实际请求 → 结束
    ↓ 否
发送OPTIONS预检请求
    ↓
服务器响应是否允许?
    ├── 否 → 请求失败 → 结束
    ↓ 是
缓存预检结果
    ↓
发送实际请求
    ↓
结束

记住关键点:

  • 预检请求是浏览器的安全机制,不是 bug
  • 只有复杂跨域请求才需要预检
  • 合理配置可以优化性能(如缓存预检结果)
  • 理解原理有助于调试和优化

通过合理的设计和配置,可以在保证安全的前提下,最小化预检请求带来的性能影响。

前端 | 浅谈预检请求 背景不知道大家有没有发现,有时候我们在调用后台接口的时候,会请求两次,如下图的其实第一次发送的就是preflight request(预检请求),那么这篇文章将讲一下,为什么要发预检请求,什么时候会发预检请求,预检请求都做了什么一. 为什么要发预检请求我们都知道浏览器的同源策略,就是出于安全考虑,浏览器会限制从脚本发起的跨域HTTP请求,像XMLHttpRequest和Fetch都遵循同源策略。 阅读详情

相关推荐

字节一面:post为什么会发送两次请求?

最近博主在**字节面试**中遇到这样一个面试题,这个问题也是前端面试的高频问题,因为在前端开发的日常开发中我们总是会与post请求打交道,一个小小的post请求也是牵扯到很多知识点的,博主在这给大家细细道来。

A_D_H_E_R_E的博客 2万+

预检请求_

只有复杂请求才发送预检请求。 非简单请求是复杂请求。 简单请求只有: 只可能有GET + POST +HEAD三种请求类型。而且必须满足下面的2和3两个特征,(POST请求也不全是简单请求,后面有例子) 不能有自定义头字段 ,只能有以下几种: Accept Accept-Language Content-Type Content-Language Content-Type的值只能是以下三种: text/plain multipart/form-data 上传文件用的 application/x-w

weixin_45543674的博客 6067

用 SAML 2.0 服务用户认证打通 ABAP 侧系统访问

摘要: SAP ABAP系统中SAML 2.0单点登录的系统集成场景下,Authentication with Service Users机制通过动态映射SAML属性到预定义服务用户,解决身份归并问题。该机制允许ABAP根据SAML断言中的属性(如公司代码、应用角色等)选择合适服务用户,而非绑定具体自然人身份。配置时需注意条件匹配顺序、属性名称一致性,并合理设置Default Service User。此方案适用于需区分业务域而非个人身份的系统集成场景,是SAP ABAP实现灵活身份联合的重要功能。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2240

关于预检请求

预检请求(Preflight Request)是一种由浏览器自动发起的请求,用于检查实际请求是否安全可行。这种请求通常在跨域请求(CORS)中出现,并且只在某些特定条件下触发。以下是触发预检请求的具体条件:

yiguoxiaohai的博客 1575

【Qualcomm高通音频】音效调试_录音文件播放有杂音,如何定位原因?

一、录音播放数据处理过程介绍 (1)TX方向 麦克风(硅麦克风或者主机体麦克风)采集模拟音频信号,经过内部CODEC芯片中的adc转换为数字信号,在经过高通音频DSP处理音效后编码为wav/aac/amr/mp3等格式文件。 (2)RX方向 音频文件经过解码、重采样还原为pcm数据,经过DSP处理音效后送到内部CODEC,然后经过DAC转换为模拟信号,送到喇叭。 二、分析步骤 1...

crow_ch的博客 5964

Grok-1.5+Ollama企业级本地化部署与合规实践指南

本文详细介绍了Grok-1.5+Ollama企业级本地化部署与合规实践指南,重点分析了本地化部署的成本优势和数据主权保障。通过RTX 4090显卡的硬件选型、Ubuntu 22.04系统优化及部署流程中的关键陷阱规避,为企业提供了高效、安全的AI模型部署方案。特别适合金融、医疗等敏感行业。

weixin_29094539的博客 177

POST请求为什么会发送两次

在前端开发中,我们经常会遇到POST请求会发送两次的情况。这种问题可能会导致数据错误或其他意料之外的结果,让开发者感到困惑和不安。本文将深入探讨POST请求为何会发送两次的原因,并从以下几个方面进行解释:同源策略、简单请求、预检请求、为什么本地使用Webpack进行dev开发时,不需要服务器端配置CORS的情况下访问到线上接口。

qq_32054169的博客 2691

基于集成运放心电放大器设计(原创)

原创作者:einyboy or alert 最近在看美剧《危机边缘》关于边缘科学的案件,科技控可以看一下,但建议不在吃东西的时候看。想像力是创造的思想源头,动手能力则是创造的车床。 自认为是没有想像力的那类,只能发挥动手能力了。 心电测量简介 图1 心电极接线图 如图1心电测试一般使用3个心电极进行测量,各个电极的说明如下: RA: Right Arm缩写,右手电极 LA: L...

huandaohack的专栏 6921

Web 中POST为什么会发送两次请求

我们在做Web开发时,经常会使用浏览器F12查看请求参数是否正确,但是会发现POST请求,一个地址发送了两次请求,第一次是OPTIONS,第二次才是POST请求,下面我们将就是问题进行深入分析。POST请求发送两次的问题看似简单,实际上涉及了很多知识点,包括浏览器的重试机制、跨域请求的预检机制、表单的自动提交、服务器配置问题以及前端代码中的逻辑错误等。

拥有必珍惜 2580

Maya vray 3S皮肤材质球设置与材质节点连接

Maya vray 3S皮肤材质球设置与材质节点连接

WETQETF的博客 2692

基于SAGE算法的宽带信道参数估计算法的MATLAB仿真

本文将介绍如何使用MATLAB进行SAGE算法的仿真,以便更好地理解和应用该算法。SAGE算法是一种基于最大期望(Expectation-Maximization)方法的迭代估计算法,用于估计未知的参数。在宽带信道参数估计中,SAGE算法通过迭代优化来估计信道的传输函数。总结起来,本文介绍了如何使用MATLAB进行基于SAGE算法的宽带信道参数提取算法的仿真。通过以上步骤,我们可以得到估计的信道响应,并与真实信道响应进行比较。此外,SAGE算法还可以与其他信道估计算法相结合,以提高估计的准确性和性能。

CodeRoarX的博客 1071

为什么有些POST请求会发送两次?

今天在浏览博客时,我发现了一个有趣的问题:“为什么POST请求会发送两次?”之前我从未注意到这个问题。我的第一反应是,是否是因为防抖处理不当导致POST请求被发送两次。😊经过进一步的资料查询,我发现在某些情况下,请求需要进行预检。本文将对“为什么POST请求会发送两次?”进行简单的解释。

懒人w的博客 1664

2025年GESP12月认证C++一级真题解析

摘要 本文包含13道C++编程选择题,每题2分,涉及语法、运算符、循环、条件判断等知识点。题目考察了变量命名规则、运算符优先级、赋值语句、循环控制、格式化输出等内容。部分题目解析与标答存在差异,例如第4题逗号表达式输出应为5但标答为4,第6题赋值运算结果应为4 4但标答为3 4等。这些题目适合用于检验基础编程能力,但需注意部分标答可能存在争议,建议结合具体编译环境验证。最后一题考察"漂亮数"判断,要求填写数字某位能被3整除的条件表达式。

lan_in的博客 731

面试官:post 为什么会发送两次请求?

最近博主在字节面试中遇到这样一个面试题,这个问题也是前端面试的高频问题,因为在前端开发的日常开发中我们总是会与post请求打交道,一个小小的post请求也是牵扯到很多知识点的,博主在这给大家细细道来。同源策略是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。如果两个 URL 的协议、主机和端口都相同,我们就称这两个 URL 同源。协议是定义了数据如何在计算机内和之间进行交换的规则的系统,例如 HTTP、HTTPS。

jj1245_的博客 707

innovus:insert Power Switch失败,报告not covered by a row问题解析

这个问题就是switch找不到可以放置的row,分析就从两方面入手,一方面从voltage area下手,一方面从row和site入手。检查发现voltage area设置正确,row也存在,只不过lef里缺少对应switch的双倍高的site。更多学习内容请关注「拾陆楼」知识星球。

m0_61544122的博客 1148

元宇宙通证- 九、IT/ICT发展史全景长图

九、IT/ICT发展史全景长图 (一)综述 IT是Information Technology的缩写,意为”信息技术“,包含现代计算机、网络、通信等信息领域技术。自1844年人类历史上发出来第一份电报,实现了信息的长图传输,信息技术发展的历史大幕随之拉开。信息技术的普遍应用,是进入信息社会的标志。 IT技术最早发源于半导体,然后发展处计算机、通信网和互联网,进而演进到现在的AI、XR、计算机、大数据、物联网、工业互联网、区块链、量子计算等。 有人说元宇宙是下一代互联网,有人说元宇宙是互联网的终极形态。 我们

沧海之巅的专栏 1万+

post为什么会发送两次请求?

POST 请求发送两次可能有几个原因,下面列出了一些常见的原因和可能的解决方法:1. 浏览器刷新或重试机制某些情况下,用户在提交表单或点击按钮后可能会误刷新页面或浏览器会自动重试请求,导致 POST 请求被发送两次。这通常发生在用户点击提交按钮多次,或者按下浏览器的“回退”按钮,再重新进入页面后。解决方法:使用 JavaS...

qq_35485206的博客 835

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

post为什么会发送两次请求详解

复杂请求是指那些不仅仅是简单的GET或POST请求的请求。使用POSTPUTDELETE等HTTP方法。请求中包含自定义的HTTP头字段。请求体(Body)中包含非文本数据(如JSON或XML当浏览器检测到跨域请求满足上述任何一个条件时,它就会发送一个OPTIONS预检请求。这个预检请求会包含一些特定的HTTP头字段,如(表示将要使用的HTTP方法)和(表示将要使用的自定义头字段)。当涉及到跨域请求,尤其是复杂请求时,POST请求可能会先发送一个OPTIONS预检请求,然后再发送实际的POST。

肥晨开发的学习之路 1744

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

深入解析之post为什么会发送两次请求?

首先,它们的目的不同。有时候,浏览器也是会先发出一个OPTIONS请求,这个请求就像是浏览器在问服务器:“嘿,我这儿有一个网页想发个请求到你的地盘,但是网页和你的话,咱俩不是同一个源的,你得先告诉我,你同不同意?那不同的源肯定不能互相瞎请求啊,比如说浏览器请求服务器A返回了一个网页,结果这个服务器A跑的A系统的网页里,有一个请求要去请求服务器B上部署的B系统,那这种就很奇怪了,对浏览器来说你们是两个服务器,也就是两个不同的系统,干什么要互相瞎访问,有没有可能A系统是一个涉灰系统在搞破坏,盗取数据,对不对?

zhudapeng929的博客 1983

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

为什么POST请求经常发送两次?

大多数初级前端程序员,在通过浏览器F12的调试工具调试网络请求时,可能都会有一个发现,在进行POST请求时,明明代码里只请求了一次,为什么network里发送了两次呢,难道我代码出bug了?带着疑问点开第一个请求才发现,原来第一个是OPTIONS类型的请求,第二个才是我代码里写的POST类型的请求。那为什么,POST请求之前默认伴随着一个OPTIONS请求呢?

CreatorRay的博客 1691

POST为什么会发送两次请求

最近遇到这样一个面试题,这个问题也是前端面试的高频问题,因为在前端开发的日常开发中我们总是会与post请求打交道,一个小小的post请求也是牵扯到很多知识点的,博主在这给大家细细道来。同源策略是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。如果两个 URL 的协议、主机和端口都相同,我们就称这两个 URL 同源。协议:协议是定义了数据如何在计算机内和之间进行交换的规则的系统,例如 HTTP、HTTPS。

wangwcy的博客 1147

浅析前端请求中post为什么会发送两次请求?

在前端开发中,有时候会遇到 POST 请求发起了两次的情况,这种情况通常是由于浏览器的预检请求(OPTIONS 请求)导致的,即跨域请求时浏览器会先发送一个 OPTIONS 请求进行预检,然后再发送实际的 POST 请求。另外,还有一种常见的情况会导致 POST 请求发送两次,那就是页面中使用了表单提交的方式触发 POST 请求,同时又通过 JavaScript 代码手动发送了 POST 请求,这样就会导致 POST 请求发送两次。等相关响应头来支持跨域请求,并实现预检请求的处理。

new_handsome的博客 3740

POST为什么发送两次请求

跨域资源共享(CORS)是浏览器的一种安全机制,用于控制不同源之间的HTTP请求。当你的前端应用(例如 https://myapp.com)尝试向不同源的服务器(例如 https://api.otherservice.com)发送请求时,浏览器会执行CORS检查。// 同源示例// 跨域示例https://myapp.com → https://api.service.com (不同域名)http://myapp.com → https://myapp.com (不同协议)

z1ztai的博客 783

post为什么会发送两次请求

1、预检请求:在跨域请求中,浏览器为了确保安全,会首先发送一个OPTIONS请求,也被称为预检请求。如果预检请求成功,浏览器才会发出实际的POST请求;4、重定向:服务器在接收到POST请求后,如果返回了302等重定向状态,浏览器可能会再次发送POST请求到新的地址。3、浏览器行为:有些情况下,比如网络连接问题或者用户刷新页面,可能会导致浏览器重新发送POST请求;2、代码问题:如果代码中不小心调用了两次POST请求,也会导致POST请求发送两次;

Fan240828的博客 640
上一篇: Ollama + DeepSeek + Dify 个人知识库本地构建完整教程
下一篇: C++智能指针循环引用问题详解与解决方案
深山技术宅
博客等级 码龄4年 1903粉丝 1662原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值