Python Requests Session set-cookie不生效的坑

我们知道 Python Requests库 中的 Session 模块有连接池和会话管理的功能,比如请求一个登录接口后,会自动处理 response 中的 set-cookie,下次再请求时会自动把 cookie 带上。但最近出现了一个诡异的事情,cookie 没有自动带上,导致请求 403。

一开始怀疑是登录接口错误了,没有 set-cookie,但抓包发现 response header 中有 set-cookie,打印请求的 response.cookies 也有需要的 cookie。又怀疑是 set-cookie 的格式不对或者其它问题,但用浏览器实际跑了下流程,发现系统一切正常,那基本就是 requests 库的问题了。

没办法,只能 debug 了,单步调试了几轮,基本了解了 requests 的处理方式,首先把请求参数转变为 Request 对象,然后对使用 prepare_request 对 Request 进行预处理,其中有一步 merge_cookies 的操作(还有各种其它处理),把传入的 cookies 和 self.cookies merge 到 RequestsCookieJar 对象上去,这一步也没啥问题,merged_cookies 变量也是对的。后续将预处理过的请求,通过内置的 http adapter 发送出去。http adapter 底层是通过 urllib3.poolmanager 获取到 urllib3.connectionpool 连接(这里是连接池的核心部分),再通过 conn.urlopen 实际发送请求。虽然跟踪了解到了整个请求逻辑,但最终发出的请求还是没有带上需要的 cookie。

问题定位一度陷入僵局,只能再回顾上面的流程,cookie 肯定就是在 merged_cookies 和 conn.urlopen 之间没的,再仔细观察发现,conn.urlopen 请求参数里面压根没有 cookie 字段。

查阅资料发现,urllib3 的作者说,连接池只处理底层连接,cookie 跟踪等事情应该上层来做。大胆猜测,那 cookie 应该是放在 header 里了,往前捣看看 request.headers 是怎么变动的(此时里面的 Cookie 字段确实不正确)。

再走查代码发现 prepare_request 里面是调用了 PreparedRequest.prepare,其中有一步 prepare_cookies,主要是调用了 cookielib.CookieJar.add_cookie_header 最终将 cookie 放到了 self.headers['Cookie']。但里面有个 request.has_header("Cookie") 的判断,header 中没有 Cookie 字段才会放,不知道为什么这么考虑(最新版 3.8 还是这样),估计是 merge cookie 比较麻烦,但问题确实就出在这里,这次请求之前,Requests Session 已经直接通过 headers['Cookie'] 设置了 cookie。复现代码(修改自官方示例):

虽然找到了问题的原因,但又不好解决,总不能不让直接操作 headers['Cookie'] 吧,先不说无法限制使用者,而且之前的代码已经这样做了,改动量非常之大。不过好在,现在自己用的框架是在 Requests Session 上封装了一层,操作 header 都是调用的统一的 update_headers 方法:

对 headers['Cookie'] 的操作拦截一下,变成对 cookies 的操作:

这样即不用改之前的调用方代码,也防止了后人掉坑。

 

参考资料

https://requests.readthedocs.io/en/master/user/advanced/#session-objects

https://stackoverflow.com/questions/2422922/python-urllib3-and-how-to-handle-cookie-support

https://urllib3.readthedocs.io/en/latest/reference/index.html#module-urllib3.poolmanager

https://urllib3.readthedocs.io/en/latest/reference/index.html#urllib3.connectionpool.HTTPConnectionPool.urlopen

https://docs.python.org/2/library/cookie.html

Filebeat核心配置详解

Filebeat简介

现在 ELK(Elasticsearch、Logstash 和 Kibana)日志分析系统非常火,但相关的介绍忽略了重要的一环:日志采集。虽然 Logstash 也能采集日志,但比较重、资源占用高,显然不适合线上和业务部署,所以一开始搞了个 logstash-forwarder 后来整合为 Filebeat。慢慢还发展成了一个 Beats 系列,支持采集各种各样的元数据。

Filebeat原理

