修改注释及代码重构
This commit is contained in:
237
libs/request.go
Normal file
237
libs/request.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ---------- 外部接口和类型定义 ----------
|
||||
|
||||
// AdbDevice 定义了通过 ADB 隧道创建设备连接的接口
|
||||
type AdbDevice interface {
|
||||
// CreateConnection 建立到设备的 TCP 连接
|
||||
// network 通常为 "tcp",port 为设备上服务监听端口
|
||||
CreateConnection(network string, port int) (net.Conn, error)
|
||||
}
|
||||
|
||||
// HTTPResponse 封装 HTTP 响应数据
|
||||
type HTTPResponse struct {
|
||||
Content []byte // 响应体内容
|
||||
Status int // HTTP 状态码
|
||||
Reason string // 状态描述
|
||||
}
|
||||
|
||||
// JSON 将响应体解析为指定的结构体
|
||||
func (r *HTTPResponse) JSON(v interface{}) error {
|
||||
return json.Unmarshal(r.Content, v)
|
||||
}
|
||||
|
||||
// Text 返回响应体的文本内容
|
||||
func (r *HTTPResponse) Text() string {
|
||||
return string(r.Content)
|
||||
}
|
||||
|
||||
// ---------- 自定义错误类型 ----------
|
||||
|
||||
var (
|
||||
// ErrHTTPTimeout 表示 HTTP 请求超时
|
||||
ErrHTTPTimeout = errors.New("HTTP 请求超时")
|
||||
// ErrHTTPFailed 表示 HTTP 请求失败
|
||||
ErrHTTPFailed = errors.New("HTTP 请求失败")
|
||||
)
|
||||
|
||||
// ---------- AdbHTTPConnection:通过 ADB 隧道发送 HTTP 请求 ----------
|
||||
|
||||
// AdbHTTPConnection 基于 net.Conn 实现的 HTTP 连接
|
||||
// 通过 ADB 端口转发直接与设备端 UIAutomator2 服务通信
|
||||
type AdbHTTPConnection struct {
|
||||
Conn net.Conn
|
||||
}
|
||||
|
||||
// NewAdbHTTPConnection 创建一个新的 ADB HTTP 连接
|
||||
// dev 为设备接口,port 为设备端服务端口,timeout 为连接超时
|
||||
func NewAdbHTTPConnection(dev AdbDevice, port int, timeout time.Duration) (*AdbHTTPConnection, error) {
|
||||
conn, err := dev.CreateConnection("tcp", port)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无法连接到 UIAutomator2 服务: %w", err)
|
||||
}
|
||||
_ = conn.SetDeadline(time.Now().Add(timeout))
|
||||
return &AdbHTTPConnection{Conn: conn}, nil
|
||||
}
|
||||
|
||||
// Close 关闭底层连接
|
||||
func (c *AdbHTTPConnection) Close() error {
|
||||
if c.Conn != nil {
|
||||
return c.Conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendRequest 将 HTTP 请求写入连接并读取响应
|
||||
// 通过原始 TCP 连接发送 HTTP 报文,避免依赖标准 http.Client
|
||||
func (c *AdbHTTPConnection) sendRequest(req *http.Request, timeout time.Duration) (*http.Response, error) {
|
||||
// 设置读写截止时间
|
||||
if timeout > 0 {
|
||||
_ = c.Conn.SetDeadline(time.Now().Add(timeout))
|
||||
} else {
|
||||
_ = c.Conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
// 序列化 HTTP 请求为原始报文
|
||||
var buf bytes.Buffer
|
||||
|
||||
// 请求行:METHOD PATH HTTP/1.1
|
||||
path := req.URL.RequestURI()
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s %s HTTP/1.1\r\n", req.Method, path)
|
||||
fmt.Fprintf(&buf, "Host: localhost\r\n")
|
||||
|
||||
// 设置默认请求头
|
||||
if req.Header.Get("User-Agent") == "" {
|
||||
req.Header.Set("User-Agent", "uiautomator2")
|
||||
}
|
||||
if req.Header.Get("Accept-Encoding") == "" {
|
||||
req.Header.Set("Accept-Encoding", "")
|
||||
}
|
||||
if req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
// 写入请求头
|
||||
for k, vals := range req.Header {
|
||||
for _, v := range vals {
|
||||
fmt.Fprintf(&buf, "%s: %s\r\n", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理请求体
|
||||
var bodyBytes []byte
|
||||
if req.Body != nil {
|
||||
var err error
|
||||
bodyBytes, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取请求体失败: %w", err)
|
||||
}
|
||||
fmt.Fprintf(&buf, "Content-Length: %d\r\n", len(bodyBytes))
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "Content-Length: 0\r\n")
|
||||
}
|
||||
|
||||
// 请求头与请求体之间的空行
|
||||
buf.WriteString("\r\n")
|
||||
|
||||
// 发送请求头
|
||||
if _, err := c.Conn.Write(buf.Bytes()); err != nil {
|
||||
return nil, fmt.Errorf("发送请求头失败: %w", err)
|
||||
}
|
||||
// 发送请求体
|
||||
if len(bodyBytes) > 0 {
|
||||
if _, err := c.Conn.Write(bodyBytes); err != nil {
|
||||
return nil, fmt.Errorf("发送请求体失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用标准库解析 HTTP 响应
|
||||
reader := bufio.NewReader(c.Conn)
|
||||
resp, err := http.ReadResponse(reader, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取 HTTP 响应失败: %w", err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ---------- HttpRequest:高层 HTTP 请求封装 ----------
|
||||
|
||||
// HttpRequest 向设备端 UIAutomator2 服务发送 HTTP 请求
|
||||
// ctx 为上下文控制,dev 为设备接口,devicePort 为设备端服务端口
|
||||
// method 为 HTTP 方法,path 为请求路径
|
||||
// data 为请求体数据(会被 JSON 编码),timeoutSecs 为超时秒数
|
||||
// printRequest 为 true 时输出 curl 风格的调试信息
|
||||
func HttpRequest(ctx context.Context, dev AdbDevice, devicePort int, method, path string, data map[string]interface{}, timeoutSecs float64, printRequest bool) (*HTTPResponse, error) {
|
||||
// 默认超时 10 秒
|
||||
if timeoutSecs <= 0 {
|
||||
timeoutSecs = 10.0
|
||||
}
|
||||
timeout := time.Duration(timeoutSecs * float64(time.Second))
|
||||
|
||||
// 调试模式:打印 curl 风格的请求信息
|
||||
if printRequest {
|
||||
now := time.Now().Format("15:04:05.000")
|
||||
url := fmt.Sprintf("http://127.0.0.1:%d%s", devicePort, path)
|
||||
if data != nil {
|
||||
b, _ := json.Marshal(data)
|
||||
fmt.Printf("# HTTP 超时=%.3f\n%s $ curl -X %s %s -d '%s'\n", timeoutSecs, now, method, url, string(b))
|
||||
} else {
|
||||
fmt.Printf("# HTTP 超时=%.3f\n%s $ curl -X %s %s\n", timeoutSecs, now, method, url)
|
||||
}
|
||||
}
|
||||
|
||||
// 构造 HTTP 请求
|
||||
var body io.Reader
|
||||
if data != nil {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("JSON 编码失败: %w", err)
|
||||
}
|
||||
body = bytes.NewReader(b)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, method, "http://localhost"+path, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建 HTTP 请求失败: %w", err)
|
||||
}
|
||||
req.Header.Set("User-Agent", "uiautomator2")
|
||||
req.Header.Set("Accept-Encoding", "")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 建立到设备的连接
|
||||
connWrapper, err := NewAdbHTTPConnection(dev, devicePort, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer connWrapper.Close()
|
||||
|
||||
// 发送请求并读取响应
|
||||
resp, err := connWrapper.sendRequest(req, timeout)
|
||||
if err != nil {
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||
return nil, fmt.Errorf("%w: %v", ErrHTTPTimeout, err)
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %v", ErrHTTPFailed, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
content, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应体失败: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP 请求失败: %d %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
response := &HTTPResponse{
|
||||
Content: content,
|
||||
Status: resp.StatusCode,
|
||||
Reason: resp.Status,
|
||||
}
|
||||
|
||||
if printRequest {
|
||||
now := time.Now().Format("15:04:05.000")
|
||||
fmt.Printf("%s 响应 >>>\n%s\n<<< 结束\n\n", now, strings.TrimRight(response.Text(), "\n"))
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
Reference in New Issue
Block a user