一篇搞懂跨域之CORS

date: 2019-12-31 18:09:11javascript

跨年是web开发中。。奥,错了。跨域是web开发中常见的一个问题,其中CORS是比较经常使用的解决跨域的方式。如今经过new XMLHttpRequest()和简单实现一个node server,完全搞懂CORS(Cross-Origin Resource Sharing)。html

几个相关响应头头信息

  • Access-Control-Allow-Origin:
    容许哪些地址访问。能够指定某些URL访问。也能够经过设置通配符*,容许因此地址访问,不过在设置Credentials时,不容许设置通配符,解决方法下面说。java

  • Access-Control-Allow-Headers:
    容许客户端设置约定好的请求头参数。node

  • Access-Control-Allow-Credentials:
    响应头是否能够将请求的响应暴露给页面。主要是指的是Cookieweb

  • Access-Control-Allow-Methods:
    容许哪些方法访问我。ajax

  • Access-Control-Max-Age:
    设置多久内不须要再进行预检请求json

几个实际的错误

实际开发中会浏览器会之间提示响应的错误信息,根据信息内容,能够很容易的推断出是什么缘由跨域,解决方案是什么。api

先写一个简单的xhr客户端:跨域

// 用http-server静态服务器端口 8080 启动
  <button id='btn'>发送ajax</button>
  <script> btn.addEventListener('click',()=>{ doRequest() }) // 发送请求 function doRequest(){ let xhr = new XMLHttpRequest(); // 。。。请求处理  let xhr = new XMLHttpRequest(); xhr.open('GET','http://localhost:3000/user',true) // todo请求设置 xhr.responseType = 'json'; // 设置服务器的响应类型 xhr.onload = function(){console.log(xhr.response)} xhr.send() } </script>
复制代码

一个简单的node服务端:浏览器

// nodemon server.js启动 
http.createServer((req,res)=>{
  let {pathname} = url.parse(req.url)
  let method = req.method
  if(req.headers.origin){ // 若是跨域了 才走跨域逻辑
    // TODO CORS处理 
  }
  // 接口逻辑处理
  if (pathname == '/user') {
  }
}).listen('3000')
复制代码

一、 第一个请求,被拒绝了。

  • 客户端请求:

    function doRequest(){
      let xhr = new XMLHttpRequest();
      xhr.open('GET','http://localhost:3000/user',true)
      xhr.responseType = 'json'; // 设置服务器的响应类型
      xhr.onload = function(){console.log(xhr.response)}
      xhr.send('a=1&b=2')
    }
    复制代码
  • 出现错误:

    当咱们经过一个简单的get请求'http://localhost:3000/user'时,错误提示很明确,从http://127.0.0.1:8080地址请求http://localhost:3000/user的接口被拒绝了。要设置Access-Control-Allow-Originheader响应头。

  • 解决方法:
    服务端设置响应头(node http模块写法):

    // ...
      res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8080') 
      // or 容许全局访问 设置通配符 * 
      res.setHeader('Access-Control-Allow-Origin', '*') 
      // or 容许全局访问 动态获取请求源地址。( * 在某些状况下不能用,下面会说)
      res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
      // ...
    复制代码

2. 请求头内携带token信息被拒绝了

  • 客户端请求:

    // ...
      // 设置请求头token
      xhr.setRequestHeader('token','xxxx');
      // ...
    }
    复制代码
  • 出现错误:

    设置请求头信息token。服务器应该要容许,如需其余参数另行添加

  • 解决方法: 服务端设置

    // ...
      res.setHeader('Access-Control-Allow-Headers','token');
      // ...
    复制代码

    其实如今再去请求又会出现另外一个错误,看下面。

3. 非简单请求的预检请求 OPTIONS