说到实时查看日志,最常用得莫过于 tail -f 命令,基于此可以自己实现一个简单的日志采集工具,https://github.com/iyaozhen/filebeat.py/blob/master/filebeat.py#L237

但这太简陋了,无法保证不丢不重。我们看看 Filebeat 是怎么实现的,官方说明:https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html

简单来说 Filebeat 有两大部分,inputs 和 harvesters,inputs 负责找文件(类似 find 命令)和管理 harvesters,一个 harvester 则和一个文件一一对应,一行行读然后发送给 output(类似tail -f)。

当然还有很多细节问题,我们结合配置文件一一详解。

Log input配置详解

官方配置说明:https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-log.html

先看一个基本例子(下面所述基于7.x版本,6.x版本也基本适用)

inputs 可以配置多块(block),就是相同类型的放一块,这个也很好理解。paths 可以配置多个文件,文件路径和文件名都支持通配。

ignore_older 和 scan_frequency

这就有两个细节问题了,一是路径下的历史文件可能很多,比如配置了按天分割,显然旧文件我们一般是不需要的。二是扫描频率如何控制,通配设置复杂的话频繁扫文件也是很大的开销。

问题一则是通过 ignore_older 参数解决,意思就是多久前的旧文件就不管了。比如设置为 1h,表示文件时间在 1h 之前的日志都不会被 input 模块搜集,直到有新日志产生。

问题二则是通过 scan_frequency 参数控制,表示多久扫描一次是否有新文件产生。比如设置 10s(默认),一个新文件产生 10s 后会被发现,或者一个旧文件(上面 ignore_older)新产生了一行日志 10s 才发现这个文件。有人说我需要实时性,是不是这个值设置的越小越好,其实是错误的,前面我们介绍了 Filebeat 架构,input 模块只是负责发现新文件,新文件是相对已经被 harvester 获取的文件,第一次发现之后就已经在被 harvester 一行行实时读取了,所以这里基本上只影响日志切分时的实时性(这种场景下的短暂延迟是可以接受的)。

close_* 和 clean_*

那么被 harvester 获取的文件就一直拿着不放吗?文件重命名或者被删除后怎么办呢?这里重点介绍这两组配置。 Continue Reading...

隐私政策

我们是谁

我们的站点地址是:https://iyaozhen.com。

我们收集何种及为何收集个人数据

媒体

如果您向此网站上传图片,您应当避免上传那些有嵌入地理位置信息(EXIF GPS)的图片。此网站的访客将可以下载并提取此网站的图片中的位置信息。

Cookies

您可以选择用cookies保存您的姓名、电子邮件地址和网站。这是通过让您可以不用再次填写相关内容而向您提供方便。这些cookies会保留一年。

其他站点的嵌入内容

此站点上的文章可能会包含嵌入的内容(如视频、图像、文章等)。来自其他站点的嵌入内容的行为和您直接访问这些其他站点没有区别。

这些站点可能会收集关于您的数据、使用cookies、嵌入额外的第三方跟踪程序及监视您与这些嵌入内容的交互,包括在您有这些站点的账户并登录了这些站点时,跟踪您与嵌入内容的交互。

统计

本站使用了百度统计,会记录你的IP、浏览器UA等信息,详见:
https://tongji.baidu.com/web/help/article?id=314&type=0

我们与谁共享您的信息

除WordPress插件本身行为外(详见下文其它信息),未主动与任何人共享任何个人数据。

我们保留多久您的信息

对于本网站的注册用户,我们也会保存用户在个人资料中提供的个人信息。所有用户可以在任何时候查看、编辑或删除他们的个人信息(除了不能变更用户名外)、站点管理员也可以查看及编辑那些信息。

您对您的信息有什么权利

如果您有此站点的账户,您可以请求我们提供我们所拥有的您的个人数据的导出文件,这也包括了所有您提供给我们的数据。您也可以要求我们抹除所有关于您的个人数据。这不包括我们因管理、法规或安全需要而必须保留的数据。

我们将您的信息发送到哪

基础访问信息可能会被发送到统计服务。

我们的联系信息

