版权信息 书名:测试工程师必备技术锦囊
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
版权 著 陈 磊
责任编辑 张 涛
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线: (010)81055410
反盗版热线: (010)81055315
作者简介 陈磊 ,前京东测试架构师,具有十多年的软件测试开发以及技术管理经验,主要负责DevOps体系建设、工具链落地,同时引入AITesting和AIOps,完善DevOps工具链的智能化和自动化程度,加速端到端交付的进程。主导了京东工程生产力平台精灵平台设计和开发以及工程生产力赋能平台奥利凡德的设计和研发,引入AI技术设计和研发了AI测试框架AAT,实现了大部分测试工作机器替代人的目标。Asian Journal of Physical Education & Computer Science in Sports(《亚洲体育与计算机科学杂志》)编委会委员;双态IT联盟《测试敏捷化白皮书》特邀编委;北方工业大学软件体系结构实验室特邀企业专家;拥有多年的测试开发和性能测试经验,熟悉质量保障流程和测试流程,对测试技术、测试方法有深入的研究,公开发表学术论文近30篇,专利20余篇。多次成为TID、NCTS、MPD,MAD的特邀讲师。技术图书《决战618:探秘京东技术取胜之道》、《京东质量团队转型实践-从测试到测试开发的蜕变 》作者之一。
内容提要 主要针对功能测试需要掌握的技术要点进行详细的讲解,很多大牛人都对这些内容做过讲解,但是对该部分系统化的讲解目前还是鲜有人做。很多人会告诉你要学fiddler、postman、Git等一大堆的工具,但是作为功能测试工程师,这些工具每一款单拿出来都是一门很系统的课程,资料繁杂、内容又掺杂各种技术细节。这就使得很多人产生一种困惑:若学习一点这样的知识又觉得不深入,学多了又感觉永远不会用到,没有复现率就会忘,也就白学了。为此,作者特意写作了此专栏。
本专栏适合任何一个正在或者准备在不久的将来变成一个合格的软件工程师的人。
前言 在2019年的新年,突然想整理一下自己这么多年的测试笔记、博客等内容。同时也给这个假期找到了一个很有兴趣的事情。这就是这本书的起因。
本专栏的内容主要针对功能测试需要掌握的技术要点进行详细的讲解,很多大牛都对这些内容做过讲解,但是对该部分系统化的讲解目前还是鲜有人做。
很多人会告诉你要学fiddler、postman、git等等一大堆的工具,但是作为功能测试工程师,这些工具每一款单拿出来是一门很系统的课程,资料繁杂、内容有掺杂各种技术细节。
这就使得很多人产生一种困惑:学习一点又觉得不深入,学多了又永远不会用到,没有复现率就会忘,也就白学了。
本书就是为解决这个问题而出现的:
1、点兵点将:本部分会从根本上告诉你要知道哪些工具和怎么用,可以满足功能测试工程师的技术需求。
2、画好边界:工具、技术、方法学到哪里就足够了,已经可以满足你对这些技术的需求了。
3、延伸阅读:对于每一个技术、工具、方法的深入学习会推荐延伸阅读,给需要或者有兴趣的同学继续学习指引方向。
专栏中会用最简单、通俗的语言将晦涩难懂的一些名字用最直白的语言告诉你是什么,从根本上解决技术学习难的问题。同时会通过学习终点的设定,让你的学习有深度优先的结束条件。
本专栏比较适合一直在做功能测试的测试工程师,有1~5年的测试工作相关经验的业务测试工程师。
特别感谢人民邮电出版社和异步社区的老师,对这本小册子追逐精益的态度,让我钦佩。谢谢在我的任何形式的技术文章中帮我修改、完善的各位朋友。谢谢我的爱人饶鸣,当我想放弃的时候一直在鼓励我继续前行。谢谢Emma教会了我保持那可纯洁的心。
陈磊
2019年于北京
第1章 关于HTTP协议和RESTful 1.1 HTTP的概念知识点 一提到HTTP所有人都会表现得很不屑,我每天都与HTTP协议打交道,怎么可能不知道不了解它呢。其实认真说起来,可能没几个人能在不参考资料的情况下将HTTP说得清楚、完全。代码清单1-1展示了HTTP的头示例。
1.HTTP的特点
无状态:事务处理没有记忆能力
无连接:每次交互只处理一个请求
2.HTTP的基本消息格式
代码清单1-1 HTTP头示例
1. 首行#开始标记#
2. GET http://viptest.net/ HTTP/1.1
3. 首行#结束标记#
4. 头部#开始标记#
5. Host: viptest.net
6. Connection: keep-alive
7. Upgrade-Insecure-Requests: 1
8. User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
9. Accept: text/html,application/xhtml+xml,application/ xml;q=0.9,image/webp,image/apng,*/*;q=0.8
10. Accept-Encoding: gzip, deflate
11. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,la;q=0.7,ja;q=0.6
12. Cookie: pgv_pvi=6676973568
13. 头部#结束标记#
14. 以下如果有内容就是正文
3.方法
GET
POST
PUT
PATCH
DELETE
HEAD
OPTIONS
除去Get和Post其他本部分都不讲,因为很少用到。
1.1.1 HTTP的GET GET主要是从指定的资源请求数据,查询的参数是从URL中发送出去的。作为一个测试,我们需要记住以下特点。
GET 请求可被缓存。
GET 请求保留在浏览器历史记录中。
GET 请求可被收藏为书签。
GET 请求不应在处理敏感数据时使用。
GET 请求有长度限制(这个现实是有URL长度约束而非参数长度)。
GET 请求只应当用于取回数据。
1.1.2 HTTP的POST POST主要是用来向指定的资源提交要被处理的数据。查询字符串是在 POST 请求的 HTTP 消息主体中发送的。POST有以下特点。
POST 请求不会被缓存。
POST 请求不会保留在浏览器历史记录中。
POST 不能被收藏为书签。
POST 请求对数据长度没有要求。
1.1.3 HTTP的头详解 1.Request的头详解 HTTP的Request头包含Accept、Connection、Cookie等,具体内容如表1-1 所示。
表1-1 Request的头信息
Header
解释
示例
Accept
指定客户端能够接收的内容类型
Accept: text/plain, text/html
Accept-Charset
浏览器可以接受的字符编码集。
Accept-Charset: iso-8859-5
Accept-Encoding
指定浏览器可以支持的web服务器返回内容压缩编码类型。
Accept-Encoding: compress, gzip
Accept-Language
浏览器可接受的语言
Accept-Language: en,zh
Accept-Ranges
可以请求网页实体的一个或者多个子范围字段
Accept-Ranges: bytes
Authorization
HTTP授权的授权证书
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control
指定请求和响应遵循的缓存机制
Cache-Control: no-cache
Connection
表示是否需要持久连接。(HTTP 1.1默认进行持久连接)
Connection: close
Cookie
HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。
Cookie: $Version=1; Skin=new;
Content-Length
请求的内容长度
Content-Length: 348
Content-Type
请求的与实体对应的MIME信息
Content-Type: application/x-www-form-urlencoded
Date
请求发送的日期和时间
Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect
请求的特定的服务器行为
Expect: 100-continue
From
发出请求的用户的Email
From: user@email.com
Host
指定请求的服务器的域名和端口号
Host: www.nudgetech.com
If-Match
只有请求内容与实体相匹配才有效
If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since
如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码
If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match
如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变
If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range
如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag
If-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since
只在实体在指定时间之后未被修改才请求成功
If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards
限制信息通过代理和网关传送的时间
Max-Forwards: 10
Pragma
用来包含实现特定的指令
Pragma: no-cache
Proxy-Authorization
连接到代理的授权证书
Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range
只请求实体的一部分,指定范围
Range: bytes=500-999
Referer
先前网页的地址,当前请求网页紧随其后,即来路
Referer: http://www.nudgetech.com/index.html
TE
客户端愿意接受的传输编码,并通知服务器接受尾加头信息
TE: trailers,deflate;q=0.5
Upgrade
向服务器指定某种传输协议以便服务器进行转换(如果支持)
Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
User-Agent
User-Agent的内容包含发出请求的用户信息
User-Agent: Mozilla/5.0 (Linux; X11)
Via
通知中间网关或代理服务器地址,通信协议
Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning
关于消息实体的警告信息
Warn: 199 Miscellaneous warning
2.Response的头详解 HTTP的Response头包含例如Accept、Age、Connect等,具体内容如表1-2 所示。
表1-2 Response的头信息
Header
解释
示例
Accept-Ranges
表明服务器是否支持指定范围请求及哪种类型的分段请求
Accept-Ranges: bytes
Age
从原始服务器到代理缓存形成的估算时间(以秒计,非负)
Age: 12
Allow
对某网络资源的有效的请求行为,不允许则返回405
Allow: GET, HEAD
Cache-Control
告诉所有的缓存机制是否可以缓存及缓存哪种类
Cache-Control: no-cache
Content-Encoding
web服务器支持的返回内容压缩编码类型
Content-Encoding: gzip
Content-Language
响应体的语言
Content-Language: en,zh
Content-Length
响应体的长度
Content-Length: 348
Content-Location
请求资源可替代的备用的另一地址
Content-Location: /index.htm
Content-MD5
返回资源的MD5校验值
Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
Content-Range
在整个返回体中本部分的字节位置
Content-Range: bytes 21010-47021/47022
Content-Type
返回内容的MIME类型
Content-Type: text/html; charset=utf-8
Date
原始服务器消息发出的时间
Date: Tue, 15 Nov 2010 08:12:31 GMT
ETag
请求变量的实体标签的当前值
ETag: “737060cd8c284d8af7ad3082f209582d”
Expires
响应过期的日期和时间
Expires: Thu, 01 Dec 2010 16:00:00 GMT
Last-Modified
请求资源的最后修改时间
Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT
Location
用来重定向接收方到非请求URL的位置来完成请求或标识新的资源
Location: http://www.nugetech.com
Pragma
包括实现特定的指令,它可应用到响应链上的任何接收方
Pragma: no-cache
Proxy-Authenticate
它指出认证方案和可应用到代理的该URL上的参数
Proxy-Authenticate: Basic
refresh
应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持)
Refresh: 5; url=http://www.nugetech.com
Retry-After
如果实体暂时不可取,通知客户端在指定时间之后再次尝试
Retry-After: 120
Server
web服务器软件名称
Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
Set-Cookie
设置Http Cookie
Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
Trailer
指出头域在分块传输编码的尾部存在
Trailer: Max-Forwards
Transfer-Encoding
文件传输编码
Transfer-Encoding:chunked
Vary
告诉下游代理是使用缓存响应还是从原始服务器请求
Vary: *
Via
告知代理客户端响应是通过哪里发送的
Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning
警告实体可能存在的问题
Warning: 199 Miscellaneous warning
WWW-Authenticate
表明客户端请求实体应该使用的授权方案
WWW-Authenticate: Basic
1.1.4 HTTP的状态码 1.消息 HTTP的返回状态码包含按照开始数组进行了分类,其中10X相关状态码信息如表1-3:
表1-3 10X状态码信息
状态码
描述
100
Continue:继续。客户端应继续其请求
101
Switching Protocols:切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
102
Processing:由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行
2.成功 HTTP的状态码用20X表示成功状态,其中详细内容如图表1-4所示。
表1-4 20X状态码信息
状态码
描述
200
Ok:请求已成功,请求所希望的响应头或数据体将随此响应返回。出现此状态码是表示正常状态
201
Created:请求已经被实现,而且有一个新的资源已经依据请求的需要而建立,且其 URI 已经随Location 头信息返回。假如需要的资源无法及时建立的话,应当返回 '202 Accepted'
202
Accepted:已接受。已经接受请求,但未处理完成
203
Non-Authoritative Information:非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204
No Content:无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205
Reset Content:重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206
Partial Content:部分内容。服务器成功处理了部分GET请求
207
Multi-Status:由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码
3.重定向 HTTP使用30X表示重定向,其中详细的对应状态说明如表1-5所示。
表1-5 30X状态码信息
状态码
描述
300
Multiple Choices:多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301
Moved Permanently:永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302
Move temporarily:临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303
See Other:查看其他地址。与301类似。使用GET和POST请求查看
304
Not Modified:未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305
Use Proxy:使用代理。所请求的资源必须通过代理访问
306
Switch Proxy:在最新版的规范中,306状态码已经不再被使用
307
Temporary Redirect:临时重定向。与302类似。使用GET请求重定向
4.请求错误 HTTP使用40X表示请求错误,其中详细的对应状态说明如表1-6所示。
表1-6 40X状态码信息
状态码
描述
400
Bad Request:1.语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求;2.请求参数有误
401
Unauthorized:请求要求用户的身份认证
402
Payment Required:该状态码是为了将来可能的需求而预留的
403
Forbidden:服务器理解请求客户端的请求,但是拒绝执行此请求
404
Not Found:服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405
Method Not Allowed:客户端请求中的方法被禁止
406
Not Acceptable:服务器无法根据客户端请求的内容特性完成请求
407
Proxy Authentication Required:请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408
Request Timeout:请求超时。客户端没有在服务器预备等待的时间内完成一个请求的发送。客户端可以随时再次提交这一请求而无需进行任何更改
409
Conflict:服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突
410
Gone:客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411
Length Required:服务器无法处理客户端发送的不带Content-Length的请求信息
412
Precondition Failed:客户端请求信息的先决条件错误
413
Request Entity Too Large:由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414
Request-URI Too Long:请求的URI过长(URI通常为网址),服务器无法处理
415
Unsupported Media Type:对于当前请求的方法和所请求的资源,请求中提交的实体并不是服务器中所支持的格式,因此请求被拒绝
416
Requested Range Not Satisfiable:客户端请求的范围无效
417
Expectation Failed:在请求头 Expect 中指定的预期内容无法被服务器满足,或者这个服务器是一个代理服务器,它有明显的证据证明在当前路由的下一个节点上,Expect 的内容无法被满足
5.服务器错误 HTTP使用50X表示服务器错误状态,其中详细的对应状态说明如表1-7所示。
表1-7 50X状态码信息
状态码
描述
500
Internal Server Error:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现
501
Not Implemented:服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求
502
Bad Gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应
503
Service Unavailable:由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504
Gateway Timeout:作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应
505
HTTP Version Not Supported:服务器不支持,或者拒绝支持在请求中使用的 HTTP 版本。这暗示着服务器不能或不愿使用与客户端相同的版本。响应中应当包含一个描述了为何版本不被支持以及服务器支持哪些协议的实体
1.2 RESTful详解 REST全称是Representational State Transfer,中文意思是表述性状态转移。Roy Fielding在其论文中提出的:“我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。”
因此我们将符合REST约束的架构称为RESTful。其中REST的主语是被忽略的,含义是Resource。因此,要理解REST就是要理解Resource(资源)、Representation(表现层)、State Transfer(状态转化)。
1.2.1 Resource 资源 这里的Resource(资源)所指网络上的一个实体,或者说是网络上的一个具体信息。例如:一段text、一个图片、一个多媒体服务文件或者一种可以提供的服务。可以通过一个URI(Uniform Resource Identifier,统一资源标识符),要和对应的URI发生交互,只需要访问对应URI就可以了。
例如:
http://api.pr.com/v1/news: 获取的新闻; http://api.pr.com/v1/group: 获取群组列表; http://api.pr.com/v1/profile: 获取个人的详细信息。
1.2.2 Representation 表现层 Resource是网络上的实体,那么将这个实体呈现出来的方式就是Representation,也就是表现层的作用。例如我们既可以用JSON描述秒数一串数据,也可以用XML进行描述。这里面URI是Resource的统一标识,对外如何提供表现是由Representation完成的。在RESTful的服务中,Server和Client之间传递某资源的一个表现形式,比如用JSON、XML传输文本,或者用JPG、WebP传输图片等。当然还可以压缩HTTP传输时的数据(on-wire data compression)。
1.2.3 State Transfer 状态转化 在我们通过网络和一个网页发生交互的时候,就产生了一次交互式的流程化的传递,这个传递过程就会有数据的传递、状态的转变。这个过程就是State Transfer(状态转化)。这个转化是在Representation之上的,因此可以说State Transfer是在表现层之上的完成的。在这个转化过程中就会应用到HTTP协议的Get、Post、Put和Delete这4种基本操作。
GET 用来获取资源。
POST 用来新建资源(或者更新资源)。
PUT 用来更新资源。
DELETE 用来删除资源。
提倡或者鼓励使用的方式如下。
DELETE http://api.pr.com/v1/group:删除群组;
POST http://api.pr.com/v1/friends:添加群组;
UPDATE http://api.pr.com/v1/profile:更新个人信息。
不提倡或者不允许使用的方式如下:
http://api.pr.com/v1/deleteGroup。
同时,RESTful要求HTTP Status Code传递Server的状态信息。比如最常用的 200 表示成功,500 表示Server内部错误等。
1.2.4 RESTful的就是这个概念 通过上面的学习,我们可以看出,RESTful就是通过HTTP的Get、Post、Put或者Delete的操作,在表现层之上和URI唯一标识的资源发生交互,产生状态转化的一个过程。
第2章 WebSocket接口测试20分钟入门到精通 2.1 WebSocket一点通 WebSocket是HTML5开始提供的一种在单个TCP 连接上进行全双工通讯的协议。在之前我们介绍过,HTTP协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这就出现了一个很严重的问题,HTTP协议永远无法从Server端发起会话。这种单向的通信模式,有需求需要服务端有状态变化后通知客户端获知的情况,很多web应用通过大量频繁的异步JavaScript和XML请求实现长轮询。这种机制效率低下、资源耗费大。
WebSocket 就是为了满足和解决这种问题而出现的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。HTTP和WebSocket的对比关系如图2-1所示。
图2-1 HTTP协议和WebSocket协议请求流程对比
Websocket的优点如下。
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)既可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
2.2 为你送上一个webSocket测试工具 在webSocket测试的时候,目前Apache JMeter支持对应的接口的sampler。知道如何使用Apache JMeter的自行搜索学习吧,本文提供了一段通用的websocket接口测试的Python脚本(Pyhton环境部署参照本书第8章),并作了详细的注释,如代码清单2-1所示。
代码清单2-1 python的webSocket测试
1.from websocket import create_connection
2.import websocket
3.import unittest
4.class TestWebSocket(unittest.TestCase):
5. def setUp(self):
6. #新建 websocket实例
7. self.ws = websocket.WebSocket()
8. # 被测试服务端方位uri(需要替换你自己的)
9. self.ws_uri = "ws://192.168.10.63:8090/v2/port/streaming?vend=Test"
10. # 访问接口需要的headers(需要替换你自己的)
11. self.ws_headers=["x-token:ajKfZgQAf6vIddwC",
12. "x-tenant:T001124",
13. "x-server:1026"]
14. #建立连接
15. self.ws.connect(self.ws_uri,
16. self.ws_headers
17. )
18. def test_login(self):
19. #需要客户端发送的内容
20. self.ws.send("Hello, World")
21. #客户端接受的内容
22. ws_response = self.ws.recv()
23. #此处在测试的时候,修改成self.assertEquals()
24. print(ws_response.title())
25. def tearDown(self):
26. self.ws.close()
注意,在使用过程中可以将其对应的部分替换掉(代码注释已经详细说明)即可。特别提示:在应用前需要 pip install websocket-client。
2.3 websocket数据帧格式和数据传递(选读章节) 2.3.1 数据帧格式 客户端与服务端数据的交换,离不开数据帧格式的定义。因此,在讲解数据交换之前,我们先来了解一下WebSocket的数据帧格式。
WebSocket客户端、服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。
2.3.2 数据帧格式概览 下面给出了WebSocket数据帧的统一格式。熟悉TCP/IP协议的读者对图2-2应该不陌生。
从左到右,单位是bit。比如FIN、RSV1各占据1bit,opcode占据4bit,内容包括了标识、操作代码、掩码、数据、数据长度等。(具体将在2.3.3节中展开讨论)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |Extended payload length|
|I|S|S|S|(4)|A| (7) | (16/64)|
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| ||
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127|
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) |Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
图2-2 WebSocket数据帧的统一格式
2.3.3 数据帧格式详解 针对前面的格式概览图,本节将进行逐个字段的讲解,如有不清楚之处,可参考协议规范,或留言交流。
1.FIN:1bit 如果是fin值是1,表示这是消息(message)的最后一个分片(fragment);如果是0,表示这不是消息(message)的最后一个分片(fragment)。
RSV1、RSV2、RSV3各占1bit。
一般情况下值全为0。当客户端、服务端协商采用WebSocket扩展时,这3个标志位可以不为0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用WebSocket扩展,则表示连接出错。
2.Opcode: 4bit 操作代码,Opcode的值决定了应该如何解析后续的数据载荷(data payload)。如果操作代码是不认识的,那么接收端应该断开连接(fail the connection)。可选的操作代码如下所示。
%x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
%x1:表示这是一个文本帧(frame)。
%x2:表示这是一个二进制帧(frame)。
%x3-7:保留的操作代码,用于后续定义的非控制帧。
%x8:表示连接断开。
%x9:表示这是一个ping操作。
%xA:表示这是一个pong操作。
%xB-F:保留的操作代码,用于后续定义的控制帧。
3.Mask: 1bit 这个值表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。
如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。如果Mask值是1,那么在Masking-key中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask值都是1。掩码的算法、用途将在下一小节进行讲解。
4.Payload length 数据载荷的长度单位是字节,为7位,或7+16位,或1+64位。假设数Payload length === x,如果x 为0~126:数据的长度为x字节。 x 为126:后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。 x 为127:后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。 此外,如果payload length占用了多个字节的话,payload length的二进制表达采用网络序(big endian,重要的位在前)。
5.Masking-key:0或4字节(32位) 所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask为1,且携带了4字节的Masking-key。如果Mask值为0,则没有Masking-key。
备注:载荷数据的长度,不包括mask key的长度。
6. Payload data:(x+y) 字节 载荷数据:包括扩展数据、应用数据。其中,扩展数据x 字节,应用数据y 字节。
扩展数据:如果没有协商使用扩展的话,扩展数据为0字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度
7.掩码算法 掩码键(Masking-key)是由客户端挑选出来的32位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法。
假设:
original-octet-i:为原始数据的第i 字节。
transformed-octet-i:为转换后的数据的第i 字节。
j:为i mod 4的结果。
masking-key-octet-j:为mask key第j 字节。
算法描述为:original-octet-i 与 masking-key-octet-j 异或后,得到 transformed-octet-i。
8.数据传递 一旦WebSocket客户端、服务端建立连接后,后续的操作都是基于数据帧的传递。
WebSocket根据opcode来区分操作的类型。比如0x8表示断开连接,0x0-0x2表示数据交互。
9.数据分片 WebSocket的每条消息可能被切分成多个数据帧。当WebSocket的接收方收到一个数据帧时,会根据FIN的值来判断,是否已经收到消息的最后一个数据帧。
FIN=1表示当前数据帧为消息的最后一个数据帧,此时接收方已经收到完整的消息,可以对消息进行处理。FIN=0,则接收方还需要继续监听接收其余的数据帧。
此外,opcode在数据交换的场景下,表示的是数据的类型。0x01表示文本,0x02表示二进制。而0x00比较特殊,表示延续帧(continuation frame),顾名思义,就是完整消息对应的数据帧还没接收完。
10.数据分片例子 直接看例子更形象些。下面例子来自MDN,可以很好地演示数据的分片。客户端向服务端两次发送消息,服务端收到消息后回应客户端,这里主要看客户端往服务端发送的消息。
(1)第一条消息:FIN=1, 表示是当前消息的最后一个数据帧。服务端收到当前数据帧后,可以处理消息。opcode=0x1,表示客户端发送的是文本类型。
(2)第二条消息如代码清单2-2所示。
代码清单2-2
1.FIN=0,opcode=0x1,表示发送的是文本类型,且消息还没发送完成,还有后续的数据帧
2.FIN=0,opcode=0x0,表示消息还没发送完成,还有后续的数据帧,当前的数据帧需要接在上一条数据帧之后
3.FIN=1,opcode=0x0,表示消息已经发送完成,没有后续的数据帧,当前的数据帧需要接在上一条数据帧之后。服务端可以将关联的数据帧组装成完整的消息
4.Client: FIN=1, opcode=0x1, msg="hello"
5.Server: (process complete message immediately) Hi.
6.Client: FIN=0, opcode=0x1, msg="and a"
7.Server: (listening, new message containing text started)
8.Client: FIN=0, opcode=0x0, msg="happy new"
9.Server: (listening, payload concatenated to previous message)
10.Client: FIN=1, opcode=0x0, msg="year!"
11.Server: (process complete message) Happy new year to you too!