364 lines
7.8 KiB
Go
364 lines
7.8 KiB
Go
// bhttp Support for Http custom parameters and proxy
|
||
package bhttp
|
||
|
||
import (
|
||
"bufio"
|
||
"bytes"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"mime/multipart"
|
||
"net/http"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// BhttpService bhttp service
|
||
type BhttpService struct {
|
||
Client *bhttpClient
|
||
}
|
||
|
||
// NewBhttpService 创建bhttp service
|
||
func NewBhttpService(opts ...Option) (*BhttpService, error) {
|
||
client, err := NewBhttpClient(opts...)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &BhttpService{
|
||
Client: client,
|
||
}, nil
|
||
}
|
||
|
||
type bhttpClient struct {
|
||
client *http.Client
|
||
httpRequest *http.Request
|
||
header *http.Header
|
||
values *url.Values
|
||
params map[string]string
|
||
postBody []byte
|
||
retry int32
|
||
checkStatusOk bool
|
||
proxyURL string
|
||
rateTimeLeft int //下次可执行剩余的时间毫秒
|
||
}
|
||
|
||
type options struct {
|
||
timeout int32
|
||
proxyURL string
|
||
retry int32
|
||
checkStatusOk bool
|
||
disableKeepAlives bool
|
||
}
|
||
type Option func(*options)
|
||
|
||
func WithTimeout(b int32) Option {
|
||
return func(c *options) {
|
||
c.timeout = b
|
||
}
|
||
}
|
||
func WithProxy(b string) Option {
|
||
return func(c *options) {
|
||
c.proxyURL = b
|
||
}
|
||
}
|
||
|
||
func WithRetry(b int32) Option {
|
||
return func(c *options) {
|
||
c.retry = b
|
||
}
|
||
}
|
||
|
||
func WithCheckStatusOk(b bool) Option {
|
||
return func(c *options) {
|
||
c.checkStatusOk = b
|
||
}
|
||
}
|
||
|
||
func WithDisableKeepAlives(b bool) Option {
|
||
return func(c *options) {
|
||
c.disableKeepAlives = b
|
||
}
|
||
}
|
||
|
||
func NewBhttpClient(opts ...Option) (*bhttpClient, error) {
|
||
proxyFunc := http.ProxyFromEnvironment
|
||
opt := options{
|
||
timeout: 60,
|
||
proxyURL: "",
|
||
retry: 0,
|
||
checkStatusOk: false,
|
||
disableKeepAlives: false,
|
||
}
|
||
for _, o := range opts {
|
||
o(&opt)
|
||
}
|
||
|
||
if opt.proxyURL != "" {
|
||
proxy, err := url.Parse(opt.proxyURL)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
proxyFunc = http.ProxyURL(proxy)
|
||
}
|
||
|
||
return &bhttpClient{
|
||
client: &http.Client{
|
||
Transport: &http.Transport{
|
||
Proxy: proxyFunc,
|
||
DisableKeepAlives: opt.disableKeepAlives,
|
||
},
|
||
Timeout: time.Duration(opt.timeout) * time.Second,
|
||
},
|
||
header: &http.Header{},
|
||
values: &url.Values{},
|
||
params: make(map[string]string),
|
||
retry: opt.retry,
|
||
checkStatusOk: opt.checkStatusOk,
|
||
proxyURL: opt.proxyURL,
|
||
}, nil
|
||
}
|
||
|
||
func (b *bhttpClient) SetParam(key, value string) *bhttpClient {
|
||
b.values.Add(key, value)
|
||
oParams := b.params
|
||
oParams[key] = value
|
||
b.params = oParams
|
||
|
||
return b
|
||
}
|
||
|
||
func (b *bhttpClient) SetBody(body []byte) *bhttpClient {
|
||
b.postBody = body
|
||
return b
|
||
}
|
||
|
||
func (b *bhttpClient) SetHeader(key, value string) *bhttpClient {
|
||
b.header.Add(key, value)
|
||
return b
|
||
}
|
||
|
||
// GetRedirectedURL 获取重定向链接
|
||
func (b *bhttpClient) GetRedirectedURL(url string) (string, error) {
|
||
var (
|
||
err error
|
||
)
|
||
|
||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
b.httpRequest = req
|
||
b.httpRequest.Header = *b.header
|
||
|
||
resp, err := b.client.Do(b.httpRequest)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
defer resp.Body.Close()
|
||
|
||
redirectedURL := resp.Request.URL.String()
|
||
return redirectedURL, nil
|
||
}
|
||
|
||
func (b *bhttpClient) DoGet(reqUrl string) ([]byte, error) {
|
||
requestURI, err := url.ParseRequestURI(reqUrl)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
reader := b.values.Encode()
|
||
if reader != "" {
|
||
if requestURI.RawQuery == "" {
|
||
requestURI.RawQuery = reader
|
||
} else {
|
||
requestURI.RawQuery = fmt.Sprintf("%s&%s", requestURI.RawQuery, reader)
|
||
}
|
||
// fmt.Println("test_debug", requestURI.String())
|
||
}
|
||
|
||
return b.doRequest(http.MethodGet, requestURI.String(), "")
|
||
}
|
||
|
||
func (b *bhttpClient) DoPost(url string) ([]byte, error) {
|
||
reader := b.values.Encode()
|
||
if len(b.postBody) > 0 {
|
||
reader = string(b.postBody)
|
||
}
|
||
return b.doRequest(http.MethodPost, url, reader)
|
||
}
|
||
|
||
// DoPatch is a patch method
|
||
func (b *bhttpClient) DoPatch(url string) ([]byte, error) {
|
||
reader := string(b.postBody)
|
||
return b.doRequest(http.MethodPatch, url, reader)
|
||
}
|
||
|
||
func (b *bhttpClient) DoPut(url string) ([]byte, error) {
|
||
reader := string(b.postBody)
|
||
return b.doRequest(http.MethodPut, url, reader)
|
||
}
|
||
|
||
func (b *bhttpClient) doRequest(method, url, reader string) ([]byte, error) {
|
||
payload := bytes.NewBuffer([]byte(reader))
|
||
//如果content_type是 multipart/form-data
|
||
cType := b.header.Get("Content-Type")
|
||
if strings.Contains(cType, "form-data") {
|
||
payload = &bytes.Buffer{}
|
||
bodyWriter := multipart.NewWriter(payload)
|
||
for k, v := range b.params {
|
||
bodyWriter.WriteField(k, v)
|
||
}
|
||
bodyWriter.Close()
|
||
b.header.Set("Content-Type", bodyWriter.FormDataContentType())
|
||
}
|
||
switch method {
|
||
case http.MethodGet:
|
||
req, err := http.NewRequest(method, url, nil)
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
b.httpRequest = req
|
||
case http.MethodPost, http.MethodPut:
|
||
req, err := http.NewRequest(method, url, payload)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
b.httpRequest = req
|
||
case http.MethodPatch:
|
||
fmt.Println("url: ", url)
|
||
fmt.Println("payload: ", reader)
|
||
payload1 := strings.NewReader(`{"status": "FINALIZED"}`)
|
||
req, err := http.NewRequest(method, url, payload1)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// 设置查询参数
|
||
// q := req.URL.Query()
|
||
// for k, v := range b.params {
|
||
// q.Add(k, v)
|
||
// }
|
||
// req.URL.RawQuery = q.Encode()
|
||
b.httpRequest = req
|
||
|
||
default:
|
||
return nil, errors.New("method not support")
|
||
}
|
||
|
||
b.httpRequest.Header = *b.header
|
||
|
||
var (
|
||
res *http.Response
|
||
err error
|
||
)
|
||
retry := b.retry + 1
|
||
for i := 1; i <= int(retry); i++ {
|
||
body, werr := b.client.Do(b.httpRequest)
|
||
err = werr
|
||
if err == nil {
|
||
res = body
|
||
break
|
||
}
|
||
fmt.Printf("bhttp-临时超时: %d , err: %s\n, proxy: %s, header-auth: %s \n", i, err.Error(), b.proxyURL, b.header.Get("Authorization"))
|
||
time.Sleep(time.Duration(i) * time.Second)
|
||
}
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
defer res.Body.Close()
|
||
|
||
rateTimeLeft := res.Header.Get("rate-limit-msec-left")
|
||
if rateTimeLeft != "" {
|
||
rateTimeLeftInt, _ := strconv.Atoi(rateTimeLeft)
|
||
if rateTimeLeftInt > 0 {
|
||
b.rateTimeLeft = rateTimeLeftInt
|
||
}
|
||
|
||
}
|
||
|
||
//ioutil.ReadAll(res.Body) //io/ioutil" has been deprecated since Go 1.19
|
||
newReader := bufio.NewReader(res.Body)
|
||
body := make([]byte, 0)
|
||
for {
|
||
data, err := newReader.ReadBytes('\n')
|
||
if err == io.EOF {
|
||
body = append(body, data...)
|
||
break
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
body = append(body, data...)
|
||
}
|
||
|
||
if b.checkStatusOk && res.StatusCode != http.StatusOK {
|
||
return nil, fmt.Errorf("status error code: %d, res: %s", res.StatusCode, string(body))
|
||
}
|
||
|
||
return body, nil
|
||
}
|
||
|
||
// GetRateTimeLeft get the reader of the request body
|
||
func (b *bhttpClient) GetRateTimeLeft() int {
|
||
return b.rateTimeLeft
|
||
}
|
||
|
||
// get the reader of the request body
|
||
func (b *bhttpClient) GetReader() string {
|
||
return b.values.Encode()
|
||
}
|
||
|
||
// GetRedirectedURL 获取重定向url
|
||
func GetRedirectedURL(initialURL string) (string, error) {
|
||
// 创建一个自定义的HTTP客户端
|
||
client := &http.Client{
|
||
// 自定义CheckRedirect函数,遇到重定向时获取Location头并返回
|
||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||
if len(via) > 0 {
|
||
// 返回重定向后的URL
|
||
return http.ErrUseLastResponse
|
||
}
|
||
return nil
|
||
},
|
||
}
|
||
|
||
// 发送GET请求
|
||
resp, err := client.Get(initialURL)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 检查响应状态码是否为302
|
||
if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently {
|
||
// 获取Location头中的重定向URL
|
||
redirectURL, err := resp.Location()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return redirectURL.String(), nil
|
||
}
|
||
|
||
return "", fmt.Errorf("no redirect found")
|
||
|
||
}
|
||
|
||
func ParsedUrlWithRetry(link string) (*url.URL, error) {
|
||
for i := 0; i <= 5; i++ {
|
||
parsedURL, err := url.Parse(link)
|
||
if err == nil {
|
||
return parsedURL, nil
|
||
}
|
||
time.Sleep(1 * time.Second)
|
||
}
|
||
|
||
return nil, fmt.Errorf("failed to parsed url: %s", link)
|
||
|
||
}
|