feat: integration usockets backend
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# V-Hono Benchmark
|
||||
|
||||
Performance benchmarks for V-Hono web framework.
|
||||
|
||||
## Backend Comparison
|
||||
|
||||
### uSockets vs Picoev (200 connections, 100K requests)
|
||||
|
||||
| Backend | RPS | Avg Latency | P95 | P99 |
|
||||
|---------|-----|-------------|-----|-----|
|
||||
| uSockets | ~22,000 | 8.75ms | 16.66ms | 25.73ms |
|
||||
| picoev | ~15,000 | 13.36ms | 18.89ms | 31.66ms |
|
||||
|
||||
**uSockets provides ~50% higher throughput under high concurrency.**
|
||||
|
||||
### High Concurrency Test (500 connections, 100K requests)
|
||||
|
||||
| Metric | uSockets |
|
||||
|--------|----------|
|
||||
| RPS | ~20,000 |
|
||||
| Errors | 0 |
|
||||
| Avg Latency | 25.04ms |
|
||||
| P50 | 24.32ms |
|
||||
| P95 | 32.97ms |
|
||||
| P99 | 64.59ms |
|
||||
|
||||
## Running Benchmarks
|
||||
|
||||
### 1. Start Test Server
|
||||
|
||||
**uSockets backend:**
|
||||
```bash
|
||||
v -enable-globals -cc gcc -ldflags "-ldbghelp" benchmark/usockets_server.v -o server.exe
|
||||
./server.exe
|
||||
```
|
||||
|
||||
**Picoev backend:**
|
||||
```bash
|
||||
v -enable-globals examples/picoev_example.v -o server.exe
|
||||
./server.exe
|
||||
```
|
||||
|
||||
### 2. Run Integration Tests
|
||||
|
||||
```bash
|
||||
go run benchmark/usockets_verify.go
|
||||
```
|
||||
|
||||
### 3. Run Performance Tests
|
||||
|
||||
```bash
|
||||
# 200 connections, 100K requests
|
||||
go run tests/main.go -url "http://127.0.0.1:9998" -c 200 -n 100000
|
||||
|
||||
# 500 connections, 100K requests
|
||||
go run tests/main.go -url "http://127.0.0.1:9998" -c 500 -n 100000
|
||||
```
|
||||
|
||||
### 4. Quick Concurrent Test (PowerShell)
|
||||
|
||||
```powershell
|
||||
powershell -File benchmark/quick_test.ps1
|
||||
```
|
||||
|
||||
## Test Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `usockets_server.v` | uSockets test server with full API |
|
||||
| `usockets_verify.go` | Go integration test (17 test cases) |
|
||||
| `quick_test.ps1` | PowerShell concurrent test script |
|
||||
| `../tests/main.go` | Go performance benchmark tool |
|
||||
|
||||
## Test Coverage
|
||||
|
||||
The integration test (`usockets_verify.go`) covers:
|
||||
|
||||
- ✅ Basic GET routes (root, health, static)
|
||||
- ✅ Dynamic routes (single/multi/nested params)
|
||||
- ✅ Query parameters
|
||||
- ✅ Response formats (JSON, HTML, custom status)
|
||||
- ✅ HTTP methods (POST, PUT, DELETE)
|
||||
- ✅ Error handling (404)
|
||||
- ✅ Keep-Alive connections
|
||||
- ✅ Throughput performance
|
||||
@@ -0,0 +1,121 @@
|
||||
// uSockets 测试服务器
|
||||
// 用于集成测试验证 uSockets 后端功能
|
||||
//
|
||||
// 编译运行:
|
||||
// v -enable-globals -cc gcc -ldflags "-ldbghelp" benchmark/usockets_test_server.v -o usockets_test.exe
|
||||
// .\usockets_test.exe
|
||||
|
||||
module main
|
||||
|
||||
import hono
|
||||
import net.http
|
||||
|
||||
fn main() {
|
||||
mut app := hono.Hono.new()
|
||||
|
||||
// 全局中间件 - 添加自定义响应头
|
||||
app.use(fn (mut c hono.Context, next fn (mut hono.Context) http.Response) http.Response {
|
||||
mut resp := next(mut c)
|
||||
c.headers['X-Middleware'] = 'applied'
|
||||
return resp
|
||||
})
|
||||
|
||||
// ==================== 基本路由 ====================
|
||||
|
||||
app.get('/', fn (mut c hono.Context) http.Response {
|
||||
return c.text('Hello World')
|
||||
})
|
||||
|
||||
app.get('/health', fn (mut c hono.Context) http.Response {
|
||||
return c.text('OK')
|
||||
})
|
||||
|
||||
app.get('/api/health', fn (mut c hono.Context) http.Response {
|
||||
return c.text('OK')
|
||||
})
|
||||
|
||||
// ==================== CRUD 路由 ====================
|
||||
|
||||
app.get('/api/users', fn (mut c hono.Context) http.Response {
|
||||
return c.json('{"users": []}')
|
||||
})
|
||||
|
||||
app.post('/api/users', fn (mut c hono.Context) http.Response {
|
||||
c.status(201)
|
||||
return c.json('{"created": true}')
|
||||
})
|
||||
|
||||
app.get('/api/users/:id', fn (mut c hono.Context) http.Response {
|
||||
id := c.params['id'] or { '' }
|
||||
return c.json('{"id": "${id}"}')
|
||||
})
|
||||
|
||||
app.put('/api/users/:id', fn (mut c hono.Context) http.Response {
|
||||
id := c.params['id'] or { '' }
|
||||
return c.json('{"id": "${id}", "updated": true}')
|
||||
})
|
||||
|
||||
app.delete('/api/users/:id', fn (mut c hono.Context) http.Response {
|
||||
id := c.params['id'] or { '' }
|
||||
return c.json('{"id": "${id}", "deleted": true}')
|
||||
})
|
||||
|
||||
app.patch('/api/users/:id', fn (mut c hono.Context) http.Response {
|
||||
id := c.params['id'] or { '' }
|
||||
return c.json('{"id": "${id}", "patched": true}')
|
||||
})
|
||||
|
||||
// ==================== 多参数路由 ====================
|
||||
|
||||
app.get('/api/users/:user_id/posts/:post_id', fn (mut c hono.Context) http.Response {
|
||||
user_id := c.params['user_id'] or { '' }
|
||||
post_id := c.params['post_id'] or { '' }
|
||||
return c.json('{"user_id": "${user_id}", "post_id": "${post_id}"}')
|
||||
})
|
||||
|
||||
app.get('/api/categories/:category/items/:item', fn (mut c hono.Context) http.Response {
|
||||
category := c.params['category'] or { '' }
|
||||
item := c.params['item'] or { '' }
|
||||
return c.json('{"category": "${category}", "item": "${item}"}')
|
||||
})
|
||||
|
||||
// ==================== 查询参数路由 ====================
|
||||
|
||||
app.get('/api/search', fn (mut c hono.Context) http.Response {
|
||||
q := c.query['q'] or { '' }
|
||||
limit := c.query['limit'] or { '10' }
|
||||
page := c.query['page'] or { '1' }
|
||||
return c.json('{"query": "${q}", "limit": ${limit}, "page": ${page}}')
|
||||
})
|
||||
|
||||
// ==================== 响应格式路由 ====================
|
||||
|
||||
app.get('/api/json', fn (mut c hono.Context) http.Response {
|
||||
return c.json('{"message": "JSON response"}')
|
||||
})
|
||||
|
||||
app.get('/api/html', fn (mut c hono.Context) http.Response {
|
||||
return c.html('<html><body><h1>HTML Response</h1></body></html>')
|
||||
})
|
||||
|
||||
app.get('/api/created', fn (mut c hono.Context) http.Response {
|
||||
c.status(201)
|
||||
return c.json('{"created": true}')
|
||||
})
|
||||
|
||||
app.get('/api/custom-header', fn (mut c hono.Context) http.Response {
|
||||
c.headers['X-Custom-Header'] = 'custom-value'
|
||||
return c.text('OK')
|
||||
})
|
||||
|
||||
// ==================== 自定义 404 ====================
|
||||
|
||||
app.not_found(fn (mut c hono.Context) http.Response {
|
||||
c.status(404)
|
||||
return c.json('{"error": "Not Found", "path": "${c.path}"}')
|
||||
})
|
||||
|
||||
// 启动 uSockets 服务器
|
||||
println('[usockets-test-server] Starting on port 9998...')
|
||||
app.listen_usockets(9998)
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
// uSockets 功能验证测试
|
||||
// go run benchmark/usockets_verify.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const baseURL = "http://127.0.0.1:9998"
|
||||
|
||||
func main() {
|
||||
fmt.Println("╔═══════════════════════════════════════════════════════════════╗")
|
||||
fmt.Println("║ uSockets 集成测试 ║")
|
||||
fmt.Println("╚═══════════════════════════════════════════════════════════════╝")
|
||||
fmt.Println()
|
||||
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
|
||||
// 检查服务器
|
||||
fmt.Print("🔍 检查测试服务器... ")
|
||||
if !checkServer(client) {
|
||||
fmt.Println("❌ 服务器未运行")
|
||||
return
|
||||
}
|
||||
fmt.Println("✅")
|
||||
fmt.Println()
|
||||
|
||||
total, passed, failed := 0, 0, 0
|
||||
var errors []string
|
||||
|
||||
// 1. 基本 GET 路由测试
|
||||
fmt.Println("📦 1. 基本 GET 路由测试")
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 GET 根路径... ")
|
||||
if checkGetRoot(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "GET 根路径")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 GET 健康检查... ")
|
||||
if checkGetHealth(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "GET 健康检查")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 GET 静态路由... ")
|
||||
if checkGetStatic(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "GET 静态路由")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 2. 动态路由测试
|
||||
fmt.Println("📦 2. 动态路由测试")
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 单参数路由... ")
|
||||
if checkSingleParam(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "单参数路由")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 多参数路由... ")
|
||||
if checkMultiParams(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "多参数路由")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 嵌套参数路由... ")
|
||||
if checkNestedParams(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "嵌套参数路由")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 3. 查询参数测试
|
||||
fmt.Println("📦 3. 查询参数测试")
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 单个查询参数... ")
|
||||
if checkSingleQuery(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "单个查询参数")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 多个查询参数... ")
|
||||
if checkMultiQuery(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "多个查询参数")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 4. 响应格式测试
|
||||
fmt.Println("📦 4. 响应格式测试")
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 JSON 响应... ")
|
||||
if checkJSONResponse(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "JSON 响应")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 HTML 响应... ")
|
||||
if checkHTMLResponse(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "HTML 响应")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 自定义状态码 201... ")
|
||||
if checkCustomStatus(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "自定义状态码 201")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 5. HTTP 方法测试
|
||||
fmt.Println("📦 5. HTTP 方法测试")
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 POST 请求... ")
|
||||
if checkPOST(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "POST 请求")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 PUT 请求... ")
|
||||
if checkPUT(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "PUT 请求")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 DELETE 请求... ")
|
||||
if checkDELETE(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "DELETE 请求")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 6. 错误处理测试
|
||||
fmt.Println("📦 6. 错误处理测试")
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 404 未找到... ")
|
||||
if checkNotFound(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "404 未找到")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 7. Keep-Alive 测试
|
||||
fmt.Println("📦 7. Keep-Alive 连接测试")
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 连接复用... ")
|
||||
if checkKeepAlive(client) {
|
||||
passed++
|
||||
fmt.Println("✅")
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "连接复用")
|
||||
fmt.Println("❌")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 8. 性能测试
|
||||
fmt.Println("📦 8. 性能测试")
|
||||
|
||||
total++
|
||||
fmt.Print(" 🧪 吞吐量测试... ")
|
||||
rps := checkThroughput(client)
|
||||
if rps > 100 {
|
||||
passed++
|
||||
fmt.Printf("✅ (%.0f req/s)\n", rps)
|
||||
} else {
|
||||
failed++
|
||||
errors = append(errors, "吞吐量")
|
||||
fmt.Printf("❌ (%.0f req/s)\n", rps)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 输出总结
|
||||
fmt.Println("═══════════════════════════════════════════════════════════════")
|
||||
fmt.Printf("📊 测试结果: %d/%d 通过\n", passed, total)
|
||||
|
||||
if failed > 0 {
|
||||
fmt.Println("❌ 失败的测试:")
|
||||
for _, err := range errors {
|
||||
fmt.Printf(" - %s\n", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("🎉 所有测试通过!uSockets 后端验证成功!")
|
||||
}
|
||||
fmt.Println("═══════════════════════════════════════════════════════════════")
|
||||
}
|
||||
|
||||
func checkServer(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/health")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return resp.StatusCode == 200
|
||||
}
|
||||
|
||||
func getBody(resp *http.Response) string {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func checkGetRoot(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && strings.Contains(body, "Hello")
|
||||
}
|
||||
|
||||
func checkGetHealth(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/health")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && body == "OK"
|
||||
}
|
||||
|
||||
func checkGetStatic(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/api/health")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && body == "OK"
|
||||
}
|
||||
|
||||
func checkSingleParam(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/api/users/456")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && strings.Contains(body, "456")
|
||||
}
|
||||
|
||||
func checkMultiParams(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/api/users/123/posts/789")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && strings.Contains(body, "123") && strings.Contains(body, "789")
|
||||
}
|
||||
|
||||
func checkNestedParams(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/api/categories/tech/items/laptop")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && strings.Contains(body, "tech") && strings.Contains(body, "laptop")
|
||||
}
|
||||
|
||||
func checkSingleQuery(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/api/search?q=test")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && strings.Contains(body, "test")
|
||||
}
|
||||
|
||||
func checkMultiQuery(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/api/search?q=hello&limit=10&page=1")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && strings.Contains(body, "hello")
|
||||
}
|
||||
|
||||
func checkJSONResponse(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/api/json")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
return resp.StatusCode == 200 && strings.Contains(ct, "application/json")
|
||||
}
|
||||
|
||||
func checkHTMLResponse(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/api/html")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
return resp.StatusCode == 200 && strings.Contains(ct, "text/html")
|
||||
}
|
||||
|
||||
func checkCustomStatus(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/api/created")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return resp.StatusCode == 201
|
||||
}
|
||||
|
||||
func checkPOST(client *http.Client) bool {
|
||||
resp, err := client.Post(baseURL+"/api/users", "application/json", nil)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 201 && strings.Contains(body, "created")
|
||||
}
|
||||
|
||||
func checkPUT(client *http.Client) bool {
|
||||
req, _ := http.NewRequest("PUT", baseURL+"/api/users/123", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && strings.Contains(body, "updated")
|
||||
}
|
||||
|
||||
func checkDELETE(client *http.Client) bool {
|
||||
req, _ := http.NewRequest("DELETE", baseURL+"/api/users/123", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body := getBody(resp)
|
||||
return resp.StatusCode == 200 && strings.Contains(body, "deleted")
|
||||
}
|
||||
|
||||
func checkNotFound(client *http.Client) bool {
|
||||
resp, err := client.Get(baseURL + "/nonexistent/path")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return resp.StatusCode == 404
|
||||
}
|
||||
|
||||
func checkKeepAlive(client *http.Client) bool {
|
||||
for i := 0; i < 10; i++ {
|
||||
resp, err := client.Get(baseURL + "/health")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkThroughput(client *http.Client) float64 {
|
||||
start := time.Now()
|
||||
success := 0
|
||||
requests := 200
|
||||
|
||||
for i := 0; i < requests; i++ {
|
||||
resp, err := client.Get(baseURL + "/health")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == 200 {
|
||||
success++
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(start).Seconds()
|
||||
return float64(success) / elapsed
|
||||
}
|
||||
Reference in New Issue
Block a user