337 lines
10 KiB
Go
337 lines
10 KiB
Go
package mmtls
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptrace"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"xiawan/wx/clientsdk/baseutils"
|
|
)
|
|
|
|
var mmHTTPClientCache sync.Map
|
|
|
|
func getMMHTTPClient(mmInfo *MMInfo, timeout time.Duration) *http.Client {
|
|
key := fmt.Sprintf("%s|%d|%p|%t", mmInfo.ShortHost, int64(timeout/time.Millisecond), mmInfo.Dialer, mmInfo.AllowDirectOnProxyFail)
|
|
if val, ok := mmHTTPClientCache.Load(key); ok {
|
|
return val.(*http.Client)
|
|
}
|
|
|
|
dialer := &net.Dialer{Timeout: timeout, KeepAlive: 30 * time.Second}
|
|
transport := &http.Transport{
|
|
DialContext: dialer.DialContext,
|
|
ResponseHeaderTimeout: timeout,
|
|
MaxIdleConns: 128,
|
|
MaxIdleConnsPerHost: 128,
|
|
IdleConnTimeout: 90 * time.Second,
|
|
DisableKeepAlives: false,
|
|
}
|
|
|
|
if mmInfo.Dialer != nil {
|
|
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
type result struct {
|
|
conn net.Conn
|
|
err error
|
|
}
|
|
resultChan := make(chan result, 1)
|
|
go func() {
|
|
conn, err := mmInfo.Dialer.Dial(network, addr)
|
|
resultChan <- result{conn: conn, err: err}
|
|
}()
|
|
select {
|
|
case res := <-resultChan:
|
|
if res.err != nil {
|
|
baseutils.PrintLog(fmt.Sprintf("Proxy dial failed: %s", res.err.Error()))
|
|
if mmInfo.AllowDirectOnProxyFail {
|
|
baseutils.PrintLog("Proxy failed, falling back to direct connection")
|
|
conn, err := dialer.DialContext(ctx, network, addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("proxy and direct connection both failed: proxy=%v, direct=%v", res.err, err)
|
|
}
|
|
return conn, nil
|
|
}
|
|
return nil, fmt.Errorf("proxy connection failed and direct fallback is disabled: %w", res.err)
|
|
}
|
|
return res.conn, nil
|
|
case <-time.After(timeout):
|
|
baseutils.PrintLog("Proxy dial timeout")
|
|
if mmInfo.AllowDirectOnProxyFail {
|
|
baseutils.PrintLog("Proxy timeout, falling back to direct connection")
|
|
conn, err := dialer.DialContext(ctx, network, addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("proxy timeout and direct connection failed: %w", err)
|
|
}
|
|
return conn, nil
|
|
}
|
|
return nil, fmt.Errorf("proxy connection timeout and direct fallback is disabled")
|
|
}
|
|
}
|
|
}
|
|
|
|
client := &http.Client{
|
|
Transport: transport,
|
|
Timeout: timeout,
|
|
}
|
|
actual, _ := mmHTTPClientCache.LoadOrStore(key, client)
|
|
return actual.(*http.Client)
|
|
}
|
|
|
|
// MMHTTPPost mmtls方式发送数据包
|
|
func MMHTTPPost(mmInfo *MMInfo, data []byte, tag string) ([]byte, error) {
|
|
startMs := time.Now().UnixMilli()
|
|
var connReused bool
|
|
var connWasIdle bool
|
|
var connIdleMs int64
|
|
var remoteAddr string
|
|
var connectStartMs int64
|
|
var connectDoneMs int64
|
|
var gotConnMs int64
|
|
var wroteRequestMs int64
|
|
var firstByteMs int64
|
|
|
|
requestURL := "http://" + mmInfo.ShortHost + mmInfo.ShortURL
|
|
req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Add("UserAgent", "MicroMessenger Client")
|
|
req.Header.Add("Accept", "*/*")
|
|
req.Header.Add("Cache-Control", "no-cache")
|
|
req.Header.Add("Connection", "Keep-Alive")
|
|
req.Header.Add("content-type", "application/octet-stream")
|
|
req.Header.Add("Upgrade", "mmtls")
|
|
req.Header.Add("Host", mmInfo.ShortHost)
|
|
// fmt.Println(mmInfo.ShortHost) //szshort.weixin.qq.com
|
|
|
|
// 获取超时配置
|
|
timeout := time.Duration(mmInfo.ShortConnTimeout) * time.Second
|
|
if timeout <= 0 {
|
|
timeout = 15 * time.Second
|
|
}
|
|
|
|
client := getMMHTTPClient(mmInfo, timeout)
|
|
if strings.Contains(tag, "openwxhb") || strings.Contains(tag, "openunionhb") || strings.Contains(tag, "receivewxhb") || strings.Contains(tag, "unionhb") {
|
|
trace := &httptrace.ClientTrace{
|
|
GotConn: func(info httptrace.GotConnInfo) {
|
|
gotConnMs = time.Now().UnixMilli()
|
|
connReused = info.Reused
|
|
connWasIdle = info.WasIdle
|
|
connIdleMs = int64(info.IdleTime / time.Millisecond)
|
|
if info.Conn != nil && info.Conn.RemoteAddr() != nil {
|
|
remoteAddr = info.Conn.RemoteAddr().String()
|
|
}
|
|
},
|
|
ConnectStart: func(network, addr string) {
|
|
connectStartMs = time.Now().UnixMilli()
|
|
},
|
|
ConnectDone: func(network, addr string, err error) {
|
|
connectDoneMs = time.Now().UnixMilli()
|
|
},
|
|
WroteRequest: func(info httptrace.WroteRequestInfo) {
|
|
wroteRequestMs = time.Now().UnixMilli()
|
|
},
|
|
GotFirstResponseByte: func() {
|
|
firstByteMs = time.Now().UnixMilli()
|
|
},
|
|
}
|
|
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
baseutils.PrintLog(err.Error())
|
|
return nil, err
|
|
}
|
|
if strings.Contains(tag, "openwxhb") || strings.Contains(tag, "openunionhb") || strings.Contains(tag, "receivewxhb") || strings.Contains(tag, "unionhb") {
|
|
fmt.Printf("MMHTTPPost resp tag=%s status=%s connHeader=%s close=%t\n", tag, resp.Status, resp.Header.Get("Connection"), resp.Close)
|
|
}
|
|
|
|
// 接收响应
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
endMs := time.Now().UnixMilli()
|
|
if strings.Contains(tag, "openwxhb") || strings.Contains(tag, "openunionhb") || strings.Contains(tag, "receivewxhb") || strings.Contains(tag, "unionhb") {
|
|
connectMs := int64(0)
|
|
if connectStartMs > 0 && connectDoneMs > 0 {
|
|
connectMs = connectDoneMs - connectStartMs
|
|
}
|
|
ttfbMs := int64(0)
|
|
if firstByteMs > 0 {
|
|
ttfbMs = firstByteMs - startMs
|
|
}
|
|
writeMs := int64(0)
|
|
if wroteRequestMs > 0 {
|
|
writeMs = wroteRequestMs - startMs
|
|
}
|
|
gotConnDelayMs := int64(0)
|
|
if gotConnMs > 0 {
|
|
gotConnDelayMs = gotConnMs - startMs
|
|
}
|
|
fmt.Printf("MMHTTPPost trace tag=%s reused=%t wasIdle=%t idle=%dms gotConn=%dms connect=%dms wrote=%dms ttfb=%dms total=%dms\n",
|
|
tag, connReused, connWasIdle, connIdleMs, gotConnDelayMs, connectMs, writeMs, ttfbMs, endMs-startMs)
|
|
if remoteAddr != "" {
|
|
fmt.Printf("MMHTTPPost peer tag=%s remote=%s\n", tag, remoteAddr)
|
|
}
|
|
}
|
|
|
|
// 返回响应数据
|
|
return body, nil
|
|
}
|
|
|
|
// MMHTTPPostData mmtls短链接方式发送请求数据包
|
|
func MMHTTPPostData(mmInfo *MMInfo, url string, data []byte) ([]byte, error) {
|
|
startMs := time.Now().UnixMilli()
|
|
createStartMs := startMs
|
|
// 创建HttpHandler
|
|
httpHandler := &HTTPHandler{}
|
|
httpHandler.URL = url
|
|
httpHandler.Host = mmInfo.ShortHost
|
|
httpHandler.MMPkg = data
|
|
|
|
// 创建发送请求项列表
|
|
sendItems, err := CreateSendPackItems(mmInfo, httpHandler)
|
|
createEndMs := time.Now().UnixMilli()
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// MMTLS-加密要发送的数据
|
|
packStartMs := time.Now().UnixMilli()
|
|
packData, err := MMHTTPPackData(mmInfo, sendItems)
|
|
packEndMs := time.Now().UnixMilli()
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// 发送数据
|
|
postStartMs := time.Now().UnixMilli()
|
|
respData, err := MMHTTPPost(mmInfo, packData, url)
|
|
postEndMs := time.Now().UnixMilli()
|
|
// 解包响应数据
|
|
decodeStartMs := time.Now().UnixMilli()
|
|
decodeData, err := MMDecodeResponseData(mmInfo, sendItems, respData)
|
|
decodeEndMs := time.Now().UnixMilli()
|
|
if err != nil {
|
|
baseutils.PrintLog(err.Error())
|
|
return nil, err
|
|
}
|
|
if strings.Contains(url, "openwxhb") || strings.Contains(url, "openunionhb") || strings.Contains(url, "receivewxhb") || strings.Contains(url, "unionhb") {
|
|
totalMs := decodeEndMs - startMs
|
|
fmt.Printf("MMHTTPPostData url=%s create=%dms pack=%dms post=%dms decode=%dms total=%dms\n",
|
|
url, createEndMs-createStartMs, packEndMs-packStartMs, postEndMs-postStartMs, decodeEndMs-decodeStartMs, totalMs)
|
|
}
|
|
return decodeData, nil
|
|
}
|
|
|
|
/*
|
|
*
|
|
纯Http请求
|
|
*/
|
|
func HTTPPost(mmInfo *MMInfo, cgi string, data []byte) ([]byte, error) {
|
|
requestURL := "http://" + mmInfo.ShortURL + cgi
|
|
request, err := http.NewRequest("POST", requestURL, bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request.Header.Add("UserAgent", "MicroMessenger Client")
|
|
request.Header.Add("Accept", "*/*")
|
|
request.Header.Add("Cache-Control", "no-cache")
|
|
request.Header.Add("Connection", "Keep-Alive")
|
|
request.Header.Add("content-type", "application/octet-stream")
|
|
|
|
// 获取超时配置
|
|
timeout := time.Duration(mmInfo.ShortConnTimeout) * time.Second
|
|
if timeout <= 0 {
|
|
timeout = 15 * time.Second
|
|
}
|
|
|
|
// 发送请求
|
|
httpTransport := &http.Transport{
|
|
Dial: func(network, addr string) (net.Conn, error) {
|
|
conn, err := net.DialTimeout(network, addr, timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
conn.SetDeadline(time.Now().Add(timeout))
|
|
return conn, nil
|
|
},
|
|
ResponseHeaderTimeout: timeout,
|
|
}
|
|
|
|
// 如果有代理
|
|
if mmInfo.Dialer != nil {
|
|
httpTransport.Dial = func(network, addr string) (net.Conn, error) {
|
|
type result struct {
|
|
conn net.Conn
|
|
err error
|
|
}
|
|
resultChan := make(chan result, 1)
|
|
|
|
go func() {
|
|
conn, err := mmInfo.Dialer.Dial(network, addr)
|
|
resultChan <- result{conn: conn, err: err}
|
|
}()
|
|
|
|
select {
|
|
case res := <-resultChan:
|
|
if res.err != nil {
|
|
if mmInfo.AllowDirectOnProxyFail {
|
|
baseutils.PrintLog("HTTPPost: Proxy failed, falling back to direct connection")
|
|
conn, err := net.DialTimeout(network, addr, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("proxy and direct connection both failed: %w", err)
|
|
}
|
|
conn.SetDeadline(time.Now().Add(timeout))
|
|
return conn, nil
|
|
}
|
|
return nil, fmt.Errorf("proxy connection failed: %w", res.err)
|
|
}
|
|
if res.conn != nil {
|
|
res.conn.SetDeadline(time.Now().Add(timeout))
|
|
}
|
|
return res.conn, nil
|
|
case <-time.After(timeout):
|
|
if mmInfo.AllowDirectOnProxyFail {
|
|
baseutils.PrintLog("HTTPPost: Proxy timeout, falling back to direct connection")
|
|
conn, err := net.DialTimeout(network, addr, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("proxy timeout and direct connection failed: %w", err)
|
|
}
|
|
conn.SetDeadline(time.Now().Add(timeout))
|
|
return conn, nil
|
|
}
|
|
return nil, fmt.Errorf("proxy connection timeout")
|
|
}
|
|
}
|
|
}
|
|
|
|
client := &http.Client{
|
|
Transport: httpTransport,
|
|
Timeout: timeout,
|
|
}
|
|
resp, err := client.Do(request)
|
|
if err != nil {
|
|
baseutils.PrintLog(err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
// 接收响应
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// 返回响应数据
|
|
return body, nil
|
|
}
|