Kibana 4.0 服务器端源码简要分析说明

1.概述

Kibana的源代码位于Git目录中 src下。这其中有两个目录kibanaserver

  • kibana目录中是浏览器端源码
  • server目录中是服务器端源码

本文以下对server中的内容作以简单说明。

server目录结构如下图所示:

输入图片说明

这些内容可以从功能上作以下分类:

  • 启动引导:bin
  • 主程序:index.js
  • Express框架Server:app.js
  • Express框架路由模块:routes
  • 全局配置:config
  • 独立的工具模块:lib
  • 视图模板:views

dev目录中的内容是开发期关于Express的相关临时设置。

2.启动引导

Kibana其实有两种启动形式,一种是在开发期,直接在git目录下启动,另一种是在发布之后。不过,无论哪种形式,启动引导的结果都是执行src/server/index.js这一主程序入口。两种形式的区别在于引导程序不同,以及相关环境变量配置不同。

2.1 开发期直接在git目录下启动引导

在git目录下直接执行grunt dev即可启动kibana。关于该grunt任务的详细解释请见《Kibana源码工程结构与相关技术和工具》。这里仅关注该任务中的最后一个子任务——kibana_server,任务定义如下:

module.exports = function (grunt) {
  grunt.registerTask('kibana_server', function (keepalive) {
    var done = this.async();
    var config = require('../src/server/config');
    config.quiet = !grunt.option('debug') && !grunt.option('verbose');
    if (grunt.option('port')) {
      config.port = config.kibana.port = grunt.option('port');
    }
    var server = require('../src/server');

    server.start(function (err) {
      if (err) return done(err);
      grunt.log.ok('Server started on port', config.kibana.port);
      if (keepalive !== 'keepalive') done();
    });
  });
};

可以看到:

  • 配置项是从src/server/config/index.js中获取的
  • server对象是从src/server/index.js中获取的,server.start方法的调用启动了kibana的主程序

2.2 发布版本中的启动引导程序

以windows系统发布版本为例。kibana的启动脚本是位于kibana/bin目录下的kibana.bat文件。其内容如下:

@echo off

SETLOCAL

set SCRIPT_DIR=%~dp0
for %%I in ("%SCRIPT_DIR%..") do set DIR=%%~dpfI

set NODE=%DIR%\node\node.exe
set SERVER=%DIR%\src\bin\kibana.js
set NODE_ENV="production"
set CONFIG_PATH=%DIR%\config\kibana.yml

TITLE Kibana Server 4.1.3-snapshot

"%NODE%" "%SERVER%" %*

:finally

ENDLOCAL

可以看到,该脚本中指定了

  • 设置引导程序为:kibana/src/bin/kibana.js
  • 配置:运行环境为production
  • 配置:配置文件为kibana/config/kibana.yml

kibana/src/bin/kibana.js的内容简要如下

#!/usr/bin/env node

//...

var env = (process.env.NODE_ENV) ? process.env.NODE_ENV : 'development';

//...

program.description('Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch.');
program.version(package.version);
program.option('-e, --elasticsearch <uri>', 'Elasticsearch instance');
program.option('-c, --config <path>', 'Path to the config file');
program.option('-p, --port <port>', 'The port to bind to', parseInt);
program.option('-q, --quiet', 'Turns off logging');
program.option('-H, --host <host>', 'The host to bind to');
program.option('-l, --log-file <path>', 'The file to log to');
program.option('--plugins <path>', 'Path to scan for plugins');
program.parse(process.argv);

// This needs to be set before the config is loaded. CONFIG_PATH is used to
// override the kibana.yml config path which gets read when the config/index.js
// is parsed for the first time.
if (program.config) {
  process.env.CONFIG_PATH = program.config;
}

// This needs to be set before the config is loaded. PLUGINS_PATH is used to
// set the external plugins folder.
if (program.plugins) {
  process.env.PLUGINS_FOLDER = program.plugins;
}

