UiAutomator系列——Appium Server 原始碼分析之啟動執行Express http伺服器(010)
通過上一個系列Appium Android Bootstrap原始碼分析我們瞭解到了appium在安卓目標機器上是如何通過bootstrap這個服務來接收appium從pc端傳送過來的命令,並最終使用uiautomator框架進行處理的。大家還沒有這方面的背景知識的話建議先去看一下,以下列出來方便大家參考:
那麼我們知道了目標機器端的處理後,我們理所當然需要搞清楚bootstrap客戶端,也就是Appium Server是如何工作的,這個就是這個系列文章的初衷。
Appium Server其實擁有兩個主要的功能:
- 它是個http伺服器,它專門接收從客戶端通過基於http的REST協議傳送過來的命令
- 他是bootstrap客戶端:它接收到客戶端的命令後,需要想辦法把這些命令傳送給目標安卓機器的bootstrap來驅動uiatuomator來做事情
同時我們也先看下Appium Server的原始碼佈局,後有一個基本的程式碼結構概念:
開始之前先宣告一下,因為appium server是基於當今熱本的nodejs編寫的,而我本人並不是寫javascript出身的,只是在寫這篇文章的時候花了幾個小時去了解了下javascript的語法,但是我相信語言是相同的,去看懂這些程式碼還是沒有太大問題的。但,萬一當中真有誤導大家的地方,還敬請大家指出來,以免禍害讀者...
1.執行引數準備
Appium 伺服器啟動的入口就在bin下面的appium.js這個檔案裡面.在一開始的時候這個javascript就會先去匯入必須的模組然後對啟動引數進行初始化:[javascript] view plaincopy
- var net = require('net')
- , repl = require('repl')
- , logFactory = require('../lib/server/logger.js')
- , parser = require('../lib/server/parser.js');
- require('colors');
- var
- var ap = require('argparse').ArgumentParser
- // Setup all the command line argument parsing
- module.exports = function () {
- var parser = new ap({
- version: pkgObj.version,
- addHelp: true,
- description: 'A webdriver-compatible server for use with native and hybrid iOS and Android applications.'
- });
- _.each(args, function (arg) {
- parser.addArgument(arg[0], arg[1]);
- });
- parser.rawArgs = args;
- return parser;
- };
- var args = [
- ...
- [['-a', '--address'], {
- defaultValue: '0.0.0.0'
- , required: false
- , example: "0.0.0.0"
- , help: 'IP Address to listen on'
- }],
- ...
- [['-p', '--port'], {
- defaultValue: 4723
- , required: false
- , type: 'int'
- , example: "4723"
- , help: 'port to listen on'
- }],
- ...
- [['-bp', '--bootstrap-port'], {
- defaultValue: 4724
- , dest: 'bootstrapPort'
- , required: false
- , type: 'int'
- , example: "4724"
- , help: '(Android-only) port to use on device to talk to Appium'
- }],
- ...
- ];
- address:指定http伺服器監聽的ip地址,沒有指定的話預設就監聽本機
- port:指定http伺服器監聽的埠,沒有指定的話預設監聽4723埠
- bootstrap-port:指定要連線上安卓目標機器端的socket監聽埠,預設4724
2. 建立Express HTTP伺服器
Appium支援兩種方式啟動,一種是在提供--shell的情況下提供互動式編輯器的啟動方式,這個就好比你直接在命令列輸入node,然後彈出命令列互動輸入介面讓你一行行的輸入除錯執行;另外一種就是我們正常的啟動方式而不需要使用者的互動,這個也就是我們今天關注的重點:
- if (process.argv[2] && process.argv[2].trim() === "--shell") {
- startRepl();
- } else {
- appium.run(args, function () { /* console.log('Rock and roll.'.grey); */ });
- }
- var args = parser().parseArgs();
- logFactory.init(args);
- var appium = require('../lib/server/main.js');
- module.exports.run = main;
- appium.run(args, function () { /* console.log('Rock and roll.'.grey); */ });
就相當於呼叫了'main.js'的: [javascript] view plaincopy
- main(args, function () { /* console.log('Rock and roll.'.grey); */ });
- var main = function (args, readyCb, doneCb) {
- ...
- var rest = express()
- , server = http.createServer(rest);
- ...
- }
只是這個http伺服器跟普通的伺服器唯一的差別是createServer方法的引數,從一個回撥函式變成了一個Epress物件的例項。它使用了express框架對http模組進行再包裝的,這樣它就可以很方便的使用express的功能和方法來快速建立http服務,比如:
- 通過 express的get,post等快速設定路由。用於指定不同的訪問路徑所對應的回撥函式,這叫做“路由”(routing),這個也是為什麼說express是符合RestFul風格的框架的原因之一了
- 使用express的use方法來設定中介軟體等。至於什麼是中介軟體,簡單說,中介軟體(middleware)就是處理HTTP請求的函式,用來完成各種特定的任務,比如檢查使用者是否登入、分析資料、以及其他在需要最終將資料傳送給使用者之前完成的任務。它最大的特點就是,一箇中間件處理完,再傳遞給下一個中介軟體。
比如上面建立http伺服器後所做的動作就是設定一堆中介軟體來完成特定的任務來處理http請求的:
[javascript] view plaincopy- var main = function (args, readyCb, doneCb) {
- ...
- rest.use(domainMiddleware());
- rest.use(morgan(function (tokens, req, res) {
- // morgan output is redirected straight to winston
- logger.info(requestEndLoggingFormat(tokens, req, res),
- (res.jsonResp || '').grey);
- }));
- rest.use(favicon(path.join(__dirname, 'static/favicon.ico')));
- rest.use(express.static(path.join(__dirname, 'static')));
- rest.use(allowCrossDomain);
- rest.use(parserWrap);
- rest.use(bodyParser.urlencoded({extended: true}));
- // 8/18/14: body-parser requires that we supply the limit field to ensure the server can
- // handle requests large enough for Appium's use cases. Neither Node nor HTTP spec defines a max
- // request size, so any hard-coded request-size limit is arbitrary. Units are in bytes (ie "gb" == "GB",
- // not "Gb"). Using 1GB because..., well because it's arbitrary and 1GB is sufficiently large for 99.99%
- // of testing scenarios while still providing an upperbounds to reduce the odds of squirrelliness.
- rest.use(bodyParser.json({limit: '1gb'}));
- ...
- }
- module.exports.domainMiddleware = function () {
- return function (req, res, next) {
- var reqDomain = domain.create();
- reqDomain.add(req);
- reqDomain.add(res);
- res.on('close', function () {
- setTimeout(function () {
- reqDomain.dispose();
- }, 5000);
- });
- reqDomain.on('error', function (err) {
- logger.error('Unhandled error:', err.stack, getRequestContext(req));
- });
- reqDomain.run(next);
- };
- };
- 先建立一個domain
- 然後把http的request和response增加到這個domain裡面
- 然後鑑定相應的事件發生,比如發生error的時候就列印相應的日記
- 然後呼叫下一個中介軟體來進行下一個任務處理
- var main = function (args, readyCb, doneCb) {
- ...
- // Instantiate the appium instance
- var appiumServer = appium(args);
- // Hook up REST http interface
- appiumServer.attachTo(rest);
- ...
- }
- var http = require('http')
- , express = require('express')
- ...
- , appium = require('../appium.js')
- var Appium = function (args) {
- this.args = _.clone(args);
- this.args.callbackAddress = this.args.callbackAddress || this.args.address;
- this.args.callbackPort = this.args.callbackPort || this.args.port;
- // we need to keep an unmodified copy of the args so that we can restore
- // any server arguments between sessions to their default values
- // (otherwise they might be overridden by session-level caps)
- this.serverArgs = _.clone(this.args);
- this.rest = null;
- this.webSocket = null;
- this.deviceType = null;
- this.device = null;
- this.sessionId = null;
- this.desiredCapabilities = {};
- this.oldDesiredCapabilities = {};
- this.session = null;
- this.preLaunched = false;
- this.sessionOverride = this.args.sessionOverride;
- this.resetting = false;
- this.defCommandTimeoutMs = this.args.defaultCommandTimeout * 1000;
- this.commandTimeoutMs = this.defCommandTimeoutMs;
- this.commandTimeout = null;
- };
- Appium.prototype.attachTo = function (rest) {
- this.rest = rest;
- };
- var main = function (args, readyCb, doneCb) {
- ...
- routing(appiumServer);
- ...
- }
- var main = function (args, readyCb, doneCb) {
- ...
- function (cb) {
- startListening(server, args, parser, appiumVer, appiumRev, appiumServer, cb);
- }
- ...
- }
- server:基於express例項建立的http伺服器例項
- args:引數
- parser:引數解析器
- appiumVer: 在‘'../../package.json'‘檔案中指定的appium版本號
- appiumRev:通過上面提及的進行伺服器基本配置時解析出來的版本修正號
- appiumServer: 剛才建立的appium伺服器例項,裡面包含了一個express例項,這個例項和第一個引數server用來建立http伺服器的express例項時一樣的
3. 啟動http伺服器監聽
到了這裡,整個基於Express的http伺服器已經準備妥當,只差一個go命令了,這個go命令就是我們這裡的啟動監聽方法: [javascript] view plaincopy- module.exports.startListening = function (server, args, parser, appiumVer, appiumRev, appiumServer, cb) {
- var alreadyReturned = false;
- server.listen(args.port, args.address, function () {
- var welcome = "Welcome to Appium v" + appiumVer;
- if (appiumRev) {
- welcome += " (REV " + appiumRev + ")";
- }
- logger.info(welcome);
- var logMessage = "Appium REST http interface listener started on " +
- args.address + ":" + args.port;
- logger.info(logMessage);
- startAlertSocket(server, appiumServer);
- if (args.nodeconfig !== null) {
- gridRegister.registerNode(args.nodeconfig, args.address, args.port);
- }
- var showArgs = getNonDefaultArgs(parser, args);
- if (_.size(showArgs)) {
- logger.debug("Non-default server args: " + JSON.stringify(showArgs));
- }
- var deprecatedArgs = getDeprecatedArgs(parser, args);
- if (_.size(deprecatedArgs)) {
- logger.warn("Deprecated server args: " + JSON.stringify(deprecatedArgs));
- }
- logger.info('Console LogLevel: ' + logger.transports.console.level);
- if (logger.transports.file) {
- logger.info('File LogLevel: ' + logger.transports.file.level);
- }
- });
- server.listen(args.port, args.address, function () {
- ...
- args.port:就是第一節提起的http伺服器的監聽埠,預設4723
- args.adress:就是第一節提及的http伺服器監聽地址,預設本地
- function:一系列回撥函式來進行錯誤處理等
4. 小結
這篇文章主要描述了appium server是如何建立一個基於express框架的http伺服器,然後啟動相應的監聽方法來獲得從appium client端傳送過來的資料,至於獲取到資料後如何與目標安卓裝置的bootstrap進行通訊,敬請大家期待本人的下一篇文章。本人更多的文章請參考我的部落格:http://blog.csdn.net/zhubaitian
Item |
Description |
Warning |
Author |
天地會珠海分舵 |
轉載請註明出處! 更多精彩文章請檢視本人部落格! |