Koajs成长之路–如何有效的使用proxy

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篡改。

还是那句话,代码调出来的,更是改出来的,更是我们知识深度以及广度的体现。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享