关于处理CORS问题

CORS(跨源资源共享)?

以下内容引用自 mdn web docs

跨源资源共享CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。

举例:在 http://localhost:8000 的网页上使用 fetch()https://sm.ms/api/v2/ 这个图床的api接口发送请求即会产生CORS问题。

解决方法

  • 在Web服务器设置HTTP头部信息,比如Access-Control-Allow-Origin

    这里以 Nginx 为例:

    # https://enable-cors.org/server_nginx.html
    # 为nginx配置一个宽松的CORS(跨域资源共享)设置
    
    location / {
        # 处理预检请求(OPTIONS请求)
        if ($request_method = 'OPTIONS') {
            # 允许所有来源的请求
            add_header 'Access-Control-Allow-Origin' '*';
            # 允许的HTTP方法
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            
            # 自定义头部和其他浏览器应该支持但可能不支持的头部
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
            
            # 告诉客户端此预检信息有效期为20天(1728000秒)
            add_header 'Access-Control-Max-Age' 1728000;
            
            # 返回一个空的响应体,状态码为204(无内容)
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }
        
        # 处理POST请求
        if ($request_method = 'POST') {
            # 允许所有来源的请求
            add_header 'Access-Control-Allow-Origin' '*' always;
            # 允许的HTTP方法
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
            # 允许的请求头部
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
            # 允许客户端访问的响应头部
            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
        }
        
        # 处理GET请求
        if ($request_method = 'GET') {
            # 允许所有来源的请求
            add_header 'Access-Control-Allow-Origin' '*' always;
            # 允许的HTTP方法
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
            # 允许的请求头部
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
            # 允许客户端访问的响应头部
            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
        }
    }
  • 使用CORS header proxy 服务器

    这里以 Cloudflare Workers 为例:

    // https://developers.cloudflare.com/workers/examples/cors-header-proxy/
    export default {
        async fetch(request) {
            const corsHeaders = {
                "Access-Control-Allow-Origin": "*", // 允许所有来源的请求
                "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", // 允许的HTTP方法
                "Access-Control-Max-Age": "86400", // 预检请求的缓存时间(24小时)
            };
    
            // 远程第三方API的URL,该API没有实现CORS
            const API_URL = "https://examples.cloudflareworkers.com/demos/demoapi";
    
            // CORS反向代理的端点
            const PROXY_ENDPOINT = "/corsproxy/";
    
            // 用于返回HTML响应的辅助函数
            function rawHtmlResponse(html) {
                return new Response(html, {
                    headers: {
                        "content-type": "text/html;charset=UTF-8", // 设置响应头为HTML
                    },
                });
            }
    
            // 演示页面的HTML内容
            const DEMO_PAGE = `
            <!DOCTYPE html>
            <html>
            <body>
              <h1>API GET without CORS Proxy</h1>
              <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful">Shows TypeError: Failed to fetch since CORS is misconfigured</a>
              <p id="noproxy-status"/>
              <code id="noproxy">Waiting</code>
              <h1>API GET with CORS Proxy</h1>
              <p id="proxy-status"/>
              <code id="proxy">Waiting</code>
              <h1>API POST with CORS Proxy + Preflight</h1>
              <p id="proxypreflight-status"/>
              <code id="proxypreflight">Waiting</code>
              <script>
              let reqs = {};
              reqs.noproxy = () => {
                return fetch("${API_URL}").then(r => r.json())
              }
              reqs.proxy = async () => {
                let href = "${PROXY_ENDPOINT}?apiurl=${API_URL}"
                return fetch(window.location.origin + href).then(r => r.json())
              }
              reqs.proxypreflight = async () => {
                let href = "${PROXY_ENDPOINT}?apiurl=${API_URL}"
                let response = await fetch(window.location.origin + href, {
                  method: "POST",
                  headers: {
                    "Content-Type": "application/json"
                  },
                  body: JSON.stringify({
                    msg: "Hello world!"
                  })
                })
                return response.json()
              }
              (async () => {
              for (const [reqName, req] of Object.entries(reqs)) {
                try {
                  let data = await req()
                  document.getElementById(reqName).innerHTML = JSON.stringify(data)
                } catch (e) {
                  document.getElementById(reqName).innerHTML = e
                }
              }
            })()
              </script>
            </body>
            </html>
          `;
    
            // 处理请求的函数
            async function handleRequest(request) {
                const url = new URL(request.url);
                let apiUrl = url.searchParams.get("apiurl"); // 获取请求中的apiurl参数
    
                if (apiUrl == null) {
                    apiUrl = API_URL; // 如果没有提供apiurl参数,则使用默认的API_URL
                }
    
                // 重写请求以指向API URL,并设置Origin头以避免CORS问题
                request = new Request(apiUrl, request);
                request.headers.set("Origin", new URL(apiUrl).origin);
                let response = await fetch(request);
    
                // 重新创建响应以便修改响应头
                response = new Response(response.body, response);
    
                // 设置CORS头
                response.headers.set("Access-Control-Allow-Origin", url.origin);
    
                // 添加Vary头以便浏览器正确缓存响应
                response.headers.append("Vary", "Origin");
    
                return response;
            }
    
            // 处理OPTIONS请求的函数
            async function handleOptions(request) {
                if (
                    request.headers.get("Origin") !== null &&
                    request.headers.get("Access-Control-Request-Method") !== null &&
                    request.headers.get("Access-Control-Request-Headers") !== null
                ) {
                    // 处理CORS预检请求
                    return new Response(null, {
                        headers: {
                            ...corsHeaders,
                            "Access-Control-Allow-Headers": request.headers.get(
                                "Access-Control-Request-Headers",
                            ),
                        },
                    });
                } else {
                    // 处理标准的OPTIONS请求
                    return new Response(null, {
                        headers: {
                            Allow: "GET, HEAD, POST, OPTIONS",
                        },
                    });
                }
            }
    
            const url = new URL(request.url);
            if (url.pathname.startsWith(PROXY_ENDPOINT)) {
                if (request.method === "OPTIONS") {
                    // 处理CORS预检请求
                    return handleOptions(request);
                } else if (
                    request.method === "GET" ||
                    request.method === "HEAD" ||
                    request.method === "POST"
                ) {
                    // 处理对API服务器的请求
                    return handleRequest(request);
                } else {
                    return new Response(null, {
                        status: 405,
                        statusText: "Method Not Allowed", // 返回405状态码,表示方法不允许
                    });
                }
            } else {
                return rawHtmlResponse(DEMO_PAGE); // 返回演示页面的HTML内容
            }
        },
    };

相关推荐

使用Cloudflare Workers转发任意请求 Zibri/cloudflare-cors-anywhere

我写的smms-proxy-workers,用于代理smms图床api duzhuoshanwai/smms-proxy-workers

mdn web docs - CORS 错误