详见:https://iyaozhen.com/about_me

其他信息

使用的插件、服务的相关隐私政策

来源:iThemes Security

我们收集何种及为何收集个人数据

Security Logs

The IP address of visitors, user ID of logged in users, and username of login attempts are conditionally logged to check for malicious activity and to protect the site from specific kinds of attacks. Examples of conditions when logging occurs include login attempts, log out requests, requests for suspicious URLs, changes to site content, and password updates. This information is retained for 60 days.

我们与谁共享您的信息

This site is scanned for potential malware and vulnerabilities by Sucuri's SiteCheck. We do not send personal information to Sucuri; however, Sucuri could find personal information posted publicly (such as in comments) during their scan. For more details, please see Sucuri's privacy policy.

我们保留多久您的信息

Security logs are retained for 60 days.

我们将您的信息发送到哪

This site is part of a network of sites that protect against distributed brute force attacks. To enable this protection, the IP address of visitors attempting to log into the site is shared with a service provided by ithemes.com. For privacy policy details, please see the iThemes Privacy Policy.

来源:腾讯云

腾讯云非常重视保护您的隐私。为方便您登录、使用相关服务,以及为您提供更个性化的用户体验和服务,您在使用我们的服务时,我们可能会收集和使用您的相关信息。详见:腾讯云隐私声明

我们如何保护您的数据

服务器使用腾讯云,网站本身使用 iThemes Security 插件保障安全,程序包括但不限于 PHP、MySQL、WordPress,保持更新。

我们有何种数据泄露处理流程

暂无。

我们从哪些第三方接收数据

网站未从第三方,如广告商,接收关于用户的数据。

我们通过用户数据进行何种自动决策及/或归纳

网站无自动决策的服务。

行业监管披露要求

遵守《中华人民共和国宪法》及相关法规,特别的,受《中华人民共和国网络安全法》监管。

Nginx+Tomcat偶现502分析

问题

业务为了负载均衡,前面放了个 Nginx,但最近 502 报警有点频繁,影响了 SLA,因此对这个问题做了较深入的研究。

502 Bad Gateway

简单来说就是 Nginx 找不到一个可用的 upstream,可能的原因有:

  1. 压根是配置错误
  2. 连接 upstream server 发生错误/超时
  3. upstream server 到了处理瓶颈

还有一个重点是轮询了 upstream server 后任然没有一个可用的。但是不管什么原因,都能在 Nginx 的 error log 中找到报错详情。

upstream prematurely closed connection

在 Nginx 中找到错误日志(开了 debug):

看了下 Nginx 代码,发现是 c->recv(); 读到的内容为 0,日志中也有显示 recv: fd:65 0 of 4096,说明没有获取到 response

同时也查了 Tomcat access 日志,请求还没到 Tomcat。看情况就像日志里面说的,连接被断掉了。

一开始怀疑是网络原因导致连接断掉了,但出现较频繁,期间内网也无网络故障,应该和网络无关。

再看日志发现报错前后有很多 keepalive 信息,尝试关掉 keepalive,502 就没了,但这会对性能有一定影响,感觉有点因噎废食了,还得继续研究。

keepalive

HTTP 持久连接(HTTP persistent connection,也称作 HTTP keep-alive 或 HTTP connection reuse)是使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,而不是为每一个新的请求/应答打开新的连接的方法。

一般 Nginx 会配置 keepalive 以提高性能:

Syntax: keepalive connections;

Activates the cache for connections to upstream servers.

The connections parameter sets the maximum number of idle keepalive connections to upstream servers that are preserved in the cache of each worker process. When this number is exceeded, the least recently used connections are closed.

可以配置每个 worker 对 upstream servers 最大长连接数量。同时这个长连接受 keepalive_requests(默认100) 和 keepalive_timeout(默认60s)配置的影响。

但 keepalive 也有缺陷,会加重 webserver 的负担,因为需要绑定一定数量的线程或者进程来维持长链接。注意 keepalive 并没有连接复用(即同一时间窗口不能处理多个请求,这个在 HTTP/2 中才实现),仅节省了新建/关闭连接的开销,类似连接池了。所以 webserver 一般都有类似 nginx 的 keepalive_requests、keepalive_timeout 配置,让空闲的连接断掉。

