接口代理的概念
正向代理
代理客户端去请求服务器,隐藏了真实客户端,服务器并不知道真实的客户端是谁。
反向代理
反向代理隐藏了真正的服务端,就像你每天使用百度的时候,只知道敲打www.baidu.com就可以打开百度搜索页面,但背后成千上万台百度服务器具体是哪一台为我们服务的,我们并不知道。我们只知道这个代理服务器,它会把我们的请求转发到真实为我们服务的那台服务器那里去。
接口代理的简单实现
一般来说,前端开发者多用正向代理的接口代理方式。所以实现的时候需要一个服务端和一个客户端。
客户端
客户端向服务端发起请求:
import axios from 'axios'
// 以/github/开头的url被认为是需要代理的接口
axios
.get('/github/search/repositories?q=react')
.then(res => console.log(res))
服务端
服务端接收到客户端的请求之后,根据路径或其他的参数进行真正的请求路径的拼接,设置相关的字段之后,由服务器发起对外网服务器的请求,再由服务器返回外网服务器返回的数据给客户端。
// proxy.js
/**
* github接口代理,防止因请求过多而被禁用ip,并且添加认证头部
*/
const githubBaseUrl = 'https://api.github.com';
const axios = require('axios');
module.exports = server => {
server.use(async (ctx, next) => {
const path = ctx.path;
// path以/github/开头,则被认为是一个需要代理的接口
if (path.startsWith('/github/')) {
// 获取当前的token和token_type
const githubAuth = ctx.session.githubAuth;
// 对应的github接口地址
const githubPath = `${githubBaseUrl}${ctx.url.replace('/github/', '/')}`;
const token = githubAuth && githubAuth.access_token;
// 根据token的有无增加字段
let headers = {};
if (token) {
headers['Authorization'] = `${githubAuth.token_type} ${token}`
}
try {
const res = await axios({
method: 'get',
url: githubPath,
headers
});
if (res.status === 200) {
// 返回数据
ctx.body = res.data;
// 设置响应头
ctx.set('Content-Type', 'application/json')
} else {
ctx.status = res.status;
ctx.body = {
success: false,
};
ctx.set('Content-Type', 'application/json')
}
} catch (e) {
ctx.body = {
success: false,
};
ctx.set('Content-Type', 'application/json')
}
} else {
await next()
}
})
};
// server.js
const Koa = require('koa');
const Router = require('koa-router');
const server = new Koa();
const router = new Router();
server.use(router.routes());
const proxy = require('./proxy');
proxy(server)
补充
接口代理正常来讲是没问题的,但是针对客户端渲染和服务端渲染的代理方式是不一样的。因为客户端渲染时,请求代理服务器会自动加上http://localhost
的路径,解释到服务端的路径为http://127.0.0.1:80
,这样的话就存在跨域问题,所以我们需要分开设置服务端的代理请求和客户端的代理请求。
客户端的接口代理
client.js
:client发起对githubAPI发起请求,不过我们只是将发起请求的一些参数传递到request方法。由request方法判断服务端和客户端的请求。
const {request} = require('../lib/api');
const Index = (props) => {
console.log(props);
return (
<span>index</span>
)
};
Index.getInitialProps = async (ctx) => {
let res = await request({
url: '/search/repositories?q=react',
}, ctx.req, ctx.res);
return {
data: res.data
}
};
api.js
const axios = require('axios');
// 判断是否是服务端的接口代理
const isServer = typeof window === 'undefined';
/**
* request用于客户端的接口代理api,已经分服务端和客户端处理请求
* @param method 请求类型
* @param url 请求路径
* @param data 请求发送的数据
* @param req node原生环境req
* @param res node原生环境res
* @returns {Promise<*>}
*/
async function request({method = 'GET', url, data = {}}, req, res) {
// 服务端的情况
if (isServer) {
let headers = {};
// 取得session中得githubAuth
const session = req.session;
const githubAuth = session && session.githubAuth;
// 根据access_token得有无添加header字段
if (githubAuth && githubAuth.access_token) {
headers['Authorization'] = `${githubAuth.token_type} ${githubAuth.access_token}`
}
// 根据已处理好得参数,请求Github
return await requestGithub(method, url, data, headers)
} else {
// 如果是客户端得得接口代理,则直接进行/github/xxx的axios请求,
// 当进行这个请求时会被/github/xxx的拦截器拦截到,进而发起真正的api请求
return await axios({
method,
// 拼接url
url: `/github${url}`,
data
})
}
}
const githubBaseUrl = `https://api.github.com`;
/**
* 请求github
* @param method 方法
* @param url 路径
* @param data 数据
* @param headers 请求头
* @returns {Promise<void>}
*/
async function requestGithub(method, url, data, headers) {
if (url) {
return await axios({
method,
// 拼接url
url: `${githubBaseUrl}${url}`,
data,
headers
})
} else {
throw Error('url must required')
}
}
module.exports = {
request, requestGithub
};
proxy.js
/**
* github接口代理,防止因请求过多而被禁用ip,并且添加认证头部
*/
const {requestGithub} = require('../lib/api');
const githubBaseUrl = 'https://api.github.com';
const axios = require('axios');
module.exports = server => {
server.use(async (ctx, next) => {
const path = ctx.path;
// 当客户端的请求被拼接上/github/的url时,被该api拦截,进行请求头的设置,然后再请求github
if (path.startsWith('/github/')) {
const {method} = ctx;
const githubAuth = ctx.session && ctx.session.githubAuth;
// 对应的github接口地址
const githubPath = `${githubBaseUrl}${ctx.url.replace('/github/', '/')}`;
const token = githubAuth && githubAuth.access_token;
// 根据token的有无增加字段
let headers = {};
if (token) {
headers['Authorization'] = `${githubAuth.token_type} ${token}`
}
const res = await requestGithub(method, githubPath, {}, headers);
ctx.status = res.status;
ctx.body = res.data;
} else {
await next()
}
})
};