213 lines
5.0 KiB
Go
213 lines
5.0 KiB
Go
|
|
package starnet
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"crypto/tls"
|
|||
|
|
"io"
|
|||
|
|
"net/http"
|
|||
|
|
"net/url"
|
|||
|
|
"strings"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// validMethod 验证 HTTP 方法是否有效
|
|||
|
|
func validMethod(method string) bool {
|
|||
|
|
return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// isNotToken 检查字符是否不是 token 字符
|
|||
|
|
func isNotToken(r rune) bool {
|
|||
|
|
return !isTokenRune(r)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// isTokenRune 检查字符是否是 token 字符
|
|||
|
|
func isTokenRune(r rune) bool {
|
|||
|
|
i := int(r)
|
|||
|
|
return i < 127 && isTokenTable[i]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// isTokenTable token 字符表
|
|||
|
|
var isTokenTable = [127]bool{
|
|||
|
|
'!': true, '#': true, '$': true, '%': true, '&': true, '\'': true, '*': true,
|
|||
|
|
'+': true, '-': true, '.': true, '0': true, '1': true, '2': true, '3': true,
|
|||
|
|
'4': true, '5': true, '6': true, '7': true, '8': true, '9': true, 'A': true,
|
|||
|
|
'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true,
|
|||
|
|
'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true,
|
|||
|
|
'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true,
|
|||
|
|
'W': true, 'X': true, 'Y': true, 'Z': true, '^': true, '_': true, '`': true,
|
|||
|
|
'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true,
|
|||
|
|
'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true,
|
|||
|
|
'o': true, 'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true,
|
|||
|
|
'v': true, 'w': true, 'x': true, 'y': true, 'z': true, '|': true, '~': true,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// hasPort 检查地址是否包含端口
|
|||
|
|
func hasPort(s string) bool {
|
|||
|
|
return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// removeEmptyPort 移除空端口
|
|||
|
|
func removeEmptyPort(host string) string {
|
|||
|
|
if hasPort(host) {
|
|||
|
|
return strings.TrimSuffix(host, ":")
|
|||
|
|
}
|
|||
|
|
return host
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// UrlEncode URL 编码
|
|||
|
|
func UrlEncode(str string) string {
|
|||
|
|
return url.QueryEscape(str)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// UrlEncodeRaw URL 编码(空格编码为 %20)
|
|||
|
|
func UrlEncodeRaw(str string) string {
|
|||
|
|
return strings.Replace(url.QueryEscape(str), "+", "%20", -1)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// UrlDecode URL 解码
|
|||
|
|
func UrlDecode(str string) (string, error) {
|
|||
|
|
return url.QueryUnescape(str)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// BuildQuery 构建查询字符串
|
|||
|
|
func BuildQuery(data map[string]string) string {
|
|||
|
|
query := url.Values{}
|
|||
|
|
for k, v := range data {
|
|||
|
|
query.Add(k, v)
|
|||
|
|
}
|
|||
|
|
return query.Encode()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// BuildPostForm 构建 POST 表单数据
|
|||
|
|
func BuildPostForm(data map[string]string) []byte {
|
|||
|
|
return []byte(BuildQuery(data))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cloneHeader 克隆 Header
|
|||
|
|
func cloneHeader(h http.Header) http.Header {
|
|||
|
|
if h == nil {
|
|||
|
|
return make(http.Header)
|
|||
|
|
}
|
|||
|
|
newHeader := make(http.Header, len(h))
|
|||
|
|
for k, v := range h {
|
|||
|
|
newHeader[k] = append([]string(nil), v...)
|
|||
|
|
}
|
|||
|
|
return newHeader
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cloneCookies 克隆 Cookies
|
|||
|
|
func cloneCookies(cookies []*http.Cookie) []*http.Cookie {
|
|||
|
|
if cookies == nil {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
newCookies := make([]*http.Cookie, len(cookies))
|
|||
|
|
for i, c := range cookies {
|
|||
|
|
newCookies[i] = &http.Cookie{
|
|||
|
|
Name: c.Name,
|
|||
|
|
Value: c.Value,
|
|||
|
|
Path: c.Path,
|
|||
|
|
Domain: c.Domain,
|
|||
|
|
Expires: c.Expires,
|
|||
|
|
RawExpires: c.RawExpires,
|
|||
|
|
MaxAge: c.MaxAge,
|
|||
|
|
Secure: c.Secure,
|
|||
|
|
HttpOnly: c.HttpOnly,
|
|||
|
|
SameSite: c.SameSite,
|
|||
|
|
Raw: c.Raw,
|
|||
|
|
Unparsed: append([]string(nil), c.Unparsed...),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return newCookies
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cloneStringMapSlice 克隆 map[string][]string
|
|||
|
|
func cloneStringMapSlice(m map[string][]string) map[string][]string {
|
|||
|
|
if m == nil {
|
|||
|
|
return make(map[string][]string)
|
|||
|
|
}
|
|||
|
|
newMap := make(map[string][]string, len(m))
|
|||
|
|
for k, v := range m {
|
|||
|
|
newMap[k] = append([]string(nil), v...)
|
|||
|
|
}
|
|||
|
|
return newMap
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cloneBytes 克隆字节切片
|
|||
|
|
func cloneBytes(b []byte) []byte {
|
|||
|
|
if b == nil {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
newBytes := make([]byte, len(b))
|
|||
|
|
copy(newBytes, b)
|
|||
|
|
return newBytes
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cloneStringSlice 克隆字符串切片
|
|||
|
|
func cloneStringSlice(s []string) []string {
|
|||
|
|
if s == nil {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
newSlice := make([]string, len(s))
|
|||
|
|
copy(newSlice, s)
|
|||
|
|
return newSlice
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cloneFiles 克隆文件列表
|
|||
|
|
func cloneFiles(files []RequestFile) []RequestFile {
|
|||
|
|
if files == nil {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
newFiles := make([]RequestFile, len(files))
|
|||
|
|
copy(newFiles, files)
|
|||
|
|
return newFiles
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cloneTLSConfig 克隆 TLS 配置
|
|||
|
|
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
|||
|
|
if cfg == nil {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
return cfg.Clone()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// copyWithProgress 带进度的复制
|
|||
|
|
func copyWithProgress(ctx context.Context, dst io.Writer, src io.Reader, filename string, total int64, progress UploadProgressFunc) (int64, error) {
|
|||
|
|
if progress == nil {
|
|||
|
|
return io.Copy(dst, src)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var written int64
|
|||
|
|
buf := make([]byte, 32*1024) // 32KB buffer
|
|||
|
|
|
|||
|
|
for {
|
|||
|
|
select {
|
|||
|
|
case <-ctx.Done():
|
|||
|
|
return written, ctx.Err()
|
|||
|
|
default:
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
nr, err := src.Read(buf)
|
|||
|
|
if nr > 0 {
|
|||
|
|
nw, ew := dst.Write(buf[:nr])
|
|||
|
|
if nw > 0 {
|
|||
|
|
written += int64(nw)
|
|||
|
|
// 同步调用进度回调(不使用 goroutine)
|
|||
|
|
progress(filename, written, total)
|
|||
|
|
}
|
|||
|
|
if ew != nil {
|
|||
|
|
return written, ew
|
|||
|
|
}
|
|||
|
|
if nr != nw {
|
|||
|
|
return written, io.ErrShortWrite
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if err != nil {
|
|||
|
|
if err == io.EOF {
|
|||
|
|
// 最后一次进度回调
|
|||
|
|
progress(filename, written, total)
|
|||
|
|
return written, nil
|
|||
|
|
}
|
|||
|
|
return written, err
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|