客户端请求时(包括GET、POST请求)增长自定义请求头信息时或者非GET、POST请求,都是非简单请求。非简单请求触发时,会先发送一次OPTIONS预检请求,

  • 客户端:设置自定义请求头字段,或者PUT请求等非GET和POST请求。

    function doRequest(){
      // ...
      xhr.open('PUT','http://localhost:3000/user',true)
      // or
      xhr.open('GET','http://localhost:3000/user',true)
      xhr.setRequestHeader('token','xxxx');
      // ...
    }
    复制代码
  • 出现错误:
    OPTIONS请求未处理,因此404

    同时出现跨域错误

  • 解决方法:
    服务端设置:同时考虑到不须要每次都进行预检请求,响应成功一次后,服务器能够告诉客户端多少秒内不须要继续OPTIONS请求了。

    // 设置options请求的间隔事件
        res.setHeader('Access-Control-Max-Age','10');
        if(method === 'OPTIONS'){
            res.end(); // 浏览器就知道了 我能够继续访问你
            return;
        }
    
    复制代码

4. 设置Cookie时出现到问题

有时候服务端须要给客户端返回Cookie用于身份凭证,客户端收到Cookie后,再次请求能够携带该Cookie代表身份。

  • 客户端:
function doRequest(){
    // ...
    xhr.withCredentials = true; // 强制携带服务器设置的cookie
    // ...
  }
复制代码
  • 出现错误:

    服务端未容许cookie设置

  • 解决方法: 服务器端设置,容许设置凭证(此处做者有疑问🤔️)

    // ...
      res.setHeader('Access-Control-Allow-Credentials',true)
      // ...
    复制代码

下面给出完整把端客户端和服务端的代码

客户端: 能够用http-server指定8080端口启动访问,模拟 http://localhost:8080/index.html访问端口 3000

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <button id='btn'>发送ajax</button>
  <script> btn.addEventListener('click',()=>{ let xhr = new XMLHttpRequest(); xhr.open('POST','http://localhost:3000/user',true) xhr.setRequestHeader('token','xxxx'); // 设置token 服务器须要赞成设置token xhr.withCredentials = true // 请求携带cookie,服务器须要同步设置容许携带 xhr.responseType = 'json'; // 设置服务器的响应类型 xhr.onload = function(){ // xhr.readyState == 4 + xhr.status == 200 // xhr.response 对象等数据 console.log(xhr.response) } xhr.send('a=1&b=2') }) </script>
</body>
</html>
复制代码

node服务器:

const http = require('http');  
const fs = require('fs');  
const url = require('url');  
const path = require('path');  
http.createServer((req,res)=>{
    // 动态服务
    let {pathname,query} = url.parse(req.url);
    // pathname 有多是客户端发起的接口请求
    let method = req.method;
    // 容许那个域 来访问我
    if(req.headers.origin){ // 若是跨域了 才走跨域逻辑
        res.setHeader('Access-Control-Allow-Origin',req.headers.origin); // 和 * 是同样
        // 容许哪些方法访问我
        res.setHeader('Access-Control-Allow-Methods','GET,PUT,DELETE,POST,OPTIONS');
        // 容许携带哪些头
        res.setHeader('Access-Control-Allow-Headers','token');
        // 设置options请求的间隔事件
        res.setHeader('Access-Control-Max-Age','10');
        res.setHeader('Access-Control-Allow-Credentials',true)
        // 跨域 cookie 凭证 若是跨域是不容许携带cookie
        if(method === 'OPTIONS'){
            res.end(); // 浏览器就知道了 我能够继续访问你
            return;
        }
    }
    if(pathname === '/user'){ // 你发送的是api接口
        switch(method){
            case 'GET':  
                // res.setHeader('Set-Cookie','name=zf'); 
                res.end(JSON.stringify({name:'zf'}))
                break;
            case 'POST':
                let arr = [];
                console.log('log')
                req.on('data',function(chunk){
                    arr.push(chunk);
                })
                req.on('end',function(){
                    console.log(Buffer.concat(arr).toString());
                    res.end('{"a":1}')
                })
                
        }
        return 
    }

    // 静态服务
    let filePath = path.join(__dirname,pathname);
    fs.stat(filePath,function(err,statObj){
        if(err){
            res.statusCode = 404;
            res.end();
            return
        }
        if(statObj.isFile()){
            // mime header
            fs.createReadStream(filePath).pipe(res);
            return
        }else{
            res.statusCode = 404;
            res.end();
            return
        }
    })
}).listen(3000);
复制代码

原地址

相关文章
相关标签/搜索