Appium源码分析(1)-程序的入口

Appium源码项目的目录结构分析 一文中我们描述了appium项目的目录结构,从中我们了解到appium server的源码文件为bin和lib两个文件夹,但是bin目录为可执行文件,所以我们不研究这个文件夹,直接进入lib目录下就行,通过package.json里main字段我们了解程序的入口文件为lib/server/main.js,ok我们从这个文件开始。

main.js

我们一步一步来分析这个文件。我采用的是eclipse的debug方式来debug我们的程序。
首先我们以debug-brk的方式启动程序:

E:\appium>node --debug-brk=9222 .
Debugger listening on port 9222

ok,这个时候程序会以debug模式启动,监听9222的端口,且会停留在源文件的第一行等待调试。ok,这个时候我们在eclipse中启动V8调试

这里写图片描述

点击Debug后,进入调试模式:

这里写图片描述

程序停留在main.js文件的第一行,这个时候我们会发现这个main.js比我们直接打开main.js多了一行代码:

(function (exports, require, module, __filename, __dirname)

这个是nodejs内部的加载机制,它会每一个本地文件包裹上这么一个函数,只要知道这些就行,其他的和正常打开该文件的代码都是一样的。我们现在进入正题。

严格模式

在nodejs中有一套规范叫严格模式,它是前人总结的一套nodejs程序编写规范,旨在提高node.js代码的可读性以及降低维护成本等。通常在文件的开头用下面语句标识:

"use strict";

初始化

var parser = require('./parser.js')()
  , logFactory = require('./logger.js')
  , logger = null
  , args = null
  , fs = require('fs')
  , path = require('path')
  , noPermsCheck = false;

require('colors');

上面的语句为初始化模块、变量的语句,以./开头的是本地模块,还有一种是核心模块,就是不以/标识的模块,比如上面的fs、path、colors模块。各个变量的意义:

变量名意义
parser加载自本地模块,是一个参数解析器,我们会在parser.js中详细介绍
logFactorylog工厂类
loggerlog器
args参数集合
fs文件模块,核心模块
path路径模块,核心模块
noPermsCheckboolean值,是否省略权限检查,默认为false,需要检查权限

解析命令行参数

process.chdir(path.resolve(__dirname, '../..'));

上面的代码是将当前运行的路径提升到根目录,也就是appium的根目录。process是一个全局变量,所以不需要通过require来加载,process.chdir()方法用来改变工作目录,path.resolve()方法是将当前目录项上倒退2个级别,也就是appium项目的根目录。

if (require.main === module) {
  args = parser.parseArgs();
  noPermsCheck = args.noPermsCheck;
  logFactory.init(args);
}

require.main标识的是启动脚本的模块,也就是你在命令行中node命令后面跟的模块,比如我们这个地方require.main就是lib/server/main.js。而module与文件/目录是一一对应的关系,当前文件中module自然指代该文件的结构。这样说法可能有点抽象,我们来看看module的变量:

这里写图片描述

里面可以看出当前module的filename值为lib/server/main.js,其还含有两个子module,一个lib/server/parser.js和lib/server/logger.js。这两个刚好是我们在初始化的时候通过require加载的模块。所以我们应该能够得出这个module一定程度上能够反映出各个模块之间的依赖关系。
那么上面的require.main==module就是看是否是程序的入口程序,如果是的话,就需要解析参数。解析参数用的就是main.js同级目录下的parse.js模块来解析的,我们会在下一篇的博客中介绍。解析完成后,会给变量args,noPermsCheck 赋值,且初始化log工厂。

权限检查

logger = logFactory.get('appium');

if (!noPermsCheck) {
  ......
  }

首先初始化变量logger,我们就拥有了log器。然后判断是否需要进行权限的检查,由于我们的noPermsCheck变量为false,所以是需要检查的。那么进入到检查代码块:

var appiumPermStat = fs.statSync(path.resolve(__dirname,
                                                '../../package.json'));
  var launchCmd = (process.env.SUDO_COMMAND || "").toLowerCase();
  var isWindows = require('appium-support').system.isWindows();

上面的语句初始化3个局部变量并赋值:

appiumPermStat:首先获得package.json文件对象。

launchCmd:判断系统环境下SUDO_COMMAND命令是否为undefined或null,如果不是这两个值,那么该值就为SUDO_COMMAND的值,否则该值就为空字符串(”“),最后再将字符串转化为小写的。

isWindows:判断是否是windows系统,调用的是第三方模块appium-support的方法。

上面3个对象赋值以后,就会判断当前环境是否具有用户权限,但是该检查只在unix和linux环境下才会做。

 if (
    !isWindows &&
    // Appium should be run by user who owns files in Appium installation directory
    appiumPermStat.uid !== process.getuid() &&
    // authorize* commands could be run using sudo
    !launchCmd.match(/authorize/)
  ) {
    logger.error("Appium will not work if used or installed with sudo. " +
                 "Please rerun/install as a non-root user. If you had to " +
                 "install Appium using `sudo npm install -g appium`, the " +
                 "solution is to reinstall Node using a method (Homebrew, " +
                 "for example) that doesn't require sudo to install global " +
                 "npm packages.");
    process.exit(1);
  }

1.上面的判断条件中isWindows这个很容易理解的,我们上面也说非windows下才会做这个检查,
2.然后判断文件的uid和进程的uid是否相等,如果相等了说明当前用户拥有该文件的执行权限,所以如果既不在window下,又不能拥有执行权限,就需要继续判断launchCmd了。
3.判断launchCmd:利用的正则表达式方法match来判断,正斜杠(/)包裹的就是带查找的字符串,上面的结果就是判断launchCmd是否包含authorize这个字符串。该判断的目的是查看是否以sudo命令启动的该脚本,因为本身该脚本不是启动程序的用户所能执行的,就需要显式的用sudo来执行。
4.当上面的3个条件有一个判断失败,就会打印下面的信息。

这里写图片描述

说明上面的种种判断都是为检查我们是否在安装appium的时候,错误的使用sudo命令。

重要

安装appium的时候千万不要使用sudo npm install -g appium 来安装。

再次初始化

var http = require('http')
  , express = require('express')
  , favicon = require('serve-favicon')
  , bodyParser = require('body-parser')
  , methodOverride = require('method-override')
  , morgan = require('morgan') // logger
  , routing = require('./routing.js')
  , path = require('path')
  , appium = require('../appium.js')
  , parserWrap = require('./middleware').parserWrap
  , appiumVer = require('../../package.json').version
  , appiumRev = null
  , async = require('async')
  , helpers = require('./helpers.js')
  , logFinalWarning = require('../helpers.js').logFinalDeprecationWarning
  , getConfig = require('../helpers.js').getAppiumConfig
  , allowCrossDomain = helpers.allowCrossDomain
  , catchAllHandler = helpers.catchAllHandler
  , checkArgs = helpers.checkArgs
  , configureServer = helpers.configureServer
  , startListening = helpers.startListening
  , conditionallyPreLaunch = helpers.conditionallyPreLaunch
  , prepareTmpDir = helpers.prepareTmpDir
  , requestStartLoggingFormat = require('./helpers.js').requestStartLoggingFormat
  , requestEndLoggingFormat = require('./helpers.js').requestEndLoggingFormat
  , domainMiddleware = require('./helpers.js').domainMiddleware;
变量名模块意义
httphttphttp模块,不解释.
expressexpress第三方模块.用于搭建http服务器.
faviconserve-favicon第三方模块,express中间件组件,改善图片缓存性能的.
bodyParserbody-parser第三方模块.express依赖的中间件组件.
methodOverridemethod-override第三方模块.可以理解为重写了http模块中一些方法,可能是核心模块http中有些方法不太好用.
morganmorgan第三方模块.类似log器,在控制台中,显示req请求的信息.
routing./routing.js本地模块.路由模块.这个模块中定义了uri对应的处理方法,我们以后会对其进行解释.
pathpath核心模块.用于处理文件路径相关的操作.
appium../appium.js本地模块.以后会解释,暂时我也不知道是个啥
parserWrap./middleware本地模块.同上.
appiumVer../../package.jsonappium的版本
appiumRev未知null
asyncasync第三方模块.是一个流程控制工具包,提供了直接而强大的异步功能.
helpers./helpers.js本地模块.以后解释
logFinalWarning./helpers.js本地模块.helpers.js模块中logFinalDeprecationWarning模块.
getConfig../helpers.js本地模块.helpers中的getAppiumConfig模块.保存有appium的配置信息.
allowCrossDomain../helpers.js回调方法对象.
catchAllHandler../helpers.js回调方法对象.
checkArgs../helpers.js回调方法对象.
configureServer../helpers.js回调方法对象.
startListening../helpers.js回调方法对象.
conditionallyPreLaunch../helpers.js回调方法对象.
prepareTmpDir../helpers.js回调方法对象.
requestStartLoggingFormat../helpers.js本地模块.helpers.js中的requestStartLoggingFormat模块.
requestEndLoggingFormat../helpers.js本地模块.helpers.js中的requestEndLoggingFormat模块.
domainMiddleware../helpers.js本地模块.helpers.js中的domainMiddleware模块.

最后2句代码

if (require.main === module) {
  main(args);
}

module.exports.run = main;

上面的代码为main.js的最后一段代码,从上面的代码可以看出,该模块中最主要的函数为main函数,提供给外部模块的是run函数。如果当前模块就是启动模块的话,那么会自己调用main函数,所以下一篇我们会单独说main函数的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值