写篇文章让自己清楚egg——(3)请求参数

1,300 阅读7分钟

上一篇文章讲解了几种url地址的处理和特殊路由public的使用,这就不可避免要引出参数的问题。

路由传参

egg支持一种特殊的路由写法

router.js

    ...
     router.get('/select/:id/:msg', controller.home.select);
    ...

通过/:id/:msg格式egg服务器可以拿到写在路由中的参数(不是get参数)。

编写Controller中对应的函数——获取路由中的参数值并响应给浏览器:

home.js

...
  async select() {
    const { ctx } = this;
    ctx.body = {//ctx.params就是路由参数的集合对象。
      id: ctx.params.id,
      msg: ctx.params.msg,
    };
  }
...

这里对于路由参数的获取和属性ctx.params有关,params是一个对象,所以应该是可以接收多个参数,所以: /:id/:msg 的格式产生的params对象包含id和msg属性;而 /:id 则产生只有id属性的params对象。
随之具体的url写法也要变化,可以写几个参数,由/:/:这种格式决定,并且url只能少写参数不能多写,多写就会匹配不到路由报404。

现在来尝试访问一下这个路由写法的url吧:juejinjin.com/select/123/…
浏览器得到的响应(浏览器插件优化JSON格式信息):

image.png


get参数

从get到post我们需要借助XHR完成浏览器的异步请求,相关知识讲解参考 写篇文章让自己清楚Ajax——(1)XMLHttpRequest是什么?

客户端加载这样一个网页:

客户端页面代码

<body>
    get请求携带字符串参数
    <script>
        setTimeout(() => {
            let xhr = new XMLHttpRequest();
            xhr.open('get','http://juejinjin.com/get?name=jinjin&msg=hi',true);//url携带get参数
            xhr.onload=function(){
                console.log('收到响应信息')
                console.log(JSON.parse(xhr.responseText))
            }
            xhr.send(null)//发送请求
            xhr.onerror=function(){console.log('err')}
        }, 200);
    </script>
</body>

egg服务器代码

    router.get('/get', controller.home.getQuery);//router.js文件
    async getQuery() {//home.js文件
        const { ctx } = this;
        console.log(ctx.query);//ctx.query用于获取get参数。
        ctx.body = {
            data: ctx.query,
        };
    }

准备好后启动页面,你会发现出现下面的报错:

Access to XMLHttpRequest at 'http://juejinjin.com/get?name=jinjin&msg=hi' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

直译CORS 策略阻止了在 'juejinjin.com/get?name=ji…' 处访问 XMLHttpRequest 来自 'null' 源:对预检请求的响应未通过访问控制检查:否 'Access-Control -Allow-Origin' 标头存在于请求的资源上。

从此刻开始我们要面临CORS,"跨域资源共享"(Cross-origin resource sharing),这部知识我们将会在http和Ajax专题里详细讲解,其他情况适当讲解。

但是get请求报文不同于post请求,get请求可以携带参数直接在浏览器地址栏直接发送。
所以这里可以避免cors问题,我们可以直接将 juejinjin.com/get?name=jinjin&msg=hi 写入地址栏并回车,此时按照代码设计是有响应body,所以网页不会报404,同时我们在服务器运行的终端可以看得到下面的图片里的打印信息。

image.png

--处理CORS

CORS是一个W3C标准,全称是"跨域资源共享"。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

所以我们知道通过地址栏可以跨域发出请求,但是XMLHttpRequest请求则无法跨域,需要借助cors等外力。

cors不在客户端使用,而是通过npm在服务端进行插件安装。

  • 终端安装cors插件
npm i egg-cors --save
  • 启用插件(plugin.js)
module.exports = {
...//添加插件配置对象
  cors: {
    enable: true,//启用
    package: 'egg-cors',//插件名
  },
...
};
  • 配置cors插件(config.default.js)
...
  config.cors = {//这里可以在config初始化的直接写进去,也可以像我这样初始化后添加进config里
    origin: '*',//任何域名
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',//任何http请求方式
  };
...

现在我们可以使用xhr请求在任意的域名下跨域发起请求。上面的客户端代码可以发出请求,在控制台看得到响应的数据

--同域xhr请求

