1 Star 1 Fork 1

xiaoyutab/xgotool

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
quest.go 9.93 KB
一键复制 编辑 原始数据 按行查看 历史
xiaoyutab 提交于 2025-08-08 10:13 +08:00 . 修复部分情况下https的整数验证问题
package https
import (
"bytes"
"compress/flate"
"compress/gzip"
"context"
"crypto/md5"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/andybalholm/brotli"
)
// 网络请求操作
// 使用GET请求
// 此处仅能使用 `Param` 进行设置请求参数
func (c *CURL) Get() *CURL {
c.hookBefore() // 请求前操作
if c.Error != nil || len(c.files) > 0 || c.option.cacheHit {
return c.handleCommonErrors()
}
// 进行GET请求
req, err := http.NewRequest("GET", c.getParamUrl("-"), nil)
if err != nil {
c.Error = err
}
c.option.method = "get"
return c.queryBody(req)
}
// 使用POST请求
// 此处仅能使用 `Param` 进行设置请求参数
func (c *CURL) Post() *CURL {
c.
headerDefault("Content-Type", "application/x-www-form-urlencoded").
hookBefore()
if c.Error != nil || c.option.cacheHit {
return c.handleCommonErrors()
}
var req *http.Request
var err error
if len(c.files) > 0 {
// 传输文件的话
bodyBuffer := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuffer)
// 读取文件
for k, v := range c.files {
if err := c.handleFileError(k, v, bodyWriter); err != nil {
return c
}
}
for k, v := range c.ParamQuest {
if err := bodyWriter.WriteField(k, v); err != nil {
c.Error = fmt.Errorf("参数 %s 追加失败:%w", v, err)
return c
}
}
c.HeaderKV("Content-Type", bodyWriter.FormDataContentType())
bodyWriter.Close()
req, err = http.NewRequest("POST", c.URI, bodyBuffer)
} else {
req, err = http.NewRequest("POST", c.URI, strings.NewReader(c.getParamUrl(".")))
}
if err != nil {
c.Error = err
}
c.option.method = "post"
return c.queryBody(req)
}
// 使用POST请求
// 此处使用 `ParamJson` 进行设置请求参数,支持使用 `Param` 设置的参数列表
func (c *CURL) PostJson() *CURL {
c.hookBefore()
if c.Error != nil || len(c.files) > 0 || c.option.cacheHit {
return c.handleCommonErrors()
}
var j []byte
if len(c.ParamJsonQuest) > 0 {
if v, ok := c.ParamJsonQuest["_"]; ok && len(c.ParamJsonQuest) == 1 {
j, _ = json.Marshal(v)
} else {
j, _ = json.Marshal(c.ParamJsonQuest)
}
} else {
j, _ = json.Marshal(c.ParamQuest)
}
req, err := http.NewRequest("POST", c.URI, strings.NewReader(string(j)))
if err != nil {
c.Error = err
}
c.option.method = "postjson"
return c.queryBody(req)
}
// 下载文件,将请求的内容保存到本地
//
// file 保存文件名
func (c *CURL) Download(file string) *CURL {
if c.Error != nil || c.HttpCode == 0 && len(c.Body) == 0 {
return c.handleDownloadErrors(file)
}
// 写入文件
return c.writeToFile(file)
}
// 下载文件到对外IO中进行重定向
//
// f 写入对象的IO方法
func (c *CURL) DownloadIO(f io.Writer) *CURL {
if c.Error != nil || c.HttpCode == 0 && len(c.Body) == 0 {
return c.handleDownloadErrors("")
}
f.Write([]byte(c.Body))
return c
}
// 获取请求客户端标识
func (c *CURL) getClient() *http.Client {
c.StartTime = time.Now()
// 如果传入了Client选项,则直接使用传入的client项
cl := &http.Client{}
if c.option.jar != nil {
cl.Jar = c.option.jar
}
tra := http.Transport{}
// 跳过HTTPS证书
if c.option.httpsContinue {
tra.TLSClientConfig = &tls.Config{
Renegotiation: tls.RenegotiateFreelyAsClient,
InsecureSkipVerify: true,
}
}
// 禁用Http2
if c.option.disableHttpV2 {
tra.ForceAttemptHTTP2 = false
tra.TLSNextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper)
}
// 进行本地host域名解析
if len(c.option.hosts) > 0 {
tra.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
ls := strings.Split(addr, ":")
if addrs, ok := c.option.hosts[ls[0]]; ok {
addr = addrs + ":" + ls[1]
} else if addrs, ok := c.option.hosts[addr]; ok {
addr = addrs
}
dialer := net.Dialer{}
return dialer.DialContext(ctx, network, addr)
}
}
cl.Transport = &tra
// 设置超时时间
if c.option.timeOut > 0 {
cl.Timeout = c.option.timeOut
}
return cl
}
// 获取缓存下标
func (c *CURL) getKey() string {
return fmt.Sprintf("https_cache_%0x", md5.Sum([]byte(fmt.Sprintf("%T: %v, param: %T: %v, param_json: %T: %v, header: %T: %v",
c.URI, c.URI,
c.ParamQuest, c.ParamQuest,
c.ParamJsonQuest, c.ParamJsonQuest,
c.HeaderQuest, c.HeaderQuest,
))))
}
// 获取CURL的GET请求网址
//
// uri 要拼接的URI前缀,传入-表示使用CURL中的URI网址 ,传入.表示只返回拼接的URL参数
func (c *CURL) getParamUrl(uri string) string {
if c.Error != nil {
return ""
}
dat := url.Values{}
for i, v := range c.ParamQuest {
dat.Set(i, v)
}
if c.URI == "" {
c.Error = errors.New("网址不能为空")
return dat.Encode()
}
switch uri {
case "-":
uri = c.URI
case ".":
return dat.Encode()
}
if strings.Contains(uri, "?") {
uri += "&" + dat.Encode()
} else {
uri += "?" + dat.Encode()
}
return uri
}
// 从quest结构体中获取到数据并回写到CURL结构体中
//
// req 请求的待处理操作
func (curl *CURL) queryBody(req *http.Request) *CURL {
if curl.Error != nil {
return curl.retry()
}
// 请求开始前的钩子处理
if hook, ok := _default.hookBefore["*"]; ok {
hook(curl)
}
if uri, err := url.Parse(curl.URI); err == nil {
if hook, ok := _default.hookBefore[uri.Host]; ok {
hook(curl)
}
}
req = req.WithContext(curl.option.context)
defer curl.hookEnd()
// 设置Header请求头
for i, v := range curl.HeaderQuest {
req.Header.Set(i, v)
}
// 执行HTTP请求
resp, err := curl.getClient().Do(req)
if err != nil {
curl.Error = err
return curl.retry()
}
defer resp.Body.Close()
// 获取相应结果
bodys, err := io.ReadAll(resp.Body)
if err != nil {
curl.Error = err
return curl.retry()
}
// 判断返回的编码是否经过了压缩
switch resp.Header.Get("Content-Encoding") {
case "gzip":
ioReader := bytes.NewBuffer(bodys)
gzReader, err := gzip.NewReader(ioReader)
if err == nil {
defer gzReader.Close()
buf := bytes.Buffer{}
if _, err := io.Copy(&buf, gzReader); err == nil {
bodys = buf.Bytes()
}
}
case "deflate":
ioReader := bytes.NewBuffer(bodys)
deflateReader := flate.NewReader(ioReader)
buf := bytes.Buffer{}
if _, err := io.Copy(&buf, deflateReader); err == nil {
bodys = buf.Bytes()
}
case "br":
ioReader := bytes.NewBuffer(bodys)
// Go原生未实现br压缩方式,此处引入外部依赖进行支持
brReader := brotli.NewReader(ioReader)
var buf bytes.Buffer
if _, err := io.Copy(&buf, brReader); err == nil {
bodys = buf.Bytes()
}
}
curl.Body = string(bodys)
curl.HttpCode = resp.StatusCode
curl.CookieQuest = resp.Header.Values("set-cookie")
curl.Version = 1.1
if f, err := strconv.ParseFloat(fmt.Sprintf("%d.%d", resp.ProtoMajor, resp.ProtoMinor), 64); err == nil {
curl.Version = f
}
// HTTP-Code错误
if curl.HttpCode > 300 || curl.HttpCode < 200 {
curl.Error = fmt.Errorf("网络请求Code返回错误:%d", curl.HttpCode)
}
if hook, ok := _default.hookAfter["*"]; ok {
hook(curl)
}
// 请求结束后的钩子处理
if uri, err := url.Parse(curl.URI); err == nil {
if hook, ok := _default.hookAfter[uri.Host]; ok {
hook(curl)
}
}
return curl.retry()
}
// 获取域名下的cookie信息
// 如果使用了jar进行cookie管理,会直接返回jar中该域名下的cookie
// 如果未使用jar进行管理,则会获取CookieQuest的值,然后使用;=进行分割,以提取其中的key/value值
// 响应的,如果未使用jar进行管理,此处会手动循环cookie中的值进行strings切分,所以此处速度会稍慢一些
func (c *CURL) Cookie() []*http.Cookie {
u, err := url.Parse(c.URI)
if err != nil {
return nil
}
if c.option == nil || c.option.jar == nil {
if len(c.CookieQuest) == 0 {
return nil
}
cookie, err := cookiejar.New(nil)
if err != nil {
return nil
}
ck := []*http.Cookie{}
for i := 0; i < len(c.CookieQuest); i++ {
// 使用;切分数组
temps := strings.Split(c.CookieQuest[i], ";")
// 使用= 切分数组
tmp := strings.Split(temps[0], "=")
ck_one := http.Cookie{
Name: tmp[0],
Value: strings.Join(tmp[1:], "="),
}
for i := 0; i < len(temps); i++ {
if i == 0 {
continue
}
tmp := strings.Split(temps[i], "=")
switch tmp[0] {
case "expires":
t, err := time.Parse(time.RFC850, tmp[1])
if err != nil {
continue
}
ck_one.Expires = t
case "max-age":
if s, err := strconv.Atoi(tmp[1]); err == nil {
ck_one.MaxAge = int(s)
}
case "path":
ck_one.Path = tmp[1]
case "domain":
ck_one.Domain = tmp[1]
}
}
ck = append(ck, &ck_one)
}
cookie.SetCookies(u, ck)
return cookie.Cookies(u)
}
return c.option.jar.Cookies(u)
}
// 处理常见错误
func (c *CURL) handleCommonErrors() *CURL {
if c.Error != nil {
return c
}
if len(c.files) > 0 {
c.Error = fmt.Errorf("文件上传暂只支持PostForm形式进行上传")
} else if c.option.cacheHit {
c.EndTime = time.Now()
}
return c
}
// 处理文件错误
func (c *CURL) handleFileError(k, v string, bodyWriter *multipart.Writer) error {
_, err := os.Stat(v)
if err != nil {
c.Error = fmt.Errorf("文件 %s 未找到:%w", v, err)
return err
}
fs, err := os.Open(v)
if err != nil {
c.Error = fmt.Errorf("文件 %s 打开失败:%w", v, err)
return err
}
defer fs.Close()
fileWriter1, _ := bodyWriter.CreateFormFile(k, v)
io.Copy(fileWriter1, fs)
return nil
}
// 处理下载错误
func (c *CURL) handleDownloadErrors(file string) *CURL {
if c.Error != nil {
return c
}
if c.HttpCode == 0 && len(c.Body) == 0 {
// 该请求还未进行最终的GET/POST请求,所以此处直接默认为GET请求
c.Get()
}
return c
}
// 写入文件
func (c *CURL) writeToFile(file string) *CURL {
f, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
c.Error = err
return c
}
defer f.Close()
f.WriteString(c.Body)
return c
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/xiaoyutab/xgotool.git
git@gitee.com:xiaoyutab/xgotool.git
xiaoyutab
xgotool
xgotool
v0.4.1

搜索帮助