mirror of
https://github.com/gospider007/requests.git
synced 2025-12-24 13:57:52 +08:00
optimize client
This commit is contained in:
89
client.go
89
client.go
@@ -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
|
||||
}
|
||||
|
||||
2
conn.go
2
conn.go
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
12
headers.go
12
headers.go
@@ -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
57
jar.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
16
requests.go
16
requests.go
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
12
tools.go
12
tools.go
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user