两种简单的Node.js转发服务实现

在很多项目中,都会涉及权限校验接口。如果有第三方的接口请求,我们会让后端帮忙做一次转发。但是对于一些简单的纯前端项目而言,我们可以自己搭建一个简单的 Node.js 服务来实现接口请求的转发。


背景


背景有点长,讲述了场景和思考的过程,不感兴趣的同学可以直接跳到下一部分。

最近在研究一个3D模型展示库,而这个第三方的模型库在初始化时,需要使用授权的token。


这个模型库提供了 2 种方式来获取token:


1、通过模型库的配置后台直接生成,但是如果连续12个小时没有使用这个token,之前的token就会过期,需要重新从配置后台进行获取。


2、可以通过模型库提供的服务器端接口,根据应用的 appKey、appSecret 实时获取访问的token。


如果采用第一种方式,我们就必须保证每12个小时内,都需要使用当前token访问一次页面,这种方案一点也不优雅,直接 pass。


第二种方案是获取通过访问api获取,我首先想到的是能不能在前端异步获取。


先说下前提,这个项目只是实现一个小demo,不用担心 appkey、secret 的泄露。所以我考虑能够直接在页面中访问对应的接口,于是有了以下实现:(项目基于umijs初始化)



import { request } from 'umi';const Authorization = btoa('appKey:appSecret');export async function getOauthToken():Promise<string> { return new Promise((resolve) => { request('/oauth2/token', { prefix: 'https://api.xxx.com', method: 'POST', headers: { Authorization: `Basic ${Authorization}`, }, }).then((res) => { if (res.code === 'success') { resolve(res.data.token); } }); });}

实际上就是请求一个接口,获取token。但是请求有CORS的报错:

CORS:全称"跨域资源共享"(Cross-origin resource sharing)。

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

出于安全性,浏览器限制脚本内发起的跨源HTTP请求。例如,XMLHttpRequest和Fetch API遵循同源策略。这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。

前后端分离的场景下, CORS 是一个很常见的问题。一般的解决方案是在服务端设置返回头:Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers 等字段。


但这种服务器端的设置不适合上面的场景,因为我们用的是第三方的服务,没法设置返回头。这种情况使用一个中间服务做转发,因为服务器端发起的请求不存在同源策略的限制。


UmiJS SSR


因为项目基于umijs进行的初始化,我起初是想直接使用服务器端渲染(UmiJS SSR),umi提供了一个方法 getInitialProps ,可以进行页面级数据获取。


具体的改造如下:



// .umirc.ts 中增加 ssr 配置,开启服务端渲染
{ ssr: {}}
// 入口文件增加 getInitialProps 方法
import fetch from 'umi-request';import { isBrowser } from 'umi';
function IndexPage(props) { const { data } = props; return ( <div> testToken: {JSON.stringify(data)} </div> );}
IndexPage.getInitialProps = async () => { if (isBrowser) { const Authorization = btoa('appKey:appSecret'); const res = await fetch.post('https://api.xxx.com/oauth2/token', { headers: { Authorization, } }) return { data: res } } return { data: '' }}export default IndexPage;

UmiJS SSR的更多用法可以参考官方demo:https://github.com/umijs/umi-example-ssr


这种修改可能是最简单的一种修改方式,只需要修改配置文件,在提供的 getInitialProps 接口加上接口请求。而且不用担心 appKey 和 appSecret 会对外暴露,我们看下页面返回的html,返回的信息中没有 getInitialProps 方法中声明 Authorization 变量。



基于Koa2 的 Node.js 服务


上面的 UmiJS SSR 的方案,限制了脚手架和框架,有一定的技术局限性。其实更常见的解决方案是自己搭建一个Node.js服务,下面将介绍怎样基于Koa2,搭建一个用来做转发的Node.js服务。


先看看 Koa2 的介绍:


Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。


初始化项目


先按照以下步骤初始化 Koa2 项目

  • 安装 Node.js 环境

  • npm init 初始化项目

  • 安装 koa 以及其他依赖 :npm install koa koa-router

  • 项目根目录下创建入口文件index.js


const Koa = require('koa');// 引入koa-routerconst Router = require('koa-router');
const app = new Koa();/* 创建路由实例 */const router = new Router();
/* 制定路由规则 */router.get('/', (ctx) => { ctx.body = 'Hello World';})
app/* 在Koa应用实例上应用制定好的路由规则 */.use(router.routes()).use(router.allowedMethods())
app.listen(8081, () => { console.log('listening on port 8081 ...');});
  • 运行入口文件:node index.js

  • 浏览器访问 http://localhost:8081/ 可以看到页面上展示了熟悉的 Hello World


代理请求


接着就完成剩余的转发工作,首先安装axiosnpm install axois),然后进行接口的请求和转发:

./index.js

const Koa = require('koa');const Router = require('koa-router');const requestToken = require('./requestToken');
const app = new Koa();/* 创建路由实例 */const router = new Router();/* 制定路由规则 */
router.get('/api/token', async (ctx) => { const result = await requestToken() ctx.body = result;})
app/* 在Koa应用实例上应用制定好的路由规则 */.use(router.routes()).use(router.allowedMethods())
app.listen(8081, () => { console.log('listening on port 8081 ...');});

./requestToken.js

const axios = require("axios");// 文档中要求Authorization需要按规则进行拼接,并转成Base64const Authorization = new Buffer('appKey:appSecret').toString('base64');
module.exports = async () => { return new Promise((resolve) => { axios.post( "https://api.xxx.com/oauth2/token", {}, { headers: { Authorization: `Basic ${Authorization}`, }, } ).then((res) => { if (res.data.code === "success") { resolve(res.data.data.token); } }); });};

执行node index.js,访问 http://localhost:8081/api/token,可以看到返回的 token 。


如果是本地开发,前端页面的dev-server端口不一样,访问同样会遇到 CORS 的报错。我们可以直接在 ./index.js 中设置 /api/token 接口的返回头:


router.get('/api/token', async (ctx) => {    const result = await requestToken()    // 允许来自所有域名请求    ctx.set("Access-Control-Allow-Origin", "*");    // 设置所允许的HTTP请求方法    ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET");    // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段.    ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");    const result = await requestToken()    ctx.body = result;})


部署


接着简单介绍下如何在服务器端进行部署。


首先是在服务器端拉取代码,并安装依赖(包括Node.js环境)。


然后在服务端启动服务,这时就不推荐用 node index.js 命令来启动服务了,因为我们需要在后台运行服务,不然启动后断开连接,这个node服务也会直接停止。


我们可以运行 nohup node index.js,让服务在后台运行。


更推荐的是使用 forerver 或者 pm2 进行 Node.js 服务进程的管理。


以 pm2 为例:


$ npm install pm2 -g #安装$ pm2 start index.js #启动$ pm2 ls # 查看服务的进程数和运行状态等信息

上面完成了服务器上启动 Node.js 服务,接着还有很重要的一步,就是配置转发。

这是我新增的 Nginx 配置:

location = /api/token {    add_header Access-Control-Allow-Origin *;    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';proxy_pass http://localhost:8081;}

当然如果在服务中已经设置了Access-Control-Allow-Origin等返回头信息,就可以去掉上面的 add_header 配置。