package utils import ( "compress/flate" "compress/gzip" "compress/zlib" "context" "crypto/tls" "fmt" "golang.org/x/net/proxy" "io" "io/ioutil" "net" "net/http" "net/url" "strings" "time" ) // Error Code接口 type IErrorCode interface { // 转换为int ToInt() int // 方便与int比较 Equals(errCode int) bool // 获取错误信息 ErrMsg(args ...interface{}) string } //easyjson:json type Res struct { Head ResHead `json:"head"` Data interface{} `json:"data,omitempty"` } //easyjson:json type ResHead struct { ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg,omitempty"` Detail string `json:"-"` HttpStatus int `json:"-"` } func E2(errDetail interface{}, errCode IErrorCode, args ...interface{}) *Res { errMsg := errCode.ErrMsg(args...) return E(errCode.ToInt(), errMsg, errDetail) } func E(status int, errMsg string, errDetail interface{}) *Res { var msg, detail string if nil != errDetail { detail = strings.TrimSpace(fmt.Sprintf("%s", errDetail)) } msg = strings.TrimSpace(errMsg) return &Res{ Head: ResHead{ ErrCode: status, ErrMsg: msg, Detail: detail, HttpStatus: http.StatusInternalServerError, }, } } func R(data interface{}) *Res { return &Res{ Head: ResHead{ ErrCode: 0, ErrMsg: "", HttpStatus: http.StatusOK, }, Data: data, } } // http请求相关默认配置 const ( DefaultHttpDialTimeout = 20 * time.Second DefaultHttpKeepAlive = 120 * time.Second DefaultHttpMaxIdleConns = 1000 DefaultHttpMaxIdleConnsPerHost = 1000 DefaultHttpIdleConnTimeout = 90 * time.Second DefaultHttpTimeout = 30 * time.Second ) // http请求配置 type RequestPromise struct { headers http.Header encoding Charset timeout time.Duration proxy func(*http.Request) (*url.URL, error) dialContext func(ctx context.Context, network, addr string) (net.Conn, error) client *http.Client isSkipTls bool } // 返回一个http请求配置对象,默认带上压缩头 func NewRequest() *RequestPromise { return (&RequestPromise{}). SetHeader("Accept-Encoding", "gzip, deflate, zlib") } // 返回一个http请求配置对象,默认带上压缩头和Content-Type = application/json; charset=utf-8 func JSONRequest() *RequestPromise { return (&RequestPromise{}). SetHeader("Accept-Encoding", "gzip, deflate, zlib"). SetHeader("Content-Type", "application/json; charset=utf-8") } // 返回一个http请求配置对象,默认带上压缩头和Content-Type = application/xml; charset=utf-8 func XMLRequest() *RequestPromise { return (&RequestPromise{}). SetHeader("Accept-Encoding", "gzip, deflate, zlib"). SetHeader("Content-Type", "application/xml; charset=utf-8") } // 返回一个采用了连接池和Keepalive配置的http client,可以配合RequestPromise的SetClient函数使用 // 默认不使用它,而是每次请求新建连接 func NewPoolingHttpClient() *http.Client { return &http.Client{ Transport: &http.Transport{ DialContext: (&net.Dialer{ Timeout: DefaultHttpDialTimeout, KeepAlive: DefaultHttpKeepAlive, }).DialContext, MaxIdleConns: DefaultHttpMaxIdleConns, MaxIdleConnsPerHost: DefaultHttpMaxIdleConnsPerHost, IdleConnTimeout: DefaultHttpIdleConnTimeout, }, Timeout: DefaultHttpTimeout, // 此处设置小于等于零的值,意为不超时 } } // 设置https忽略本地证书校验 func (v *RequestPromise) SetSkipTls() *RequestPromise { v.isSkipTls = true return v } // 设置http header func (v *RequestPromise) SetHeader(key string, value string) *RequestPromise { if len(strings.TrimSpace(key)) == 0 { return v } key = strings.TrimSpace(key) if nil == v.headers { v.headers = make(http.Header) } v.headers.Set(key, value) return v } // 设置http响应的编码,默认utf8 func (v *RequestPromise) SetEncoding(encoding Charset) *RequestPromise { if encoding == UTF8 { return v } v.encoding = encoding return v } // 设置超时时间,从连接到接收到响应的总时间 // 如果此处不设置则采用http client中设置的超时时间,默认http client超时时间30秒 // 如果此处设置不等于零的值,则覆盖http client中设置的超时时间 // 如果此处设置小于零的值,意为不超时 func (v *RequestPromise) SetTimeout(timeout time.Duration) *RequestPromise { if timeout == 0 { return v } v.timeout = timeout return v } // 设置http或https代理,默认无代理 func (v *RequestPromise) SetHttpProxy(proxyUri string) *RequestPromise { if len(strings.TrimSpace(proxyUri)) == 0 { return v } proxyUri = strings.TrimSpace(proxyUri) uri, err := (&url.URL{}).Parse(proxyUri) if nil != err { return v } v.proxy = http.ProxyURL(uri) return v } // 设置socket5代理,默认无代理 func (v *RequestPromise) SetSocket5Proxy(proxyUri string) *RequestPromise { if len(strings.TrimSpace(proxyUri)) == 0 { return v } proxyUri = strings.TrimSpace(proxyUri) dialer, err := proxy.SOCKS5("tcp", proxyUri, nil, proxy.Direct) if nil != err { return v } v.dialContext = func(_ context.Context, network string, address string) (net.Conn, error) { return dialer.Dial(network, address) } return v } // 设置事先实例化好的http client,默认每次请求会新建一个http client func (v *RequestPromise) SetClient(client *http.Client) *RequestPromise { if nil == client { return v } v.client = client return v } // 发起请求并返回响应内容 // FORM方式提交数据请设置Content-Type=application/x-www-form-urlencoded请求头,且io.Reader传url.Values.Encode得到的字符串的reader func (v *RequestPromise) Call(httpMethod string, targetUri string, data io.Reader) ([]byte, error) { body, _, err := v.CallResponse(httpMethod, targetUri, data) return body, err } // 发起请求并返回响应内容和响应对象引用 func (v *RequestPromise) CallResponse(httpMethod string, targetUri string, data io.Reader) ([]byte, *http.Response, error) { targetUri = strings.TrimSpace(targetUri) if len(targetUri) == 0 { return nil, nil, nil } // http request handle if len(strings.TrimSpace(httpMethod)) == 0 { httpMethod = http.MethodGet } else { httpMethod = strings.ToUpper(strings.TrimSpace(httpMethod)) } req, err := http.NewRequest(httpMethod, targetUri, data) if err != nil { return nil, nil, err } if nil != v.headers { req.Header = v.headers } v.initClient() // send http request & get http response resp, err := v.client.Do(req) if err != nil { return nil, nil, err } var body []byte body, err = v.readResponseBody(resp) return body, resp, err } func (v *RequestPromise) initClient() { // http client handle if nil == v.client { // create new http client instance v.client = &http.Client{Timeout: DefaultHttpTimeout} // default timeout } if v.timeout < 0 { v.timeout = DefaultHttpTimeout // default timeout } if v.timeout > 0 { v.client.Timeout = v.timeout } if v.isSkipTls { if nil == v.client.Transport { v.client.Transport = &http.Transport{} } transport := (v.client.Transport).(*http.Transport) transport.TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, //VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { // return nil //}, } } if nil != v.proxy || nil != v.dialContext { if nil == v.client.Transport { v.client.Transport = &http.Transport{} } transport := (v.client.Transport).(*http.Transport) if nil != v.proxy { transport.Proxy = v.proxy } if nil != v.dialContext { transport.DialContext = v.dialContext } } } func (v *RequestPromise) readResponseBody(resp *http.Response) ([]byte, error) { defer func(body io.ReadCloser) { _ = body.Close() }(resp.Body) var reader io.ReadCloser switch resp.Header.Get("Content-Encoding") { case "gzip": reader, _ = gzip.NewReader(resp.Body) defer func(reader io.Closer) { _ = reader.Close() }(reader) case "deflate": reader = flate.NewReader(resp.Body) defer func(reader io.Closer) { _ = reader.Close() }(reader) case "zlib": reader, _ = zlib.NewReader(resp.Body) defer func(reader io.Closer) { _ = reader.Close() }(reader) default: reader = resp.Body } body, err := ioutil.ReadAll(reader) if err != nil { return nil, err } if v.encoding != "" { body = ConvertToEncodingBytes(body, v.encoding) } else { cType := resp.Header.Get("Content-Type") if cType != "" { cType = strings.ReplaceAll(strings.ToLower(cType), " ", "") if strings.Contains(cType, "charset=gbk") { body = ConvertToEncodingBytes(body, GBK) } else if strings.Contains(cType, "charset=gb18030") { body = ConvertToEncodingBytes(body, GB18030) } } } return body, nil } // 获取客户端IP地址 func GetRemoteIP(req *http.Request) string { remoteAddr := req.RemoteAddr if ip := req.Header.Get("Remote_addr"); ip != "" { remoteAddr = ip } else { remoteAddr, _, _ = net.SplitHostPort(remoteAddr) } if remoteAddr == "::1" { remoteAddr = "127.0.0.1" } return remoteAddr }