- 分离 Request 的配置态与执行态,修复二次 Do、raw 模式网络配置失效和 body 来源互斥问题 - 新增 starnet trace 抽象,补齐 DNS/连接/TLS/重试事件,并优化动态 transport 缓存与代理解析路径 - 收紧非法代理为 fail-fast,多目标目标回退仅限幂等请求,修复 Host/TLS/SNI 等语义边界 - 补充防御性拷贝、专项回归测试、本地代理/TLS 用例与 README 行为说明
221 lines
4.6 KiB
Go
221 lines
4.6 KiB
Go
package starnet
|
||
|
||
import (
|
||
"encoding/json"
|
||
"io"
|
||
"os"
|
||
)
|
||
|
||
// SetBody 设置请求体(字节)
|
||
func (r *Request) SetBody(body []byte) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
if r.doRaw {
|
||
return r
|
||
}
|
||
setBytesBodyConfig(&r.config.Body, body)
|
||
r.invalidatePreparedState()
|
||
return r
|
||
}
|
||
|
||
// SetBodyReader 设置请求体(Reader)。
|
||
// 出于避免重复写的保守策略,Reader 形态的 body 在非幂等方法上不会自动参与 retry。
|
||
func (r *Request) SetBodyReader(reader io.Reader) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
if r.doRaw {
|
||
return r
|
||
}
|
||
setReaderBodyConfig(&r.config.Body, reader)
|
||
r.invalidatePreparedState()
|
||
return r
|
||
}
|
||
|
||
// SetBodyString 设置请求体(字符串)
|
||
func (r *Request) SetBodyString(body string) *Request {
|
||
return r.SetBody([]byte(body))
|
||
}
|
||
|
||
// SetJSON 设置 JSON 请求体
|
||
func (r *Request) SetJSON(v interface{}) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
|
||
data, err := json.Marshal(v)
|
||
if err != nil {
|
||
r.err = wrapError(err, "marshal json")
|
||
return r
|
||
}
|
||
|
||
return r.SetContentType(ContentTypeJSON).SetBody(data)
|
||
}
|
||
|
||
// SetFormData 设置表单数据(覆盖)
|
||
func (r *Request) SetFormData(data map[string][]string) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
if r.doRaw {
|
||
return r
|
||
}
|
||
setFormBodyConfig(&r.config.Body, data)
|
||
r.invalidatePreparedState()
|
||
return r
|
||
}
|
||
|
||
// AddFormData 添加表单数据
|
||
func (r *Request) AddFormData(key, value string) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
if r.doRaw {
|
||
return r
|
||
}
|
||
ensureFormMode(&r.config.Body)
|
||
r.config.Body.FormData[key] = append(r.config.Body.FormData[key], value)
|
||
r.invalidatePreparedState()
|
||
return r
|
||
}
|
||
|
||
// AddFormDataMap 批量添加表单数据
|
||
func (r *Request) AddFormDataMap(data map[string]string) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
if r.doRaw {
|
||
return r
|
||
}
|
||
ensureFormMode(&r.config.Body)
|
||
for key, value := range data {
|
||
r.config.Body.FormData[key] = append(r.config.Body.FormData[key], value)
|
||
}
|
||
r.invalidatePreparedState()
|
||
return r
|
||
}
|
||
|
||
// AddFile 添加文件(从路径)
|
||
func (r *Request) AddFile(formName, filePath string) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
|
||
stat, err := os.Stat(filePath)
|
||
if err != nil {
|
||
r.err = wrapError(ErrFileNotFound, "file: %s", filePath)
|
||
return r
|
||
}
|
||
|
||
ensureMultipartMode(&r.config.Body)
|
||
r.config.Body.Files = append(r.config.Body.Files, RequestFile{
|
||
FormName: formName,
|
||
FileName: stat.Name(),
|
||
FilePath: filePath,
|
||
FileSize: stat.Size(),
|
||
FileType: ContentTypeOctetStream,
|
||
})
|
||
r.invalidatePreparedState()
|
||
|
||
return r
|
||
}
|
||
|
||
// AddFileWithName 添加文件(指定文件名)
|
||
func (r *Request) AddFileWithName(formName, filePath, fileName string) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
|
||
stat, err := os.Stat(filePath)
|
||
if err != nil {
|
||
r.err = wrapError(ErrFileNotFound, "file: %s", filePath)
|
||
return r
|
||
}
|
||
|
||
ensureMultipartMode(&r.config.Body)
|
||
r.config.Body.Files = append(r.config.Body.Files, RequestFile{
|
||
FormName: formName,
|
||
FileName: fileName,
|
||
FilePath: filePath,
|
||
FileSize: stat.Size(),
|
||
FileType: ContentTypeOctetStream,
|
||
})
|
||
r.invalidatePreparedState()
|
||
|
||
return r
|
||
}
|
||
|
||
// AddFileWithType 添加文件(指定 MIME 类型)
|
||
func (r *Request) AddFileWithType(formName, filePath, fileType string) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
|
||
stat, err := os.Stat(filePath)
|
||
if err != nil {
|
||
r.err = wrapError(ErrFileNotFound, "file: %s", filePath)
|
||
return r
|
||
}
|
||
|
||
ensureMultipartMode(&r.config.Body)
|
||
r.config.Body.Files = append(r.config.Body.Files, RequestFile{
|
||
FormName: formName,
|
||
FileName: stat.Name(),
|
||
FilePath: filePath,
|
||
FileSize: stat.Size(),
|
||
FileType: fileType,
|
||
})
|
||
r.invalidatePreparedState()
|
||
|
||
return r
|
||
}
|
||
|
||
// AddFileStream 添加文件流
|
||
func (r *Request) AddFileStream(formName, fileName string, size int64, reader io.Reader) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
|
||
if reader == nil {
|
||
r.err = ErrNilReader
|
||
return r
|
||
}
|
||
|
||
ensureMultipartMode(&r.config.Body)
|
||
r.config.Body.Files = append(r.config.Body.Files, RequestFile{
|
||
FormName: formName,
|
||
FileName: fileName,
|
||
FileData: reader,
|
||
FileSize: size,
|
||
FileType: ContentTypeOctetStream,
|
||
})
|
||
r.invalidatePreparedState()
|
||
|
||
return r
|
||
}
|
||
|
||
// AddFileStreamWithType 添加文件流(指定 MIME 类型)
|
||
func (r *Request) AddFileStreamWithType(formName, fileName, fileType string, size int64, reader io.Reader) *Request {
|
||
if r.err != nil {
|
||
return r
|
||
}
|
||
|
||
if reader == nil {
|
||
r.err = ErrNilReader
|
||
return r
|
||
}
|
||
|
||
ensureMultipartMode(&r.config.Body)
|
||
r.config.Body.Files = append(r.config.Body.Files, RequestFile{
|
||
FormName: formName,
|
||
FileName: fileName,
|
||
FileData: reader,
|
||
FileSize: size,
|
||
FileType: fileType,
|
||
})
|
||
r.invalidatePreparedState()
|
||
|
||
return r
|
||
}
|