// Load the config
var config = require('../config');

if (program.elasticsearch) {
  config.elasticsearch = program.elasticsearch;
}

if (program.port) {
  config.port = program.port;
}

if (program.quiet) {
  config.quiet = program.quiet;
}

if (program.logFile) {
  config.log_file = program.logFile;
}

if (program.host) {
  config.host = program.host;
}


// Load and start the server. This must happen after all the config changes
// have been made since the server also requires the config.
var server = require('../');
var logger = require('../lib/logger');
server.start(function (err) {
  // If we get here then things have gone sideways and we need to give up.
  if (err) {
    logger.fatal({ err: err });
    process.exit(1);
  }

  if (config.kibana.pid_file) {
    return fs.writeFile(config.kibana.pid_file, process.pid, function (err) {
      if (err) {
        logger.fatal({ err: err }, 'Failed to write PID file to %s', config.kibana.pid_file);
        process.exit(1);
      }
    });
  }

});

其中内容主要是

  • 判断了process.env.NODE_ENV环境变量
  • kibana/config/index.js中获取config对象(注意:kibana/config/index.js文件就是源码目录下src/server/config/index.js文件,文件路径的变化发生在构建任务grunt build期间,详情参见《Kibana源码工程结构与相关技术和工具 》)
  • 处理命令行参数信息
  • kibana/src/index.js中获取获取server对象,并且调用server.start方法启动程序(注意:该index.js文件就是源码目录下的src/server/index.js文件,文件路径的变化发生在构建任务grunt build期间,详情参见《Kibana源码工程结构与相关技术和工具 》)

3.读取配置

配置读取在程序引导过程中,由一句如此的代码完成:

//git目录下直接启动
var config = require('../src/server/config');
//发布版本中,由kibana.js启动
var config = require('../config');

两种形式下,虽然路径有所改变,但其实执行的是同一份代码,以git源码目录为准是:src/server/config/index.js。(文件路径改变在grunt build时期)

在该脚本中,主要是区别了两种形势下,从不同的目录中加载kibana.yml文件,相关代码如下:

//如果系统环境变量中设定了yml文件位置,则以设定为准,否则加载相同目录下的kibana.yml文件(发布版本中的系统启动脚本中设定了此变量)
5: var configPath = process.env.CONFIG_PATH || path.join(__dirname, 'kibana.yml');
6: var kibana = yaml.safeLoad(fs.readFileSync(configPath, 'utf8'));

还区别了,两种形式下,Web静态资源目录的位置,相关代码如下:

30: // Check if the local public folder is present. This means we are running in
31: // the NPM module. If it's not there then we are running in the git root.
32: var public_folder = path.resolve(__dirname, '..', 'public');
33: if (!checkPath(public_folder)) public_folder = path.resolve(__dirname, '..', '..', 'kibana');

4.主程序 src/server/index.js

为外层的引导程序包装了server对象,相关源码如下:

line 81:
module.exports = {
  server: server,
  start: function (cb) {
    return initialization()
      .then(start)
      .then(function () {
        cb && cb();
      }, function (err) {
        logger.error({ err: err });
        if (cb) {
          cb(err);
        } else {
          process.exit();
        }
      });
  }
};

被包装的server对象是一个http服务对象,其创建过程如下:

/**
 * Create HTTPS/HTTP server.
 */
  //...

35: server = http.createServer(app);

  //...

这其中前后还包括了关于应用日志的设置,还有关于是否开启https服务的判断。不过普通地创建http服务对象的代码如上句所示。那么app对象是哪里来的呢?

5: var app = require('./app');

在同级目录的app.js文件中定义了app对象。

5.src/server/app.js与app对象

前文提到了app.js文件中定义了app对象,以供index.js中创建http server对象。

app对象是使用express框架定义的服务对象。参考Express项目主页上更多信息