查看了 upstream(tomcat) 配置的 timeout 是20s,lighttpd 的 requests 是16,timeout 是 5s,都远小于 nginx 的配置。

原因就清楚了,upstream servers 先断了 keepalive 的长连接,但 nginx 仍使用了这个已经断掉的连接。

至于 nginx 为什么不主动检测一下连接是否可用呢?猜测应该是性能原因,一直检查连接池中的连接是否可用没必要,keepalive 协议本身也没业务心跳啥的。PS:商业版的 ngx_http_upstream_hc_module 可以主动监测(世界加钱可及)。

proxy_next_upstream

那么 Nginx 如何保证高可用呢?答案是重试。这就涉及另一个重要配置 proxy_next_upstream,在什么情况下进行重试,默认为 error timeout。按理说我们这个场景应该会重试,但没有重试。这是因为 Nginx 较新版本(1.9.13)多了个 non_idempotent 配置,默认 POSTLOCKPATCH 等请求不重试,因为这些操作是非幂等操作,会对服务端数据造成影响(比如发消息接口,重试可能会重复发消息)。日志中也发现 502 的全都是 POST 请求。

要完全解决这个问题就需要对 proxy_next_upstream 配置簇深入看看,我们先做个测试。 Continue Reading...

Windows 10生产力提升之WSL实践

Mac 和 Windows 哪个更好,估计还能再吵几年。抛开部分硬件上的差距,Windows 用作开发上确实有点不顺手,毕竟大部分后端程序都是运行在 Linux 服务器上,macOS 占有先天优势。之前都是通过在 Windows 上起虚拟机来解决这个问题,如今 Windows 10 推出的 WSL(Windows Subsystem for Linux),成为了新的选择。

WSL 的官方介绍

The Windows Subsystem for Linux lets developers run GNU/Linux environment - including most command-line tools, utilities, and applications - directly on Windows, unmodified, without the overhead of a virtual machine

简单来说就是不需要修改,无需虚拟机,就可以在 Windows 上直接使用 Linux 环境(grepsedawkd等),运行 Linux 程序(vim, MySQL, Apache等)。这就很舒服了,小孩子才做选择,大人全都要,实际如何我们来实践一番。

安装

首先是按照,非常简单,开启子系统功能后在应用商场选择一个发行版的 Linux 安装就行。官方指南:https://learn.microsoft.com/en-us/windows/wsl/install

启动之后就可以使用了,你可以根据自己使用习惯,做一些初始化的设置(oh-my-zsh啥的)这里就不细说了。WSL 默认是通过安装的应用进入的(直接运行 bash.exe 也行),但大家还是喜欢通过 Xshell 等工具进入了,这就需要做一些配置。WSL 和 Windows 本机是端口共用的(这点特别注意,防止程序端口冲突了),直接通过 127.0.0.1 就能连接上,剩下的就和直接使用 Linux 差不多了。

但是,等你重启电脑,再使用 Xshell 连接时发现连不上了,没有 sshd 进程了,每次都需要打开 WSL 程序然后再运行 sudo service ssh start 有点麻烦。这就是 WSL 不一样的地方了,WSL 不是真正的一个系统,没有开机启动这一说,把 sshd 设置成开机启动也没有用。既然不能加入到 WSL 的启动项,那就加入到 Windows 的启动项中:

编写 run_wsl.vbs 脚本,用来在 bash.exe 中启动 sshd,然后将脚本放到系统启动目录里面(WIN+R 运行 shell:Common Startup 或者 shell:startup)就行了。

不过需要注意 sudo 需要输入密码,还得把密码关了。WSL 中运行 sudo visudo 增加一行:ubuntu ALL=(root) NOPASSWD: ALL,表示 ubuntu (换成自己的)这个用户使用 sudo 运行任何命令时都不用输入密码。

