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中详细介绍 |
logFactory | log工厂类 |
logger | log器 |
args | 参数集合 |
fs | 文件模块,核心模块 |
path | 路径模块,核心模块 |
noPermsCheck | boolean值,是否省略权限检查,默认为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;
变量名 | 模块 | 意义 |
---|---|---|
http | http | http模块,不解释. |
express | express | 第三方模块.用于搭建http服务器. |
favicon | serve-favicon | 第三方模块,express中间件组件,改善图片缓存性能的. |
bodyParser | body-parser | 第三方模块.express依赖的中间件组件. |
methodOverride | method-override | 第三方模块.可以理解为重写了http模块中一些方法,可能是核心模块http中有些方法不太好用. |
morgan | morgan | 第三方模块.类似log器,在控制台中,显示req请求的信息. |
routing | ./routing.js | 本地模块.路由模块.这个模块中定义了uri对应的处理方法,我们以后会对其进行解释. |
path | path | 核心模块.用于处理文件路径相关的操作. |
appium | ../appium.js | 本地模块.以后会解释,暂时我也不知道是个啥 |
parserWrap | ./middleware | 本地模块.同上. |
appiumVer | ../../package.json | appium的版本 |
appiumRev | 未知 | null |
async | async | 第三方模块.是一个流程控制工具包,提供了直接而强大的异步功能. |
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函数的内容。