测试服务化之Tornado使用指南
应领导要求,我们以后的所有脚本都要服务化,这样我们就可以通过调用相应的接口就能完成我们的测试操作。经过调研,我们采用python轻量级的Tornado来搭建我们的服务器,下面我们就介绍一下如何使用Tornado来完成我们的测试脚本服务化。
1,Tronado简介
Tornado是使用Python编写的一个强大的、可扩展的Web服务器。它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中。
1.1 Tronado的学习
在学习技术的时候,我们通常会遇到这样的问题,新的知识在网上往往没有太多的资料 ,相关书籍也比较少。我在网上搜了一下,发现一本《Introduction to Tornado》的书,网址:http://item.jd.com/1142122804.html,138页,221块钱,还是挺贵的,而且是英文版本的,估计不太想读。
后来又在网上搜到一个在线的中文教程,地址:http://docs.pythontab.com/tornado/introduction-to-tornado/index.html,应该是那本书的中文版,感觉还不错,建议大家学习一下。
1.2 Tornado的使用
由于目前我们不需要做太复杂的东西,只需要把各个python脚本转化成接口化的服务,所以我们先不关注Tornado其他相关的东西,只需要先学习一下第一章节的示例和第五章节的相关内容即可。而具体的使用方法 ,请看下面的章节。
Tornado的安装
在大部分*nix系统中安装Tornado非常容易--你既可以从PyPI获取(并使用easy_install或pip安装),也可以从Github上下载源码编译安装,如下所示:
$ curl -L -O https://github.com/facebook/tornado/archive/v3.1.0.tar.gz
$ tar xvzf v3.1.0.tar.gz
$ cd tornado-3.1.0
$ python setup.py build
$ sudo python setup.py install
Tornado官方并不支持Windows,但你可以通过ActivePython的PyPM包管理器进行安装,类似如下所示:
C:\> pypm install tornado
安装完成后,我们可以检测一下,进入Python提示符下,import tornado,如果没有任何错误提示,则表示安装成功;否则需要重要安装。
2,如何将脚本服务化
在以往我们的测试工作中,我们写过不少python脚本来辅助我们的测试工作。但是我们的使用方法通常是,拿着脚本文件修改相应的配置后,再去直接运行脚本文件。可是如何将我们脚本做成服务化呢?
2.1 优化python脚本
python脚本是轻量级的,用来辅助测试是非常便捷的利器,可是我们在写脚本的时候,往往是针对具体的问题来写对应的脚本,通用性不高。为了达到测试的服务化,我们需要按以下标准来优化我们的测试脚本:
(1)脚本必须参数化。
在我们的python脚本中不能存在写死的代码(hard code),任何常量都需要通过参数来传给脚本。脚本通过解析参数的值,来完成不同的操作。
(2)功能必须函数化
如果一个脚本完成很多功能,我们必须封装成不同的参数,这样在我们服务化的时候,才可以灵活调用相应的函数。或是根据不同的业务,调用不同的函数来组合业务场景。
(3)返回值为dict类型
在我们通过不同的函数完成相应的功能后,要返回用于识别程序执行结果的dict类型,如成功时:
{“errno”:0,”data”:{….}}
或者,失败的时候:
{“errno”:1000,”error”:”error message!!!”}
(4)必须添加注释
在团队开发和代码共享的情况下,适当的注释非常重要。不管使用你代码的人的编码能力如何,我也肯定他是不愿意一行一行地读你的代码的。对我们脚本中的类,函数,参数和返回值必须加上注释,程序中使用到的特殊代码,特殊变量等也需要加上注释。
(5)自行通过调用函数,传不同的参数调试通过
在我们的脚本完成之后,请自行通过调用函数,传不同的参数完成不同的功能,检测是否符合我们的要求。如果完成,我们在后面的服务化的时候,直接将脚本导入到“/usr/lib/python2.6/site-packages”目录下,在服务脚本中直接导入使用。
2.2 Python脚本服务化
根据需求,我们需要把我们前期做的所有python脚本给做成服务化,我研究了一下Tornado框架,并将diffjson的功能做成了服务接入了进去。下面我介绍一下,如何将一个python的功能接入到Tornado框架中。
2.2.1完善python脚本
根据我们上面的要求,将你的脚本完善,调试通过。如:diffjson功能,先从网上(https://git.fedorahosted.org/cgit/json_diff.git/commit/)下载了一个对比json的开源的代码,根据我们的需要,做了一些儿修改,能根据不同的参数来完成不同的对比。
2.2.2 导入脚本
当我们完善脚本后,需要将我们的脚本导入。我修改的是网上的开源代码,可以直接运行setup.py进行安装,如果我们是整理自己的脚本代码的话,直接将脚本导入“/usr/lib/python2.6/site-packages”中。
2.2.3服务化脚本
Tornado是一个编写对HTTP请求响应的框架。作为程序员,你的工作是编写响应特定条件HTTP请求的响应的handler。下面我以diffjson服务为例,介绍一下如何将python脚本服务化:
示例代码如下:
1. # -*- coding: utf-8 -*-
2. import tornado.httpserver
3. import tornado.ioloop
4. import tornado.options
5. import tornado.web
6. try:
7. import json
8. except ImportError:
9. import simplejson as json
10. import json_diff
11. from StringIO import StringIO
12. from tornado.options import define, options
13. define("port", default=8000, help="run on the given port", type=int)
14. class OptionsClass(object):
15. '''
16. json_diff模块的对比参数
17. exc:为忽略的字段
18. inc:仅需要对比的字段
19. ign:为true时,忽略新字段,否则对比新增加字段
20. '''
21. def __init__(self, inc=None, exc=None, ign=None, model=None):
22. self.exclude = exc
23. self.include = inc
24. self.ignore_append = ign
25. self.diff_model = model
26. class IndexHandler(tornado.web.RequestHandler):
27. def get(self):
28. '''
29. 根据需求对比两个json字符串
30. :param jsdata1 需要对比的json字符串1
31. :param jsdata2 需要对比的json字符串2
32. :param exfields 需要过滤掉的字段,如:['boy','sex']
33. :param diffmodel 字符串对比的模式,"value":比较各个字段的值,
34. "structure":只比较两个json字符串的结果
35. :return: 对比的两个json字符串的差异
36. '''
37. jsdata1 = self.get_argument('jsdata1', '{}')
38. jsdata2 = self.get_argument('jsdata2','{}')
39. exfields = self.get_argument('exfields',None)
40. diffmodel=self.get_argument('diffmodel',None)
41. if (jsdata1=='{}'):
42. errdata={"errno":1000,"error":"Please input jsdata1!"}
43. self.write(errdata)
44. elif (jsdata2 == '{}'):
45. errdata = {"errno": 1000, "error": "Please input jsdata2!"}
46. self.write(errdata)
47. else:
48. #json数据处理
49. jsdata1 = jsdata1.encode("utf-8").strip("/'")
50. jsdata2 = jsdata2.encode("utf-8").strip("/'")
51. #fields处理
52. if (exfields is not None):
53. exfields = exfields.encode("utf-8").lstrip("[").rstrip("]").replace("\'", "").replace('\"', "")
54. filist = exfields.split(",")
55. else:
56. filist = []
57. #处理对比模式
58. if (diffmodel is not None):
59. diffmodel = diffmodel.encode("utf-8").strip("/'").strip('/"').split(",")
60. #对比json字符串
61. diffator = json_diff.Comparator(StringIO(jsdata1),StringIO(jsdata2), OptionsClass(exc=filist,model=diffmodel))
62. diff = diffator.compare_dicts()
63. self.write(diff)
64. if __name__ == "__main__":
65. tornado.options.parse_command_line()
66. app = tornado.web.Application(handlers=[(r"/diffjson", IndexHandler)])
67. http_server = tornado.httpserver.HTTPServer(app)
68. http_server.listen(options.port)
69. tornado.ioloop.IOLoop.instance().start()
70.
代码解析:
(1) 从第2到第12行为导入相应的功能模块,Tornado相关的模块我们就不详情介绍了,大家可以去看我们前面提供的教程以及下面代码中的应用。注意第10行,这一行是导入我们编写的脚本,即:json_diff.py文件。
(2) 第13行,为定义Tornado本服务监听的端口号,在示例中的diffjson服务的端口号为8000。你可以为你的服务设置任意端口号,注意不要和其他的服务相同就可以了。
(3) 第14到25行为diffjson服务调用json_diff.py相应的函数需要用到的一些儿选项,大家可以忽略。
(4) 第26至63行,为接口的主要内容,也就是我们在Tornado中需要响应特定条件HTTP请求的响应的handler,当然类名字你可以命名为“XXXHandler”,必须继承tornado.web.RequestHandler类。
A, 第37到40行,为获取相应的接口参数,tornado通过self.get_argument('jsdata1', '{}')来获取对应的参数值 ,并且能指定默认值 。
B, 第41到59行,为处理获取到的参数。对于必选参数 ,如果没有传递,我们的接口要有相应的报错。从地址栏中获取的到参数为unicode的,需要转化成UTF-8,并且你通过地址栏传过来的所有字符均有效。如:地址栏,location=”beijing”,则Tornado接收过来的值为:loc=’”beijing”’,所以要加上处理不必要的字符的代码段。
C, 第60至63行,调用我们导入的脚本函数,完成具体的操作。如果将函数结果返回的话,就调用Tornado的write()函数 ,返回值直接是json格式的。
(5)第64到69行,为tornado服务设置代码。其中第66行app = tornado.web.Application(handlers=[(r"/diffjson", IndexHandler)]),指定访问的路径,如:http://IP:端口号/diffjson,而第二个参数为具体处理请求的函数IndexHandler。其他的代码不用需要,直接可以调用。
2.2.4调试服务脚本
当我们把脚本按上面的要求写成服务后,就可以运行调用了。进入脚本所在的路径,运行python 脚本名字.py,服务即处于监听状态,我们就可以从浏览器进行访问了。
如果在本地运行调用,则浏览器会给出相应的返回:
到此为止,我们已经利用tornado搭建出一个Get请求的diffjson接口服务,通过在地址栏中直接调用,或是通过其他语言调用接口的方法就可以使用我们服务去做相关的操作。
此时,我们的脚本需要一直处于运行状态,如果我们有很多服务脚本需要运行的话,我们不可能在多个命令行下运行各个脚本,所以我们需要借助于supervisor来管理进程的。
3,使用Supervisor监控Tornado进程
Supervisor是一个进程管理工具,官方的说法:用途就是有一个进程需要每时每刻不断的跑,但是这个进程又有可能由于各种原因有可能中断。当进程中断的时候我希望能自动重新启动它,此时,我就需要使用到了Supervisor。
3.1 Supervisor的安装
一,easy_install安装
# yum install python-setuptools
# easy_install supervisor
二,源码安装
官网下载地址:https://pypi.python.org/pypi/supervisor/
# tar zxf supervisor-3.2.3.tar.gz
# cd supervisor
# python setup.py install
成功安装后可以登陆python控制台输入import supervisor 查看是否能成功加载。
3.2 supervisor的配置
Supervisor安装成功后,我们需要进行配置才可以使用。
(1) 生成配置文件(supervisord.conf):
#cd /usr/bin
#echo_supervisord_conf > /etc/supervisord.conf
(2) 修改配置文件
进入到/etc下,打开配置文件supervisord.conf,文件中有很多默认的配置,我们就不具体讲解了,感觉兴趣的同学可以参考:http://blog.chinaunix.net/uid-26000296-id-4759916.html。下面添加我们的配置要求:
a.我的diffjson服务脚本位置是:/opt/ershouservices/diffjson
b.脚本文件名是:Json_Diff_Service.py
c.如果产生错误日志,我也希望是在这个路径下面。
d.为了管理方便,我们需要给二手服务脚本创建一个组。
所以我们在/etc/supervisord.conf文件的最后面添加我们的配置如下:
;The following is ershou services setting!
[group:ershouservices]
programs=jsondiff
[program:jsondiff]
command=python /opt/ershouservices/diffjson/Json_Diff_Service.py
directory=/opt/ershouservices/diffjson
user=root
autorestart=true
redirect_stderr=true
stdout_logfile=/opt/ershouservices/diffjson/diffjson.log
loglevel=info
配置详解:
a) 在supervisord.conf文件中,分号“;”后面的内容表示注释
b) [group:组名],设置一个服务分组,programs后面跟组内所有服务的名字,以分号分格。
c) [program:服务名],下面是这个服务的具体设置:
Command:启用Tornado服务文件的命令,也就是我们手动启动的命令。
Directory:服务文件所在的目录
User:启用服务的用户
Autorestart:是否自动重启服务
stdout_logfile:服务的产生的日起文件
loglevel:日志级别
通过上面的配置,我们已经将我的diffjson的服务添加到supervisor中进行管理了。
3.3运行服务:
(1)在supervisor的安装路径下,运行下面的命令:
#supervisord -c /etc/supervisord.conf //启动supervisor
命令没有任何输出,表示启动成功!
(2)查看运行状态的话,需要运行下面的命令:
# supervisorctl status //输出正在运行的服务
当你看到你的服务的状态是RUNNING状态的时候,表示你的服务处于运行状态,可以访问了。
3.4 访问服务
Tornado服务运行后,我们可以通过http://IP地址:端口号/访问路径来访问服务的。我们现在的IP地址是:XX.XX.XX.XX,端口号是:8000,访问路径是:/diffjson,所以我们的访问地址是:
http://XX.XX.XX.XX:8000/diffjson,如果添加上我们需要对比的两个参数的话:jsdata1='{"boy":"sxf","sex":"male"}'&jsdata2='{"boy":"dragon","sex":"female"}'。服务调用结果如下所示:
3.5 编写接口文档
我们的服务接口是供大家使用的,所以在接口调试通过后,并通过Supervisor管理起来。此时我们就可以通过http://IP地址:端口号/访问路径来访问服务,当然,为了让大家很好地使用这个服务,我们也要编写相应的接口文档。
下面是接口diffjson的接口文档示例:
接口名称:diffjson
功能描述:
根据需要对比两个json字符串,返回这两个字符串的差异。
请求URL:
http://XX.XX.XX.XX:8000/diffjson
请求方式:
Get
参数:
参数名 |
必选 |
类型 |
说明 |
示例 |
jsdata1 |
是 |
Json |
要对比的Json字符串 |
{“name”:”sxf”, ”sex”:”male”} |
jsdata2 |
是 |
Json |
要对比的Json字符串 |
{“name”:”Lily”, ”sex”:”female”} |
exfields |
否 |
List |
不需要对比的字段 |
[“name”,”sex”] |
diffmodel |
否 |
String |
对比类型:value,对比结构和数值 ;Structure:只对比结构 |
“value” or “structure” |
返回示例:
(1)正确返回示例:
{
“_update”: {
“boy”: “dragon”,
“sex”: “female”
}
}
(2)错误返回示例:
{
"errno": 1000,
"error": "Please input jsdata1!"
}
当你完成相应的服务接口后,也要编写类似的接口文档,以供大家查阅。
3.6 supervisor常用命令汇总
(1)生成配置文件
#echo_supervisord_conf > /etc/supervisord.conf
(2)启动所有的服务
# supervisord -c /etc/supervisord.conf
(3)查看服务运行状态
# supervisorctl status
(4)停止服务
#supervisorctl stop groupname:pogramname
(1) 启动某个服务
#supervisorctl start groupname:pogramname
(6)停止所有的服务
# supervisorctl stop all
(7)载入最新的配置文件
#supervisorctl reload
(8)根据最新的配置文件,启动新配置或有改动的进程
#supervisorctl update
3.7 Supervisor常见的错误及解决办法
1,在使用nginx做代理时候,可能因为端口号设置的问题,导致supervisor没有办法启动了,问题显示如下:
bash-3.2# supervisord -c /etc/supervisord.conf
Error: Another program is already listening on a port that one of our HTTP servers is configured to use. Shut this program down first before starting supervisord.
For help, use /usr/local/bin/supervisord –h
解决办法:
http://stackoverflow.com/questions/14479894/stopping-supervisord-shut-down
(1)查找supervisord的进程
#ps -ef | grep supervisord
(2)用下面的方法杀掉进程
#kill -s SIGTERM 29646
(3)重启所有的服务
# supervisord -c /etc/supervisord.conf
2,supervisor unknown error making dispatchers for 'server name': EACCES
解决办法:这个问题是因为权限引起的,我们需要以创建/etc/supervisord.conf的用户去启动去。
3,直接启动服务的时候,如果遇到socket.error: [Errno 98] Address already in use!
解决办法:运行命令killall python,然后再去直接启动服务即可解决这个问题
4,小 结
通过上面的介绍,我们可以把我们以前写的各种功能的Python脚本变成一个个通用的服务,这样需要使用的同学,直接调用我们的服务接口。根据接口文档的要求来使用即可,大大简化了脚本的使用难度。现在总结一下:
一, 脚本必须规范化,可以通过参数来调用。
二, 通过Tornado将脚本变成服务接口,并配置到服务器上。
三, 配置supervisor,将服务管理起来,启动服务调试成功后,我们需要编写相应接口的接口文档,方便大家使用,后期放到指定的位置。
四, 后期根据大家使用的反馈,维护相应的服务,使我们的服务接口能更好地为大家服务!
最后,希望本文档能帮助大家搭建起自己的服务,如有问题欢迎大家随时联系,RTX:songxianfeng01