关于这里的跨域,我们要提出ip地址的使用问题,跨域实际是围绕ip的而导致的问题(同源策略)。

有跨域就存在不跨域的情况,xhr在同域的情况可以随意使用,所以我们要分析我们项目目前存在的跨域以及如何修改成同域。

  1. 网页和服务器

网页的地址就是浏览器的地址栏中显示的内容(隐藏协议和默认端口),服务器的地址就是本机。我们前面一篇文章中将ip地址换成主机名,导致网页的地址变成主机名,然后映射成本地的ip地址。于是同源策略就认为主机名(juejinjin.com)和服务器ip地址不一样,所以产生跨域,xhr请求报错。

解决: 修改网页的主机名部分,改成本机ip地址(因为服务器public静态资源就在ip下,启动服务器就可以使用ip直接请求得到静态网页,用来发起get请求的静态网页)。当网页url地址中包含ip和服务器所在主机ip一致时,不再跨域。

  1. http请求

http请求同样需要请求这个共用的ip,不能使用服务器设置的主机名,因为我们这里xhr请求的url值使用了设置的host主机名,所以同样是跨域效果,就是浏览器ip地址(用hostname也会映射成ip)和请求的host名的服务器地址产生了跨域(虽然服务器host名会映射成ip地址)。

解决: 很简单,和浏览器的地址栏url值一样,我们直接写ip地址而不是用主机名映射。

》》完成上面两步,现在你将cors插件在plugin.js和config.default.js文件中的配置注释掉,然后再次测试用服务器上的静态资源用xhr向服务器发送get请求,这次你将成功!

虽然可以同域发送get请求,不过我们后面还是将主机名使用进来,配置跨域(cors)不仅是为了我们使用主机名,更多是为了服务器可以接收其他主机发出的请求,即他人可访问。

post参数

xhr发送get请求时需要egg安装并配置cors插件,来处理xhr请求的跨域问题,使用xhr进行post请求同样适用。不过egg作为后端框架,对CSRF攻击进行防御,所有内置了egg-security插件。

因为egg-security插件对CSRF进行防御,所以post请求再次被阻止

Egg 内置的 egg-security 插件默认对所有『非安全』的方法,例如 POST,PUT,DELETE 都进行 CSRF 校验。

  • 因为内置,所以只需要配置即可使用。

config.default.js

...
  config.security = {
    csrf: {
      enable: false,
      ignoreJSON: true,
    },
    domainWhiteList: [ '*' ],
  };
...

同时post请求需要新的xhr请求代码和egg新的router对象接收和controller对象处理。

  • 客户端代码(这里的setRequestHeader是必须要设置的)
        setTimeout(() => {
            let xhr = new XMLHttpRequest();
            xhr.open('post','http://juejinjin.com/post',true);
            xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded ');
            xhr.onload=function(){
                console.log('post响应信息')
                console.log(JSON.parse(xhr.responseText))
            }
            xhr.send("name=juejinjin&age=12");
            xhr.onerror=function(){
                console.log('err')
            }
        }, 200);

关于Content-typesend的不同写法具体在http系列讲解,这里直接使用常用格式。

  • 服务器代码 ( 简写-参考之前代码 )
    router.post('/post', controller.home.postParams);//router.js
    async postParams() {//controller的home.js
        const { ctx } = this;
        console.log(ctx.request.body);
        ctx.body = ctx.request.body;//ctx.body其实是ctx.response.body的简写。
    }
  • 联合get部分客户端发送get和post请求后应该得到的响应信息:

image.png

总结

三种向服务器发送数据的方式,我们主要记忆 参数书写方式 以及服务器如何 获取到对应的数据

  • 路由传参:url+/:id/:name —— ctx.params获取参数集合对象
  • get传参:url+?+id=xx&name=yy —— ctx.query获取参数集合对象
  • post传参:根据content-type发送不同类型数据 —— ctx.request.body获取参数集合对象

重要配置,前面出现我们配置过hostname以及host文件,但是由于ip地址的变化,所以有时候你的egg项目会突然出现报错,而且报错问题会出在@egg-cluster插件和ip地址上。所以如果报错找不到问题,可以尝试注释config对象中cluster属性,或则直接查看ip地址是否改变,然后即时去修改host文件。