This commit is contained in:
gospider
2025-02-21 16:36:32 +08:00
parent a31eb9ed6a
commit 5e4c6cfb35
11 changed files with 515 additions and 321 deletions

View File

@@ -126,16 +126,10 @@ func (obj *OrderMap) parseForm(ctx context.Context) (io.Reader, string, bool, er
writer := multipart.NewWriter(pw)
go func() {
stop := context.AfterFunc(ctx, func() {
pr.CloseWithError(ctx.Err())
pw.CloseWithError(ctx.Err())
})
defer stop()
err := obj.formWriteMain(writer)
if err == nil {
err = io.EOF
}
pr.CloseWithError(err)
pw.CloseWithError(err)
pw.CloseWithError(obj.formWriteMain(writer))
}()
return pr, writer.FormDataContentType(), true, nil
}

206
conn.go
View File

@@ -1,17 +1,18 @@
package requests
import (
"bufio"
"context"
"errors"
"io"
"iter"
"net"
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/gospider007/ja3"
"github.com/gospider007/tools"
)
@@ -19,119 +20,16 @@ var maxRetryCount = 10
type Conn interface {
CloseWithError(err error) error
DoRequest(*http.Request, []string) (*http.Response, error)
DoRequest(*http.Request, []string) (*http.Response, context.Context, error)
CloseCtx() context.Context
Stream() io.ReadWriteCloser
}
type conn struct {
err error
r *bufio.Reader
w *bufio.Writer
conn net.Conn
bodyLock sync.Mutex
bodyRun atomic.Bool
closeFunc func(error)
closeCtx context.Context
closeCnl context.CancelCauseFunc
}
var errIoCopyClosedOk = errors.New("io copy is closed ok")
func newConn(ctx context.Context, con net.Conn, closeFunc func(error)) *conn {
c := &conn{
conn: con,
closeFunc: closeFunc,
}
c.closeCtx, c.closeCnl = context.WithCancelCause(ctx)
pr, pw := io.Pipe()
// c.r = bufio.NewReader(pr)
// c.w = bufio.NewWriter(con)
c.r = bufio.NewReader(pr)
c.w = bufio.NewWriter(c)
go func() {
stop := context.AfterFunc(ctx, func() {
pr.CloseWithError(ctx.Err())
pw.CloseWithError(ctx.Err())
})
defer stop()
_, err := io.Copy(pw, c.conn)
c.closeCnl(err)
if c.err == nil {
if err == nil {
c.CloseWithError(errIoCopyClosedOk)
} else {
c.CloseWithError(err)
}
}
pr.CloseWithError(c.err)
pw.CloseWithError(c.err)
}()
return c
}
func (obj *conn) CloseCtx() context.Context {
return obj.closeCtx
}
func (obj *conn) Close() error {
return obj.CloseWithError(nil)
}
func (obj *conn) CloseWithError(err error) error {
if err == nil {
obj.err = errors.New("connecotr closeWithError close")
} else {
obj.err = tools.WrapError(err, "connecotr closeWithError close")
}
if obj.closeFunc != nil {
obj.closeFunc(obj.err)
}
return obj.conn.Close()
}
func (obj *conn) DoRequest(req *http.Request, orderHeaders []string) (*http.Response, error) {
go func() {
obj.httpWrite(req, orderHeaders)
}()
res, err := http.ReadResponse(obj.r, req)
if err != nil {
err = tools.WrapError(err, "http1 read error")
return nil, err
}
if res == nil {
err = errors.New("response is nil")
}
return res, err
}
func (obj *conn) Read(b []byte) (i int, err error) {
return obj.r.Read(b)
}
func (obj *conn) Write(b []byte) (int, error) {
return obj.conn.Write(b)
}
func (obj *conn) LocalAddr() net.Addr {
return obj.conn.LocalAddr()
}
func (obj *conn) RemoteAddr() net.Addr {
return obj.conn.RemoteAddr()
}
func (obj *conn) SetDeadline(t time.Time) error {
return obj.conn.SetDeadline(t)
}
func (obj *conn) SetReadDeadline(t time.Time) error {
return obj.conn.SetReadDeadline(t)
}
func (obj *conn) SetWriteDeadline(t time.Time) error {
return obj.conn.SetWriteDeadline(t)
}
type connecotr struct {
parentForceCtx context.Context //parent force close
forceCtx context.Context //force close
forceCnl context.CancelCauseFunc
safeCtx context.Context //safe close
safeCnl context.CancelCauseFunc
bodyCtx context.Context //body close
bodyCnl context.CancelCauseFunc
Conn Conn
c net.Conn
@@ -155,14 +53,23 @@ func (obj *connecotr) CloseWithError(err error) error {
}
func (obj *connecotr) wrapBody(task *reqTask) {
body := new(readWriteCloser)
obj.bodyCtx, obj.bodyCnl = context.WithCancelCause(task.reqCtx.Context())
body.body = task.reqCtx.response.Body
rawBody := task.reqCtx.response.Body
body.body = rawBody
body.conn = obj
task.reqCtx.response.Body = body
}
func (obj *connecotr) httpReq(task *reqTask, done chan struct{}) {
defer close(done)
task.reqCtx.response, task.err = obj.Conn.DoRequest(task.reqCtx.request, task.reqCtx.option.OrderHeaders)
if task.reqCtx.option.OrderHeaders == nil {
task.reqCtx.option.OrderHeaders = ja3.DefaultOrderHeaders()
} else {
orderHeaders := make([]string, len(task.reqCtx.option.OrderHeaders))
for i, v := range task.reqCtx.option.OrderHeaders {
orderHeaders[i] = strings.ToLower(v)
}
task.reqCtx.option.OrderHeaders = orderHeaders
}
task.reqCtx.response, task.bodyCtx, task.err = obj.Conn.DoRequest(task.reqCtx.request, task.reqCtx.option.OrderHeaders)
if task.reqCtx.response != nil {
obj.wrapBody(task)
}
@@ -171,62 +78,51 @@ func (obj *connecotr) httpReq(task *reqTask, done chan struct{}) {
}
}
func (obj *connecotr) taskMain(task *reqTask) (isNotice bool) {
task.head = make(chan struct{})
func (obj *connecotr) taskMain(task *reqTask) {
defer func() {
if task.err != nil && task.reqCtx.option.ErrCallBack != nil {
task.reqCtx.err = task.err
if err2 := task.reqCtx.option.ErrCallBack(task.reqCtx); err2 != nil {
isNotice = false
task.isNotice = false
task.disRetry = true
task.err = err2
}
}
if task.err != nil {
obj.CloseWithError(errors.New("taskMain retry close"))
if task.reqCtx.response != nil && task.reqCtx.response.Body != nil {
task.reqCtx.response.Body.Close()
if errors.Is(task.err, errLastTaskRuning) {
task.isNotice = true
}
} else {
if task.reqCtx.response != nil && task.reqCtx.response.Body != nil {
task.cnl()
select {
case <-obj.bodyCtx.Done(): //wait body close
if task.err = context.Cause(obj.bodyCtx); !errors.Is(task.err, errGospiderBodyClose) {
task.err = tools.WrapError(task.err, "bodyCtx close")
}
case <-task.reqCtx.Context().Done(): //wait request close
task.err = tools.WrapError(context.Cause(task.reqCtx.Context()), "requestCtx close")
case <-obj.forceCtx.Done(): //force conn close
task.err = tools.WrapError(context.Cause(obj.forceCtx), "connecotr force close")
}
if task.reqCtx.option.Logger != nil {
task.reqCtx.option.Logger(Log{
Id: task.reqCtx.requestId,
Time: time.Now(),
Type: LogType_ResponseBody,
Msg: "response body",
})
}
}
if task.err != nil {
obj.CloseWithError(task.err)
if task.reqCtx.response != nil && task.reqCtx.response.Body != nil {
task.reqCtx.response.Body.Close()
obj.CloseWithError(errors.New("taskMain close with error"))
}
task.cnl()
if task.err == nil && task.reqCtx.response != nil && task.reqCtx.response.Body != nil {
select {
case <-obj.safeCtx.Done():
task.err = context.Cause(obj.safeCtx)
case <-obj.forceCtx.Done():
task.err = context.Cause(obj.forceCtx)
case <-task.bodyCtx.Done():
if context.Cause(task.bodyCtx) != context.Canceled {
task.err = context.Cause(task.bodyCtx)
}
}
}
}()
select {
case <-obj.safeCtx.Done():
task.err = obj.safeCtx.Err()
task.err = context.Cause(obj.safeCtx)
task.enableRetry = true
isNotice = true
task.isNotice = true
return
case <-obj.forceCtx.Done(): //force conn close
task.err = obj.forceCtx.Err()
task.err = context.Cause(obj.forceCtx)
task.enableRetry = true
isNotice = true
task.isNotice = true
return
case <-obj.Conn.CloseCtx().Done():
task.err = context.Cause(obj.Conn.CloseCtx())
task.enableRetry = true
task.isNotice = true
return
default:
}
@@ -253,7 +149,6 @@ func (obj *connecotr) taskMain(task *reqTask) (isNotice bool) {
case <-obj.forceCtx.Done(): //force conn close
task.err = tools.WrapError(context.Cause(obj.forceCtx), "taskMain delete ctx error: ")
}
return false
}
type connPool struct {
@@ -294,15 +189,6 @@ func (obj *connPools) Range() iter.Seq2[string, *connPool] {
}
}
func (obj *connPool) notices(task *reqTask) bool {
select {
case obj.tasks <- task:
return true
default:
task.isNotice = true
return false
}
}
func (obj *connPool) rwMain(done chan struct{}, conn *connecotr) {
conn.withCancel(obj.forceCtx, obj.safeCtx)
defer func() {
@@ -319,17 +205,13 @@ func (obj *connPool) rwMain(done chan struct{}, conn *connecotr) {
return
case <-conn.forceCtx.Done(): //force close conn
return
case <-conn.Conn.CloseCtx().Done():
return
case task := <-obj.tasks: //recv task
if task == nil {
return
}
task.isNotice = false
task.disRetry = false
task.enableRetry = false
task.err = nil
if !conn.taskMain(task) || !obj.notices(task) {
task.cnl()
}
conn.taskMain(task)
if task.err != nil {
return
}

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"sync"
"syscall"
"time"
"github.com/gospider007/gtls"
@@ -61,6 +62,14 @@ func newDialer(option DialOption) dialer {
KeepAlive: option.KeepAlive,
LocalAddr: option.LocalAddr,
FallbackDelay: time.Nanosecond,
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) // 启用地址重用
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1) // 启用端口重用
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 1)
syscall.SetsockoptLinger(int(fd), syscall.SOL_SOCKET, syscall.SO_LINGER, &syscall.Linger{Onoff: 1, Linger: 0})
})
},
KeepAliveConfig: net.KeepAliveConfig{
Enable: true,
Idle: time.Second * 5,

2
go.mod
View File

@@ -17,7 +17,6 @@ require (
github.com/quic-go/quic-go v0.49.0
github.com/refraction-networking/uquic v0.0.6
github.com/refraction-networking/utls v1.6.7
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f
golang.org/x/net v0.35.0
)
@@ -73,6 +72,7 @@ require (
go.uber.org/zap/exp v0.3.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect
golang.org/x/image v0.24.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/sync v0.11.0 // indirect

395
http.go Normal file
View File

@@ -0,0 +1,395 @@
package requests
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"slices"
"sort"
"time"
"github.com/gospider007/tools"
"golang.org/x/net/http/httpguts"
)
type httpTask struct {
req *http.Request
res *http.Response
orderHeaders []string
err error
ctx context.Context
cnl context.CancelFunc
writeCtx context.Context
writeCnl context.CancelFunc
readCtx context.Context
readCnl context.CancelCauseFunc
}
type conn2 struct {
err error
tasks chan *httpTask
conn net.Conn
r *bufio.Reader
w *bufio.Writer
closeFunc func(error)
ctx context.Context
closeCtx context.Context
closeCnl context.CancelCauseFunc
keepMsgNotice chan struct{}
keepEnableNotice chan struct{}
keepDisNotice chan struct{}
keepCloseCtx context.Context
keepCloseCnl context.CancelFunc
readCtx context.Context
}
type httpBody struct {
r *io.PipeReader
}
func (obj *httpBody) Read(b []byte) (i int, err error) {
return obj.r.Read(b)
}
func (obj *httpBody) Close() error {
return obj.r.Close()
}
func newConn2(ctx context.Context, con net.Conn, closeFunc func(error)) *conn2 {
closeCtx, closeCnl := context.WithCancelCause(ctx)
keepCloseCtx, keepCloseCnl := context.WithCancel(closeCtx)
c := &conn2{
closeCtx: closeCtx,
closeCnl: closeCnl,
ctx: ctx,
conn: con,
closeFunc: closeFunc,
r: bufio.NewReader(con),
w: bufio.NewWriter(con),
tasks: make(chan *httpTask),
keepMsgNotice: make(chan struct{}),
keepEnableNotice: make(chan struct{}),
keepDisNotice: make(chan struct{}),
keepCloseCtx: keepCloseCtx,
keepCloseCnl: keepCloseCnl,
}
go c.run()
go c.CheckTCPAliveSafe()
return c
}
func (obj *conn2) CheckTCPAliveSafe() {
for {
select {
case <-obj.ctx.Done():
return
case <-obj.closeCtx.Done():
return
case <-obj.keepDisNotice:
obj.keepSendMsg()
case <-obj.keepCloseCtx.Done():
return
case <-obj.keepEnableNotice:
obj.keepSendMsg()
if obj.CheckTCPAliveSafeEnable() {
return
}
}
}
}
func (obj *conn2) CheckTCPAliveSafeEnable() (closed bool) {
select {
case <-obj.ctx.Done():
return true
case <-obj.keepCloseCtx.Done():
return true
case <-obj.keepDisNotice:
obj.keepSendMsg()
return false
case <-obj.readCtx.Done():
}
for {
select {
case <-obj.ctx.Done():
return true
case <-obj.closeCtx.Done():
return true
case <-obj.keepCloseCtx.Done():
return true
case <-obj.keepDisNotice:
obj.keepSendMsg()
return false
case <-time.After(time.Second * 30):
err := obj.conn.SetReadDeadline(time.Now().Add(2 * time.Millisecond))
if err != nil {
obj.CloseWithError(err)
return true
}
if _, err = obj.r.Peek(1); err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
err = nil
} else {
obj.CloseWithError(err)
return true
}
}
if err = obj.conn.SetReadDeadline(time.Time{}); err != nil {
obj.CloseWithError(err)
return true
}
}
}
}
func (obj *conn2) keepSendMsg() {
select {
case obj.keepMsgNotice <- struct{}{}:
case <-obj.ctx.Done():
return
case <-obj.closeCtx.Done():
return
case <-obj.keepCloseCtx.Done():
return
}
}
func (obj *conn2) keepRecvMsg() {
select {
case <-obj.keepMsgNotice:
case <-obj.ctx.Done():
return
case <-obj.closeCtx.Done():
return
case <-obj.keepCloseCtx.Done():
return
}
}
func (obj *conn2) keepSendEnable(task *httpTask) {
obj.readCtx = task.readCtx
select {
case obj.keepEnableNotice <- struct{}{}:
case <-obj.ctx.Done():
return
case <-obj.closeCtx.Done():
return
case <-obj.keepCloseCtx.Done():
return
}
obj.keepRecvMsg()
}
func (obj *conn2) keepSendDisable() {
select {
case obj.keepDisNotice <- struct{}{}:
case <-obj.ctx.Done():
return
case <-obj.closeCtx.Done():
return
case <-obj.keepCloseCtx.Done():
return
}
obj.keepRecvMsg()
}
func (obj *conn2) keepSendClose() {
obj.keepCloseCnl()
}
var errLastTaskRuning = errors.New("last task is running")
func (obj *conn2) run() (err error) {
defer obj.CloseWithError(err)
for {
select {
case <-obj.ctx.Done():
return
case <-obj.closeCtx.Done():
return
case task := <-obj.tasks:
obj.keepSendDisable()
go obj.httpWrite(task, task.req.Header.Clone())
task.res, task.err = http.ReadResponse(obj.r, nil)
if task.res != nil && task.res.Body != nil {
rawBody := task.res.Body
pr, pw := io.Pipe()
go func() {
var readErr error
defer task.readCnl(readErr)
_, readErr = io.Copy(pw, rawBody)
log.Print(readErr)
pw.CloseWithError(readErr)
if readErr != nil && readErr != io.EOF && readErr != io.ErrUnexpectedEOF {
task.err = tools.WrapError(readErr, "failed to read response body")
} else {
readErr = nil
}
if readErr != nil {
obj.CloseWithError(readErr)
} else {
select {
case <-task.writeCtx.Done():
default:
readErr = tools.WrapError(errLastTaskRuning, errors.New("last task not write done with read done"))
task.err = readErr
obj.CloseWithError(readErr)
return
}
}
}()
task.res.Body = &httpBody{r: pr}
} else {
task.readCnl(nil)
}
task.cnl()
if task.res == nil || task.err != nil {
return
}
obj.keepSendEnable(task)
}
}
}
func (obj *conn2) CloseCtx() context.Context {
return obj.closeCtx
}
func (obj *conn2) Close() error {
return obj.CloseWithError(nil)
}
func (obj *conn2) CloseWithError(err error) error {
obj.closeCnl(err)
if err == nil {
obj.err = tools.WrapError(obj.err, "connecotr closeWithError close")
} else {
obj.err = tools.WrapError(err, "connecotr closeWithError close")
}
if obj.closeFunc != nil {
obj.closeFunc(obj.err)
}
return obj.conn.Close()
}
func (obj *conn2) DoRequest(req *http.Request, orderHeaders []string) (*http.Response, context.Context, error) {
readCtx, readCnl := context.WithCancelCause(obj.closeCtx)
writeCtx, writeCnl := context.WithCancel(obj.closeCtx)
ctx, cnl := context.WithCancel(req.Context())
task := &httpTask{
readCtx: readCtx,
readCnl: readCnl,
writeCtx: writeCtx,
writeCnl: writeCnl,
req: req,
orderHeaders: orderHeaders,
ctx: ctx,
cnl: cnl,
}
select {
case <-obj.ctx.Done():
return nil, task.readCtx, obj.ctx.Err()
case <-obj.closeCtx.Done():
return nil, task.readCtx, obj.closeCtx.Err()
case obj.tasks <- task:
}
select {
case <-obj.ctx.Done():
return nil, task.readCtx, obj.ctx.Err()
case <-obj.closeCtx.Done():
return nil, task.readCtx, obj.closeCtx.Err()
case <-task.ctx.Done():
}
if task.err != nil {
obj.CloseWithError(task.err)
}
return task.res, task.readCtx, task.err
}
func (obj *conn2) Stream() io.ReadWriteCloser {
obj.keepSendClose()
return obj.conn
}
func (obj *conn2) httpWrite(task *httpTask, rawHeaders http.Header) {
defer task.writeCnl()
defer func() {
if task.err != nil {
obj.CloseWithError(tools.WrapError(task.err, "failed to send request body"))
}
}()
host := task.req.Host
if host == "" {
host = task.req.URL.Host
}
host, task.err = httpguts.PunycodeHostPort(host)
if task.err != nil {
return
}
host = removeZone(host)
if rawHeaders.Get("Host") == "" {
rawHeaders.Set("Host", host)
}
if rawHeaders.Get("Connection") == "" {
rawHeaders.Set("Connection", "keep-alive")
}
if rawHeaders.Get("User-Agent") == "" {
rawHeaders.Set("User-Agent", tools.UserAgent)
}
if rawHeaders.Get("Content-Length") == "" && task.req.ContentLength != 0 && shouldSendContentLength(task.req) {
rawHeaders.Set("Content-Length", fmt.Sprint(task.req.ContentLength))
}
writeHeaders := [][2]string{}
for k, vs := range rawHeaders {
for _, v := range vs {
writeHeaders = append(writeHeaders, [2]string{k, v})
}
}
sort.Slice(writeHeaders, func(x, y int) bool {
xI := slices.Index(task.orderHeaders, writeHeaders[x][0])
yI := slices.Index(task.orderHeaders, writeHeaders[y][0])
if xI < 0 {
return false
}
if yI < 0 {
return true
}
if xI <= yI {
return true
}
return false
})
ruri := task.req.URL.RequestURI()
if task.req.Method == "CONNECT" && task.req.URL.Path == "" {
if task.req.URL.Opaque != "" {
ruri = task.req.URL.Opaque
} else {
ruri = host
}
}
if _, task.err = obj.w.WriteString(fmt.Sprintf("%s %s %s\r\n", task.req.Method, ruri, task.req.Proto)); task.err != nil {
return
}
for _, kv := range writeHeaders {
if _, task.err = obj.w.WriteString(fmt.Sprintf("%s: %s\r\n", kv[0], kv[1])); task.err != nil {
return
}
}
if _, task.err = obj.w.WriteString("\r\n"); task.err != nil {
return
}
if task.req.Body == nil {
task.err = obj.w.Flush()
return
}
if _, task.err = io.Copy(obj.w, task.req.Body); task.err != nil {
return
}
task.err = obj.w.Flush()
}

View File

@@ -13,7 +13,6 @@ import (
"net/http"
"github.com/gospider007/gtls"
"github.com/gospider007/ja3"
"github.com/gospider007/re"
"github.com/gospider007/tools"
"github.com/gospider007/websocket"
@@ -198,15 +197,6 @@ func (obj *Client) request(ctx *Response) (err error) {
if headers != nil && ctx.option.UserAgent != "" {
headers.Set("User-Agent", ctx.option.UserAgent)
}
//设置 h2 请求头顺序
if ctx.option.OrderHeaders != nil {
if !ctx.option.HSpec.IsSet() {
ctx.option.HSpec = ja3.DefaultHSpec()
ctx.option.HSpec.OrderHeaders = ctx.option.OrderHeaders
} else if ctx.option.HSpec.OrderHeaders == nil {
ctx.option.HSpec.OrderHeaders = ctx.option.OrderHeaders
}
}
//init tls timeout
if ctx.option.TlsHandshakeTimeout == 0 {
ctx.option.TlsHandshakeTimeout = time.Second * 15
@@ -233,10 +223,6 @@ func (obj *Client) request(ctx *Response) (err error) {
if headers == nil {
headers = defaultHeaders()
}
//设置 h1 请求头顺序
if ctx.option.OrderHeaders == nil {
ctx.option.OrderHeaders = ja3.DefaultOrderHeaders()
}
//init ctx,cnl
if ctx.option.Timeout > 0 { //超时
ctx.ctx, ctx.cnl = context.WithTimeout(ctx.Context(), ctx.option.Timeout)
@@ -324,7 +310,8 @@ func (obj *Client) request(ctx *Response) (err error) {
ctx.rawConn = ctx.Body().(*readWriteCloser)
}
if ctx.response.StatusCode == 101 {
ctx.webSocket = websocket.NewClientConn(ctx.rawConn.Conn(), websocket.GetResponseHeaderOption(ctx.response.Header))
ctx.Body()
ctx.webSocket = websocket.NewClientConn(newFakeConn(ctx.rawConn.connStream()), websocket.GetResponseHeaderOption(ctx.response.Header))
} else if strings.Contains(ctx.response.Header.Get("Content-Type"), "text/event-stream") {
ctx.sse = newSSE(ctx)
} else if encoding := ctx.ContentEncoding(); encoding != "" {
@@ -339,5 +326,6 @@ func (obj *Client) request(ctx *Response) (err error) {
ctx.response.Body = unCompressionBody
}
}
return
}

View File

@@ -7,6 +7,7 @@ import (
"errors"
"io"
"iter"
"log"
"net/http"
"net/url"
"strconv"
@@ -262,6 +263,10 @@ func (obj *Response) ContentType() string {
if obj.filePath != "" {
return http.DetectContentType(obj.content)
}
if obj.response.Header == nil {
log.Print(obj.response.Header == nil)
}
contentType := obj.response.Header.Get("Content-Type")
if contentType == "" {
contentType = http.DetectContentType(obj.content)

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"log"
"net"
"strings"
"time"
@@ -21,16 +22,15 @@ import (
)
type reqTask struct {
head chan struct{}
bodyCtx context.Context
ctx context.Context
cnl context.CancelFunc
reqCtx *Response
err error
enableRetry bool
disRetry bool
isNotice bool
key string
isNotice bool
key string
}
func (obj *reqTask) suppertRetry() bool {
@@ -244,18 +244,18 @@ func (obj *roundTripper) dial(ctx *Response) (conn *connecotr, err error) {
}
func (obj *roundTripper) dialConnecotr(ctx *Response, conne *connecotr, h2 bool) (err error) {
if h2 {
if ctx.option.HSpec.OrderHeaders != nil {
ctx.option.OrderHeaders = ctx.option.HSpec.OrderHeaders
}
if conne.Conn, err = http2.NewClientConn(ctx.Context(), conne.c, ctx.option.HSpec, func() {
conne.forceCnl(errors.New("http2 client close"))
}); err != nil {
return err
}
} else {
conne.Conn = newConn(conne.forceCtx, conne.c, func(err error) {
conne.Conn = newConn2(conne.safeCtx, conne.c, func(err error) {
conne.forceCnl(tools.WrapError(err, "http1 client close"))
})
// conne.Conn = newRoudTrip(conne.forceCtx, conne.c, func(err error) {
// conne.forceCnl(tools.WrapError(err, "http1 client close"))
// })
}
return err
}
@@ -339,11 +339,7 @@ func (obj *roundTripper) initProxys(ctx *Response) ([]Address, error) {
func (obj *roundTripper) poolRoundTrip(task *reqTask) {
connPool := obj.connPools.get(task)
if connPool == nil {
obj.createPool(task)
if task.err != nil {
return
}
obj.poolRoundTrip(task)
obj.newRoudTrip(task)
return
}
task.ctx, task.cnl = context.WithTimeout(task.reqCtx.Context(), task.reqCtx.option.ResponseHeaderTimeout)
@@ -354,15 +350,12 @@ func (obj *roundTripper) poolRoundTrip(task *reqTask) {
task.err = context.Cause(task.ctx)
}
default:
obj.createPool(task)
if task.err != nil {
return
}
obj.poolRoundTrip(task)
obj.newRoudTrip(task)
return
}
}
func (obj *roundTripper) createPool(task *reqTask) {
func (obj *roundTripper) newRoudTrip(task *reqTask) {
task.reqCtx.isNewConn = true
conn, err := obj.dial(task.reqCtx)
if err != nil {
@@ -377,6 +370,7 @@ func (obj *roundTripper) createPool(task *reqTask) {
}
if task.err == nil {
obj.putConnPool(task, conn)
obj.poolRoundTrip(task)
}
}
@@ -398,6 +392,7 @@ func (obj *roundTripper) newReqTask(ctx *Response) *reqTask {
}
task := new(reqTask)
task.reqCtx = ctx
task.reqCtx.response = nil
task.key = getKey(ctx) //pool key
return task
}
@@ -414,14 +409,15 @@ func (obj *roundTripper) RoundTrip(ctx *Response) (err error) {
return err
}
}
task := obj.newReqTask(ctx)
currentRetry := 0
for currentRetry := 0; currentRetry <= maxRetryCount; currentRetry++ {
var task *reqTask
for ; currentRetry <= maxRetryCount; currentRetry++ {
select {
case <-ctx.Context().Done():
return context.Cause(ctx.Context())
default:
}
task = obj.newReqTask(ctx)
obj.poolRoundTrip(task)
if task.err == nil || !task.suppertRetry() {
break
@@ -429,6 +425,7 @@ func (obj *roundTripper) RoundTrip(ctx *Response) (err error) {
if task.isNotice {
currentRetry--
}
log.Print(task.err, " currentRetry: ", currentRetry, !task.suppertRetry())
}
if currentRetry > maxRetryCount {
if task.err == nil {

12
rw.go
View File

@@ -4,7 +4,6 @@ import (
"context"
"errors"
"io"
"net"
"sync/atomic"
"github.com/gospider007/tools"
@@ -17,8 +16,8 @@ type readWriteCloser struct {
isClosed atomic.Bool
}
func (obj *readWriteCloser) Conn() net.Conn {
return obj.conn.Conn.(net.Conn)
func (obj *readWriteCloser) connStream() io.ReadWriteCloser {
return obj.conn.Conn.Stream()
}
func (obj *readWriteCloser) Read(p []byte) (n int, err error) {
if obj.isClosed.Load() {
@@ -37,8 +36,6 @@ func (obj *readWriteCloser) Proxys() []Address {
return obj.conn.proxys
}
var errGospiderBodyClose = errors.New("gospider body close error")
func (obj *readWriteCloser) Close() (err error) {
return obj.CloseWithError(nil)
}
@@ -47,20 +44,19 @@ func (obj *readWriteCloser) ConnCloseCtx() context.Context {
}
func (obj *readWriteCloser) CloseWithError(err error) error {
if err == nil {
err = errGospiderBodyClose
obj.err = io.EOF
} else {
err = tools.WrapError(obj.err, err)
obj.err = err
}
obj.isClosed.Store(true)
obj.conn.bodyCnl(err)
// obj.conn.bodyCnl(err)
return obj.body.Close() //reuse conn
}
// safe close conn
func (obj *readWriteCloser) CloseConn() {
obj.conn.bodyCnl(errors.New("readWriterCloser close conn"))
// obj.conn.bodyCnl(errors.New("readWriterCloser close conn"))
obj.conn.safeCnl(errors.New("readWriterCloser close conn"))
}

View File

@@ -12,7 +12,7 @@ func TestHttp3(t *testing.T) {
resp, err := requests.Get(context.TODO(), "https://cloudflare-quic.com/", requests.RequestOption{
ClientOption: requests.ClientOption{
// H3: true,
ForceHttp3: true,
},
},
)

140
tools.go
View File

@@ -5,20 +5,15 @@ import (
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/textproto"
"net/url"
"strconv"
"strings"
"time"
_ "unsafe"
"github.com/gospider007/gtls"
"github.com/gospider007/ja3"
"github.com/gospider007/tools"
"golang.org/x/exp/slices"
"golang.org/x/net/http/httpguts"
)
func getHost(req *http.Request) string {
@@ -113,11 +108,6 @@ func cloneUrl(u *url.URL) *url.URL {
return &r
}
var replaceMap = map[string]string{
"Sec-Ch-Ua": "sec-ch-ua",
"Sec-Ch-Ua-Mobile": "sec-ch-ua-mobile",
"Sec-Ch-Ua-Platform": "sec-ch-ua-platform",
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
@@ -215,101 +205,6 @@ func redirectBehavior(reqMethod string, resp *http.Response, ireq *http.Request)
return redirectMethod, shouldRedirect, includeBody
}
var filterHeaderKeys = ja3.DefaultOrderHeadersWithH2()
func (obj *conn) httpWrite(r *http.Request, orderHeaders []string) (err error) {
obj.bodyLock.Lock()
defer obj.bodyLock.Unlock()
if obj.bodyRun.Load() {
log.Print("body already run")
return errors.New("body already run")
}
for i := range orderHeaders {
orderHeaders[i] = textproto.CanonicalMIMEHeaderKey(orderHeaders[i])
}
host := r.Host
if host == "" {
host = r.URL.Host
}
host, err = httpguts.PunycodeHostPort(host)
if err != nil {
return err
}
host = removeZone(host)
ruri := r.URL.RequestURI()
if r.Method == "CONNECT" && r.URL.Path == "" {
if r.URL.Opaque != "" {
ruri = r.URL.Opaque
} else {
ruri = host
}
}
if r.Header.Get("Host") == "" {
r.Header.Set("Host", host)
}
if r.Header.Get("Connection") == "" {
r.Header.Set("Connection", "keep-alive")
}
if r.Header.Get("User-Agent") == "" {
r.Header.Set("User-Agent", tools.UserAgent)
}
if r.Header.Get("Content-Length") == "" && r.ContentLength != 0 && shouldSendContentLength(r) {
r.Header.Set("Content-Length", fmt.Sprint(r.ContentLength))
}
if _, err = obj.w.WriteString(fmt.Sprintf("%s %s %s\r\n", r.Method, ruri, r.Proto)); err != nil {
return err
}
for _, k := range orderHeaders {
if vs, ok := r.Header[k]; ok {
if k2, ok := replaceMap[k]; ok {
k = k2
}
if slices.Contains(filterHeaderKeys, k) {
continue
}
for _, v := range vs {
if _, err = obj.w.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)); err != nil {
return err
}
}
}
}
for k, vs := range r.Header {
if !slices.Contains(orderHeaders, k) {
if k2, ok := replaceMap[k]; ok {
k = k2
}
if slices.Contains(filterHeaderKeys, k) {
continue
}
for _, v := range vs {
if _, err = obj.w.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)); err != nil {
return err
}
}
}
}
if _, err = obj.w.WriteString("\r\n"); err != nil {
return err
}
if r.Body == nil {
return obj.w.Flush()
}
go func() {
obj.bodyRun.Store(true)
defer obj.bodyRun.Store(false)
if _, err = io.Copy(obj.w, r.Body); err != nil {
obj.CloseWithError(tools.WrapError(err, "failed to send request body"))
return
} else if err = obj.w.Flush(); err != nil {
obj.CloseWithError(tools.WrapError(err, "failed to flush request body"))
return
}
}()
return nil
// return obj.w.Flush()
}
type requestBody struct {
r io.Reader
}
@@ -368,3 +263,36 @@ func addCookie(req *http.Request, cookies Cookies) {
req.Header.Set("Cookie", result)
}
}
type fakeConn struct {
body io.ReadWriteCloser
}
func (obj *fakeConn) Read(b []byte) (n int, err error) {
return obj.body.Read(b)
}
func (obj *fakeConn) Write(b []byte) (n int, err error) {
return obj.body.Write(b)
}
func (obj *fakeConn) Close() error {
return obj.body.Close()
}
func (obj *fakeConn) LocalAddr() net.Addr {
return &net.TCPAddr{}
}
func (obj *fakeConn) RemoteAddr() net.Addr {
return &net.TCPAddr{}
}
func (obj *fakeConn) SetDeadline(t time.Time) error {
return nil
}
func (obj *fakeConn) SetReadDeadline(t time.Time) error {
return nil
}
func (obj *fakeConn) SetWriteDeadline(t time.Time) error {
return nil
}
func newFakeConn(body io.ReadWriteCloser) *fakeConn {
return &fakeConn{body: body}
}