app.js的源码中可以看到,其中为http server定义了:

  • 日志中间件
  • cookie中间件
  • 认证中间件
  • 请求报文内容解析中间件
  • 等等……

并且,挂载了两个路由模块:

  • routes/index.js:处理路径/configGET请求
  • routes/proxy.js:处理路径/elasticsearch的请求

5.1 /elasticsearch请求代理

routes/proxy.js中的内容表明了,发送至/elasticsearch的请求是如何被处理的。处理过程很清晰,如下所示:

第一步:收集请求数据:

// We need to capture the raw body before moving on
router.use(function (req, res, next) {
  var chunks = [];
  req.on('data', function (chunk) {
    chunks.push(chunk);
  });
  req.on('end', function () {
    req.rawBody = Buffer.concat(chunks);
    next();
  });
});

第二步:校验请求内容是否合法:

router.use(function (req, res, next) {
  try {
    validateRequest(req);
    //console.info(req);
    return next();
  } catch (err) {
    logger.error({ req: req }, err.message || 'Bad Request');
    res.status(403).send(err.message || 'Bad Request');
  }
});

第三步:准备转发请求内容:

  var uri = _.defaults({}, router.proxyTarget);

  // Add a slash to the end of the URL so resolve doesn't remove it.
  var path = (/\/$/.test(uri.path)) ? uri.path : uri.path + '/';
  path = url.resolve(path, '.' + req.url);

  if (uri.auth) {
    var auth = new Buffer(uri.auth);
    req.headers.authorization = 'Basic ' + auth.toString('base64');
  }

  var options = {
    agent: router.proxyAgent,
    url: uri.protocol + '//' + uri.host + path,
    method: req.method,
    headers: _.defaults({}, req.headers),
    strictSSL: config.kibana.verify_ssl,
    timeout: config.request_timeout
  };

  options.headers['x-forward-for'] = req.connection.remoteAddress || req.socket.remoteAddress;
  options.headers['x-forward-port'] = getPort(req);
  options.headers['x-forward-proto'] = req.connection.pair ? 'https' : 'http';

  // Only send the body if it's a PATCH, PUT, or POST
  if (req.rawBody) {
    options.headers['content-length'] = req.rawBody.length;
    options.body = req.rawBody.toString('utf8');
  } else {
    options.headers['content-length'] = 0;
  }

  // To support the elasticsearch_preserve_host feature we need to change the
  // host header to the target host header. I don't quite understand the value
  // of this... but it's a feature we had before so I guess we are keeping it.
  if (config.kibana.elasticsearch_preserve_host) {
    options.headers.host = router.proxyTarget.host;
  }

  // Create the request and pipe the response
  console.info('options//');
  console.info(options);

第四步:向ES转发请求,并将请求响应直接对接到用户请求的响应上:

  var esRequest = request(options);

  esRequest.on('error', function (err) {
    logger.error({ err: err });
    var code = 502;
    var body = { message: 'Bad Gateway' };

    if (err.code === 'ECONNREFUSED') {
      body.message = 'Unable to connect to Elasticsearch';
    }

    if (err.message === 'DEPTH_ZERO_SELF_SIGNED_CERT') {
      body.message = 'SSL handshake with Elasticsearch failed';
    }

    body.err = err.message;
    if (!res.headersSent) res.status(code).json(body);
  });

  //将请求响应直接对接到用户请求响应上
  esRequest.pipe(res);
});

以上,共四大步骤。

另外,这个脚本中的29行代码如下:

if (app.get('env') === 'development') {
  require('./dev')(app);
}

该段意思在于,通过var app = express()获得app对象后,为了适应开发期的目录结构,刻意做了临时的设置变更,这些设置变更将在src/server/dev/index.js中完成。

6.总结

总体上,kibana的服务端充当了ES查询代理角色。附加地做了配置管理和一些基本的认证和日志工作。

转载于:https://my.oschina.net/yumg/blog/517110

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值