Vue 中可以设置代理,但它的代理都是本地 Node 代理,只在开发中用,解决开发过程中跨域之类的问题;
Nuxtjs 则不同,服务端渲染应用部署方式下,代理功能要随页面发布到线上;如果是静态应用部署方式,功能就和 Vue 一样了,只在开发中用;
这里主要讨论服务端渲染应用部署方式下的代理;
0、代理和反向代理
代理这个词生活中还是比较常见的,比如“代理人”,一般指可以帮我们去做某些事情的人;
针对 Web 程序,代理和反向代理大体是这个意思:
代理
主体(主语)是页面;页面让其他程序来帮他获取数据,就叫代理;如果这个“其他程序”是一个服务器,就可以叫他代理服务器;
开发中常常用来解决跨域问题,页面直接请求报跨域错误,让开了跨域的服务器帮页面去获取数据;
反向代理
主体(主语)是服务器;服务器收到一个请求,然后让其他程序(或其他服务器)来处理,最后得到一个处理结果,再响应刚才的请求;
反向代理的一般判断逻辑是通过请求 url 做区分(代理也是如此),比如 a.com 这个服务器接收到两个请求,http://a.com/api/v1/getList 和 http://a.com/api/v2/getList,/api/v1/ 可以让A来处理,/api/v2/ 可以让B来处理;
1、asyncData 做代理
放在 asyncData 中的请求,会自动转换成 node 端调用(代理去调用接口),然后将返回数据设置到 data 属性中,无需特殊处理;
但是,上面的情况只发生在第一次进站内页面或刷新页面的时候,如果还是跳站内页,asyncData 中的请求会以 ajax 形式发送,不再是 node 端调用接口并渲染了(这个我还没搞懂为什么,且并未从官方文档里找到相关解释;暂时的推测是:为了让页面加载更快吧),所以这时候 node 的自动代理不起作用,跨域问题又暴露出来了;
2、@nuxt/proxy 方案
官方有一个 proxy 的库,地址在这里;需要在 nuxt.config.js 中进行配置,一种语法是下面这样:
modules: ['@nuxtjs/proxy',],
proxy: {
'/news': 'https://a.com/api/',
},
上面的配置项,表示 /news/ 开头的请求路径,都代理到 http://a.com/api/ 这个地址上;转换关系为:
/news/getlist ==> http://a.com/api/news/getlist
缺点:只能用于页面ajax异步请求的代理,asyncData 中 node 转发的情况就不适用了;
3、服务端手动判断并转发
一般在创建项目时选了 Server Framework 才会用到;选了框架会生成 server/index.js,具体逻辑就可以写在这里了;
现在有一个接口 http://xxx.com/news/getlist,设计成代理的方式,“页面请求node服务器,node服务器去请求 xxx.com/news/getlist” 这种模式下,页面请求可以加一个前缀用来标识需要 node 代理的接口,比如 /proxy/,node 匹配到 /proxy/ 开头的 path,就进行代理操作;
如果用 Koa 框架,server/index.js 的 start 方法 app.use 一行,做如下改造(用路径前缀 /proxy/ 表示需要代理的接口)
// use 中的函数改为异步 async
app.use(async (ctx) => {
// 本次变动添加代码
let url = ctx.request.url;
if (/\/proxy\//gmi.test(url)) { // 需要本机代理的ajax接口,来源:页面ajax异步调用
let link = url.replace(/\/proxy/gmi, '');
let res = await serverAxios.get(link);
ctx.response.body = res.data;
return;
}
// 默认生成代码
ctx.status = 200
ctx.respond = false // Bypass Koa's built-in response handling
ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
nuxt.render(ctx.req, ctx.res)
})
app.listen(port, host)
如果是用 express,代码大体是这样(同样用路径前缀 /proxy/ 标识代理接口):
const app = express()
const router = express.Router();
router.get('/proxy/', function (req, res, next) {
let url = req.url;
res.json({a:1});
res.end();
});
app.use(router);
前端页面就是这样发请求:
const axios = require('axios');
axios.get('/proxy/news/getlist').then(function(response){
// 响应成功,返回数据
});
*实际项目中,Koa 框架下,Node 服务端代码中,我用了 koa-router 来实现代理请求的匹配 ,yarn dev 时没有问题,可以正常代理转发接口,yarn build、yarn start 之后,页面上的 ajax 请求就 404 了,地址是对的,但 node 端没有匹配该请求,最终也没找到问题,代码是这样的:
const Router = require('koa-router'); // koa 路由中间件
const router = new Router(); // 实例化路由
// 代理 /proxy/api/ 请求,/proxy/ 相当于一个标识符,实际请求是不带的
router.get('/proxy/api/*', async (ctx, next) => {
let link = ctx.request.url.replace(/\/proxy/gmi, '');
let res = await axios.get(link);
ctx.response.body = res.data;
});
自己的测试项目中是没有问题的,对应下面这个提交:
使用的时候注意下吧,如果 build、start 之后 404,可以考虑是否为这种情况;
4、serverMiddleware 方案
serverMiddleware 官方文档在这里;
不要将它与客户端或SSR中Vue在每条路由之前调用的 routes middleware 混淆。
serverMiddleware 只是在 vue-server-renderer 之前在服务器端运行,可用于服务器特定的任务,如处理API请求或服务资产。
nuxt.config.js 中,添加 serverMiddleware 属性,可以为特定 path 指定一个处理文件:
serverMiddleware: [
{path: '/proxy', handler: '@/server/proxy.js'},
],
server/proxy.js 代码:
const serverAxios = require('./serverAxios');
export default async function (req, res, next) {
// req 是 Node.js http request 对象
// res 是 Node.js http response 对象
let res1 = await serverAxios.get(req.url);
res.setHeader('Content-Type','application/json;charset=utf-8');
res.end(JSON.stringify(res1.data));
// next是一个调用下一个中间件的函数
// 如果您的中间件不是最终执行,请不要忘记在最后调用next!
// next();
}
前端页面还是这样发请求:
const axios = require('axios');
axios.get('/proxy/news/getlist').then(function(response){
// 响应成功,返回数据
});
和 “3、服务端手动判断并转发” 方案不同的是,这里的处理程序 server/proxy.js 中,传过来的 req.url 不带 /proxy 前缀,已经自动去掉了,无需再手动处理;
3和4的方案扩展性更强,可以在转发之前做一些操作;
比如接口需要进行一些验证,才能返回数据,可以在转发的时候带上token,