上一篇文章讲解了几种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格式信息):
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,同时我们在服务器运行的终端可以看得到下面的图片里的打印信息。
--处理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在同域的情况可以随意使用,所以我们要分析我们项目目前存在的跨域以及如何修改成同域。
- 网页和服务器
网页的地址就是浏览器的地址栏中显示的内容(隐藏协议和默认端口),服务器的地址就是本机。我们前面一篇文章中将ip地址换成主机名,导致网页的地址变成主机名,然后映射成本地的ip地址。于是同源策略就认为主机名(juejinjin.com)和服务器ip地址不一样,所以产生跨域,xhr请求报错。
解决: 修改网页的主机名部分,改成本机ip地址(因为服务器public静态资源就在ip下,启动服务器就可以使用ip直接请求得到静态网页,用来发起get请求的静态网页)。当网页url地址中包含ip和服务器所在主机ip一致时,不再跨域。
- 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-type和send的不同写法具体在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请求后应该得到的响应信息:
总结
三种向服务器发送数据的方式,我们主要记忆 参数书写方式 以及服务器如何 获取到对应的数据 。
- 路由传参:
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文件。