mirror of
https://github.com/gospider007/requests.git
synced 2025-12-24 13:57:52 +08:00
sync
This commit is contained in:
8
body.go
8
body.go
@@ -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
206
conn.go
@@ -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
|
||||
}
|
||||
|
||||
9
dial.go
9
dial.go
@@ -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
2
go.mod
@@ -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
395
http.go
Normal 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()
|
||||
}
|
||||
18
requests.go
18
requests.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
12
rw.go
@@ -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"))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
140
tools.go
@@ -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}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user