package curl import ( "bytes" "context" "crypto/tls" "encoding/json" "fmt" "io" "io/ioutil" "net" "net/http" netURL "net/url" "strings" "time" ) type curlV1 struct { DefaultHeader http.Header } func V1(defaultHeader http.Header) *curlV1 { return &curlV1{ DefaultHeader: defaultHeader, } } func (v1 *curlV1) getDomain(url string) string { strs := strings.Split(url, "//") if len(strs) < 2 { return "" } schema := v1.getSchema(url) paths := strings.Split(strs[1], "/") switch schema { case "unix": tmpstrs := []string{} for _, p := range paths { tmpstrs = append(tmpstrs, p) if strings.Contains(p, ".sock") { break } } return strings.Join(tmpstrs, "/") case "http": return paths[0] case "https": return paths[0] default: return paths[0] } } func (v1 *curlV1) getSchema(url string) string { strs := strings.Split(url, ":") return strs[0] } func (v1 *curlV1) getPath(url string) string { schema := v1.getSchema(url) domain := v1.getDomain(url) s := fmt.Sprintf("%s://%s", schema, domain) if len(url) <= len(s) { return "" } return url[len(s):] } func (v1 *curlV1) NewClient(url string) (*http.Client, string) { schema := v1.getSchema(url) domain := v1.getDomain(url) path := v1.getPath(url) if schema == "unix" { defaultTimeout := 60 * time.Second tr := new(http.Transport) tr.DisableCompression = true tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { return net.DialTimeout("unix", domain, defaultTimeout) } tr.TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, } client := &http.Client{Transport: tr} return client, path } // 绕过github等可能因为特征码返回503问题 // https://www.imwzk.com/posts/2021-03-14-why-i-always-get-503-with-golang/ defaultCipherSuites := []uint16{0xc02f, 0xc030, 0xc02b, 0xc02c, 0xcca8, 0xcca9, 0xc013, 0xc009, 0xc014, 0xc00a, 0x009c, 0x009d, 0x002f, 0x0035, 0xc012, 0x000a} client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, CipherSuites: append(defaultCipherSuites[8:], defaultCipherSuites[:8]...), }, }, // 获取301重定向 CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } return client, path } func (v1 *curlV1) DoRequest(method, url string, body interface{}, header http.Header) (*http.Response, error) { var reqBody io.Reader contentType := "application/json" switch v := body.(type) { case string: reqBody = strings.NewReader(v) contentType = "text/plain" case []byte: reqBody = bytes.NewReader(v) case map[string]string: val := netURL.Values{} for k, v := range v { val.Set(k, v) } reqBody = strings.NewReader(val.Encode()) contentType = "application/x-www-form-urlencoded" default: data, err := json.Marshal(v) if err != nil { return nil, err } reqBody = bytes.NewReader(data) } if header == nil { header = http.Header{} } if header.Get("Content-Type") == "" { header.Set("Content-Type", contentType) } for k, values := range v1.DefaultHeader { if header.Get(k) == "" { for _, v := range values { header.Add(k, v) } } } client, location := v1.NewClient(url) schema := v1.getSchema(url) req, err := http.NewRequest(method, location, reqBody) if err != nil { return nil, err } req.URL.Scheme = schema if schema == "unix" { req.URL.Scheme = "http" } req.URL.Host = v1.getDomain(url) req.Header = header return client.Do(req) } func (v1 *curlV1) DoRequestReturnBytes(method, url string, body interface{}, header http.Header) ([]byte, error) { resp, err := v1.DoRequest(method, url, body, header) if err != nil { return []byte{}, err } defer resp.Body.Close() res, err := ioutil.ReadAll(resp.Body) return res, err } func (v1 *curlV1) Get(url string, body interface{}, header http.Header) ([]byte, error) { return v1.DoRequestReturnBytes(http.MethodGet, url, body, header) } func (v1 *curlV1) Put(url string, body interface{}, header http.Header) ([]byte, error) { return v1.DoRequestReturnBytes(http.MethodPut, url, body, header) } func (v1 *curlV1) Post(url string, body interface{}, header http.Header) ([]byte, error) { return v1.DoRequestReturnBytes(http.MethodPost, url, body, header) } func (v1 *curlV1) Patch(url string, body interface{}, header http.Header) ([]byte, error) { return v1.DoRequestReturnBytes(http.MethodPatch, url, body, header) } func (v1 *curlV1) Delete(url string, body interface{}, header http.Header) ([]byte, error) { return v1.DoRequestReturnBytes(http.MethodDelete, url, body, header) }