package starnet import ( "context" "crypto/tls" "fmt" "net" "net/http" "net/url" "strings" "time" ) const ( HEADER_FORM_URLENCODE = `application/x-www-form-urlencoded` HEADER_FORM_DATA = `multipart/form-data` HEADER_JSON = `application/json` HEADER_PLAIN = `text/plain` ) var ( DefaultDialTimeout = 5 * time.Second DefaultTimeout = 10 * time.Second DefaultFetchRespBody = false DefaultHttpClient = NewHttpClientNoErr() ) func UrlEncodeRaw(str string) string { strs := strings.Replace(url.QueryEscape(str), "+", "%20", -1) return strs } func UrlEncode(str string) string { return url.QueryEscape(str) } func UrlDecode(str string) (string, error) { return url.QueryUnescape(str) } func BuildQuery(queryData map[string]string) string { query := url.Values{} for k, v := range queryData { query.Add(k, v) } return query.Encode() } // BuildPostForm takes a map of string keys and values, converts it into a URL-encoded query string, // and then converts that string into a byte slice. This function is useful for preparing data for HTTP POST requests, // where the server expects the request body to be URL-encoded form data. // // Parameters: // queryMap: A map where the key-value pairs represent the form data to be sent in the HTTP POST request. // // Returns: // A byte slice representing the URL-encoded form data. func BuildPostForm(queryMap map[string]string) []byte { return []byte(BuildQuery(queryMap)) } func Get(uri string, opts ...RequestOpt) (*Response, error) { return NewSimpleRequestWithClient(DefaultHttpClient, uri, "GET", opts...).Do() } func Post(uri string, opts ...RequestOpt) (*Response, error) { return NewSimpleRequestWithClient(DefaultHttpClient, uri, "POST", opts...).Do() } func Options(uri string, opts ...RequestOpt) (*Response, error) { return NewSimpleRequestWithClient(DefaultHttpClient, uri, "OPTIONS", opts...).Do() } func Put(uri string, opts ...RequestOpt) (*Response, error) { return NewSimpleRequestWithClient(DefaultHttpClient, uri, "PUT", opts...).Do() } func Delete(uri string, opts ...RequestOpt) (*Response, error) { return NewSimpleRequestWithClient(DefaultHttpClient, uri, "DELETE", opts...).Do() } func Head(uri string, opts ...RequestOpt) (*Response, error) { return NewSimpleRequestWithClient(DefaultHttpClient, uri, "HEAD", opts...).Do() } func Patch(uri string, opts ...RequestOpt) (*Response, error) { return NewSimpleRequestWithClient(DefaultHttpClient, uri, "PATCH", opts...).Do() } func Trace(uri string, opts ...RequestOpt) (*Response, error) { return NewSimpleRequestWithClient(DefaultHttpClient, uri, "TRACE", opts...).Do() } func Connect(uri string, opts ...RequestOpt) (*Response, error) { return NewSimpleRequestWithClient(DefaultHttpClient, uri, "CONNECT", opts...).Do() } func DefaultCheckRedirectFunc(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } func DefaultDialFunc(ctx context.Context, netType, addr string) (net.Conn, error) { var lastErr error var addrs []string if dialFn, ok := ctx.Value("dialFunc").(func(context.Context, string, string) (net.Conn, error)); ok { if dialFn != nil { return dialFn(ctx, netType, addr) } } customIP, ok := ctx.Value("customIP").([]string) if !ok { customIP = nil } dialTimeout, ok := ctx.Value("dialTimeout").(time.Duration) if !ok { dialTimeout = DefaultDialTimeout } timeout, ok := ctx.Value("timeout").(time.Duration) if !ok { timeout = DefaultTimeout } lookUpIPfn, ok := ctx.Value("lookUpIP").(func(context.Context, string) ([]net.IPAddr, error)) if !ok { lookUpIPfn = net.DefaultResolver.LookupIPAddr } host, port, err := net.SplitHostPort(addr) if err != nil { return nil, err } proxy, ok := ctx.Value("proxy").(string) if !ok { proxy = "" } if proxy == "" && len(customIP) > 0 { for _, v := range customIP { ipAddr := net.ParseIP(v) if ipAddr == nil { return nil, fmt.Errorf("invalid custom ip: %s", customIP) } tmpAddr := net.JoinHostPort(v, port) addrs = append(addrs, tmpAddr) } } else { ipLists, err := lookUpIPfn(ctx, host) if err != nil { return nil, err } for _, v := range ipLists { tmpAddr := net.JoinHostPort(v.String(), port) addrs = append(addrs, tmpAddr) } } for _, addr := range addrs { c, err := net.DialTimeout(netType, addr, dialTimeout) if err != nil { lastErr = err continue } if timeout != 0 { err = c.SetDeadline(time.Now().Add(timeout)) } return c, nil } return nil, lastErr } func DefaultDialTlsFunc(ctx context.Context, netType, addr string) (net.Conn, error) { conn, err := DefaultDialFunc(ctx, netType, addr) if err != nil { return nil, err } tlsConfig, ok := ctx.Value("tlsConfig").(*tls.Config) if !ok || tlsConfig == nil { return nil, fmt.Errorf("tlsConfig is not set in context") } tlsConn := tls.Client(conn, tlsConfig) if err := tlsConn.Handshake(); err != nil { return nil, fmt.Errorf("tls handshake failed: %w", err) } return tlsConn, nil } func DefaultProxyURL() func(*http.Request) (*url.URL, error) { return func(req *http.Request) (*url.URL, error) { if req == nil { return nil, fmt.Errorf("request is nil") } proxyURL, ok := req.Context().Value("proxy").(string) if !ok || proxyURL == "" { return nil, nil } parsedURL, err := url.Parse(proxyURL) if err != nil { return nil, fmt.Errorf("failed to parse proxy URL: %w", err) } return parsedURL, nil } }