This commit is contained in:
gospider
2025-07-29 16:04:47 +08:00
parent 4a7b21da35
commit a08970461a
7 changed files with 90 additions and 63 deletions

View File

@@ -20,13 +20,11 @@
<h2 align="center">Requests is a fully featured HTTP client library for Golang. Network requests can be completed with just a few lines of code. Unified support for http1, http2, http3, websocket, sse, utls, uquic</h2>
## Innovative Features
| **gospider007/requests** | **Other Request Libraries** |
|---------------------------|----------------------------|
| Unlimited chained proxy | Not supported |
| HTTP/3 fingerprint spoofing protection | Not supported |
| Arbitrary closure of underlying connections | Not supported |
| Genuine request-level proxy settings | Not supported |
| Unique transport layer management mechanism, fully unifying HTTP/1, HTTP/2, HTTP/3, WebSocket, SSE, UTLS, QUIC protocol handling | Not supported |
* Unlimited chained proxy
* HTTP/3 fingerprint spoofing protection
* Arbitrary closure of underlying connections
* Genuine request-level proxy settings
* Unique transport layer management mechanism, fully unifying HTTP/1, HTTP/2, HTTP/3, WebSocket, SSE, UTLS, QUIC protocol handling
## Features
* [Simple for settings and Request](https://github.com/gospider007/requests#quickly-send-requests)
@@ -69,7 +67,7 @@
* [Well tested client library](https://github.com/gospider007/requests/tree/master/test)
## Supported Go Versions
Recommended to use `go1.23.0` and above.
Recommended to use `go1.24.0` and above.
Initially Requests started supporting `go modules`
## Installation

View File

@@ -220,11 +220,11 @@ func NewReaderCompression(conn io.Reader, arch Compression) (*ReaderCompression,
func NewCompressionConn(conn net.Conn, arch Compression) (net.Conn, error) {
w, err := NewWriterCompression(conn, arch)
if err != nil {
return nil, err
return conn, err
}
r, err := NewReaderCompression(conn, arch)
if err != nil {
return nil, err
return conn, err
}
ccon := &CompressionConn{
conn: conn,

53
conn.go
View File

@@ -62,29 +62,42 @@ func taskMain(conn http1.Conn, task *reqTask) (err error) {
defer close(done)
response, derr = conn.DoRequest(task.reqCtx.request, &http1.Option{OrderHeaders: task.reqCtx.option.orderHeaders.Data()})
}()
select {
case <-conn.Context().Done(): //force conn close
err = tools.WrapError(context.Cause(conn.Context()), "taskMain delete ctx error: ")
case <-time.After(task.reqCtx.option.ResponseHeaderTimeout):
err = errors.New("ResponseHeaderTimeout error: ")
case <-task.ctx.Done():
err = context.Cause(task.ctx)
case <-done:
if derr != nil {
err = tools.WrapError(derr, "roundTrip error")
} else {
task.reqCtx.response = response
task.reqCtx.response.Request = task.reqCtx.request
if task.reqCtx.option.ResponseHeaderTimeout > 0 {
select {
case <-conn.Context().Done(): //force conn close
err = tools.WrapError(context.Cause(conn.Context()), "taskMain delete ctx error: ")
case <-time.After(task.reqCtx.option.ResponseHeaderTimeout):
err = errors.New("ResponseHeaderTimeout error: ")
case <-task.ctx.Done():
err = context.Cause(task.ctx)
case <-done:
}
if task.reqCtx.option.Logger != nil {
task.reqCtx.option.Logger(Log{
Id: task.reqCtx.requestId,
Time: time.Now(),
Type: LogType_ResponseHeader,
Msg: "response header",
})
} else {
select {
case <-conn.Context().Done(): //force conn close
err = tools.WrapError(context.Cause(conn.Context()), "taskMain delete ctx error: ")
case <-task.ctx.Done():
err = context.Cause(task.ctx)
case <-done:
}
}
if err != nil {
err = tools.WrapError(err, "roundTrip error")
} else if derr != nil {
err = tools.WrapError(derr, "roundTrip error")
} else {
task.reqCtx.response = response
task.reqCtx.response.Request = task.reqCtx.request
}
if task.reqCtx.option.Logger != nil {
task.reqCtx.option.Logger(Log{
Id: task.reqCtx.requestId,
Time: time.Now(),
Type: LogType_ResponseHeader,
Msg: "response header",
})
}
return
}

View File

@@ -54,8 +54,8 @@ type ClientOption struct {
MaxRetries int //try num
MaxRedirect int //redirect num ,<0 no redirect,==0 no limit
Timeout time.Duration //request timeout
ResponseHeaderTimeout time.Duration //ResponseHeaderTimeout ,default:300
TlsHandshakeTimeout time.Duration //tls timeout,default:15
ResponseHeaderTimeout time.Duration //ResponseHeaderTimeout
TlsHandshakeTimeout time.Duration //tls timeout
ForceHttp3 bool //force use http3 send requests
ForceHttp1 bool //force use http1 send requests
DisCookie bool //disable cookies
@@ -85,8 +85,8 @@ type RequestOption struct {
MaxRetries int //try num
MaxRedirect int //redirect num ,<0 no redirect,==0 no limit
Timeout time.Duration //request timeout
ResponseHeaderTimeout time.Duration //ResponseHeaderTimeout ,default:300
TlsHandshakeTimeout time.Duration //tls timeout,default:15
ResponseHeaderTimeout time.Duration //ResponseHeaderTimeout
TlsHandshakeTimeout time.Duration //tls timeout
ForceHttp3 bool //force use http3 send requests
ForceHttp1 bool //force use http1 send requests
DisCookie bool //disable cookies
@@ -201,10 +201,9 @@ func merge(option *RequestOption, clientOption *ClientOption) {
option.Bar = clientOption.Bar
}
}
func (obj *Client) newRequestOption(option RequestOption) (RequestOption, error) {
// err := tools.Merge(&option, obj.ClientOption)
merge(&option, obj.ClientOption)
//end
if option.MaxRetries < 0 {
option.MaxRetries = 0
}

View File

@@ -6,7 +6,6 @@ import (
"io"
"os"
"strings"
"time"
"net/url"
@@ -239,10 +238,6 @@ func (obj *Client) request(ctx *Response) (err error) {
return
}
}
//init tls timeout
if ctx.option.TlsHandshakeTimeout == 0 {
ctx.option.TlsHandshakeTimeout = time.Second * 15
}
//init proxy
if ctx.option.Proxy != nil {
ctx.proxys, err = parseProxy(ctx.option.Proxy)

View File

@@ -115,6 +115,11 @@ func (obj *roundTripper) ghttp3Dial(ctx *Response, remoteAddress Address, proxyA
if err != nil {
return nil, err
}
defer func() {
if err != nil {
udpConn.Close()
}
}()
tlsConfig := ctx.option.TlsConfig.Clone()
tlsConfig.NextProtos = []string{http3.NextProtoH3}
tlsConfig.ServerName = remoteAddress.Host
@@ -133,7 +138,6 @@ func (obj *roundTripper) ghttp3Dial(ctx *Response, remoteAddress Address, proxyA
return nil, err
}
cctx, ccnl := context.WithCancelCause(obj.ctx)
// conn = obj.newConnecotr()
conn = http3.NewClient(cctx, netConn, udpConn, func() {
ccnl(errors.New("http3 client close"))
})
@@ -156,6 +160,11 @@ func (obj *roundTripper) uhttp3Dial(ctx *Response, remoteAddress Address, proxyA
if err != nil {
return nil, err
}
defer func() {
if err != nil {
udpConn.Close()
}
}()
tlsConfig := ctx.option.UtlsConfig.Clone()
tlsConfig.NextProtos = []string{http3.NextProtoH3}
tlsConfig.ServerName = remoteAddress.Host
@@ -227,18 +236,22 @@ func (obj *roundTripper) dial(ctx *Response) (conn http1.Conn, err error) {
}
rawNetConn, err = obj.dialer.DialContext(ctx, "tcp", remoteAddress)
}
defer func() {
if err != nil && rawNetConn != nil {
if err != nil {
if rawNetConn != nil {
rawNetConn.Close()
}
}()
if err != nil {
return nil, err
}
var h2 bool
var rawConn net.Conn
if ctx.request.URL.Scheme == "https" {
rawConn, h2, err = obj.dialAddTls(ctx.option, ctx.request, rawNetConn)
if ctx.option.TlsHandshakeTimeout > 0 {
tlsCtx, tlsCnl := context.WithTimeout(ctx.Context(), ctx.option.TlsHandshakeTimeout)
rawConn, h2, err = obj.dialAddTls(tlsCtx, ctx.option, ctx.request, rawNetConn)
tlsCnl()
} else {
rawConn, h2, err = obj.dialAddTls(ctx.Context(), ctx.option, ctx.request, rawNetConn)
}
if ctx.option.Logger != nil {
ctx.option.Logger(Log{
Id: ctx.requestId,
@@ -247,17 +260,23 @@ func (obj *roundTripper) dial(ctx *Response) (conn http1.Conn, err error) {
Msg: fmt.Sprintf("host:%s, h2:%t", getHost(ctx.request), h2),
})
}
if err != nil {
return nil, err
}
} else {
rawConn = rawNetConn
}
if err != nil {
if rawConn != nil {
rawConn.Close()
}
return nil, err
}
if arch != nil {
rawConn, err = NewCompressionConn(rawConn, arch)
if err != nil {
return nil, err
}
if err != nil {
if rawConn != nil {
rawConn.Close()
}
return nil, err
}
return obj.dialConnecotr(ctx, rawConn, h2)
}
@@ -278,9 +297,7 @@ func (obj *roundTripper) dialConnecotr(ctx *Response, rawCon net.Conn, h2 bool)
}
return
}
func (obj *roundTripper) dialAddTls(option *RequestOption, req *http.Request, netConn net.Conn) (net.Conn, bool, error) {
ctx, cnl := context.WithTimeout(req.Context(), option.TlsHandshakeTimeout)
defer cnl()
func (obj *roundTripper) dialAddTls(ctx context.Context, option *RequestOption, req *http.Request, netConn net.Conn) (net.Conn, bool, error) {
if option.gospiderSpec != nil && option.gospiderSpec.TLSSpec != nil {
if tlsConn, err := obj.dialer.addJa3Tls(ctx, netConn, getHost(req), option.gospiderSpec.TLSSpec, option.UtlsConfig.Clone(), option.ForceHttp1); err != nil {
return tlsConn, false, tools.WrapError(err, "add ja3 tls error")
@@ -345,6 +362,9 @@ func (obj *roundTripper) newRoundTrip(task *reqTask) error {
task.reqCtx.isNewConn = true
conn, err := obj.dial(task.reqCtx)
if err != nil {
if conn != nil {
conn.CloseWithError(err)
}
err = tools.WrapError(err, "newRoudTrip dial error")
if task.reqCtx.option.ErrCallBack != nil {
task.reqCtx.err = err
@@ -362,9 +382,6 @@ func (obj *roundTripper) newRoundTrip(task *reqTask) error {
}
func (obj *roundTripper) newReqTask(ctx *Response) (*reqTask, error) {
if ctx.option.ResponseHeaderTimeout == 0 {
ctx.option.ResponseHeaderTimeout = time.Second * 300
}
task := new(reqTask)
task.reqCtx = ctx
task.reqCtx.response = nil

View File

@@ -27,8 +27,9 @@ func TestStream(t *testing.T) {
}
}
func TestStreamWithConn(t *testing.T) {
session, _ := requests.NewClient(nil)
for i := 0; i < 2; i++ {
resp, err := requests.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
resp, err := session.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
if err != nil {
t.Fatal(err)
}
@@ -52,8 +53,9 @@ func TestStreamWithConn(t *testing.T) {
}
func TestStreamWithConn2(t *testing.T) {
session, _ := requests.NewClient(nil)
for i := 0; i < 2; i++ {
resp, err := requests.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
resp, err := session.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
if err != nil {
t.Fatal(err)
}
@@ -77,8 +79,9 @@ func TestStreamWithConn2(t *testing.T) {
}
func TestStreamWithConn3(t *testing.T) {
session, _ := requests.NewClient(nil)
for i := 0; i < 2; i++ {
resp, err := requests.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
resp, err := session.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
if err != nil {
t.Fatal(err)
}
@@ -102,8 +105,9 @@ func TestStreamWithConn3(t *testing.T) {
}
func TestStreamWithConn4(t *testing.T) {
session, _ := requests.NewClient(nil)
for i := 0; i < 2; i++ {
resp, err := requests.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
resp, err := session.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
if err != nil {
t.Fatal(err)
}
@@ -129,8 +133,9 @@ func TestStreamWithConn4(t *testing.T) {
}
}
func TestStreamWithConn5(t *testing.T) {
session, _ := requests.NewClient(nil)
for i := 0; i < 2; i++ {
resp, err := requests.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
resp, err := session.Get(nil, "https://httpbin.org/anything", requests.RequestOption{Stream: true})
if err != nil {
t.Fatal(err)
}