diff --git a/.gitignore b/.gitignore
index 66b3677..7c6337f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,8 +14,11 @@
main
main_linux
main_win.exe
-go_stress_testing_mac
-go_stress_testing_linux
-go_stress_testing_win.exe
-curl.txt
-*curl.txt
\ No newline at end of file
+go-stress-testing-mac
+go-stress-testing-linux
+go-stress-testing-win.exe
+model/modeltest/*
+model/modeltest*
+server.go
+vendor
+test.sh
\ No newline at end of file
diff --git a/README.md b/README.md
index 7f9b248..fc302b3 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
-# 压测工具如何选择? ab、locust、Jmeter、go压测工具【单台机器100w连接压测实战】
+# go实现的压测工具【单台机器100w连接压测实战】
本文介绍压测是什么,解释压测的专属名词,教大家如何压测。介绍市面上的常见压测工具(ab、locust、Jmeter、go实现的压测工具、云压测),对比这些压测工具,教大家如何选择一款适合自己的压测工具,本文还有两个压测实战项目:
-- 单台机器对HTTP短连接 QPS 1W+ 的压测实战
-- 单台机器100W长连接的压测实战
+- 单台机器对 HTTP 短连接 QPS 1W+ 的压测实战
+- 单台机器 100W 长连接的压测实战
+- 对 grpc 接口进行压测
+> 简单扩展即可支持 私有协议
## 目录
- [1、项目说明](#1项目说明)
@@ -21,7 +23,7 @@
- [3、常见的压测工具](#3常见的压测工具)
- [3.1 ab](#31-ab)
- [3.2 locust](#32-locust)
- - [3.3 Jmeter](#33-Jmeter)
+ - [3.3 JMeter](#33-JMeter)
- [3.4 云压测](#34-云压测)
- [3.4.1 云压测介绍](#341-云压测介绍)
- [3.4.2 阿里云 性能测试 PTS](#342-阿里云-性能测试-PTS)
@@ -31,6 +33,7 @@
- [4.2 用法](#42-用法)
- [4.3 实现](#43-实现)
- [4.4 go-stress-testing 对 Golang web 压测](#44-go-stress-testing-对-golang-web-压测)
+ - [4.5 grpc压测](#45-grpc压测)
- [5、压测工具的比较](#5压测工具的比较)
- [5.1 比较](#51-比较)
- [5.2 如何选择压测工具](#52-如何选择压测工具)
@@ -40,40 +43,34 @@
- [6.3 客户端配置](#63-客户端配置)
- [6.4 准备](#64-准备)
- [6.5 压测数据](#65-压测数据)
-- [7、总结](#7总结)
-- [8、参考文献](#8参考文献)
-
-
+- [7、常见问题](#7常见问题)
+- [8、总结](#8总结)
+- [9、参考文献](#9参考文献)
## 1、项目说明
### 1.1 go-stress-testing
-go 实现的压测工具,每个用户用一个协程的方式模拟,最大限度的利用CPU资源
+go 实现的压测工具,每个用户用一个协程的方式模拟,最大限度的利用 CPU 资源
### 1.2 项目体验
- 可以在 mac/linux/windows 不同平台下执行的命令
+- [go-stress-testing](https://github.com/link1st/go-stress-testing/releases) 压测工具下载地址
+
参数说明:
`-c` 表示并发数
-`-n` 每个并发执行请求的次数,总请求的次数 = 并发数 * 每个并发执行请求的次数
+`-n` 每个并发执行请求的次数,总请求的次数 = 并发数 `*` 每个并发执行请求的次数
`-u` 需要压测的地址
-```shell script
-
-# clone 项目
-git clone https://github.com/link1st/go-stress-testing.git
-
-# 进入项目目录
-cd go-stress-testing
-
-# 运行
-go run main.go -c 1 -n 100 -u https://www.baidu.com/
+```shell
+# 运行 以mac为示例
+./go-stress-testing-mac -c 1 -n 100 -u https://www.baidu.com/
```
@@ -131,7 +128,6 @@ go run main.go -c 1 -n 100 -u https://www.baidu.com/
**错误码**: 压测中,接口返回的 code码:返回次数的集合
-
## 2、压测
### 2.1 压测是什么
@@ -144,7 +140,6 @@ go run main.go -c 1 -n 100 -u https://www.baidu.com/
- 压测的目的就是通过压测(模拟真实用户的行为),测算出机器的性能(单台机器的QPS),从而推算出系统在承受指定用户数(100W)时,需要多少机器能支撑得住
- 压测是在上线前为了应对未来可能达到的用户数量的一次预估(提前演练),压测以后通过优化程序的性能或准备充足的机器,来保证用户的体验。
-
### 2.3 压测名词解释
#### 2.3.1 压测类型解释
@@ -167,9 +162,9 @@ go run main.go -c 1 -n 100 -u https://www.baidu.com/
| 请求成功数(Request Success Number) | 在一次压测中,请求成功的数量 |
| 请求失败数(Request Failures Number) | 在一次压测中,请求失败的数量 |
| 错误率(Error Rate) | 在压测中,请求成功的数量与请求失败数量的比率 |
-| 最大响应时间(Max Response Time) | 在一次事务中,从发出请求或指令系统做出的反映(响应)的最大时间 |
-| 最少响应时间(Mininum Response Time) | 在一次事务中,从发出请求或指令系统做出的反映(响应)的最少时间 |
-| 平均响应时间(Average Response Time) | 在一次事务中,从发出请求或指令系统做出的反映(响应)的平均时间 |
+| 最大响应时间(Max Response Time) | 在一次压测中,从发出请求或指令系统做出的反映(响应)的最大时间 |
+| 最少响应时间(Mininum Response Time) | 在一次压测中,从发出请求或指令系统做出的反映(响应)的最少时间 |
+| 平均响应时间(Average Response Time) | 在一次压测中,从发出请求或指令系统做出的反映(响应)的平均时间 |
#### 2.3.3 机器性能指标解释
@@ -189,29 +184,29 @@ go run main.go -c 1 -n 100 -u https://www.baidu.com/
### 2.4 如何计算压测指标
-- 压测我们需要有目的性的压测,这次压测我们需要达到什么目标(如:单台机器的性能为100QPS?网站能同时满足100W人同时在线)
+- 压测我们需要有目的性的压测,这次压测我们需要达到什么目标(如:单台机器的性能为 100QPS?网站能同时满足100W人同时在线)
- 可以通过以下计算方法来进行计算:
- 压测原则:每天80%的访问量集中在20%的时间里,这20%的时间就叫做峰值
-- 公式: ( 总PV数*80% ) / ( 每天的秒数*20% ) = 峰值时间每秒钟请求数(QPS)
+- 公式: ( 总PV数`*`80% ) / ( 每天的秒数`*`20% ) = 峰值时间每秒钟请求数(QPS)
- 机器: 峰值时间每秒钟请求数(QPS) / 单台机器的QPS = 需要的机器的数量
- 假设:网站每天的用户数(100W),每天的用户的访问量约为3000W PV,这台机器的需要多少QPS?
-> `( 30000000*0.8 ) / (86400 * 0.2) ≈ 1389 (QPS)`
+> ( 30000000\*0.8 ) / (86400 * 0.2) ≈ 1389 (QPS)
- 假设:单台机器的的QPS是69,需要需要多少台机器来支撑?
-> `1389 / 69 ≈ 20`
+> 1389 / 69 ≈ 20
## 3、常见的压测工具
### 3.1 ab
- 简介
-ApacheBench 是 Apache服务器自带的一个web压力测试工具,简称ab。ab又是一个命令行工具,对发起负载的本机要求很低,根据ab命令可以创建很多的并发访问线程,模拟多个访问者同时对某一URL地址进行访问,因此可以用来测试目标服务器的负载压力。总的来说ab工具小巧简单,上手学习较快,可以提供需要的基本性能指标,但是没有图形化结果,不能监控。
+ApacheBench 是 Apache 服务器自带的一个web压力测试工具,简称 ab。ab 又是一个命令行工具,对发起负载的本机要求很低,根据 ab 命令可以创建很多的并发访问线程,模拟多个访问者同时对某一 URL 地址进行访问,因此可以用来测试目标服务器的负载压力。总的来说 ab 工具小巧简单,上手学习较快,可以提供需要的基本性能指标,但是没有图形化结果,不能监控。
-ab属于一个轻量级的压测工具,结果不会特别准确,可以用作参考。
+ab 属于一个轻量级的压测工具,结果不会特别准确,可以用作参考。
- 安装
-```shell script
+```shell
# 在linux环境安装
sudo yum -y install httpd
```
@@ -232,7 +227,7 @@ Options are:
- 压测命令
-```shell script
+```shell
# 使用ab压测工具,对百度的链接 请求100次,并发数1
ab -n 100 -c 1 https://www.baidu.com/
```
@@ -297,7 +292,7 @@ Percentage of the requests served within a certain time (ms)
- `Failed requests` 失败个数
-- `Requests per second` 吞吐量,指的是某个并发用户下单位时间内处理的请求数。等效于QPS,其实可以看作同一个统计方式,只是叫法不同而已。
+- `Requests per second` 吞吐量,指的是某个并发用户下单位时间内处理的请求数。等效于 QPS,其实可以看作同一个统计方式,只是叫法不同而已。
- `Time per request` 用户平均请求等待时间
@@ -307,11 +302,11 @@ Percentage of the requests served within a certain time (ms)
- 简介
-是非常简单易用、分布式、python开发的压力测试工具。有图形化界面,支持将压测数据导出。
+是非常简单易用、分布式、python 开发的压力测试工具。有图形化界面,支持将压测数据导出。
- 安装
-```shell script
+```shell
# pip3 安装locust
pip3 install locust
# 查看是否安装成功
@@ -327,7 +322,7 @@ pip3 install websocket-client
编写压测脚本 **test.py**
```python
-from locust import HttpLocust, TaskSet, task
+from locust import HttpUser, TaskSet, task
# 定义用户行为
class UserBehavior(TaskSet):
@@ -336,16 +331,15 @@ class UserBehavior(TaskSet):
def baidu_index(self):
self.client.get("/")
-
-class WebsiteUser(HttpLocust):
- task_set = UserBehavior # 指向一个定义的用户行为类
+class WebsiteUser(HttpUser):
+ task = [UserBehavior] # 指向一个定义的用户行为类
min_wait = 3000 # 执行事务之间用户等待时间的下界(单位:毫秒)
max_wait = 6000 # 执行事务之间用户等待时间的上界(单位:毫秒)
```
- 启动压测
-```shell script
+```shell
locust -f test.py --host=https://www.baidu.com
```
@@ -387,7 +381,7 @@ Hatch rate (users spawned/second) 每秒钟增加用户数

-### 3.3 Jmeter
+### 3.3 JMeter
- 简介
@@ -435,52 +429,75 @@ PTS(Performance Testing Service)是面向所有技术背景人员的云化
### 4.1 介绍
-- go-stress-testing 是go语言实现的简单压测工具,源码开源、支持二次开发,可以压测http、webSocket请求,使用协程模拟单个用户,可以更高效的利用CPU资源。
+- go-stress-testing 是go语言实现的简单压测工具,源码开源、支持二次开发,可以压测http、webSocket请求、私有rpc调用,使用协程模拟单个用户,可以更高效的利用CPU资源。
- 项目地址 [https://github.com/link1st/go-stress-testing](https://github.com/link1st/go-stress-testing)
### 4.2 用法
+- [go-stress-testing](https://github.com/link1st/go-stress-testing/releases) 下载地址
+- clone 项目源码运行的时候,需要将项目 clone 到 **$GOPATH** 目录下
- 支持参数:
```
-Usage of ./go_stress_testing_mac:
+Usage of ./go-stress-testing-mac:
-c uint
- 并发数 (default 1)
- -d string
- 调试模式 (default "false")
+ 并发数 (default 1)
-n uint
- 请求总数 (default 1)
- -p string
- curl文件路径
+ 请求数(单个并发/协程) (default 1)
-u string
- 请求地址
+ 压测地址
+ -d string
+ 调试模式 (default "false")
+ -H value
+ 自定义头信息传递给服务器 示例:-H 'Content-Type: application/json'
+ -data string
+ HTTP POST方式传送数据
-v string
- 验证方法 http 支持:statusCode、json webSocket支持:json (default "statusCode")
+ 验证方法 http 支持:statusCode、json webSocket支持:json
+ -p string
+ curl文件路径
```
- `-n` 是单个用户请求的次数,请求总次数 = `-c`* `-n`, 这里考虑的是模拟用户行为,所以这个是每个用户请求的次数
+- 下载以后执行下面命令即可压测
- 使用示例:
```
# 查看用法
-go run main.go
+./go-stress-testing-mac
# 使用请求百度页面
-go run main.go -c 1 -n 100 -u https://www.baidu.com/
+./go-stress-testing-mac -c 1 -n 100 -u https://www.baidu.com/
# 使用debug模式请求百度页面
-go run main.go -c 1 -n 1 -d true -u https://www.baidu.com/
+./go-stress-testing-mac -c 1 -n 1 -d true -u https://www.baidu.com/
# 使用 curl文件(文件在curl目录下) 的方式请求
-go run main.go -c 1 -n 1 -p curl/baidu.curl.txt
+./go-stress-testing-mac -c 1 -n 1 -p curl/baidu.curl.txt
# 压测webSocket连接
-go run main.go -c 10 -n 10 -u ws://127.0.0.1:8089/acc
+./go-stress-testing-mac -c 10 -n 10 -u ws://127.0.0.1:8089/acc
```
+- 完整压测命令示例
+```shell script
+# 更多参数 支持 header、post body
+go run main.go -c 1 -n 1 -d true -u 'https://page.aliyun.com/delivery/plan/list' \
+ -H 'authority: page.aliyun.com' \
+ -H 'accept: application/json, text/plain, */*' \
+ -H 'content-type: application/x-www-form-urlencoded' \
+ -H 'origin: https://cn.aliyun.com' \
+ -H 'sec-fetch-site: same-site' \
+ -H 'sec-fetch-mode: cors' \
+ -H 'sec-fetch-dest: empty' \
+ -H 'referer: https://cn.aliyun.com/' \
+ -H 'accept-language: zh-CN,zh;q=0.9' \
+ -H 'cookie: aliyun_choice=CN; JSESSIONID=J8866281-CKCFJ4BUZ7GDO9V89YBW1-KJ3J5V9K-GYUW7; maliyun_temporary_console0=1AbLByOMHeZe3G41KYd5WWZvrM%2BGErkaLcWfBbgveKA9ifboArprPASvFUUfhwHtt44qsDwVqMk8Wkdr1F5LccYk2mPCZJiXb0q%2Bllj5u3SQGQurtyPqnG489y%2FkoA%2FEvOwsXJTvXTFQPK%2BGJD4FJg%3D%3D; cna=L3Q5F8cHDGgCAXL3r8fEZtdU; isg=BFNThsmSCcgX-sUcc5Jo2s2T4tF9COfKYi8g9wVwr3KphHMmjdh3GrHFvPTqJD_C; l=eBaceXLnQGBjstRJBOfwPurza77OSIRAguPzaNbMiT5POw1B5WAlWZbqyNY6C3GVh6lwR37EODnaBeYBc3K-nxvOu9eFfGMmn' \
+ -data 'adPlanQueryParam=%7B%22adZone%22%3A%7B%22positionList%22%3A%5B%7B%22positionId%22%3A83%7D%5D%7D%2C%22requestId%22%3A%2217958651-f205-44c7-ad5d-f8af92a6217a%22%7D'
+```
- 使用 curl文件进行压测
@@ -489,8 +506,11 @@ curl是Linux在命令行下的工作的文件传输工具,是一款很强大
使用curl文件可以压测使用非GET的请求,支持设置http请求的 method、cookies、header、body等参数
-chrome 浏览器生成 curl文件,打开开发者模式(快捷键F12),如图所示,生成 curl 在终端执行命令
-
+**I:** chrome 浏览器生成 curl文件,打开开发者模式(快捷键F12),如图所示,生成 curl 在终端执行命令
+
+
+**II:** postman 生成 curl 命令
+
生成内容粘贴到项目目录下的**curl/baidu.curl.txt**文件中,执行下面命令就可以从curl.txt文件中读取需要压测的内容进行压测了
@@ -555,6 +575,7 @@ package main
import (
"log"
"net/http"
+ "runtime"
)
const (
@@ -566,7 +587,7 @@ func main() {
runtime.GOMAXPROCS(runtime.NumCPU() - 1)
hello := func(w http.ResponseWriter, req *http.Request) {
- data := "Hello, World!"
+ data := "Hello, go-stress-testing! \n"
w.Header().Add("Server", "golang")
w.Write([]byte(data))
@@ -587,11 +608,12 @@ func main() {
- go_stress_testing 压测命令
```
-./go_stress_testing_linux -c 100 -n 10000 -u http://127.0.0.1:8088/
+./go-stress-testing-linux -c 100 -n 10000 -u http://127.0.0.1:8088/
```
- 压测结果
+- [压测结果 示例](https://github.com/link1st/go-stress-testing/issues/32)
| 并发数 | go_stress_testing QPS |
| :----: | :----: |
@@ -603,10 +625,51 @@ func main() {
| 50 | 19922.56 |
| 80 | 19155.33 |
| 100 | 18336.46 |
-| 200 | 16813.86 |
从压测的结果上看:效果还不错,压测QPS有接近2W
+### 4.5 grpc压测
+- 介绍如何压测 grpc 接口
+> [添加对 grpc 接口压测 commit](https://github.com/link1st/go-stress-testing/commit/2b4b14aaf026d08276531cf76f42de90efd3bc61)
+- 1. 启动Server
+```shell script
+# 进入 grpc server 目录
+cd tests/grpc
+
+# 启动 grpc server
+go run main.go
+```
+
+- 2. 对 grpc server 协议进行压测
+```
+# 回到项目根目录
+go run main.go -c 300 -n 1000 -u grpc://127.0.0.1:8099 -data world
+
+开始启动 并发数:300 请求数:1000 请求参数:
+request:
+ form:grpc
+ url:grpc://127.0.0.1:8099
+ method:POST
+ headers:map[Content-Type:application/x-www-form-urlencoded; charset=utf-8]
+ data:world
+ verify:
+ timeout:30s
+ debug:false
+
+─────┬───────┬───────┬───────┬────────┬────────┬────────┬────────┬────────┬────────┬────────
+ 耗时 │ 并发数 │ 成功数 │ 失败数 │ qps │最长耗时 │最短耗时 │平均耗时 │下载字节 │字节每秒 │ 错误码
+─────┼───────┼───────┼───────┼────────┼────────┼────────┼────────┼────────┼────────┼────────
+ 1s│ 186│ 14086│ 0│34177.69│ 22.40│ 0.63│ 8.78│ │ │200:14086
+ 2s│ 265│ 30408│ 0│26005.09│ 32.68│ 0.63│ 11.54│ │ │200:30408
+ 3s│ 300│ 46747│ 0│21890.46│ 40.84│ 0.63│ 13.70│ │ │200:46747
+ 4s│ 300│ 62837│ 0│20057.06│ 45.81│ 0.63│ 14.96│ │ │200:62837
+ 5s│ 300│ 79119│ 0│19134.52│ 45.81│ 0.63│ 15.68│ │ │200:79119
+```
+
+- 如何扩展其它私有协议
+> 由于私有协议、grpc 协议 都涉及到代码的书写,所以需要 编写go 的代码才能完成
+> 参考 [添加对 grpc 接口压测 commit](https://github.com/link1st/go-stress-testing/commit/2b4b14aaf026d08276531cf76f42de90efd3bc61)
+
## 5、压测工具的比较
### 5.1 比较
@@ -701,7 +764,7 @@ centOS 7.6 上述设置不生效,需要手动修改配置文件
core 是限制内核文件的大小,这里设置为 unlimited
```
-# 添加一下参数
+# 添加以下参数
root soft nofile 1040000
root hard nofile 1040000
@@ -755,6 +818,8 @@ net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216
```
+`sysctl -p` 修改配置以后使得配置生效命令
+
配置解释:
- `ip_local_port_range` 表示TCP/UDP协议允许使用的本地端口号 范围:1024~65000
@@ -836,7 +901,17 @@ VmSize: 27133804 kB
至此,压测已经全部完成,单台机器支持100W连接已经满足~
-## 7、总结
+## 7.常见问题
+- **Q:** 压测过程中会出现大量 **TIME_WAIT**
+
+ A: 参考TCP四次挥手原理,主动关闭连接的一方会出现 **TIME_WAIT** 状态,等待的时长为 2MSL(约1分钟左右)
+
+ 原因是:主动断开的一方回复 ACK 消息可能丢失,TCP 是可靠的传输协议,在没有收到 ACK 消息的另一端会重试,重新发送FIN消息,所以主动关闭的一方会等待 2MSL 时间,防止对方重试,这就出现了大量 **TIME_WAIT** 状态(参考: 四次挥手的最后两次)
+
+TCP 握手:
+
+
+## 8、总结
到这里压测总算完成,本次压测花费16元巨款。
@@ -845,7 +920,7 @@ VmSize: 27133804 kB
本文通过介绍什么是压测,在什么情况下需要压测,通过单台机器100W长连接的压测实战了解Linux内核的参数的调优。如果觉得现有的压测工具不适用,可以自己实现或者是改造成属于自己的自己的工具。
-## 8、参考文献
+## 9、参考文献
[性能测试工具](https://testerhome.com/topics/17068)
@@ -866,3 +941,12 @@ VmSize: 27133804 kB
[https://github.com/link1st/go-stress-testing](https://github.com/link1st/go-stress-testing)
github 搜:link1st 查看项目 go-stress-testing
+
+### 意见反馈
+
+- 在项目中遇到问题可以直接在这里找找答案或者提问 [issues](https://github.com/link1st/go-stress-testing/issues)
+- 也可以添加我的微信(申请信息填写:公司、姓名,我好备注下),直接反馈给我
+
+
+
+
diff --git a/build.sh b/build.sh
index 822276c..818dd21 100755
--- a/build.sh
+++ b/build.sh
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# 编译mac下可以执行文件
-go build -o go_stress_testing_mac main.go
+go build -o go-stress-testing-mac main.go
# 使用交叉编译 linux和windows版本可以执行的文件
-CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o go_stress_testing_linux main.go
-CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o go_stress_testing_win.exe main.go
+CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o go-stress-testing-linux main.go
+CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o go-stress-testing-win.exe main.go
diff --git a/curl/test.chrome.curl.txt b/curl/test.chrome.curl.txt
new file mode 100644
index 0000000..9f53972
--- /dev/null
+++ b/curl/test.chrome.curl.txt
@@ -0,0 +1,11 @@
+curl 'https://www.baidu.com/sugrec?prod=pc_his&from=pc_web&json=1&sid=1464_21098_31424_31341_31464_31229_30823_31163_31475&hisdata=&req=2&csor=0' \
+ -H 'Connection: keep-alive' \
+ -H 'Accept: application/json, text/javascript, */*; q=0.01' \
+ -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36' \
+ -H 'Sec-Fetch-Site: same-origin' \
+ -H 'Sec-Fetch-Mode: cors' \
+ -H 'Sec-Fetch-Dest: empty' \
+ -H 'Referer: https://www.baidu.com/' \
+ -H 'Accept-Language: zh-CN,zh;q=0.9' \
+ -H 'Cookie: BIDUPSID=A2CDAA36D74F85E5007CAA415E35B9DF; PSTM=1588732560; BAIDUID=A2CDAA36D74F85E59E4B8060EC4A0230:FG=1; BD_HOME=1; BD_UPN=123253; H_PS_PSSID=1464_21098_31424_31341_31464_31229_30823_31163_31475' \
+ --compressed
\ No newline at end of file
diff --git a/curl/test.curl.txt b/curl/test.curl.txt
new file mode 100644
index 0000000..9f53972
--- /dev/null
+++ b/curl/test.curl.txt
@@ -0,0 +1,11 @@
+curl 'https://www.baidu.com/sugrec?prod=pc_his&from=pc_web&json=1&sid=1464_21098_31424_31341_31464_31229_30823_31163_31475&hisdata=&req=2&csor=0' \
+ -H 'Connection: keep-alive' \
+ -H 'Accept: application/json, text/javascript, */*; q=0.01' \
+ -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36' \
+ -H 'Sec-Fetch-Site: same-origin' \
+ -H 'Sec-Fetch-Mode: cors' \
+ -H 'Sec-Fetch-Dest: empty' \
+ -H 'Referer: https://www.baidu.com/' \
+ -H 'Accept-Language: zh-CN,zh;q=0.9' \
+ -H 'Cookie: BIDUPSID=A2CDAA36D74F85E5007CAA415E35B9DF; PSTM=1588732560; BAIDUID=A2CDAA36D74F85E59E4B8060EC4A0230:FG=1; BD_HOME=1; BD_UPN=123253; H_PS_PSSID=1464_21098_31424_31341_31464_31229_30823_31163_31475' \
+ --compressed
\ No newline at end of file
diff --git a/curl/test.post.curl.txt b/curl/test.post.curl.txt
new file mode 100644
index 0000000..63f8c3b
--- /dev/null
+++ b/curl/test.post.curl.txt
@@ -0,0 +1,14 @@
+curl 'https://page.aliyun.com/delivery/plan/list' \
+ -H 'authority: page.aliyun.com' \
+ -H 'accept: application/json, text/plain, */*' \
+ -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36' \
+ -H 'content-type: application/x-www-form-urlencoded' \
+ -H 'origin: https://cn.aliyun.com' \
+ -H 'sec-fetch-site: same-site' \
+ -H 'sec-fetch-mode: cors' \
+ -H 'sec-fetch-dest: empty' \
+ -H 'referer: https://cn.aliyun.com/' \
+ -H 'accept-language: zh-CN,zh;q=0.9' \
+ -H 'cookie: aliyun_choice=CN; JSESSIONID=J8866281-CKCFJ4BUZ7GDO9V89YBW1-KJ3J5V9K-GYUW7; maliyun_temporary_console0=1AbLByOMHeZe3G41KYd5WWZvrM%2BGErkaLcWfBbgveKA9ifboArprPASvFUUfhwHtt44qsDwVqMk8Wkdr1F5LccYk2mPCZJiXb0q%2Bllj5u3SQGQurtyPqnG489y%2FkoA%2FEvOwsXJTvXTFQPK%2BGJD4FJg%3D%3D; cna=L3Q5F8cHDGgCAXL3r8fEZtdU; isg=BFNThsmSCcgX-sUcc5Jo2s2T4tF9COfKYi8g9wVwr3KphHMmjdh3GrHFvPTqJD_C; l=eBaceXLnQGBjstRJBOfwPurza77OSIRAguPzaNbMiT5POw1B5WAlWZbqyNY6C3GVh6lwR37EODnaBeYBc3K-nxvOu9eFfGMmn' \
+ --data 'adPlanQueryParam=%7B%22adZone%22%3A%7B%22positionList%22%3A%5B%7B%22positionId%22%3A83%7D%5D%7D%2C%22requestId%22%3A%2217958651-f205-44c7-ad5d-f8af92a6217a%22%7D' \
+ --compressed
\ No newline at end of file
diff --git a/curl/test.postman.curl.txt b/curl/test.postman.curl.txt
new file mode 100644
index 0000000..ad81434
--- /dev/null
+++ b/curl/test.postman.curl.txt
@@ -0,0 +1,4 @@
+curl -X GET \
+ 'https://www.baidu.com/sugrec?prod=pc_his&from=pc_web&json=1&sid=1464_21098_31424_31341_31464_31229_30823_31163_31475&hisdata=&req=2&csor=0' \
+ -H 'Postman-Token: c9b71950-61fd-43be-a38a-6596de238f0f' \
+ -H 'cache-control: no-cache'
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..6150d8d
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,10 @@
+module go-stress-testing
+
+go 1.14
+
+require (
+ github.com/golang/protobuf v1.4.2
+ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
+ golang.org/x/text v0.3.0
+ google.golang.org/grpc v1.35.0
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..ed4a46a
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,83 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/helper/helper.go b/helper/helper.go
new file mode 100644
index 0000000..666331a
--- /dev/null
+++ b/helper/helper.go
@@ -0,0 +1,40 @@
+/**
+* Created by GoLand.
+* User: link1st
+* Date: 2019-08-21
+* Time: 15:40
+ */
+
+package helper
+
+import (
+ "math/rand"
+ "time"
+)
+
+// DiffNano 时间差,纳秒
+func DiffNano(startTime time.Time) (diff int64) {
+ diff = int64(time.Since(startTime))
+ return
+}
+
+// InArrayStr 判断字符串是否在数组内
+func InArrayStr(str string, arr []string) (inArray bool) {
+ for _, s := range arr {
+ if s == str {
+ inArray = true
+ break
+ }
+ }
+ return
+}
+
+func GetRandomString(len int) string {
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+ bytes := make([]byte, len)
+ for i := 0; i < len; i++ {
+ b := r.Intn(26) + 65
+ bytes[i] = byte(b)
+ }
+ return string(bytes)
+}
diff --git a/heper/heper.go b/heper/heper.go
deleted file mode 100644
index fe4f626..0000000
--- a/heper/heper.go
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
-* Created by GoLand.
-* User: link1st
-* Date: 2019-08-21
-* Time: 15:40
- */
-
-package heper
-
-import (
- "time"
-)
-
-// 时间差,纳秒
-func DiffNano(startTime time.Time) (diff int64) {
-
- startTimeStamp := startTime.UnixNano()
- endTimeStamp := time.Now().UnixNano()
-
- diff = endTimeStamp - startTimeStamp
-
- return
-}
diff --git a/main.go b/main.go
index 3fc9e4a..b7e4861 100644
--- a/main.go
+++ b/main.go
@@ -16,44 +16,58 @@ import (
"strings"
)
-// go 实现的压测工具
-//
-// 编译可执行文件
-//go:generate go build main.go
-func main() {
+type array []string
- runtime.GOMAXPROCS(1)
+func (a *array) String() string {
+ return fmt.Sprint(*a)
+}
- var (
- concurrency uint64 // 并发数
- totalNumber uint64 // 请求总数(单个并发)
- debugStr string // 是否是debug
- requestUrl string // 压测的url 目前支持,http/https ws/wss
- path string // curl文件路径 http接口压测,自定义参数设置
- verify string // verify 验证方法 在server/verify中 http 支持:statusCode、json webSocket支持:json
- )
+func (a *array) Set(s string) error {
+ *a = append(*a, s)
- flag.Uint64Var(&concurrency, "c", 1, "并发数")
- flag.Uint64Var(&totalNumber, "n", 1, "请求总数")
- flag.StringVar(&debugStr, "d", "false", "调试模式")
+ return nil
+}
+
+var (
+ concurrency uint64 = 1 // 并发数
+ totalNumber uint64 = 1 // 请求数(单个并发/协程)
+ debugStr = "false" // 是否是debug
+ requestUrl string // 压测的url 目前支持,http/https ws/wss
+ path string // curl文件路径 http接口压测,自定义参数设置
+ verify string // verify 验证方法 在server/verify中 http 支持:statusCode、json webSocket支持:json
+ headers array // 自定义头信息传递给服务器
+ body string // HTTP POST方式传送数据
+)
+
+func init() {
+ flag.Uint64Var(&concurrency, "c", concurrency, "并发数")
+ flag.Uint64Var(&totalNumber, "n", totalNumber, "请求数(单个并发/协程)")
+ flag.StringVar(&debugStr, "d", debugStr, "调试模式")
flag.StringVar(&requestUrl, "u", "", "压测地址")
flag.StringVar(&path, "p", "", "curl文件路径")
flag.StringVar(&verify, "v", "", "验证方法 http 支持:statusCode、json webSocket支持:json")
-
+ flag.Var(&headers, "H", "自定义头信息传递给服务器 示例:-H 'Content-Type: application/json'")
+ flag.StringVar(&body, "data", "", "HTTP POST方式传送数据")
// 解析参数
flag.Parse()
+}
+
+// go 实现的压测工具
+// 编译可执行文件
+//go:generate go build main.go
+func main() {
+
+ runtime.GOMAXPROCS(1)
if concurrency == 0 || totalNumber == 0 || (requestUrl == "" && path == "") {
fmt.Printf("示例: go run main.go -c 1 -n 1 -u https://www.baidu.com/ \n")
fmt.Printf("压测地址或curl路径必填 \n")
fmt.Printf("当前请求参数: -c %d -n %d -d %v -u %s \n", concurrency, totalNumber, debugStr, requestUrl)
-
flag.Usage()
return
}
-
debug := strings.ToLower(debugStr) == "true"
- request, err := model.NewRequest(requestUrl, verify, 0, debug, path)
+ request, err := model.NewRequest(requestUrl, verify, 0, debug, path, headers, body)
if err != nil {
fmt.Printf("参数不合法 %v \n", err)
diff --git a/model/curl_model.go b/model/curl_model.go
index 191c4bd..db4a5cd 100644
--- a/model/curl_model.go
+++ b/model/curl_model.go
@@ -10,10 +10,11 @@ package model
import (
"encoding/json"
"errors"
- "io"
"io/ioutil"
"os"
"strings"
+
+ "go-stress-testing/helper"
)
// curl参数解析
@@ -21,6 +22,25 @@ type CURL struct {
Data map[string][]string
}
+func (c *CURL) getDataValue(keys []string) []string {
+ var (
+ value = make([]string, 0)
+ )
+
+ for _, key := range keys {
+ var (
+ ok bool
+ )
+
+ value, ok = c.Data[key]
+ if ok {
+ break
+ }
+ }
+
+ return value
+}
+
// 从文件中解析curl
func ParseTheFile(path string) (curl *CURL, err error) {
@@ -45,39 +65,98 @@ func ParseTheFile(path string) (curl *CURL, err error) {
file.Close()
}()
- data, err := ioutil.ReadAll(file)
+ dataBytes, err := ioutil.ReadAll(file)
if err != nil {
err = errors.New("读取文件失败:" + err.Error())
return
}
+ data := string(dataBytes)
- dataStr := string(data)
- for true {
- index := strings.Index(dataStr, "'")
+ for len(data) > 0 {
+ if strings.HasPrefix(data, "curl") {
+ data = data[5:]
+ }
+
+ data = strings.TrimSpace(data)
+ var (
+ key string
+ value string
+ )
+
+ index := strings.Index(data, " ")
if index <= 0 {
break
}
+ key = strings.TrimSpace(data[:index])
+ data = data[index+1:]
+ data = strings.TrimSpace(data)
+
+ // url
+ if !strings.HasPrefix(key, "-") {
+ key = strings.Trim(key, "'")
+ curl.Data["curl"] = []string{key}
+
+ // 去除首尾空格
+ data = strings.TrimFunc(data, func(r rune) bool {
+ if r == ' ' || r == '\\' || r == '\n' {
+ return true
+ }
+
+ return false
+ })
+ continue
+ }
- key := strings.TrimSpace(dataStr[:index])
- key = strings.ReplaceAll(key, "\n", "")
+ if strings.HasPrefix(data, "-") {
+ continue
+ }
- dataStr = dataStr[index+1:]
+ var (
+ endSymbol = " "
+ )
- index = strings.Index(dataStr, "'")
+ if strings.HasPrefix(data, "'") {
+ endSymbol = "'"
+ data = data[1:]
+ }
- if index <= 0 {
- break
+ index = strings.Index(data, endSymbol)
+ if index <= -1 {
+ index = len(data)
+ // break
+ }
+ value = data[:index]
+ if len(data) >= index+1 {
+ data = data[index+1:]
+ } else {
+ data = ""
}
- value := dataStr[:index]
+ // 去除首尾空格
+ data = strings.TrimFunc(data, func(r rune) bool {
+ if r == ' ' || r == '\\' || r == '\n' {
+ return true
+ }
- dataStr = dataStr[index+1:]
+ return false
+ })
+
+ if key == "" {
+ continue
+ }
curl.Data[key] = append(curl.Data[key], value)
+ // break
+
}
+ // debug
+ // for key, value := range curl.Data {
+ // fmt.Println("key:", key, "value:", value)
+ // }
+
return
}
@@ -89,12 +168,9 @@ func (c *CURL) String() (url string) {
// GetUrl
func (c *CURL) GetUrl() (url string) {
- value, ok := c.Data["curl"]
- if !ok {
-
- return
- }
+ keys := []string{"curl", "--url"}
+ value := c.getDataValue(keys)
if len(value) <= 0 {
return
@@ -107,34 +183,25 @@ func (c *CURL) GetUrl() (url string) {
// GetMethod
func (c *CURL) GetMethod() (method string) {
- method = "GET"
-
- if _, ok := c.Data["--data"]; ok {
- method = "POST"
-
- return
- }
-
- // TODO::目前发送不了
- if _, ok := c.Data["--data-binary $"]; ok {
- method = "POST"
-
- return
- }
-
- value, ok := c.Data["-X"]
- if !ok {
-
- return
- }
-
+ keys := []string{"-X", "--request"}
+ value := c.getDataValue(keys)
if len(value) <= 0 {
-
- return
+ return c.defaultMethod()
}
-
method = strings.ToUpper(value[0])
+ if helper.InArrayStr(method, []string{"GET", "POST", "PUT", "DELETE"}) {
+ return method
+ }
+ return c.defaultMethod()
+}
+// defaultMethod 获取默认方法
+func (c *CURL) defaultMethod() (method string) {
+ method = "GET"
+ body := c.GetBody()
+ if len(body) > 0 {
+ return "POST"
+ }
return
}
@@ -142,22 +209,11 @@ func (c *CURL) GetMethod() (method string) {
func (c *CURL) GetHeaders() (headers map[string]string) {
headers = make(map[string]string, 0)
- value, ok := c.Data["-H"]
- if !ok {
-
- return
- }
+ keys := []string{"-H", "--header"}
+ value := c.getDataValue(keys)
for _, v := range value {
- index := strings.Index(v, ":")
- if index < 0 {
- continue
- }
-
- vIndex := index + 2
- if len(v) >= vIndex {
- headers[v[:index]] = v[vIndex:]
- }
+ getHeaderValue(v, headers)
}
return
@@ -172,37 +228,35 @@ func (c *CURL) GetHeadersStr() string {
}
// 获取body
-func (c *CURL) GetBody() (body io.Reader) {
+func (c *CURL) GetBody() (body string) {
- value, ok := c.Data["--data"]
- if !ok {
- // data-binary
- value, ok = c.Data["--data-binary $"]
- if !ok {
-
- return
- }
- }
+ keys := []string{"--data", "-d", "--data-urlencode", "--data-raw", "--data-binary"}
+ value := c.getDataValue(keys)
if len(value) <= 0 {
+ body = c.getPostForm()
return
}
- body = strings.NewReader(value[0])
+ // body = strings.NewReader(value[0])
+ body = value[0]
return
}
-// 获取body
-func (c *CURL) GetBodyStr() (str string) {
+func (c *CURL) getPostForm() (body string) {
+ keys := []string{"--form", "-F", "--form-string"}
+ value := c.getDataValue(keys)
- body := c.GetBody()
- if body == nil {
+ if len(value) <= 0 {
return
}
- bytes, _ := ioutil.ReadAll(body)
- str = string(bytes)
+
+ body = strings.Join(value, "&")
+
return
}
+
+
diff --git a/model/curl_model_test.go b/model/curl_model_test.go
index dbf7ec4..ef6a7aa 100644
--- a/model/curl_model_test.go
+++ b/model/curl_model_test.go
@@ -25,7 +25,7 @@ func TestCurl(t *testing.T) {
fmt.Printf("url:%s \n", c.GetUrl())
fmt.Printf("method:%s \n", c.GetMethod())
fmt.Printf("body:%v \n", c.GetBody())
- fmt.Printf("body string:%v \n", c.GetBodyStr())
+ fmt.Printf("body string:%v \n", c.GetBody())
fmt.Printf("headers:%s \n", c.GetHeadersStr())
diff --git a/model/request_model.go b/model/request_model.go
index 35e479d..066f411 100644
--- a/model/request_model.go
+++ b/model/request_model.go
@@ -15,6 +15,9 @@ import (
"strings"
"sync"
"time"
+ "regexp"
+ "strconv"
+ "go-stress-testing/helper"
)
const (
@@ -25,6 +28,7 @@ const (
FormTypeHttp = "http"
FormTypeWebSocket = "webSocket"
+ FormTypeGRPC = "grpc"
)
var (
@@ -66,33 +70,70 @@ type VerifyWebSocket func(request *Request, seq string, msg []byte) (code int, i
// 请求结果
type Request struct {
- Url string // Url
- Form string // http/webSocket/tcp
- Method string // 方法 get/post/put
- Headers map[string]string // Headers
- Body io.Reader // body
- Verify string // 验证的方法
- VerifyHttp VerifyHttp // 验证的方法
- VerifyWebSocket VerifyWebSocket // 验证的方法
- Timeout time.Duration // 请求超时时间
- Debug bool // 是否开启Debug模式
+ Url string // Url
+ Form string // http/webSocket/tcp
+ Method string // 方法 GET/POST/PUT
+ Headers map[string]string // Headers
+ Body string // body
+ Verify string // 验证的方法
+ Timeout time.Duration // 请求超时时间
+ Debug bool // 是否开启Debug模式
// 连接以后初始化事件
// 循环事件 切片 时间 动作
}
+func (r *Request) GetBody() (body io.Reader) {
+ reg1 := regexp.MustCompile(`random(\d+)?`)
+ result := r.Body
+ match := reg1.FindAllStringSubmatch(result, -1)
+ if match != nil {
+ for _, v := range match {
+ l, _ := strconv.Atoi(v[1])
+ result = strings.Replace(result, v[0], helper.GetRandomString(l), 1)
+ }
+ }
+ body = strings.NewReader(result)
+ return
+}
+
+func (r *Request) getVerifyKey() (key string) {
+ key = fmt.Sprintf("%s.%s", r.Form, r.Verify)
+
+ return
+}
+
+// 获取数据校验方法
+func (r *Request) GetVerifyHttp() VerifyHttp {
+ verify, ok := verifyMapHttp[r.getVerifyKey()]
+ if !ok {
+ panic("GetVerifyHttp 验证方法不存在:" + r.Verify)
+ }
+
+ return verify
+}
+
+func (r *Request) GetVerifyWebSocket() VerifyWebSocket {
+ verify, ok := verifyMapWebSocket[r.getVerifyKey()]
+ if !ok {
+ panic("GetVerifyWebSocket 验证方法不存在:" + r.Verify)
+ }
+
+ return verify
+}
+
// NewRequest
// url 压测的url
// verify 验证方法 在server/verify中 http 支持:statusCode、json webSocket支持:json
// timeout 请求超时时间
// debug 是否开启debug
// path curl文件路径 http接口压测,自定义参数设置
-func NewRequest(url string, verify string, timeout time.Duration, debug bool, path string) (request *Request, err error) {
+func NewRequest(url string, verify string, timeout time.Duration, debug bool, path string, reqHeaders []string, reqBody string) (request *Request, err error) {
var (
method = "GET"
headers = make(map[string]string)
- body io.Reader
+ body string
)
if path != "" {
@@ -109,6 +150,18 @@ func NewRequest(url string, verify string, timeout time.Duration, debug bool, pa
method = curl.GetMethod()
headers = curl.GetHeaders()
body = curl.GetBody()
+ } else {
+
+ if reqBody != "" {
+ method = "POST"
+ body = reqBody
+
+ headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
+ }
+
+ for _, v := range reqHeaders {
+ getHeaderValue(v, headers)
+ }
}
form := ""
@@ -116,6 +169,11 @@ func NewRequest(url string, verify string, timeout time.Duration, debug bool, pa
form = FormTypeHttp
} else if strings.HasPrefix(url, "ws://") || strings.HasPrefix(url, "wss://") {
form = FormTypeWebSocket
+ } else if strings.HasPrefix(url, "grpc://") || strings.HasPrefix(url, "rpc://") {
+ form = FormTypeGRPC
+ } else {
+ form = FormTypeHttp
+ url = fmt.Sprintf("http://%s", url)
}
if form == "" {
@@ -125,9 +183,7 @@ func NewRequest(url string, verify string, timeout time.Duration, debug bool, pa
}
var (
- verifyHttp VerifyHttp
- verifyWebSocket VerifyWebSocket
- ok bool
+ ok bool
)
switch form {
@@ -138,7 +194,7 @@ func NewRequest(url string, verify string, timeout time.Duration, debug bool, pa
}
key := fmt.Sprintf("%s.%s", form, verify)
- verifyHttp, ok = verifyMapHttp[key]
+ _, ok = verifyMapHttp[key]
if !ok {
err = errors.New("验证器不存在:" + key)
@@ -151,7 +207,7 @@ func NewRequest(url string, verify string, timeout time.Duration, debug bool, pa
}
key := fmt.Sprintf("%s.%s", form, verify)
- verifyWebSocket, ok = verifyMapWebSocket[key]
+ _, ok = verifyMapWebSocket[key]
if !ok {
err = errors.New("验证器不存在:" + key)
@@ -161,26 +217,42 @@ func NewRequest(url string, verify string, timeout time.Duration, debug bool, pa
}
if timeout == 0 {
- timeout = 3 * time.Second
+ timeout = 30 * time.Second
}
request = &Request{
- Url: url,
- Form: form,
- Method: strings.ToUpper(method),
- Headers: headers,
- Body: body,
- Verify: verify,
- VerifyHttp: verifyHttp,
- VerifyWebSocket: verifyWebSocket,
- Timeout: timeout,
- Debug: debug,
+ Url: url,
+ Form: form,
+ Method: strings.ToUpper(method),
+ Headers: headers,
+ Body: body,
+ Verify: verify,
+ Timeout: timeout,
+ Debug: debug,
}
return
}
+func getHeaderValue(v string, headers map[string]string) {
+ index := strings.Index(v, ":")
+ if index < 0 {
+ return
+ }
+
+ vIndex := index + 1
+ if len(v) >= vIndex {
+ value := strings.TrimPrefix(v[vIndex:], " ")
+
+ if _, ok := headers[v[:index]]; ok {
+ headers[v[:index]] = fmt.Sprintf("%s; %s", headers[v[:index]], value)
+ } else {
+ headers[v[:index]] = value
+ }
+ }
+}
+
// 打印
func (r *Request) Print() {
if r == nil {
@@ -188,8 +260,9 @@ func (r *Request) Print() {
return
}
- result := fmt.Sprintf("request:\n url:%s \n form:%s \n method:%s \n headers:%v \n", r.Url, r.Form, r.Method, r.Headers)
- result = fmt.Sprintf("%s verify:%s \n timeout:%s \n debu:%v \n", result, r.Verify, r.Timeout, r.Debug)
+ result := fmt.Sprintf("request:\n form:%s \n url:%s \n method:%s \n headers:%v \n", r.Form, r.Url, r.Method, r.Headers)
+ result = fmt.Sprintf("%s data:%v \n", result, r.Body)
+ result = fmt.Sprintf("%s verify:%s \n timeout:%s \n debug:%v \n", result, r.Verify, r.Timeout, r.Debug)
fmt.Println(result)
return
@@ -207,24 +280,23 @@ func (r *Request) IsParameterLegal() (err error) {
r.Verify = "json"
key := fmt.Sprintf("%s.%s", r.Form, r.Verify)
- value, ok := verifyMapHttp[key]
+ _, ok := verifyMapHttp[key]
if !ok {
return errors.New("验证器不存在:" + key)
}
- r.VerifyHttp = value
-
return
}
// 请求结果
type RequestResults struct {
- Id string // 消息Id
- ChanId uint64 // 消息Id
- Time uint64 // 请求时间 纳秒
- IsSucceed bool // 是否请求成功
- ErrCode int // 错误码
+ Id string // 消息Id
+ ChanId uint64 // 消息Id
+ Time uint64 // 请求时间 纳秒
+ IsSucceed bool // 是否请求成功
+ ErrCode int // 错误码
+ ReceivedBytes int64
}
func (r *RequestResults) SetId(chanId uint64, number uint64) {
diff --git a/proto/pb.pb.go b/proto/pb.pb.go
new file mode 100644
index 0000000..9d25eae
--- /dev/null
+++ b/proto/pb.pb.go
@@ -0,0 +1,229 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: pb.proto
+
+package protobuf
+
+import (
+ context "context"
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+// 请求
+type Request struct {
+ // UserName 用户昵称
+ UserName string `protobuf:"bytes,1,opt,name=userName,proto3" json:"userName,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Request) Reset() { *m = Request{} }
+func (m *Request) String() string { return proto.CompactTextString(m) }
+func (*Request) ProtoMessage() {}
+func (*Request) Descriptor() ([]byte, []int) {
+ return fileDescriptor_f80abaa17e25ccc8, []int{0}
+}
+
+func (m *Request) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Request.Unmarshal(m, b)
+}
+func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Request.Marshal(b, m, deterministic)
+}
+func (m *Request) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Request.Merge(m, src)
+}
+func (m *Request) XXX_Size() int {
+ return xxx_messageInfo_Request.Size(m)
+}
+func (m *Request) XXX_DiscardUnknown() {
+ xxx_messageInfo_Request.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Request proto.InternalMessageInfo
+
+func (m *Request) GetUserName() string {
+ if m != nil {
+ return m.UserName
+ }
+ return ""
+}
+
+// 响应
+type Response struct {
+ // 状态码
+ Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
+ // 状态码说明
+ Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"`
+ // Data 返回数据
+ Data string `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Response) Reset() { *m = Response{} }
+func (m *Response) String() string { return proto.CompactTextString(m) }
+func (*Response) ProtoMessage() {}
+func (*Response) Descriptor() ([]byte, []int) {
+ return fileDescriptor_f80abaa17e25ccc8, []int{1}
+}
+
+func (m *Response) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Response.Unmarshal(m, b)
+}
+func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Response.Marshal(b, m, deterministic)
+}
+func (m *Response) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Response.Merge(m, src)
+}
+func (m *Response) XXX_Size() int {
+ return xxx_messageInfo_Response.Size(m)
+}
+func (m *Response) XXX_DiscardUnknown() {
+ xxx_messageInfo_Response.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Response proto.InternalMessageInfo
+
+func (m *Response) GetCode() int32 {
+ if m != nil {
+ return m.Code
+ }
+ return 0
+}
+
+func (m *Response) GetMsg() string {
+ if m != nil {
+ return m.Msg
+ }
+ return ""
+}
+
+func (m *Response) GetData() string {
+ if m != nil {
+ return m.Data
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*Request)(nil), "protobuf.Request")
+ proto.RegisterType((*Response)(nil), "protobuf.Response")
+}
+
+func init() { proto.RegisterFile("pb.proto", fileDescriptor_f80abaa17e25ccc8) }
+
+var fileDescriptor_f80abaa17e25ccc8 = []byte{
+ // 170 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x28, 0x48, 0xd2, 0x2b,
+ 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x00, 0x53, 0x49, 0xa5, 0x69, 0x4a, 0xaa, 0x5c, 0xec, 0x41,
+ 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x52, 0x5c, 0x1c, 0xa5, 0xc5, 0xa9, 0x45, 0x7e, 0x89,
+ 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x70, 0xbe, 0x92, 0x0b, 0x17, 0x47, 0x50,
+ 0x6a, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x90, 0x10, 0x17, 0x4b, 0x72, 0x7e, 0x0a, 0x44, 0x0d,
+ 0x6b, 0x10, 0x98, 0x2d, 0x24, 0xc0, 0xc5, 0x9c, 0x5b, 0x9c, 0x2e, 0xc1, 0x04, 0xd6, 0x06, 0x62,
+ 0x82, 0x54, 0xa5, 0x24, 0x96, 0x24, 0x4a, 0x30, 0x83, 0x85, 0xc0, 0x6c, 0x23, 0x27, 0x2e, 0x4e,
+ 0xc7, 0x82, 0xcc, 0xe0, 0xd4, 0xa2, 0xb2, 0xd4, 0x22, 0x21, 0x53, 0x2e, 0x2e, 0x8f, 0xd4, 0x9c,
+ 0x9c, 0xfc, 0xf0, 0xfc, 0xa2, 0x9c, 0x14, 0x21, 0x41, 0x3d, 0x98, 0x93, 0xf4, 0xa0, 0xee, 0x91,
+ 0x12, 0x42, 0x16, 0x82, 0xd8, 0xad, 0xc4, 0x90, 0xc4, 0x06, 0x16, 0x34, 0x06, 0x04, 0x00, 0x00,
+ 0xff, 0xff, 0x72, 0xb6, 0x62, 0x67, 0xcd, 0x00, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// ApiServerClient is the client API for ApiServer service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type ApiServerClient interface {
+ // 接口
+ HelloWorld(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
+}
+
+type apiServerClient struct {
+ cc *grpc.ClientConn
+}
+
+func NewApiServerClient(cc *grpc.ClientConn) ApiServerClient {
+ return &apiServerClient{cc}
+}
+
+func (c *apiServerClient) HelloWorld(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
+ out := new(Response)
+ err := c.cc.Invoke(ctx, "/protobuf.ApiServer/HelloWorld", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// ApiServerServer is the server API for ApiServer service.
+type ApiServerServer interface {
+ // 接口
+ HelloWorld(context.Context, *Request) (*Response, error)
+}
+
+// UnimplementedApiServerServer can be embedded to have forward compatible implementations.
+type UnimplementedApiServerServer struct {
+}
+
+func (*UnimplementedApiServerServer) HelloWorld(ctx context.Context, req *Request) (*Response, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method HelloWorld not implemented")
+}
+
+func RegisterApiServerServer(s *grpc.Server, srv ApiServerServer) {
+ s.RegisterService(&_ApiServer_serviceDesc, srv)
+}
+
+func _ApiServer_HelloWorld_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(Request)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ApiServerServer).HelloWorld(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/protobuf.ApiServer/HelloWorld",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ApiServerServer).HelloWorld(ctx, req.(*Request))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+var _ApiServer_serviceDesc = grpc.ServiceDesc{
+ ServiceName: "protobuf.ApiServer",
+ HandlerType: (*ApiServerServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "HelloWorld",
+ Handler: _ApiServer_HelloWorld_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "pb.proto",
+}
diff --git a/proto/pb.proto b/proto/pb.proto
new file mode 100644
index 0000000..75198d6
--- /dev/null
+++ b/proto/pb.proto
@@ -0,0 +1,26 @@
+syntax = "proto3";
+
+package protobuf;
+
+// ApiServer api 接口
+service ApiServer {
+ // HelloWorld 接口
+ rpc HelloWorld (Request) returns (Response) {
+ }
+}
+
+// 请求
+message Request {
+ // UserName 用户昵称
+ string userName = 1;
+}
+
+// 响应
+message Response {
+ // 状态码
+ int32 code = 1;
+ // 状态码说明
+ string msg = 2;
+ // Data 返回数据
+ string data = 3;
+}
\ No newline at end of file
diff --git a/server/client/grpc_client.go b/server/client/grpc_client.go
new file mode 100644
index 0000000..475d337
--- /dev/null
+++ b/server/client/grpc_client.go
@@ -0,0 +1,66 @@
+/**
+* Created by GoLand.
+* User: link1st
+* Date: 2019-08-15
+* Time: 21:03
+ */
+
+package client
+
+import (
+ "context"
+ "fmt"
+ "google.golang.org/grpc"
+ "strings"
+ "time"
+)
+
+type GrpcSocket struct {
+ conn *grpc.ClientConn
+ address string
+}
+
+func NewGrpcSocket(address string) (s *GrpcSocket) {
+ var (
+ newAddr string
+ )
+ arr := strings.Split(address, "//")
+ if len(arr) >= 2 {
+ newAddr = arr[1]
+ }
+ s = &GrpcSocket{
+ address: newAddr,
+ }
+ return
+}
+
+func (g *GrpcSocket) getAddress() (address string) {
+ return g.address
+}
+
+// 关闭
+func (g *GrpcSocket) Close() (err error) {
+ if g == nil {
+ return
+ }
+ if g.conn == nil {
+ return
+ }
+ g.conn.Close()
+ return
+}
+
+// Link 建立连接
+func (g *GrpcSocket) Link() (err error) {
+ ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
+ conn, err := grpc.DialContext(ctx, g.address, grpc.WithInsecure(), grpc.WithBlock())
+ if err != nil {
+ return fmt.Errorf("GetConn: 连接失败 address:%s %w", g.address, err)
+ }
+ g.conn = conn
+ return
+}
+
+func (g *GrpcSocket) GetConn() (conn *grpc.ClientConn) {
+ return g.conn
+}
diff --git a/server/client/http_client.go b/server/client/http_client.go
index 48f5c18..b0fe7bc 100644
--- a/server/client/http_client.go
+++ b/server/client/http_client.go
@@ -9,19 +9,24 @@ package client
import (
"crypto/tls"
- "fmt"
"io"
+ "log"
"net/http"
+ "os"
"time"
+
+ "go-stress-testing/helper"
)
+var logErr = log.New(os.Stderr, "", 0)
+
// HTTP 请求
// method 方法 GET POST
// url 请求的url
// body 请求的body
// headers 请求头信息
// timeout 请求超时时间
-func HttpRequest(method, url string, body io.Reader, headers map[string]string, timeout time.Duration) (resp *http.Response, err error) {
+func HttpRequest(method, url string, body io.Reader, headers map[string]string, timeout time.Duration) (resp *http.Response, requestTime uint64, err error) {
// 跳过证书验证
tr := &http.Transport{
@@ -38,7 +43,11 @@ func HttpRequest(method, url string, body io.Reader, headers map[string]string,
return
}
-
+ req.Close = true
+ // 在req中设置Host,解决在header中设置Host不生效问题
+ if _, ok := headers["Host"]; ok {
+ req.Host = headers["Host"]
+ }
// 设置默认为utf-8编码
if _, ok := headers["Content-Type"]; !ok {
if headers == nil {
@@ -51,9 +60,11 @@ func HttpRequest(method, url string, body io.Reader, headers map[string]string,
req.Header.Set(key, value)
}
+ startTime := time.Now()
resp, err = client.Do(req)
+ requestTime = uint64(helper.DiffNano(startTime))
if err != nil {
- fmt.Println("请求失败:", err)
+ logErr.Println("请求失败:", err)
return
}
@@ -63,3 +74,4 @@ func HttpRequest(method, url string, body io.Reader, headers map[string]string,
return
}
+
diff --git a/server/dispose.go b/server/dispose.go
index ee28403..3d078a3 100644
--- a/server/dispose.go
+++ b/server/dispose.go
@@ -90,6 +90,15 @@ func Dispose(concurrency, totalNumber uint64, request *model.Request) {
panic(data)
}
+ case model.FormTypeGRPC:
+ // 连接以后再启动协程
+ ws := client.NewGrpcSocket(request.Url)
+ err := ws.Link()
+ if err != nil {
+ fmt.Println("连接失败:", i, err)
+ continue
+ }
+ go golink.Grpc(i, ch, totalNumber, &wg, request, ws)
default:
// 类型不支持
wg.Done()
diff --git a/server/golink/grpc_link.go b/server/golink/grpc_link.go
new file mode 100644
index 0000000..cd43c02
--- /dev/null
+++ b/server/golink/grpc_link.go
@@ -0,0 +1,83 @@
+/**
+* Created by GoLand.
+* User: link1st
+* Date: 2019-08-21
+* Time: 15:43
+ */
+
+package golink
+
+import (
+ "context"
+ "sync"
+ "time"
+
+ "go-stress-testing/helper"
+ pb "go-stress-testing/proto"
+
+ "go-stress-testing/model"
+ "go-stress-testing/server/client"
+)
+
+// Grpc grpc 接口请求
+func Grpc(chanId uint64, ch chan<- *model.RequestResults, totalNumber uint64, wg *sync.WaitGroup,
+ request *model.Request, ws *client.GrpcSocket) {
+
+ defer func() {
+ wg.Done()
+ }()
+ defer func() {
+ ws.Close()
+ }()
+ for i := uint64(0); i < totalNumber; i++ {
+ grpcRequest(chanId, ch, i, request, ws)
+ }
+ return
+}
+
+// 请求
+func grpcRequest(chanId uint64, ch chan<- *model.RequestResults, i uint64, request *model.Request,
+ ws *client.GrpcSocket) {
+ var (
+ startTime = time.Now()
+ isSucceed = false
+ errCode = model.HttpOk
+ )
+
+ // 需要发送的数据
+ conn := ws.GetConn()
+ if conn == nil {
+ errCode = model.RequestErr
+ } else {
+ // TODO::请求接口示例
+ c := pb.NewApiServerClient(conn)
+ var (
+ ctx = context.Background()
+ req = &pb.Request{
+ UserName: request.Body,
+ }
+ )
+ rsp, err := c.HelloWorld(ctx, req)
+ // fmt.Printf("rsp:%+v", rsp)
+ if err != nil {
+ errCode = model.RequestErr
+ } else {
+ // 200 为成功
+ if rsp.Code != 200 {
+ errCode = model.RequestErr
+ } else {
+ isSucceed = true
+ }
+ }
+ }
+ requestTime := uint64(helper.DiffNano(startTime))
+ requestResults := &model.RequestResults{
+ Time: requestTime,
+ IsSucceed: isSucceed,
+ ErrCode: errCode,
+ }
+ requestResults.SetId(chanId, i)
+
+ ch <- requestResults
+}
+
diff --git a/server/golink/http_link.go b/server/golink/http_link.go
index 1d70292..b4a4407 100644
--- a/server/golink/http_link.go
+++ b/server/golink/http_link.go
@@ -8,11 +8,10 @@
package golink
import (
- "go-stress-testing/heper"
+ "sync"
+
"go-stress-testing/model"
"go-stress-testing/server/client"
- "sync"
- "time"
)
// http go link
@@ -25,26 +24,15 @@ func Http(chanId uint64, ch chan<- *model.RequestResults, totalNumber uint64, wg
// fmt.Printf("启动协程 编号:%05d \n", chanId)
for i := uint64(0); i < totalNumber; i++ {
- var (
- startTime = time.Now()
- isSucceed = false
- errCode = model.HttpOk
- )
-
- resp, err := client.HttpRequest(request.Method, request.Url, request.Body, request.Headers, request.Timeout)
- requestTime := uint64(heper.DiffNano(startTime))
- // resp, err := server.HttpGetResp(request.Url)
- if err != nil {
- errCode = model.RequestErr // 请求错误
- } else {
- // 验证请求是否成功
- errCode, isSucceed = request.VerifyHttp(request, resp)
- }
+ list := getRequestList(request)
+
+ isSucceed, errCode, requestTime, contentLength := sendList(list)
requestResults := &model.RequestResults{
- Time: requestTime,
- IsSucceed: isSucceed,
- ErrCode: errCode,
+ Time: requestTime,
+ IsSucceed: isSucceed,
+ ErrCode: errCode,
+ ReceivedBytes: contentLength,
}
requestResults.SetId(chanId, i)
@@ -54,3 +42,47 @@ func Http(chanId uint64, ch chan<- *model.RequestResults, totalNumber uint64, wg
return
}
+
+// 多个接口分步压测
+func sendList(requestList []*model.Request) (isSucceed bool, errCode int, requestTime uint64, contentLength int64) {
+
+ errCode = model.HttpOk
+ for _, request := range requestList {
+ succeed, code, u, length := send(request)
+ isSucceed = succeed
+ errCode = code
+ requestTime = requestTime + u
+ contentLength = contentLength + length
+ if succeed == false {
+
+ break
+ }
+ }
+
+ return
+}
+
+// send 发送一次请求
+func send(request *model.Request) (bool, int, uint64, int64) {
+ var (
+ // startTime = time.Now()
+ isSucceed = false
+ errCode = model.HttpOk
+ contentLength = int64(0)
+ )
+
+ newRequest := getRequest(request)
+ // newRequest := request
+
+ resp, requestTime, err := client.HttpRequest(newRequest.Method, newRequest.Url, newRequest.GetBody(), newRequest.Headers, newRequest.Timeout)
+ // requestTime := uint64(heper.DiffNano(startTime))
+ if err != nil {
+ errCode = model.RequestErr // 请求错误
+ } else {
+ contentLength = resp.ContentLength
+
+ // 验证请求是否成功
+ errCode, isSucceed = newRequest.GetVerifyHttp()(newRequest, resp)
+ }
+ return isSucceed, errCode, requestTime, contentLength
+}
diff --git a/server/golink/http_link_many.go b/server/golink/http_link_many.go
new file mode 100644
index 0000000..427985d
--- /dev/null
+++ b/server/golink/http_link_many.go
@@ -0,0 +1,82 @@
+/**
+* Created by GoLand.
+* User: link1st
+* Date: 2020/7/31
+* Time: 8:36 下午
+ */
+
+package golink
+
+import (
+ "time"
+
+ "go-stress-testing/model"
+)
+
+// 接口分步压测
+type ReqListMany struct {
+ list []*model.Request
+}
+
+func (r *ReqListMany) getCount() int {
+ return len(r.list)
+}
+
+var (
+ clientList *ReqListMany
+)
+
+// 接口分步压测示例
+func init() {
+
+ clientList = &ReqListMany{}
+
+ // TODO::接口分步压测示例
+ // 需要压测的接口参数
+ clients := make([]*model.Request, 0)
+
+ // 压测第一步
+ clients = append(clients, &model.Request{
+ Url: "https://page.aliyun.com/delivery/plan/list", // 请求url
+ Form: "http", // 请求方式 示例参数:http/webSocket/tcp
+ Method: "POST", // 请求方法 示例参数:GET/POST/PUT
+ Headers: map[string]string{
+ "referer": "https://cn.aliyun.com/",
+ "cookie": "aliyun_choice=CN; JSESSIONID=J8866281-CKCFJ4BUZ7GDO9V89YBW1-KJ3J5V9K-GYUW7; maliyun_temporary_console0=1AbLByOMHeZe3G41KYd5WWZvrM%2BGErkaLcWfBbgveKA9ifboArprPASvFUUfhwHtt44qsDwVqMk8Wkdr1F5LccYk2mPCZJiXb0q%2Bllj5u3SQGQurtyPqnG489y%2FkoA%2FEvOwsXJTvXTFQPK%2BGJD4FJg%3D%3D; cna=L3Q5F8cHDGgCAXL3r8fEZtdU; isg=BFNThsmSCcgX-sUcc5Jo2s2T4tF9COfKYi8g9wVwr3KphHMmjdh3GrHFvPTqJD_C; l=eBaceXLnQGBjstRJBOfwPurza77OSIRAguPzaNbMiT5POw1B5WAlWZbqyNY6C3GVh6lwR37EODnaBeYBc3K-nxvOu9eFfGMmn",
+ }, // headers 头信息
+ Body: "adPlanQueryParam=%7B%22adZone%22%3A%7B%22positionList%22%3A%5B%7B%22positionId%22%3A83%7D%5D%7D%2C%22requestId%22%3A%2217958651-f205-44c7-ad5d-f8af92a6217a%22%7D", // 消息体
+ Verify: "statusCode", // 验证的方法 示例参数:statusCode、json
+ Timeout: 30 * time.Second, // 是否开启Debug模式
+ Debug: false, // 是否开启Debug模式
+ })
+
+ // 压测第二步
+ clients = append(clients, &model.Request{
+ Url: "https://page.aliyun.com/delivery/plan/list", // 请求url
+ Form: "http", // 请求方式 示例参数:http/webSocket/tcp
+ Method: "POST", // 请求方法 示例参数:GET/POST/PUT
+ Headers: map[string]string{
+ "referer": "https://cn.aliyun.com/",
+ "cookie": "aliyun_choice=CN; JSESSIONID=J8866281-CKCFJ4BUZ7GDO9V89YBW1-KJ3J5V9K-GYUW7; maliyun_temporary_console0=1AbLByOMHeZe3G41KYd5WWZvrM%2BGErkaLcWfBbgveKA9ifboArprPASvFUUfhwHtt44qsDwVqMk8Wkdr1F5LccYk2mPCZJiXb0q%2Bllj5u3SQGQurtyPqnG489y%2FkoA%2FEvOwsXJTvXTFQPK%2BGJD4FJg%3D%3D; cna=L3Q5F8cHDGgCAXL3r8fEZtdU; isg=BFNThsmSCcgX-sUcc5Jo2s2T4tF9COfKYi8g9wVwr3KphHMmjdh3GrHFvPTqJD_C; l=eBaceXLnQGBjstRJBOfwPurza77OSIRAguPzaNbMiT5POw1B5WAlWZbqyNY6C3GVh6lwR37EODnaBeYBc3K-nxvOu9eFfGMmn",
+ }, // headers 头信息
+ Body: "adPlanQueryParam=%7B%22adZone%22%3A%7B%22positionList%22%3A%5B%7B%22positionId%22%3A83%7D%5D%7D%2C%22requestId%22%3A%2217958651-f205-44c7-ad5d-f8af92a6217a%22%7D", // 消息体
+ Verify: "statusCode", // 验证的方法 示例参数:statusCode、json
+ Timeout: 30 * time.Second, // 是否开启Debug模式
+ Debug: false, // 是否开启Debug模式
+ })
+
+ clientList.list = clients
+
+ // TODO::注释下面一行代码
+ clientList.list = nil
+}
+
+func getRequestList(request *model.Request) []*model.Request {
+
+ if len(clientList.list) <= 0 {
+
+ return []*model.Request{request}
+ }
+
+ return clientList.list
+}
diff --git a/server/golink/http_link_weigh.go b/server/golink/http_link_weigh.go
new file mode 100644
index 0000000..8c59fcf
--- /dev/null
+++ b/server/golink/http_link_weigh.go
@@ -0,0 +1,110 @@
+/**
+* Created by GoLand.
+* User: link1st
+* Date: 2020/7/31
+* Time: 8:36 下午
+ */
+
+package golink
+
+import (
+ "math/rand"
+ "time"
+
+ "go-stress-testing/model"
+)
+
+// 接口加权压测
+type ReqListWeigh struct {
+ list []Req
+ weighCount uint32 // 总权重
+}
+
+type Req struct {
+ req *model.Request // 请求信息
+ weights uint32 // 权重,数字越大访问频率越高
+}
+
+func (r *ReqListWeigh) setWeighCount() {
+ r.weighCount = 0
+ for _, value := range r.list {
+ r.weighCount = r.weighCount + value.weights
+ }
+}
+
+var (
+ clientWeigh *ReqListWeigh
+ r *rand.Rand
+)
+
+// 多接口压测示例
+func init() {
+
+ // TODO::压测多个接口示例
+ // 需要压测的接口参数
+ clients := make([]Req, 0)
+ clients = append(clients, Req{req: &model.Request{
+ Url: "https://page.aliyun.com/delivery/plan/list", // 请求url
+ Form: "http", // 请求方式 示例参数:http/webSocket/tcp
+ Method: "POST", // 请求方法 示例参数:GET/POST/PUT
+ Headers: map[string]string{
+ "referer": "https://cn.aliyun.com/",
+ "cookie": "aliyun_choice=CN; JSESSIONID=J8866281-CKCFJ4BUZ7GDO9V89YBW1-KJ3J5V9K-GYUW7; maliyun_temporary_console0=1AbLByOMHeZe3G41KYd5WWZvrM%2BGErkaLcWfBbgveKA9ifboArprPASvFUUfhwHtt44qsDwVqMk8Wkdr1F5LccYk2mPCZJiXb0q%2Bllj5u3SQGQurtyPqnG489y%2FkoA%2FEvOwsXJTvXTFQPK%2BGJD4FJg%3D%3D; cna=L3Q5F8cHDGgCAXL3r8fEZtdU; isg=BFNThsmSCcgX-sUcc5Jo2s2T4tF9COfKYi8g9wVwr3KphHMmjdh3GrHFvPTqJD_C; l=eBaceXLnQGBjstRJBOfwPurza77OSIRAguPzaNbMiT5POw1B5WAlWZbqyNY6C3GVh6lwR37EODnaBeYBc3K-nxvOu9eFfGMmn",
+ }, // headers 头信息
+ Body: "adPlanQueryParam=%7B%22adZone%22%3A%7B%22positionList%22%3A%5B%7B%22positionId%22%3A83%7D%5D%7D%2C%22requestId%22%3A%2217958651-f205-44c7-ad5d-f8af92a6217a%22%7D", // 消息体
+ Verify: "statusCode", // 验证的方法 示例参数:statusCode、json
+ Timeout: 30 * time.Second, // 是否开启Debug模式
+ Debug: false, // 是否开启Debug模式
+ }, weights: 2})
+
+ clients = append(clients, Req{req: &model.Request{
+ Url: "https://page.aliyun.com/delivery/plan/list", // 请求url
+ Form: "http", // 请求方式 示例参数:http/webSocket/tcp
+ Method: "POST", // 请求方法 示例参数:GET/POST/PUT
+ Headers: map[string]string{
+ "referer": "https://cn.aliyun.com/",
+ "cookie": "aliyun_choice=CN; JSESSIONID=J8866281-CKCFJ4BUZ7GDO9V89YBW1-KJ3J5V9K-GYUW7; maliyun_temporary_console0=1AbLByOMHeZe3G41KYd5WWZvrM%2BGErkaLcWfBbgveKA9ifboArprPASvFUUfhwHtt44qsDwVqMk8Wkdr1F5LccYk2mPCZJiXb0q%2Bllj5u3SQGQurtyPqnG489y%2FkoA%2FEvOwsXJTvXTFQPK%2BGJD4FJg%3D%3D; cna=L3Q5F8cHDGgCAXL3r8fEZtdU; isg=BFNThsmSCcgX-sUcc5Jo2s2T4tF9COfKYi8g9wVwr3KphHMmjdh3GrHFvPTqJD_C; l=eBaceXLnQGBjstRJBOfwPurza77OSIRAguPzaNbMiT5POw1B5WAlWZbqyNY6C3GVh6lwR37EODnaBeYBc3K-nxvOu9eFfGMmn",
+ }, // headers 头信息
+ Body: "adPlanQueryParam=%7B%22adZone%22%3A%7B%22positionList%22%3A%5B%7B%22positionId%22%3A83%7D%5D%7D%2C%22requestId%22%3A%2217958651-f205-44c7-ad5d-f8af92a6217a%22%7D", // 消息体
+ Verify: "statusCode", // 验证的方法 示例参数:statusCode、json
+ Timeout: 30 * time.Second, // 是否开启Debug模式
+ Debug: false, // 是否开启Debug模式
+ }, weights: 1})
+
+ r = rand.New(rand.NewSource(time.Now().Unix()))
+
+ clientWeigh = &ReqListWeigh{
+ list: clients,
+ }
+
+ // TODO::注释下面一行代码
+ clientWeigh.list = nil
+
+ clientWeigh.setWeighCount()
+}
+
+func getRequest(request *model.Request) *model.Request {
+
+ if clientWeigh == nil || clientWeigh.weighCount <= 0 {
+
+ return request
+ }
+
+ n := uint32(r.Int31n(int32(clientWeigh.weighCount)))
+
+ var (
+ count uint32
+ )
+
+ for _, value := range clientWeigh.list {
+ if count >= n {
+ // value.req.Print()
+ return value.req
+ }
+ count = count + value.weights
+ }
+
+ panic("getRequest err")
+
+ return nil
+}
diff --git a/server/golink/websocket_link.go b/server/golink/websocket_link.go
index 0a44cfb..caffdad 100644
--- a/server/golink/websocket_link.go
+++ b/server/golink/websocket_link.go
@@ -9,11 +9,12 @@ package golink
import (
"fmt"
- "go-stress-testing/heper"
- "go-stress-testing/model"
- "go-stress-testing/server/client"
"sync"
"time"
+
+ "go-stress-testing/helper"
+ "go-stress-testing/model"
+ "go-stress-testing/server/client"
)
const (
@@ -100,11 +101,11 @@ func webSocketRequest(chanId uint64, ch chan<- *model.RequestResults, i uint64,
fmt.Println("读取数据 失败~")
} else {
// fmt.Println(msg)
- errCode, isSucceed = request.VerifyWebSocket(request, seq, msg)
+ errCode, isSucceed = request.GetVerifyWebSocket()(request, seq, msg)
}
}
- requestTime := uint64(heper.DiffNano(startTime))
+ requestTime := uint64(helper.DiffNano(startTime))
requestResults := &model.RequestResults{
Time: requestTime,
@@ -117,3 +118,4 @@ func webSocketRequest(chanId uint64, ch chan<- *model.RequestResults, i uint64,
ch <- requestResults
}
+
diff --git a/server/statistics/statistics.go b/server/statistics/statistics.go
index 00b5ed7..390cca0 100644
--- a/server/statistics/statistics.go
+++ b/server/statistics/statistics.go
@@ -9,15 +9,21 @@ package statistics
import (
"fmt"
- "go-stress-testing/model"
+ "sort"
"strings"
"sync"
"time"
+
+ "go-stress-testing/model"
+
+ "golang.org/x/text/language"
+ "golang.org/x/text/message"
)
var (
// 输出统计数据的时间
exportStatisticsTime = 1 * time.Second
+ p = message.NewPrinter(language.English)
)
// 接收结果并处理
@@ -43,6 +49,7 @@ func ReceivingResults(concurrent uint64, ch <-chan *model.RequestResults, wg *sy
failureNum uint64 // 处理失败数,code不为0
chanIdLen int // 并发数
chanIds = make(map[uint64]bool)
+ receivedBytes int64
)
statTime := uint64(time.Now().UnixNano())
@@ -58,7 +65,7 @@ func ReceivingResults(concurrent uint64, ch <-chan *model.RequestResults, wg *sy
case <-ticker.C:
endTime := uint64(time.Now().UnixNano())
requestTime = endTime - statTime
- go calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum, chanIdLen, errCode)
+ go calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum, chanIdLen, errCode, receivedBytes)
case <-stopChan:
// 处理完成
@@ -97,6 +104,8 @@ func ReceivingResults(concurrent uint64, ch <-chan *model.RequestResults, wg *sy
errCode[data.ErrCode] = 1
}
+ receivedBytes += data.ReceivedBytes
+
if _, ok := chanIds[data.ChanId]; !ok {
chanIds[data.ChanId] = true
chanIdLen = len(chanIds)
@@ -109,14 +118,14 @@ func ReceivingResults(concurrent uint64, ch <-chan *model.RequestResults, wg *sy
endTime := uint64(time.Now().UnixNano())
requestTime = endTime - statTime
- calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum, chanIdLen, errCode)
+ calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum, chanIdLen, errCode, receivedBytes)
fmt.Printf("\n\n")
fmt.Println("************************* 结果 stat ****************************")
fmt.Println("处理协程数量:", concurrent)
// fmt.Println("处理协程数量:", concurrent, "程序处理总时长:", fmt.Sprintf("%.3f", float64(processingTime/concurrent)/1e9), "秒")
- fmt.Println("请求总数:", successNum+failureNum, "总请求时间:", fmt.Sprintf("%.3f", float64(requestTime)/1e9),
+ fmt.Println("请求总数(并发数*请求数 -c * -n):", successNum+failureNum, "总请求时间:", fmt.Sprintf("%.3f", float64(requestTime)/1e9),
"秒", "successNum:", successNum, "failureNum:", failureNum)
fmt.Println("************************* 结果 end ****************************")
@@ -125,7 +134,7 @@ func ReceivingResults(concurrent uint64, ch <-chan *model.RequestResults, wg *sy
}
// 计算数据
-func calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum uint64, chanIdLen int, errCode map[int]int) {
+func calculateData(concurrent, processingTime, requestTime, maxTime, minTime, successNum, failureNum uint64, chanIdLen int, errCode map[int]int, receivedBytes int64) {
if processingTime == 0 {
processingTime = 1
}
@@ -145,7 +154,7 @@ func calculateData(concurrent, processingTime, requestTime, maxTime, minTime, su
// 平均时长 总耗时/总请求数/并发数 纳秒=>毫秒
if successNum != 0 && concurrent != 0 {
- averageTime = float64(processingTime) / float64(successNum*1e6*concurrent)
+ averageTime = float64(processingTime) / float64(successNum*1e6)
}
// 纳秒=>毫秒
@@ -156,27 +165,52 @@ func calculateData(concurrent, processingTime, requestTime, maxTime, minTime, su
// 打印的时长都为毫秒
// result := fmt.Sprintf("请求总数:%8d|successNum:%8d|failureNum:%8d|qps:%9.3f|maxTime:%9.3f|minTime:%9.3f|平均时长:%9.3f|errCode:%v", successNum+failureNum, successNum, failureNum, qps, maxTimeFloat, minTimeFloat, averageTime, errCode)
// fmt.Println(result)
- table(successNum, failureNum, errCode, qps, averageTime, maxTimeFloat, minTimeFloat, requestTimeFloat, chanIdLen)
+ table(successNum, failureNum, errCode, qps, averageTime, maxTimeFloat, minTimeFloat, requestTimeFloat, chanIdLen, receivedBytes)
}
// 打印表头信息
func header() {
fmt.Printf("\n\n")
// 打印的时长都为毫秒 总请数
- fmt.Println("─────┬───────┬───────┬───────┬────────┬────────┬────────┬────────┬────────")
- result := fmt.Sprintf(" 耗时│ 并发数│ 成功数│ 失败数│ qps │最长耗时│最短耗时│平均耗时│ 错误码")
+ fmt.Println("─────┬───────┬───────┬───────┬────────┬────────┬────────┬────────┬────────┬────────┬────────")
+ result := fmt.Sprintf(" 耗时│ 并发数│ 成功数│ 失败数│ qps │最长耗时│最短耗时│平均耗时│下载字节│字节每秒│ 错误码")
fmt.Println(result)
// result = fmt.Sprintf("耗时(s) │总请求数│成功数│失败数│QPS│最长耗时│最短耗时│平均耗时│错误码")
// fmt.Println(result)
- fmt.Println("─────┼───────┼───────┼───────┼────────┼────────┼────────┼────────┼────────")
+ fmt.Println("─────┼───────┼───────┼───────┼────────┼────────┼────────┼────────┼────────┼────────┼────────")
return
}
// 打印表格
-func table(successNum, failureNum uint64, errCode map[int]int, qps, averageTime, maxTimeFloat, minTimeFloat, requestTimeFloat float64, chanIdLen int) {
+func table(successNum, failureNum uint64, errCode map[int]int, qps, averageTime, maxTimeFloat, minTimeFloat, requestTimeFloat float64, chanIdLen int, receivedBytes int64) {
+ var (
+ speed int64
+ )
+
+ if requestTimeFloat > 0 {
+ speed = int64(float64(receivedBytes) / requestTimeFloat)
+ } else {
+ speed = 0
+ }
+ var (
+ receivedBytesStr string
+ speedStr string
+ )
+ // 判断获取下载字节长度是否是未知
+ if receivedBytes <= 0 {
+ receivedBytesStr = ""
+ speedStr = ""
+ } else {
+ receivedBytesStr = p.Sprintf("%d", receivedBytes)
+ speedStr = p.Sprintf("%d", speed)
+ }
+
// 打印的时长都为毫秒
- result := fmt.Sprintf("%4.0fs│%7d│%7d│%7d│%8.2f│%8.2f│%8.2f│%8.2f│%v", requestTimeFloat, chanIdLen, successNum, failureNum, qps, maxTimeFloat, minTimeFloat, averageTime, printMap(errCode))
+ result := fmt.Sprintf("%4.0fs│%7d│%7d│%7d│%8.2f│%8.2f│%8.2f│%8.2f│%8s│%8s│%v",
+ requestTimeFloat, chanIdLen, successNum, failureNum, qps, maxTimeFloat, minTimeFloat, averageTime,
+ receivedBytesStr, speedStr,
+ printMap(errCode))
fmt.Println(result)
return
@@ -192,6 +226,8 @@ func printMap(errCode map[int]int) (mapStr string) {
mapArr = append(mapArr, fmt.Sprintf("%d:%d", key, value))
}
+ sort.Strings(mapArr)
+
mapStr = strings.Join(mapArr, ";")
return
diff --git a/server/statistics/statistics_test.go b/server/statistics/statistics_test.go
new file mode 100644
index 0000000..6a60833
--- /dev/null
+++ b/server/statistics/statistics_test.go
@@ -0,0 +1,36 @@
+/**
+* Package statistics
+*
+* User: link1st
+* Date: 2020/9/28
+* Time: 14:02
+ */
+
+package statistics
+
+import (
+ "reflect"
+ "testing"
+)
+
+// TestPrintMap
+func TestPrintMap(t *testing.T) {
+
+ tt := map[string]struct {
+ a map[int]int
+ result string
+ }{
+ "test1": {a: map[int]int{
+ 200: 50,
+ 500: 10,
+ 100: 20,
+ }, result: "100:20;200:50;500:10"},
+ }
+
+ for _, value := range tt {
+ str := printMap(value.a)
+ if !reflect.DeepEqual(value.result, str) {
+ t.Errorf("数据不一致 预期:%v 实际:%v", value.result, str)
+ }
+ }
+}
diff --git a/server/verify/http_verify.go b/server/verify/http_verify.go
index 8d29b23..d3ba561 100644
--- a/server/verify/http_verify.go
+++ b/server/verify/http_verify.go
@@ -8,6 +8,7 @@
package verify
import (
+ "bytes"
"compress/gzip"
"encoding/json"
"fmt"
@@ -29,9 +30,8 @@ func getZipData(response *http.Response) (body []byte, err error) {
default:
reader = response.Body
}
-
body, err = ioutil.ReadAll(reader)
-
+ response.Body = ioutil.NopCloser(bytes.NewReader(body))
return
}
diff --git a/tests/grpc/main.go b/tests/grpc/main.go
new file mode 100644
index 0000000..c144fb9
--- /dev/null
+++ b/tests/grpc/main.go
@@ -0,0 +1,51 @@
+/**
+* Created by GoLand.
+* User: link1st
+* Date: 2021/2/3
+* Time: 23:44
+ */
+
+package main
+
+import (
+ "context"
+ "fmt"
+ pb "go-stress-testing/proto"
+ "google.golang.org/grpc"
+ "log"
+ "net"
+)
+
+const (
+ // port 监听端口
+ port = ":8099"
+)
+
+// server is used to implement helloWorld.GreeterServer.
+type server struct {
+ pb.UnimplementedApiServerServer
+}
+
+// HelloWorld hello world 接口
+func (s *server) HelloWorld(_ context.Context, req *pb.Request) (rsp *pb.Response, err error) {
+ rsp = &pb.Response{
+ Code: 200,
+ Msg: "success",
+ Data: fmt.Sprintf("hello %s !", req.UserName),
+ }
+ return
+}
+
+// main 主函数
+func main() {
+ fmt.Println("trpc server 启动中...")
+ lis, err := net.Listen("tcp", port)
+ if err != nil {
+ log.Fatalf("failed to listen: %v", err)
+ }
+ s := grpc.NewServer()
+ pb.RegisterApiServerServer(s, &server{})
+ if err := s.Serve(lis); err != nil {
+ log.Fatalf("failed to serve: %v", err)
+ }
+}
diff --git a/tests/servers.go b/tests/servers.go
new file mode 100644
index 0000000..61578c9
--- /dev/null
+++ b/tests/servers.go
@@ -0,0 +1,39 @@
+/**
+* Created by GoLand.
+* User: link1st
+* Date: 2020/8/1
+* Time: 09:27
+ */
+
+package main
+
+import (
+ "log"
+ "net/http"
+ "runtime"
+)
+
+const (
+ httpPort = "8088"
+)
+
+func main() {
+
+ runtime.GOMAXPROCS(runtime.NumCPU() - 1)
+
+ hello := func(w http.ResponseWriter, req *http.Request) {
+ data := "Hello, go-stress-testing! \n"
+
+ w.Header().Add("Server", "golang")
+ w.Write([]byte(data))
+
+ return
+ }
+
+ http.HandleFunc("/", hello)
+ err := http.ListenAndServe(":"+httpPort, nil)
+
+ if err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+}
diff --git a/vendor/golang.org/x/net/LICENSE b/vendor/golang.org/x/net/LICENSE
deleted file mode 100644
index 6a66aea..0000000
--- a/vendor/golang.org/x/net/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (c) 2009 The Go Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
- * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/golang.org/x/net/PATENTS b/vendor/golang.org/x/net/PATENTS
deleted file mode 100644
index 7330990..0000000
--- a/vendor/golang.org/x/net/PATENTS
+++ /dev/null
@@ -1,22 +0,0 @@
-Additional IP Rights Grant (Patents)
-
-"This implementation" means the copyrightable works distributed by
-Google as part of the Go project.
-
-Google hereby grants to You a perpetual, worldwide, non-exclusive,
-no-charge, royalty-free, irrevocable (except as stated in this section)
-patent license to make, have made, use, offer to sell, sell, import,
-transfer and otherwise run, modify and propagate the contents of this
-implementation of Go, where such license applies only to those patent
-claims, both currently owned or controlled by Google and acquired in
-the future, licensable by Google that are necessarily infringed by this
-implementation of Go. This grant does not include claims that would be
-infringed only as a consequence of further modification of this
-implementation. If you or your agent or exclusive licensee institute or
-order or agree to the institution of patent litigation against any
-entity (including a cross-claim or counterclaim in a lawsuit) alleging
-that this implementation of Go or any code incorporated within this
-implementation of Go constitutes direct or contributory patent
-infringement, or inducement of patent infringement, then any patent
-rights granted to you under this License for this implementation of Go
-shall terminate as of the date such litigation is filed.
diff --git a/vendor/golang.org/x/net/websocket/client.go b/vendor/golang.org/x/net/websocket/client.go
deleted file mode 100644
index 69a4ac7..0000000
--- a/vendor/golang.org/x/net/websocket/client.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package websocket
-
-import (
- "bufio"
- "io"
- "net"
- "net/http"
- "net/url"
-)
-
-// DialError is an error that occurs while dialling a websocket server.
-type DialError struct {
- *Config
- Err error
-}
-
-func (e *DialError) Error() string {
- return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
-}
-
-// NewConfig creates a new WebSocket config for client connection.
-func NewConfig(server, origin string) (config *Config, err error) {
- config = new(Config)
- config.Version = ProtocolVersionHybi13
- config.Location, err = url.ParseRequestURI(server)
- if err != nil {
- return
- }
- config.Origin, err = url.ParseRequestURI(origin)
- if err != nil {
- return
- }
- config.Header = http.Header(make(map[string][]string))
- return
-}
-
-// NewClient creates a new WebSocket client connection over rwc.
-func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
- br := bufio.NewReader(rwc)
- bw := bufio.NewWriter(rwc)
- err = hybiClientHandshake(config, br, bw)
- if err != nil {
- return
- }
- buf := bufio.NewReadWriter(br, bw)
- ws = newHybiClientConn(config, buf, rwc)
- return
-}
-
-// Dial opens a new client connection to a WebSocket.
-func Dial(url_, protocol, origin string) (ws *Conn, err error) {
- config, err := NewConfig(url_, origin)
- if err != nil {
- return nil, err
- }
- if protocol != "" {
- config.Protocol = []string{protocol}
- }
- return DialConfig(config)
-}
-
-var portMap = map[string]string{
- "ws": "80",
- "wss": "443",
-}
-
-func parseAuthority(location *url.URL) string {
- if _, ok := portMap[location.Scheme]; ok {
- if _, _, err := net.SplitHostPort(location.Host); err != nil {
- return net.JoinHostPort(location.Host, portMap[location.Scheme])
- }
- }
- return location.Host
-}
-
-// DialConfig opens a new client connection to a WebSocket with a config.
-func DialConfig(config *Config) (ws *Conn, err error) {
- var client net.Conn
- if config.Location == nil {
- return nil, &DialError{config, ErrBadWebSocketLocation}
- }
- if config.Origin == nil {
- return nil, &DialError{config, ErrBadWebSocketOrigin}
- }
- dialer := config.Dialer
- if dialer == nil {
- dialer = &net.Dialer{}
- }
- client, err = dialWithDialer(dialer, config)
- if err != nil {
- goto Error
- }
- ws, err = NewClient(config, client)
- if err != nil {
- client.Close()
- goto Error
- }
- return
-
-Error:
- return nil, &DialError{config, err}
-}
diff --git a/vendor/golang.org/x/net/websocket/dial.go b/vendor/golang.org/x/net/websocket/dial.go
deleted file mode 100644
index 2dab943..0000000
--- a/vendor/golang.org/x/net/websocket/dial.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package websocket
-
-import (
- "crypto/tls"
- "net"
-)
-
-func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) {
- switch config.Location.Scheme {
- case "ws":
- conn, err = dialer.Dial("tcp", parseAuthority(config.Location))
-
- case "wss":
- conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig)
-
- default:
- err = ErrBadScheme
- }
- return
-}
diff --git a/vendor/golang.org/x/net/websocket/hybi.go b/vendor/golang.org/x/net/websocket/hybi.go
deleted file mode 100644
index 8cffdd1..0000000
--- a/vendor/golang.org/x/net/websocket/hybi.go
+++ /dev/null
@@ -1,583 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package websocket
-
-// This file implements a protocol of hybi draft.
-// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
-
-import (
- "bufio"
- "bytes"
- "crypto/rand"
- "crypto/sha1"
- "encoding/base64"
- "encoding/binary"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "strings"
-)
-
-const (
- websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-
- closeStatusNormal = 1000
- closeStatusGoingAway = 1001
- closeStatusProtocolError = 1002
- closeStatusUnsupportedData = 1003
- closeStatusFrameTooLarge = 1004
- closeStatusNoStatusRcvd = 1005
- closeStatusAbnormalClosure = 1006
- closeStatusBadMessageData = 1007
- closeStatusPolicyViolation = 1008
- closeStatusTooBigData = 1009
- closeStatusExtensionMismatch = 1010
-
- maxControlFramePayloadLength = 125
-)
-
-var (
- ErrBadMaskingKey = &ProtocolError{"bad masking key"}
- ErrBadPongMessage = &ProtocolError{"bad pong message"}
- ErrBadClosingStatus = &ProtocolError{"bad closing status"}
- ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
- ErrNotImplemented = &ProtocolError{"not implemented"}
-
- handshakeHeader = map[string]bool{
- "Host": true,
- "Upgrade": true,
- "Connection": true,
- "Sec-Websocket-Key": true,
- "Sec-Websocket-Origin": true,
- "Sec-Websocket-Version": true,
- "Sec-Websocket-Protocol": true,
- "Sec-Websocket-Accept": true,
- }
-)
-
-// A hybiFrameHeader is a frame header as defined in hybi draft.
-type hybiFrameHeader struct {
- Fin bool
- Rsv [3]bool
- OpCode byte
- Length int64
- MaskingKey []byte
-
- data *bytes.Buffer
-}
-
-// A hybiFrameReader is a reader for hybi frame.
-type hybiFrameReader struct {
- reader io.Reader
-
- header hybiFrameHeader
- pos int64
- length int
-}
-
-func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
- n, err = frame.reader.Read(msg)
- if frame.header.MaskingKey != nil {
- for i := 0; i < n; i++ {
- msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
- frame.pos++
- }
- }
- return n, err
-}
-
-func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
-
-func (frame *hybiFrameReader) HeaderReader() io.Reader {
- if frame.header.data == nil {
- return nil
- }
- if frame.header.data.Len() == 0 {
- return nil
- }
- return frame.header.data
-}
-
-func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
-
-func (frame *hybiFrameReader) Len() (n int) { return frame.length }
-
-// A hybiFrameReaderFactory creates new frame reader based on its frame type.
-type hybiFrameReaderFactory struct {
- *bufio.Reader
-}
-
-// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
-// See Section 5.2 Base Framing protocol for detail.
-// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
-func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
- hybiFrame := new(hybiFrameReader)
- frame = hybiFrame
- var header []byte
- var b byte
- // First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
- b, err = buf.ReadByte()
- if err != nil {
- return
- }
- header = append(header, b)
- hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
- for i := 0; i < 3; i++ {
- j := uint(6 - i)
- hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
- }
- hybiFrame.header.OpCode = header[0] & 0x0f
-
- // Second byte. Mask/Payload len(7bits)
- b, err = buf.ReadByte()
- if err != nil {
- return
- }
- header = append(header, b)
- mask := (b & 0x80) != 0
- b &= 0x7f
- lengthFields := 0
- switch {
- case b <= 125: // Payload length 7bits.
- hybiFrame.header.Length = int64(b)
- case b == 126: // Payload length 7+16bits
- lengthFields = 2
- case b == 127: // Payload length 7+64bits
- lengthFields = 8
- }
- for i := 0; i < lengthFields; i++ {
- b, err = buf.ReadByte()
- if err != nil {
- return
- }
- if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits
- b &= 0x7f
- }
- header = append(header, b)
- hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
- }
- if mask {
- // Masking key. 4 bytes.
- for i := 0; i < 4; i++ {
- b, err = buf.ReadByte()
- if err != nil {
- return
- }
- header = append(header, b)
- hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
- }
- }
- hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
- hybiFrame.header.data = bytes.NewBuffer(header)
- hybiFrame.length = len(header) + int(hybiFrame.header.Length)
- return
-}
-
-// A HybiFrameWriter is a writer for hybi frame.
-type hybiFrameWriter struct {
- writer *bufio.Writer
-
- header *hybiFrameHeader
-}
-
-func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
- var header []byte
- var b byte
- if frame.header.Fin {
- b |= 0x80
- }
- for i := 0; i < 3; i++ {
- if frame.header.Rsv[i] {
- j := uint(6 - i)
- b |= 1 << j
- }
- }
- b |= frame.header.OpCode
- header = append(header, b)
- if frame.header.MaskingKey != nil {
- b = 0x80
- } else {
- b = 0
- }
- lengthFields := 0
- length := len(msg)
- switch {
- case length <= 125:
- b |= byte(length)
- case length < 65536:
- b |= 126
- lengthFields = 2
- default:
- b |= 127
- lengthFields = 8
- }
- header = append(header, b)
- for i := 0; i < lengthFields; i++ {
- j := uint((lengthFields - i - 1) * 8)
- b = byte((length >> j) & 0xff)
- header = append(header, b)
- }
- if frame.header.MaskingKey != nil {
- if len(frame.header.MaskingKey) != 4 {
- return 0, ErrBadMaskingKey
- }
- header = append(header, frame.header.MaskingKey...)
- frame.writer.Write(header)
- data := make([]byte, length)
- for i := range data {
- data[i] = msg[i] ^ frame.header.MaskingKey[i%4]
- }
- frame.writer.Write(data)
- err = frame.writer.Flush()
- return length, err
- }
- frame.writer.Write(header)
- frame.writer.Write(msg)
- err = frame.writer.Flush()
- return length, err
-}
-
-func (frame *hybiFrameWriter) Close() error { return nil }
-
-type hybiFrameWriterFactory struct {
- *bufio.Writer
- needMaskingKey bool
-}
-
-func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
- frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
- if buf.needMaskingKey {
- frameHeader.MaskingKey, err = generateMaskingKey()
- if err != nil {
- return nil, err
- }
- }
- return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
-}
-
-type hybiFrameHandler struct {
- conn *Conn
- payloadType byte
-}
-
-func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) {
- if handler.conn.IsServerConn() {
- // The client MUST mask all frames sent to the server.
- if frame.(*hybiFrameReader).header.MaskingKey == nil {
- handler.WriteClose(closeStatusProtocolError)
- return nil, io.EOF
- }
- } else {
- // The server MUST NOT mask all frames.
- if frame.(*hybiFrameReader).header.MaskingKey != nil {
- handler.WriteClose(closeStatusProtocolError)
- return nil, io.EOF
- }
- }
- if header := frame.HeaderReader(); header != nil {
- io.Copy(ioutil.Discard, header)
- }
- switch frame.PayloadType() {
- case ContinuationFrame:
- frame.(*hybiFrameReader).header.OpCode = handler.payloadType
- case TextFrame, BinaryFrame:
- handler.payloadType = frame.PayloadType()
- case CloseFrame:
- return nil, io.EOF
- case PingFrame, PongFrame:
- b := make([]byte, maxControlFramePayloadLength)
- n, err := io.ReadFull(frame, b)
- if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
- return nil, err
- }
- io.Copy(ioutil.Discard, frame)
- if frame.PayloadType() == PingFrame {
- if _, err := handler.WritePong(b[:n]); err != nil {
- return nil, err
- }
- }
- return nil, nil
- }
- return frame, nil
-}
-
-func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
- handler.conn.wio.Lock()
- defer handler.conn.wio.Unlock()
- w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
- if err != nil {
- return err
- }
- msg := make([]byte, 2)
- binary.BigEndian.PutUint16(msg, uint16(status))
- _, err = w.Write(msg)
- w.Close()
- return err
-}
-
-func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
- handler.conn.wio.Lock()
- defer handler.conn.wio.Unlock()
- w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
- if err != nil {
- return 0, err
- }
- n, err = w.Write(msg)
- w.Close()
- return n, err
-}
-
-// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
-func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
- if buf == nil {
- br := bufio.NewReader(rwc)
- bw := bufio.NewWriter(rwc)
- buf = bufio.NewReadWriter(br, bw)
- }
- ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
- frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
- frameWriterFactory: hybiFrameWriterFactory{
- buf.Writer, request == nil},
- PayloadType: TextFrame,
- defaultCloseStatus: closeStatusNormal}
- ws.frameHandler = &hybiFrameHandler{conn: ws}
- return ws
-}
-
-// generateMaskingKey generates a masking key for a frame.
-func generateMaskingKey() (maskingKey []byte, err error) {
- maskingKey = make([]byte, 4)
- if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
- return
- }
- return
-}
-
-// generateNonce generates a nonce consisting of a randomly selected 16-byte
-// value that has been base64-encoded.
-func generateNonce() (nonce []byte) {
- key := make([]byte, 16)
- if _, err := io.ReadFull(rand.Reader, key); err != nil {
- panic(err)
- }
- nonce = make([]byte, 24)
- base64.StdEncoding.Encode(nonce, key)
- return
-}
-
-// removeZone removes IPv6 zone identifer from host.
-// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080"
-func removeZone(host string) string {
- if !strings.HasPrefix(host, "[") {
- return host
- }
- i := strings.LastIndex(host, "]")
- if i < 0 {
- return host
- }
- j := strings.LastIndex(host[:i], "%")
- if j < 0 {
- return host
- }
- return host[:j] + host[i:]
-}
-
-// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
-// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
-func getNonceAccept(nonce []byte) (expected []byte, err error) {
- h := sha1.New()
- if _, err = h.Write(nonce); err != nil {
- return
- }
- if _, err = h.Write([]byte(websocketGUID)); err != nil {
- return
- }
- expected = make([]byte, 28)
- base64.StdEncoding.Encode(expected, h.Sum(nil))
- return
-}
-
-// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
-func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
- bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
-
- // According to RFC 6874, an HTTP client, proxy, or other
- // intermediary must remove any IPv6 zone identifier attached
- // to an outgoing URI.
- bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
- bw.WriteString("Upgrade: websocket\r\n")
- bw.WriteString("Connection: Upgrade\r\n")
- nonce := generateNonce()
- if config.handshakeData != nil {
- nonce = []byte(config.handshakeData["key"])
- }
- bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
- bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
-
- if config.Version != ProtocolVersionHybi13 {
- return ErrBadProtocolVersion
- }
-
- bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
- if len(config.Protocol) > 0 {
- bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
- }
- // TODO(ukai): send Sec-WebSocket-Extensions.
- err = config.Header.WriteSubset(bw, handshakeHeader)
- if err != nil {
- return err
- }
-
- bw.WriteString("\r\n")
- if err = bw.Flush(); err != nil {
- return err
- }
-
- resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
- if err != nil {
- return err
- }
- if resp.StatusCode != 101 {
- return ErrBadStatus
- }
- if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
- strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
- return ErrBadUpgrade
- }
- expectedAccept, err := getNonceAccept(nonce)
- if err != nil {
- return err
- }
- if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
- return ErrChallengeResponse
- }
- if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
- return ErrUnsupportedExtensions
- }
- offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
- if offeredProtocol != "" {
- protocolMatched := false
- for i := 0; i < len(config.Protocol); i++ {
- if config.Protocol[i] == offeredProtocol {
- protocolMatched = true
- break
- }
- }
- if !protocolMatched {
- return ErrBadWebSocketProtocol
- }
- config.Protocol = []string{offeredProtocol}
- }
-
- return nil
-}
-
-// newHybiClientConn creates a client WebSocket connection after handshake.
-func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
- return newHybiConn(config, buf, rwc, nil)
-}
-
-// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
-type hybiServerHandshaker struct {
- *Config
- accept []byte
-}
-
-func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
- c.Version = ProtocolVersionHybi13
- if req.Method != "GET" {
- return http.StatusMethodNotAllowed, ErrBadRequestMethod
- }
- // HTTP version can be safely ignored.
-
- if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
- !strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
- return http.StatusBadRequest, ErrNotWebSocket
- }
-
- key := req.Header.Get("Sec-Websocket-Key")
- if key == "" {
- return http.StatusBadRequest, ErrChallengeResponse
- }
- version := req.Header.Get("Sec-Websocket-Version")
- switch version {
- case "13":
- c.Version = ProtocolVersionHybi13
- default:
- return http.StatusBadRequest, ErrBadWebSocketVersion
- }
- var scheme string
- if req.TLS != nil {
- scheme = "wss"
- } else {
- scheme = "ws"
- }
- c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
- if err != nil {
- return http.StatusBadRequest, err
- }
- protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
- if protocol != "" {
- protocols := strings.Split(protocol, ",")
- for i := 0; i < len(protocols); i++ {
- c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
- }
- }
- c.accept, err = getNonceAccept([]byte(key))
- if err != nil {
- return http.StatusInternalServerError, err
- }
- return http.StatusSwitchingProtocols, nil
-}
-
-// Origin parses the Origin header in req.
-// If the Origin header is not set, it returns nil and nil.
-func Origin(config *Config, req *http.Request) (*url.URL, error) {
- var origin string
- switch config.Version {
- case ProtocolVersionHybi13:
- origin = req.Header.Get("Origin")
- }
- if origin == "" {
- return nil, nil
- }
- return url.ParseRequestURI(origin)
-}
-
-func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
- if len(c.Protocol) > 0 {
- if len(c.Protocol) != 1 {
- // You need choose a Protocol in Handshake func in Server.
- return ErrBadWebSocketProtocol
- }
- }
- buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
- buf.WriteString("Upgrade: websocket\r\n")
- buf.WriteString("Connection: Upgrade\r\n")
- buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
- if len(c.Protocol) > 0 {
- buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
- }
- // TODO(ukai): send Sec-WebSocket-Extensions.
- if c.Header != nil {
- err := c.Header.WriteSubset(buf, handshakeHeader)
- if err != nil {
- return err
- }
- }
- buf.WriteString("\r\n")
- return buf.Flush()
-}
-
-func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
- return newHybiServerConn(c.Config, buf, rwc, request)
-}
-
-// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
-func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
- return newHybiConn(config, buf, rwc, request)
-}
diff --git a/vendor/golang.org/x/net/websocket/server.go b/vendor/golang.org/x/net/websocket/server.go
deleted file mode 100644
index 0895dea..0000000
--- a/vendor/golang.org/x/net/websocket/server.go
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package websocket
-
-import (
- "bufio"
- "fmt"
- "io"
- "net/http"
-)
-
-func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) {
- var hs serverHandshaker = &hybiServerHandshaker{Config: config}
- code, err := hs.ReadHandshake(buf.Reader, req)
- if err == ErrBadWebSocketVersion {
- fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
- fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
- buf.WriteString("\r\n")
- buf.WriteString(err.Error())
- buf.Flush()
- return
- }
- if err != nil {
- fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
- buf.WriteString("\r\n")
- buf.WriteString(err.Error())
- buf.Flush()
- return
- }
- if handshake != nil {
- err = handshake(config, req)
- if err != nil {
- code = http.StatusForbidden
- fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
- buf.WriteString("\r\n")
- buf.Flush()
- return
- }
- }
- err = hs.AcceptHandshake(buf.Writer)
- if err != nil {
- code = http.StatusBadRequest
- fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
- buf.WriteString("\r\n")
- buf.Flush()
- return
- }
- conn = hs.NewServerConn(buf, rwc, req)
- return
-}
-
-// Server represents a server of a WebSocket.
-type Server struct {
- // Config is a WebSocket configuration for new WebSocket connection.
- Config
-
- // Handshake is an optional function in WebSocket handshake.
- // For example, you can check, or don't check Origin header.
- // Another example, you can select config.Protocol.
- Handshake func(*Config, *http.Request) error
-
- // Handler handles a WebSocket connection.
- Handler
-}
-
-// ServeHTTP implements the http.Handler interface for a WebSocket
-func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- s.serveWebSocket(w, req)
-}
-
-func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
- rwc, buf, err := w.(http.Hijacker).Hijack()
- if err != nil {
- panic("Hijack failed: " + err.Error())
- }
- // The server should abort the WebSocket connection if it finds
- // the client did not send a handshake that matches with protocol
- // specification.
- defer rwc.Close()
- conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
- if err != nil {
- return
- }
- if conn == nil {
- panic("unexpected nil conn")
- }
- s.Handler(conn)
-}
-
-// Handler is a simple interface to a WebSocket browser client.
-// It checks if Origin header is valid URL by default.
-// You might want to verify websocket.Conn.Config().Origin in the func.
-// If you use Server instead of Handler, you could call websocket.Origin and
-// check the origin in your Handshake func. So, if you want to accept
-// non-browser clients, which do not send an Origin header, set a
-// Server.Handshake that does not check the origin.
-type Handler func(*Conn)
-
-func checkOrigin(config *Config, req *http.Request) (err error) {
- config.Origin, err = Origin(config, req)
- if err == nil && config.Origin == nil {
- return fmt.Errorf("null origin")
- }
- return err
-}
-
-// ServeHTTP implements the http.Handler interface for a WebSocket
-func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- s := Server{Handler: h, Handshake: checkOrigin}
- s.serveWebSocket(w, req)
-}
diff --git a/vendor/golang.org/x/net/websocket/websocket.go b/vendor/golang.org/x/net/websocket/websocket.go
deleted file mode 100644
index 1f4f7be..0000000
--- a/vendor/golang.org/x/net/websocket/websocket.go
+++ /dev/null
@@ -1,451 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package websocket implements a client and server for the WebSocket protocol
-// as specified in RFC 6455.
-//
-// This package currently lacks some features found in an alternative
-// and more actively maintained WebSocket package:
-//
-// https://godoc.org/github.com/gorilla/websocket
-//
-package websocket // import "golang.org/x/net/websocket"
-
-import (
- "bufio"
- "crypto/tls"
- "encoding/json"
- "errors"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "net/url"
- "sync"
- "time"
-)
-
-const (
- ProtocolVersionHybi13 = 13
- ProtocolVersionHybi = ProtocolVersionHybi13
- SupportedProtocolVersion = "13"
-
- ContinuationFrame = 0
- TextFrame = 1
- BinaryFrame = 2
- CloseFrame = 8
- PingFrame = 9
- PongFrame = 10
- UnknownFrame = 255
-
- DefaultMaxPayloadBytes = 32 << 20 // 32MB
-)
-
-// ProtocolError represents WebSocket protocol errors.
-type ProtocolError struct {
- ErrorString string
-}
-
-func (err *ProtocolError) Error() string { return err.ErrorString }
-
-var (
- ErrBadProtocolVersion = &ProtocolError{"bad protocol version"}
- ErrBadScheme = &ProtocolError{"bad scheme"}
- ErrBadStatus = &ProtocolError{"bad status"}
- ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"}
- ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"}
- ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
- ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
- ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"}
- ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"}
- ErrBadFrame = &ProtocolError{"bad frame"}
- ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"}
- ErrNotWebSocket = &ProtocolError{"not websocket protocol"}
- ErrBadRequestMethod = &ProtocolError{"bad method"}
- ErrNotSupported = &ProtocolError{"not supported"}
-)
-
-// ErrFrameTooLarge is returned by Codec's Receive method if payload size
-// exceeds limit set by Conn.MaxPayloadBytes
-var ErrFrameTooLarge = errors.New("websocket: frame payload size exceeds limit")
-
-// Addr is an implementation of net.Addr for WebSocket.
-type Addr struct {
- *url.URL
-}
-
-// Network returns the network type for a WebSocket, "websocket".
-func (addr *Addr) Network() string { return "websocket" }
-
-// Config is a WebSocket configuration
-type Config struct {
- // A WebSocket server address.
- Location *url.URL
-
- // A Websocket client origin.
- Origin *url.URL
-
- // WebSocket subprotocols.
- Protocol []string
-
- // WebSocket protocol version.
- Version int
-
- // TLS config for secure WebSocket (wss).
- TlsConfig *tls.Config
-
- // Additional header fields to be sent in WebSocket opening handshake.
- Header http.Header
-
- // Dialer used when opening websocket connections.
- Dialer *net.Dialer
-
- handshakeData map[string]string
-}
-
-// serverHandshaker is an interface to handle WebSocket server side handshake.
-type serverHandshaker interface {
- // ReadHandshake reads handshake request message from client.
- // Returns http response code and error if any.
- ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error)
-
- // AcceptHandshake accepts the client handshake request and sends
- // handshake response back to client.
- AcceptHandshake(buf *bufio.Writer) (err error)
-
- // NewServerConn creates a new WebSocket connection.
- NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
-}
-
-// frameReader is an interface to read a WebSocket frame.
-type frameReader interface {
- // Reader is to read payload of the frame.
- io.Reader
-
- // PayloadType returns payload type.
- PayloadType() byte
-
- // HeaderReader returns a reader to read header of the frame.
- HeaderReader() io.Reader
-
- // TrailerReader returns a reader to read trailer of the frame.
- // If it returns nil, there is no trailer in the frame.
- TrailerReader() io.Reader
-
- // Len returns total length of the frame, including header and trailer.
- Len() int
-}
-
-// frameReaderFactory is an interface to creates new frame reader.
-type frameReaderFactory interface {
- NewFrameReader() (r frameReader, err error)
-}
-
-// frameWriter is an interface to write a WebSocket frame.
-type frameWriter interface {
- // Writer is to write payload of the frame.
- io.WriteCloser
-}
-
-// frameWriterFactory is an interface to create new frame writer.
-type frameWriterFactory interface {
- NewFrameWriter(payloadType byte) (w frameWriter, err error)
-}
-
-type frameHandler interface {
- HandleFrame(frame frameReader) (r frameReader, err error)
- WriteClose(status int) (err error)
-}
-
-// Conn represents a WebSocket connection.
-//
-// Multiple goroutines may invoke methods on a Conn simultaneously.
-type Conn struct {
- config *Config
- request *http.Request
-
- buf *bufio.ReadWriter
- rwc io.ReadWriteCloser
-
- rio sync.Mutex
- frameReaderFactory
- frameReader
-
- wio sync.Mutex
- frameWriterFactory
-
- frameHandler
- PayloadType byte
- defaultCloseStatus int
-
- // MaxPayloadBytes limits the size of frame payload received over Conn
- // by Codec's Receive method. If zero, DefaultMaxPayloadBytes is used.
- MaxPayloadBytes int
-}
-
-// Read implements the io.Reader interface:
-// it reads data of a frame from the WebSocket connection.
-// if msg is not large enough for the frame data, it fills the msg and next Read
-// will read the rest of the frame data.
-// it reads Text frame or Binary frame.
-func (ws *Conn) Read(msg []byte) (n int, err error) {
- ws.rio.Lock()
- defer ws.rio.Unlock()
-again:
- if ws.frameReader == nil {
- frame, err := ws.frameReaderFactory.NewFrameReader()
- if err != nil {
- return 0, err
- }
- ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
- if err != nil {
- return 0, err
- }
- if ws.frameReader == nil {
- goto again
- }
- }
- n, err = ws.frameReader.Read(msg)
- if err == io.EOF {
- if trailer := ws.frameReader.TrailerReader(); trailer != nil {
- io.Copy(ioutil.Discard, trailer)
- }
- ws.frameReader = nil
- goto again
- }
- return n, err
-}
-
-// Write implements the io.Writer interface:
-// it writes data as a frame to the WebSocket connection.
-func (ws *Conn) Write(msg []byte) (n int, err error) {
- ws.wio.Lock()
- defer ws.wio.Unlock()
- w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
- if err != nil {
- return 0, err
- }
- n, err = w.Write(msg)
- w.Close()
- return n, err
-}
-
-// Close implements the io.Closer interface.
-func (ws *Conn) Close() error {
- err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
- err1 := ws.rwc.Close()
- if err != nil {
- return err
- }
- return err1
-}
-
-// IsClientConn reports whether ws is a client-side connection.
-func (ws *Conn) IsClientConn() bool { return ws.request == nil }
-
-// IsServerConn reports whether ws is a server-side connection.
-func (ws *Conn) IsServerConn() bool { return ws.request != nil }
-
-// LocalAddr returns the WebSocket Origin for the connection for client, or
-// the WebSocket location for server.
-func (ws *Conn) LocalAddr() net.Addr {
- if ws.IsClientConn() {
- return &Addr{ws.config.Origin}
- }
- return &Addr{ws.config.Location}
-}
-
-// RemoteAddr returns the WebSocket location for the connection for client, or
-// the Websocket Origin for server.
-func (ws *Conn) RemoteAddr() net.Addr {
- if ws.IsClientConn() {
- return &Addr{ws.config.Location}
- }
- return &Addr{ws.config.Origin}
-}
-
-var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn")
-
-// SetDeadline sets the connection's network read & write deadlines.
-func (ws *Conn) SetDeadline(t time.Time) error {
- if conn, ok := ws.rwc.(net.Conn); ok {
- return conn.SetDeadline(t)
- }
- return errSetDeadline
-}
-
-// SetReadDeadline sets the connection's network read deadline.
-func (ws *Conn) SetReadDeadline(t time.Time) error {
- if conn, ok := ws.rwc.(net.Conn); ok {
- return conn.SetReadDeadline(t)
- }
- return errSetDeadline
-}
-
-// SetWriteDeadline sets the connection's network write deadline.
-func (ws *Conn) SetWriteDeadline(t time.Time) error {
- if conn, ok := ws.rwc.(net.Conn); ok {
- return conn.SetWriteDeadline(t)
- }
- return errSetDeadline
-}
-
-// Config returns the WebSocket config.
-func (ws *Conn) Config() *Config { return ws.config }
-
-// Request returns the http request upgraded to the WebSocket.
-// It is nil for client side.
-func (ws *Conn) Request() *http.Request { return ws.request }
-
-// Codec represents a symmetric pair of functions that implement a codec.
-type Codec struct {
- Marshal func(v interface{}) (data []byte, payloadType byte, err error)
- Unmarshal func(data []byte, payloadType byte, v interface{}) (err error)
-}
-
-// Send sends v marshaled by cd.Marshal as single frame to ws.
-func (cd Codec) Send(ws *Conn, v interface{}) (err error) {
- data, payloadType, err := cd.Marshal(v)
- if err != nil {
- return err
- }
- ws.wio.Lock()
- defer ws.wio.Unlock()
- w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
- if err != nil {
- return err
- }
- _, err = w.Write(data)
- w.Close()
- return err
-}
-
-// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores
-// in v. The whole frame payload is read to an in-memory buffer; max size of
-// payload is defined by ws.MaxPayloadBytes. If frame payload size exceeds
-// limit, ErrFrameTooLarge is returned; in this case frame is not read off wire
-// completely. The next call to Receive would read and discard leftover data of
-// previous oversized frame before processing next frame.
-func (cd Codec) Receive(ws *Conn, v interface{}) (err error) {
- ws.rio.Lock()
- defer ws.rio.Unlock()
- if ws.frameReader != nil {
- _, err = io.Copy(ioutil.Discard, ws.frameReader)
- if err != nil {
- return err
- }
- ws.frameReader = nil
- }
-again:
- frame, err := ws.frameReaderFactory.NewFrameReader()
- if err != nil {
- return err
- }
- frame, err = ws.frameHandler.HandleFrame(frame)
- if err != nil {
- return err
- }
- if frame == nil {
- goto again
- }
- maxPayloadBytes := ws.MaxPayloadBytes
- if maxPayloadBytes == 0 {
- maxPayloadBytes = DefaultMaxPayloadBytes
- }
- if hf, ok := frame.(*hybiFrameReader); ok && hf.header.Length > int64(maxPayloadBytes) {
- // payload size exceeds limit, no need to call Unmarshal
- //
- // set frameReader to current oversized frame so that
- // the next call to this function can drain leftover
- // data before processing the next frame
- ws.frameReader = frame
- return ErrFrameTooLarge
- }
- payloadType := frame.PayloadType()
- data, err := ioutil.ReadAll(frame)
- if err != nil {
- return err
- }
- return cd.Unmarshal(data, payloadType, v)
-}
-
-func marshal(v interface{}) (msg []byte, payloadType byte, err error) {
- switch data := v.(type) {
- case string:
- return []byte(data), TextFrame, nil
- case []byte:
- return data, BinaryFrame, nil
- }
- return nil, UnknownFrame, ErrNotSupported
-}
-
-func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
- switch data := v.(type) {
- case *string:
- *data = string(msg)
- return nil
- case *[]byte:
- *data = msg
- return nil
- }
- return ErrNotSupported
-}
-
-/*
-Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
-To send/receive text frame, use string type.
-To send/receive binary frame, use []byte type.
-
-Trivial usage:
-
- import "websocket"
-
- // receive text frame
- var message string
- websocket.Message.Receive(ws, &message)
-
- // send text frame
- message = "hello"
- websocket.Message.Send(ws, message)
-
- // receive binary frame
- var data []byte
- websocket.Message.Receive(ws, &data)
-
- // send binary frame
- data = []byte{0, 1, 2}
- websocket.Message.Send(ws, data)
-
-*/
-var Message = Codec{marshal, unmarshal}
-
-func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) {
- msg, err = json.Marshal(v)
- return msg, TextFrame, err
-}
-
-func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
- return json.Unmarshal(msg, v)
-}
-
-/*
-JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
-
-Trivial usage:
-
- import "websocket"
-
- type T struct {
- Msg string
- Count int
- }
-
- // receive JSON type T
- var data T
- websocket.JSON.Receive(ws, &data)
-
- // send JSON type T
- websocket.JSON.Send(ws, data)
-*/
-var JSON = Codec{jsonMarshal, jsonUnmarshal}
diff --git a/vendor/vendor.json b/vendor/vendor.json
deleted file mode 100644
index 54a8a83..0000000
--- a/vendor/vendor.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "comment": "",
- "ignore": "test",
- "package": [
- {
- "checksumSHA1": "F+tqxPGFt5x7DKZakbbMmENX1oQ=",
- "path": "golang.org/x/net/websocket",
- "revision": "ca1201d0de80cfde86cb01aea620983605dfe99b",
- "revisionTime": "2019-07-23T18:48:14Z",
- "tree": true
- }
- ],
- "rootPath": "go-stress-testing"
-}