当然安装之后可能会遇到一些其它问题,主要是一些安全软件和 WSL 不兼容,比如卡巴斯基会让 WSL 无法联网,腾讯的 TGP 会影响绑定端口,遇到了只能暂时关掉相关软件,相信之后这些问题会越来越少。

使用 Redis

之前在 Windows 上使用 Redis 比较麻烦,有一个 Windows 版的 Redis 但已经很久没有更新了,还有就是选择虚拟机。现在直接在 WSL 里面编译安装(make & make install)就行了。写好配置文件,sudo redis-server /etc/redis/redis.conf 运行即可。

不过有些同学可能会像我之前把 Redis 注册为 systemd 服务,但发现会报错:System has not been booted with systemd as init system (PID 1). Can't operate 说的也比较明白,系统不是通过 systemd 启动的。其实 WSL 不是一个真正的系统,应该说是一个容器,类似 Docker(这么一说感觉微软野心好大)。

LXSS diagram

(图片来源:https://learn.microsoft.com/zh-cn/archive/blogs/wsl/windows-subsystem-for-linux-overview

在 WSL 中运行的程序是真正的 Linux 二进制文件,不是移植版,不过当程序由用户态切换到内核态时lxss.syslxcore.sys驱动将会将 Linux 的系统调用翻译为  NT APIs 来模拟 Linux 内核。简单来说 WSL 是将 LInux 底层 API 用 NT 实现了一遍(所以 WSL 中没有 Linux kernel 代码),而且有些操作还是 Linux 独有的,比如fork(),可想而知这里面工作量有多大,任重而道远。

言归正传,Redis 已经在 WSL 中启动了,我们试试在 Windows 上能不能连上:

完美,就和 Windows 本地装的 Redis 一样。

与IDE集成

虽然说大部分语言都是跨平台的,但是还是有很多差别,比如 Python 的 epoll 只能在 Linux 下使用。在 Windows 上开发不是很方便,没有智能提示,还需要上传到服务器运行、调试。有了 WSL,现在可以在 Windows 上开发,然后直接在 WSL 里面运行。 Continue Reading...

phpMyAdmin浏览数据页面卡死

我们使用phpMyAdmin经常是一进去就直接展开数据库,点击表名查看数据,这样可以清楚的了解表的结构方便写 SQL。默认情况下是显示 25 条数据,即使有几亿行也很快。

但最近我换了一个 read 账号登录,点击表名的时候直接卡死了

应该是查询卡住了,命令行连上数据库,show full processlist发现有个查询确实卡住了:

一开始只关注到状态是Creating Sort Index,再结合使用 write 账号登录打开却很快的现象,认为是 read 账号没有 create 权限,导致卡着了(没有权限的话应该是直接报错的)。但是查了下 read 账号其实给的就是全部权限,一度陷入了僵局。

调整心态,回到原点,还是从「使用 write 账号登录打开却很快」的现象出发,查看日志,发现 write 账号进入的话点击表名浏览数据使用的 SQL 没有ORDER BY,那就是自动加的,查了下,phpMyAdmin 会自动保存上一次对字段排序的操作,再次操作字段能取消排序,但问题是我现在已经无法进入浏览页面了。

那考虑是不是能在哪里设置,关闭这个。页面上有个浏览模式设置,但这个试了下好像没啥用。

还是看下官方文档吧,找到了设置项,需要把$cfg['Servers'][$i]['table_uiprefs']$cfg['RememberSorting']设置为false。这个功能还是好的,但在多人使用同一账号,数据量又比较大的情况下,还是关闭比较好,关闭也不影响排序功能,只是排序的操作不会保存下来。

Since release 3.5.0 phpMyAdmin can be configured to remember several things (sorted column $cfg['RememberSorting'], column order, and column visibility from a database table) for browsing tables. Without configuring the storage, these features still can be used, but the values will disappear after you logout.

然后还得把已经保存的设置项清除掉,位置在数据库:phpmyadmin(默认)表:pma__table_uiprefs,把这个表清空即可又回到之前丝滑的感觉了。

参考资料

https://stackoverflow.com/questions/33809816/phpmyadmin-automatically-adding-order-by-to-browse

https://docs.phpmyadmin.net/en/latest/config.html#cfg_Servers_table_uiprefs

tomcat设置remote JVM debug后stop失败

最近突然出现执行 tomcat/bin/shutdown.sh 停止 tomcat 失败的情况,报错如下:

看了一下 catalina.sh: line 365 就是 stop 那里执行失败了,然后调用系统的 kill -15 杀死进程

再仔细看看报错,发现有端口被占用、dt_socket failed to initialize 等提示,觉得应该是加了 debug 命令的原因,去掉就好了。

但这个感觉也不太好,不能因噎废食呀。搜了下相关情况,发现是 JAVA_OPTS 用的不对,应该用 CATALINA_OPTS,因为 CATALINA_OPTS 变量在 stop 的时候不会被使用(如上图)。

所以最优的做法是使用 CATALINA_OPTS 变量设置 remote debug 相关参数。

 

参考资料

解决tomcat shutdown时的地址被占用问题,https://my.oschina.net/u/1770666/blog/370620

https://stackoverflow.com/questions/11222365/catalina-opts-vs-java-opts-what-is-the-difference

现代化的PHP-写好注释

注释

有个段子:程序员最恨写注释和不写注释的人。个人认为注释主要作用是方便别人或者自己以后能快速理解这段代码逻辑和提升开发效率,这样要说的是如何使用注释来提升开发效率。

众所周知,PHP 是弱类型语言,变量的类型是可以变的,但在实际业务中,一个变量或者函数的返回值往往是确定的,确定的类型能提升我们的编码效率,也能把一些低级的错误(弱类型不代表无类型)扼杀在摇篮里。

PHP 主要通过类型声明来说明变量的类型,PHP7 开始允许更多的函数参数类型限定。比如下面这个例子,声明入参是 int 类型,但使用了 array 函数来处理,PhpStorm 能给出实时的提醒。

调用函数参数错误的话也有提示:

但 PHP 会有隐式类型转换,如上面传入浮点数的时候并没有提示错误,因为这也是合理的。不过 PHP 可以开启严格模式来检查此类错误。

PHP 内置的类型声明基本上够用了,但一般使用注释来实现更加丰富的功能。PHPDoc 是注释 PHP 代码的非正式标准,它有许多不同的标记可以使用。PhpStorm 能根据函数/类方法自动生成基本的标记:

基本标记 @param@return 也很好理解,参数和返回值,作用和 PHP 自带的类型声明差不多,主要用来限制调用方传参和使用返回值。除了这两个标记之外,还有很多其它比较好用的标记。

Continue Reading...

GitHub 研发链 travis-ci 和 codecov 介绍

经常混迹于 GitHub 的话就会发现很多项目都有一些徽章,出现最多的就数这两个了:

这些徽章都是可以点击的,第一个点进去是 https://www.travis-ci.com/,travis-ci 是一个 CI(Continuous integration,持续集成) 平台,主要提供集群编译、单测、集成测试的环境。.org 的服务对公有仓库免费,.com 面向私人、团队、公司的项目提供商业支持(收费)。使用起来非常简单,使用 Github 帐号登录进去,就能看见开始界面:

核心就是.travis.yml的文件配置,一开始可以根据自己的语言选择初始的配置文件:https://docs.travis-ci.com/user/customizing-the-build#Specifying-Runtime-Versions。我这边的项目使用的是 Python,初始配置是:

官方注释可以说非常详细,我就不画蛇添足做说明了,根据自己代码实际情况做调整即可:

配置好之后再 push 代码,就能在 travis-ci 页面上看见项目的构建、测试结果了:

点击图标就能生成我们需要的 MD 语法的代码了,粘贴进 README.MD 中就能显示了。

第二个徽章是 codecov.io(单测覆盖率统计平台),接入过程也很简单,也是不同语言选择不同的配置文件,codecov 可以无缝衔接 travis-ci,只需要在原来的配置文件上稍作修改即可,核心就是生成单测的结果文件。修改后的 .travis.yml

在原来的基础上多了 codecov nose 插件的下载和运行单测时多了--with-coverage等参数。最后还是再次 push 代码就能看见单测覆盖率报告了:

codecov 还有自己的配置文件:codecov.yml。用来实现一些定制化需求,比如说我需要排除一些模块,不进入覆盖率统计:

徽章生成代码的话在setting中可以找到:

除了上面介绍的这两个徽章,还可以通过 shields.io 平台生成一些其它的徽章,甚至可以自定义,比如 https://img.shields.io/badge/Python-2.7-brightgreen.svg 就可以生成一个表示 Python 版本的徽章,别人能看懂就行。

当然徽章不是重点,不是越多的越牛逼,重要的是规范整个研发流程。上面说的两个其实都是属于 CI(持续集成)中最具代表性的两个环节,算是入门了。关于 CI 是一个很大的话题了,这个有机会以后再说一下。

参考资料:

开源项目徽章集锦,https://segmentfault.com/a/1190000004278253

使用 Python nose 组织 HTTP 接口测试

现在前端 Web、移动端 APP 开发基本上是面向接口编程,后端各种 HTTP 接口已经提供好了,大家只要实现逻辑交互和展示就行。那么问题来了,这么多接口如何方便的进行测试呢?根据个人经验,我认为需要解决三个问题:1. HTTP 接口多种多样,测试程序如何统一?2. 测试程序如何规范组织?3. 接口间有上下文依赖,如何解决?

HTTP 接口多种多样,测试程序如何统一?

后端接口可能来自各个系统,GET/POST 协议的、遵循 RESTful 规范的,使用 session 认证、token 认证的,各式各样的接口都存在。但无论怎么变都无法脱离 HTTP 协议。因为组内的技术栈是 Python,这就很容易想到使用 Python 的 requests 库。首先我们使用requests.Session()会话对象,来进行会话保持和 Header、Cookie 等处理。这里我们可以简单封装一个 HttpSession 类,把一些常用操作和设置封装一下:

通过HttpSession()来获取一个requests session对象,后续的 HTTP 请求都通过它发起。requests 提供 get()、post() 等各种方法调用,但这会让测试代码显得不统一,这里我们直接调用底层的 request 方法,该方法几乎提供了发起一个 HTTP 请求需要的所有参数,非常强大。

这样每一个 HTTP 接口的测试都可以通过准备参数发起请求断言结果三板斧来解决。

测试程序如何规范组织?

前面我们已经解决了如何快捷的发起一个 HTTP 请求的问题,这让我们几行代码就可以测试一个接口的返回值。但你会发现新的问题又来了,各种脚本散乱在一堆,返回值解析各种中间变量,各种配置硬编码在代码中,测试结果全靠 print,这时 nose 框架就派上用场了。nose 是 Python 最流行的一个单测框架,提供测试 case 设置标签、测试 case 查找、强大的断言函数,格式化/可视化报告输出等功能。这样就可以像写单测一样写 HTTP 接口测试了。我们可以把一类接口的测试放在一个类里面,甚至可以把基础变量的定义放在基础测试类里面,其它测试类继承这个基类。

HttpTest基类只做了两个工作:1. 创建http会话对象;2. 读取配置文件到类变量config中。配置文件是一个很好的编程实践,这让我们测试程序和数据能够分离,可以通过调用不同的配置文件来测试不同环境的接口,让我们测试程序不光能做线下测试,还能做线上回归和监控,切换成本非常低。

我这里选择 yaml 语法来组织配置信息,yaml 语法风格类似 json,自带基础数据结构,但更加易于书写和阅读,非常适合做配置文件。通过-env=xxx指定配置文件路径(或者 nose.cfg 配置文件中指定),使用 ruamel.yaml 来解析 yaml,转换为 Python 中能直接操作的数据结构。

这一切都放在setUpClass方法中,在具体测试 case 运行之前就已经准备好这些工作了。 Continue Reading...

本 Blog 不支持评论,如有疑问或建议请联系我,以完善内容,期望帮助到更多的同学