proxy,顾名思义,就是代理.市面上的代理软件可太多了,其中以nginx最为使用广泛.今天我们就以koajs通过nginx代理之后,如何才能有效的使用proxy为题说明究竟如何才能更好的使用proxy。
首先我们查看官网针对proxy的说明:
app.proxy
当真正的代理头字段将被信任时app.proxyIpHeader
代理 ip 消息头, 默认为X-Forwarded-For
app.maxIpsCount
从代理 ip 消息头读取的最大 ips, 默认为 0 (代表无限)
大白话就是只有设置proxy为true,koa才会真正的使用代理应该使用的方式去进行操作。那么我们使用代理的意义是什么呢?
我个人认为使用代理的作用有三:
1、负载均衡服务器,用以提升整体服务器的使用效率,避免单点故障导致的整体宕机。
2、缓存服务器,针对静态文件、极少变化的动态内容进行缓存,提升访问速度。
3、避免真实服务器ip暴露,减少网络攻击的风险。
既然代理的作用很大,那么我们可以认为,访问量相对较大的网站都会部署,那么我们就认为代理必定存在。这对我们的编码也会造成改变,我们要深刻理解代理服务器增加以后,编码人员究竟如何才能有效的获取我们想要得到的数据。
我们以例子来进行分析,我们网站要增加一个功能,针对相同IP的访问频率进行限制。针对这个需求,我们必须获取到访问者的真实IP才可能实现,因为针对现在局域网部署,各种不同网络部署的情况,我们必须得到真实IP,才能避免误伤。
那么问题来了,我们如何才能获取到用户的真实IP呢?
如果不使用代理,我们的代码应该是这样。
const Koa = require("koa");
const app = new Koa();
const koaRouter = require("koa-router");
const router = new koaRouter();
const compress = require("koa-compress");
app.on("error", (err, ctx) => { console.log(arguments.length); console.log("this is custom error");})/
// 中间件启用压缩
app.use(compress({}));
router.get("/", (ctx) => {
// 获取客户端真实IP地址
let remoteAddress = ctx.req.socket.remoteAddress;
ctx.body = remoteAddress;});
router.post("/", async (ctx) => { ctx.body = "this is body ";})
app.use(router.routes());app.use(router.allowedMethods());
app.listen(3000);
复制代码
我们通过使用ctx.req.socket.remoteAddress 来获取真实的IP地址。针对我本机,获取到的值为::1。 这代表我启用了IP6,返回的IPv6的回环地址。我禁用了IP6,但我获取到的值为::ffff:127.0.0.1,仍旧不是我们认为的一个固定ip地址。因此,我们必须针对性的处理这些。
我们增加代理nginx,nginx使用端口8080,监听端口为我们的koa程序3000.我们应该如何获取客户端真实ip呢?
const Koa = require("koa");const app = new Koa();
const koaRouter = require("koa-router");
const router = new koaRouter();const compress = require("koa-compress");
app.on("error", (err, ctx) => { console.log(arguments.length); console.log("this is custom error");})/// 中间件启用压缩app.use(compress({}));//开启代理
app.proxy = true;router.get("/", (ctx) => {
// 获取客户端真实IP地址 使用nodejs方式进行获取客户端ip
let remoteAddress = ctx.req.socket.remoteAddress;
// 在不启用ip6的时候,该值为127.0.0.1 // 启用ip6,该值仍然为127.0.0.1
let proxyRemoteAddress = ctx.request.ip;
ctx.body = remoteAddress + "\r\n" + proxyRemoteAddress;});
router.post("/", async (ctx) => { ctx.body = "this is body ";})
app.use(router.routes());app.use(router.allowedMethods());
app.listen(3000);
复制代码
在使用nginx,koajs开启代理proxy=true时,可以获取到对应的ip地址。那么究竟是如何获取到真实IP地址的呢?让我们打开koajs源码一探究竟。
/** * When `app.proxy` is `true`, parse * the "X-Forwarded-For" ip address list. * * For example if the value was "client, proxy1, proxy2" * you would receive the array `["client", "proxy1", "proxy2"]` * where "proxy2" is the furthest down-stream. * * @return {Array} * @api public */ get ips() { const proxy = this.app.proxy; const val = this.get(this.app.proxyIpHeader); let ips = proxy && val ? val.split(/\s*,\s*/) : []; if (this.app.maxIpsCount > 0) { ips = ips.slice(-this.app.maxIpsCount); } return ips; }, /** * Return request's remote address * When `app.proxy` is `true`, parse * the "X-Forwarded-For" ip address list and return the first one * * @return {String} * @api public */
get ip() {
if (!this[IP]) {
this[IP] = this.ips[0] || this.socket.remoteAddress || ''; }
return this[IP]; },
复制代码
在koajs源码request.js 中,我们看到获取ip的方式,我们来逐行分析下。
get ips() {
// 查看是否Koajs启用了proxy const proxy = this.app.proxy;
// 首先从header中获取this.app.proxyIpHeader的值,
this.app.proxyIpHeader 的默认值X-Forwarded-For
即从消息头header中获取key为X-Forwarded-For 的值
const val = this.get(this.app.proxyIpHeader);
//如果使用代理,那么将获取到的值进行拆分 否则设置成为空数组[]
let ips = proxy && val ? val.split(/\s*,\s*/) : [];
// 如果设定了最大ip个数长度 那么进行截取 以符合设定长度
if (this.app.maxIpsCount > 0) {
ips = ips.slice(-this.app.maxIpsCount); }
return ips; },
复制代码
查看get ips() 方法,其实就是获取消息头X-Forwarded-For的值,并进行拆分成数组形式,如果未设定preoxy=true,那么该方法永远返回空数组。
get ip() {
if (!this[IP]) {
this[IP] = this.ips[0] || this.socket.remoteAddress || ''; }
return this[IP]; }
复制代码
查看获取ip的方法,你会惊讶的发现,它首先获取代理所有的ip列表,并且取第一个作为客户端真实ip。如果为空,则取this.socket.remoteAddress ,即不设置代理时我们获取客户端ip的方式。
这就带来一个疑问,为什么取第一个ip作为客户端真实ip?
其实这个主要代理服务器的作用,每次经过一个代理,代理服务器都会追加上一个服务器ip,那么到达我们真正处理程序的时候,获取到的代理ip值应该为clientip;proxy1;proxy2 …
所以我们认定第一个值即为我们的真实访问客户端ip地址。
总结:
1、为了保证程序的运行正确,我们应该保证proxy=true。
2、尽量使用koajs内置的获取客户端ip的方式,而不是自己编码,因为网络以及代理的存在,获取ip的方式也会不同。
3、代理会在消息头X-Forwarded-For增加前一个服务器的ip,这也就是我们使用这个值获取的原因。当然这些都是可以在代理配置中进行更改,但当前这是一个约定的配置,毕竟现在讲究约定大于配置。
4、了解koajs源码真的非常有效。但我们仍然需要了解代理服务器配置、消息头、消息头格式等,学不完的东西。
本次我们说明了增加代理对获取客户端ip的影响,实际上代理的存在对koajs很多获取数据的方式都有影响,例如host、hostname等等。
::1 为什么会出现这种地址?
知其然,更要知其所以然。
课后作业:X-Forwarded-For消息头、X-Forwarded-Host消息头格式,如何绕过X-Forwarded-For进行真实ip篡改。
还是那句话,代码调出来的,更是改出来的,更是我们知识深度以及广度的体现。