Files
requests/option.go
2023-11-10 18:02:22 +08:00

247 lines
8.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package requests
import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"net/textproto"
"net/url"
"time"
"github.com/gospider007/gtls"
"github.com/gospider007/ja3"
"github.com/gospider007/websocket"
)
// Options for sending requests
type RequestOption struct {
ForceHttp1 bool //force use http1 send requests
OrderHeaders []string //order headers with http1
Ja3 bool //enable ja3 fingerprint
Ja3Spec ja3.Ja3Spec //custom ja3Spec,use ja3.CreateSpecWithStr or ja3.CreateSpecWithId create
H2Ja3Spec ja3.H2Ja3Spec //custom h2 fingerprint
Proxy string //proxy,support http,https,socks5,examplehttp://127.0.0.1:7005
DisCookie bool //disable cookies,not use cookies
DisDecode bool //disable auto decode
DisUnZip bool //disable auto zip decode
DisAlive bool //disable keepalive
Bar bool //enable bar display
Timeout time.Duration //request timeout
OptionCallBack func(ctx context.Context, client *Client, option *RequestOption) error //option callback,if error is returnd, break request
ResultCallBack func(ctx context.Context, client *Client, response *Response) error //result callback,if error is returnd,next errCallback
ErrCallBack func(ctx context.Context, client *Client, err error) error //error callback,if error is returnd,break request
RequestCallBack func(ctx context.Context, request *http.Request, response *http.Response) error //request and response callback,if error is returnd,reponse is error
TryNum int //try num
MaxRedirectNum int //redirect num ,<0 no redirect,==0 no limit
Headers any //request headersjson,mapheader
ResponseHeaderTimeout time.Duration //ResponseHeaderTimeout ,default:30
TlsHandshakeTimeout time.Duration
//network card ip
DialTimeout time.Duration //dial tcp timeout,default:15
KeepAlive time.Duration //keepalive,default:30
LocalAddr *net.TCPAddr
Dns *net.UDPAddr //dns
AddrType gtls.AddrType //dns parse addr type //tls timeout,default:15
Jar *Jar //custom cookies
Stream bool //disable auto read
Referer string //set headers referer value
Method string //method
Url *url.URL
Host string
Cookies any // cookies,support :json,map,strhttp.Header
Files []File //send multipart/form-data, file upload
Params any //url paramsjoin url query,json,map
Json any //send application/json,support io.Reader,string,[]bytes,json,map
Data any //send application/x-www-form-urlencoded, support io.Reader, string,[]bytes,json,map
Form any //send multipart/form-data,file upload,support io.Reader, json,map
Text any //send text/xml,support: io.Reader, string,[]bytes,json,map
Body any //not setting context-type,support io.Reader, string,[]bytes,json,map
ContentType string //headers Content-Type value
WsOption websocket.Option //websocket option
DisProxy bool //force disable proxy
// converUrl string
// body io.Reader
once bool
}
// Upload files with form-data
type File struct {
Key string
Val []byte
FileName string
ContentType string
}
func (obj *RequestOption) fileWrite(writer *multipart.Writer) (err error) {
for _, file := range obj.Files {
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(file.Key), escapeQuotes(file.FileName)))
if file.ContentType == "" {
h.Set("Content-Type", http.DetectContentType(file.Val))
} else {
h.Set("Content-Type", file.ContentType)
}
if wp, err := writer.CreatePart(h); err != nil {
return err
} else if _, err = wp.Write(file.Val); err != nil {
return err
}
}
if err = writer.Close(); err != nil {
return err
}
if obj.ContentType == "" {
obj.ContentType = writer.FormDataContentType()
}
return err
}
func (obj *RequestOption) initBody() (body io.Reader, err error) {
if obj.Body != nil {
return obj.newBody(obj.Body, rawType, nil)
} else if obj.Form != nil {
dataMap := map[string][]string{}
if body, err = obj.newBody(obj.Form, formType, dataMap); err != nil {
return
}
tempBody := bytes.NewBuffer(nil)
writer := multipart.NewWriter(tempBody)
for key, vals := range dataMap {
for _, val := range vals {
if err = writer.WriteField(key, val); err != nil {
return
}
}
}
return tempBody, obj.fileWrite(writer)
} else if obj.Files != nil {
tempBody := bytes.NewBuffer(nil)
writer := multipart.NewWriter(tempBody)
return tempBody, obj.fileWrite(writer)
} else if obj.Data != nil {
if body, err = obj.newBody(obj.Data, dataType, nil); err != nil {
return
}
if obj.ContentType == "" {
obj.ContentType = "application/x-www-form-urlencoded"
}
} else if obj.Json != nil {
if body, err = obj.newBody(obj.Json, jsonType, nil); err != nil {
return
}
if obj.ContentType == "" {
obj.ContentType = "application/json"
}
} else if obj.Text != nil {
if body, err = obj.newBody(obj.Text, textType, nil); err != nil {
return
}
if obj.ContentType == "" {
obj.ContentType = "text/plain"
}
}
return
}
func (obj *RequestOption) initUrl() (string, error) {
if obj.Params == nil {
return obj.Url.String(), nil
}
dataMap := map[string][]string{}
if _, err := obj.newBody(obj.Params, paramsType, dataMap); err != nil {
return "", err
}
if len(dataMap) == 0 {
return obj.Url.String(), nil
}
pu := cloneUrl(obj.Url)
puValues := pu.Query()
for kk, vvs := range dataMap {
for _, vv := range vvs {
puValues.Add(kk, vv)
}
}
pu.RawQuery = puValues.Encode()
return pu.String(), nil
}
func (obj *Client) newRequestOption(option RequestOption) RequestOption {
if option.TryNum < 0 {
option.TryNum = 0
} else if option.TryNum == 0 {
option.TryNum = obj.tryNum
}
if option.OptionCallBack == nil {
option.OptionCallBack = obj.optionCallBack
}
if option.ResultCallBack == nil {
option.ResultCallBack = obj.resultCallBack
}
if option.ErrCallBack == nil {
option.ErrCallBack = obj.errCallBack
}
if option.Headers == nil {
option.Headers = obj.headers
}
if !option.Bar {
option.Bar = obj.bar
}
if option.MaxRedirectNum == 0 {
option.MaxRedirectNum = obj.maxRedirectNum
}
if option.Timeout == 0 {
option.Timeout = obj.timeout
}
if option.ResponseHeaderTimeout == 0 {
option.ResponseHeaderTimeout = obj.responseHeaderTimeout
}
if option.AddrType == 0 {
option.AddrType = obj.addrType
}
if option.TlsHandshakeTimeout == 0 {
option.TlsHandshakeTimeout = obj.tlsHandshakeTimeout
}
if !option.DisCookie {
option.DisCookie = obj.disCookie
}
if !option.DisDecode {
option.DisDecode = obj.disDecode
}
if !option.DisUnZip {
option.DisUnZip = obj.disUnZip
}
if !option.ForceHttp1 {
option.ForceHttp1 = obj.forceHttp1
}
if !option.DisAlive {
option.DisAlive = obj.disAlive
}
if option.OrderHeaders == nil {
option.OrderHeaders = obj.orderHeaders
}
if !option.Ja3Spec.IsSet() {
if obj.ja3Spec.IsSet() {
option.Ja3Spec = obj.ja3Spec
} else if option.Ja3 {
option.Ja3Spec = ja3.DefaultJa3Spec()
}
}
if !option.H2Ja3Spec.IsSet() {
option.H2Ja3Spec = obj.h2Ja3Spec
}
if option.RequestCallBack == nil {
option.RequestCallBack = obj.requestCallBack
}
return option
}