optimize client

This commit is contained in:
bxd
2023-12-09 21:08:47 +08:00
parent ee825da4a0
commit 6830a982e0
9 changed files with 129 additions and 75 deletions

View File

@@ -2,6 +2,8 @@ package requests
import (
"context"
"fmt"
"io"
"net/url"
"net/http"
@@ -12,23 +14,14 @@ import (
// Connection Management
type Client struct {
option ClientOption
client *http.Client
transport *roundTripper
ctx context.Context
cnl context.CancelFunc
transport *roundTripper
closed bool
}
var defaultClient, _ = NewClient(nil)
func checkRedirect(req *http.Request, via []*http.Request) error {
ctxData := GetReqCtxData(req.Context())
if ctxData.maxRedirect == 0 || ctxData.maxRedirect >= len(via) {
return nil
}
return http.ErrUseLastResponse
}
// New Connection Management
func NewClient(preCtx context.Context, options ...ClientOption) (*Client, error) {
if preCtx == nil {
@@ -41,14 +34,12 @@ func NewClient(preCtx context.Context, options ...ClientOption) (*Client, error)
result := new(Client)
result.ctx, result.cnl = context.WithCancel(preCtx)
result.transport = newRoundTripper(result.ctx, option)
result.client = &http.Client{Transport: result.transport, CheckRedirect: checkRedirect}
result.option = option
//cookiesjar
if !result.option.DisCookie {
if result.option.Jar == nil {
result.option.Jar = NewJar()
}
result.client.Jar = result.option.Jar.jar
}
var err error
if result.option.Proxy != "" {
@@ -88,21 +79,67 @@ func (obj *Client) Close() {
obj.cnl()
}
func (obj *Client) send(option *RequestOption, reqs *http.Request) (*http.Response, error) {
if option.DisCookie {
return (&http.Client{
Transport: obj.client.Transport,
CheckRedirect: obj.client.CheckRedirect,
Timeout: obj.client.Timeout,
}).Do(reqs)
func checkRedirect(req *http.Request, via []*http.Request) error {
ctxData := GetReqCtxData(req.Context())
if ctxData.maxRedirect == 0 || ctxData.maxRedirect >= len(via) {
return nil
}
return http.ErrUseLastResponse
}
func (obj *Client) do(req *http.Request, option *RequestOption) (resp *http.Response, err error) {
var redirectNum int
for {
redirectNum++
resp, err = obj.send(req, option)
if req.Body != nil {
req.Body.Close()
}
if err != nil {
return
}
if option.MaxRedirect < 0 { //dis redirect
return
}
if option.MaxRedirect > 0 && redirectNum > option.MaxRedirect {
return
}
loc := resp.Header.Get("Location")
if loc == "" {
return resp, nil
}
u, err := req.URL.Parse(loc)
if err != nil {
return resp, fmt.Errorf("failed to parse Location header %q: %v", loc, err)
}
ireq, err := newRequestWithContext(req.Context(), http.MethodGet, u, nil)
if err != nil {
return resp, err
}
ireq.Response = resp
ireq.Header = defaultHeaders()
ireq.Header.Set("Referer", req.URL.String())
if getDomain(u) == getDomain(req.URL) {
ireq.Header.Set("Cookie", Cookies(req.Cookies()).String())
addCookie(ireq, resp.Cookies())
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
req = ireq
}
}
func (obj *Client) send(req *http.Request, option *RequestOption) (resp *http.Response, err error) {
if option.Jar != nil {
addCookie(req, option.Jar.GetCookies(req.URL))
}
resp, err = obj.transport.RoundTrip(req)
if err != nil {
return nil, err
}
if option.Jar != nil {
return (&http.Client{
Transport: obj.client.Transport,
CheckRedirect: obj.client.CheckRedirect,
Timeout: obj.client.Timeout,
Jar: option.Jar.jar,
}).Do(reqs)
if rc := resp.Cookies(); len(rc) > 0 {
option.Jar.SetCookies(req.URL, rc)
}
}
return obj.client.Do(reqs)
return resp, nil
}

View File

@@ -5,7 +5,6 @@ import (
"context"
"errors"
"io"
"log"
"net"
"net/textproto"
"sync"
@@ -218,7 +217,6 @@ func (obj *connPool) rwMain(conn *connecotr) {
}
}()
if err := conn.waitBodyClose(); err != nil {
log.Print(err)
return
}
for {

View File

@@ -62,6 +62,9 @@ func (obj Cookies) GetVal(name string) string {
return vals[i-1]
}
}
func (obj Cookies) append(cook *http.Cookie) Cookies {
return append(obj, cook)
}
// read cookies or parse cookies,support json,map,[]string,http.Header,string
func ReadCookies(val any) (Cookies, error) {

View File

@@ -12,6 +12,18 @@ const (
AcceptLanguage = "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"
)
func defaultHeaders() http.Header {
return http.Header{
"User-Agent": []string{UserAgent},
"Accept": []string{"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"},
"Accept-Encoding": []string{"gzip, deflate, br"},
"Accept-Language": []string{AcceptLanguage},
"Sec-Ch-Ua": []string{SecChUa},
"Sec-Ch-Ua-Mobile": []string{"?0"},
"Sec-Ch-Ua-Platform": []string{`"Windows"`},
}
}
func (obj *RequestOption) initHeaders() (http.Header, error) {
if obj.Headers == nil {
return nil, nil

57
jar.go
View File

@@ -1,7 +1,6 @@
package requests
import (
"errors"
"net/http/cookiejar"
"net/url"
@@ -10,63 +9,59 @@ import (
)
// cookies jar
type Jar struct {
type jar struct {
jar *cookiejar.Jar
}
// new cookies jar
func NewJar() *Jar {
jar, _ := cookiejar.New(nil)
return &Jar{
jar: jar,
func NewJar() *jar {
j, _ := cookiejar.New(nil)
return &jar{
jar: j,
}
}
// get cookies
func (obj *Client) GetCookies(href string) (Cookies, error) {
func (obj *Client) GetCookies(href *url.URL) Cookies {
if obj.option.Jar == nil {
return nil
}
return obj.option.Jar.GetCookies(href)
}
// set cookies
func (obj *Client) SetCookies(href string, cookies ...any) error {
func (obj *Client) SetCookies(href *url.URL, cookies ...any) error {
if obj.option.Jar == nil {
return nil
}
return obj.option.Jar.SetCookies(href, cookies...)
}
// clear cookies
func (obj *Client) ClearCookies() {
if obj.client.Jar != nil {
obj.option.Jar.ClearCookies()
obj.client.Jar = obj.option.Jar.jar
if obj.option.Jar == nil {
return
}
obj.option.Jar.ClearCookies()
}
// Get cookies
func (obj *Jar) GetCookies(href string) (Cookies, error) {
if obj.jar == nil {
return nil, errors.New("jar is nil")
}
u, err := url.Parse(href)
if err != nil {
return nil, err
}
return obj.jar.Cookies(u), nil
func (obj *jar) GetCookies(u *url.URL) Cookies {
return obj.jar.Cookies(u)
}
// Set cookies
func (obj *Jar) SetCookies(href string, cookies ...any) error {
if obj.jar == nil {
return errors.New("jar is nil")
}
u, err := url.Parse(href)
if err != nil {
return err
}
func getDomain(u *url.URL) string {
domain := u.Hostname()
if _, addType := gtls.ParseHost(domain); addType == 0 {
if tlp, err := publicsuffix.EffectiveTLDPlusOne(domain); err == nil {
domain = tlp
}
}
return domain
}
// Set cookies
func (obj *jar) SetCookies(u *url.URL, cookies ...any) error {
domain := getDomain(u)
for _, cookie := range cookies {
cooks, err := ReadCookies(cookie)
if err != nil {
@@ -86,7 +81,7 @@ func (obj *Jar) SetCookies(href string, cookies ...any) error {
}
// Clear cookies
func (obj *Jar) ClearCookies() {
func (obj *jar) ClearCookies() {
jar, _ := cookiejar.New(nil)
obj.jar = jar
}

View File

@@ -44,7 +44,7 @@ type ClientOption struct {
LocalAddr *net.TCPAddr
Dns *net.UDPAddr //dns
AddrType gtls.AddrType //dns parse addr type
Jar *Jar //custom cookies
Jar *jar //custom cookies
//other option
GetProxy func(ctx context.Context, url *url.URL) (string, error) //proxy callback:support https,http,socks5 proxy
@@ -82,7 +82,7 @@ type RequestOption struct {
LocalAddr *net.TCPAddr
Dns *net.UDPAddr //dns
AddrType gtls.AddrType //dns parse addr type //tls timeout,default:15
Jar *Jar //custom cookies
Jar *jar //custom cookies
// other option
Method string //method
@@ -298,5 +298,8 @@ func (obj *Client) newRequestOption(option RequestOption) RequestOption {
if !option.Ja3Spec.IsSet() && option.Ja3 {
option.Ja3Spec = ja3.DefaultJa3Spec()
}
if option.DisCookie {
option.Jar = nil
}
return option
}

View File

@@ -288,15 +288,7 @@ func (obj *Client) request(ctx context.Context, option *RequestOption) (response
return response, tools.WrapError(err, errors.New("tempRequest init headers error"), err)
}
if headers == nil {
headers = http.Header{
"User-Agent": []string{UserAgent},
"Accept": []string{"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"},
"Accept-Encoding": []string{"gzip, deflate, br"},
"Accept-Language": []string{AcceptLanguage},
"Sec-Ch-Ua": []string{SecChUa},
"Sec-Ch-Ua-Mobile": []string{"?0"},
"Sec-Ch-Ua-Platform": []string{`"Windows"`},
}
headers = defaultHeaders()
}
//init ctxData
ctxData, err := NewReqCtxData(ctx, option)
@@ -374,12 +366,10 @@ func (obj *Client) request(ctx context.Context, option *RequestOption) (response
return response, tools.WrapError(err, errors.New("tempRequest init cookies error"), err)
}
if cookies != nil {
for _, vv := range cookies {
reqs.AddCookie(vv)
}
addCookie(reqs, cookies)
}
//send req
response.response, err = obj.send(option, reqs)
response.response, err = obj.do(reqs, option)
response.isNewConn = ctxData.isNewConn
if err != nil {
err = tools.WrapError(err, "roundTripper error")

View File

@@ -111,7 +111,11 @@ func (obj *Response) Sse() *Sse {
// return URL redirected address
func (obj *Response) Location() (*url.URL, error) {
return obj.response.Location()
u, err := obj.response.Location()
if err == http.ErrNoLocation {
err = nil
}
return u, err
}
// return response Proto

View File

@@ -255,3 +255,15 @@ func readResponse(tp *textproto.Reader, req *http.Request) (*http.Response, erro
resp.Header = http.Header(mimeHeader)
return resp, readTransfer(resp, tp.R)
}
func addCookie(req *http.Request, cookies Cookies) {
cooks := Cookies(readCookies(req.Header, ""))
for _, cook := range cookies {
if val := cooks.Get(cook.Name); val == nil {
cooks = cooks.append(cook)
}
}
if result := cooks.String(); result != "" {
req.Header.Set("Cookie", result)
}
}