http_client.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. package datacenter
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/md5"
  6. "encoding/json"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "sort"
  12. "strings"
  13. "time"
  14. "go-common/library/log"
  15. pkgerr "github.com/pkg/errors"
  16. "go-common/library/stat"
  17. "strconv"
  18. )
  19. /*
  20. 访问数据平台的http client,处理了签名、接口监控等
  21. */
  22. //ClientConfig client config
  23. type ClientConfig struct {
  24. Key string
  25. Secret string
  26. Dial time.Duration
  27. Timeout time.Duration
  28. KeepAlive time.Duration
  29. }
  30. //New new client
  31. func New(c *ClientConfig) *HttpClient {
  32. return &HttpClient{
  33. client: &http.Client{},
  34. conf: c,
  35. }
  36. }
  37. //HttpClient http client
  38. type HttpClient struct {
  39. client *http.Client
  40. conf *ClientConfig
  41. Debug bool
  42. }
  43. //Response response
  44. type Response struct {
  45. Code int `json:"code"`
  46. Msg string `json:"msg"`
  47. Result interface{} `json:"result"`
  48. }
  49. const (
  50. keyAppKey = "appKey"
  51. keyAppID = "apiId"
  52. keyTimeStamp = "timestamp"
  53. keySign = "sign"
  54. keySignMethod = "signMethod"
  55. keyVersion = "version"
  56. //TimeStampFormat time format in second
  57. TimeStampFormat = "2006-01-02 15:04:05"
  58. )
  59. var (
  60. clientStats = stat.HTTPClient
  61. )
  62. // Get issues a GET to the specified URL.
  63. func (client *HttpClient) Get(c context.Context, uri string, params url.Values, res interface{}) (err error) {
  64. req, err := client.NewRequest(http.MethodGet, uri, params)
  65. if err != nil {
  66. return
  67. }
  68. return client.Do(c, req, res)
  69. }
  70. // NewRequest new http request with method, uri, ip, values and headers.
  71. // TODO(zhoujiahui): param realIP should be removed later.
  72. func (client *HttpClient) NewRequest(method, uri string, params url.Values) (req *http.Request, err error) {
  73. signStr, err := client.sign(params)
  74. if err != nil {
  75. err = pkgerr.Wrapf(err, "uri:%s,params:%v", uri, params)
  76. return
  77. }
  78. params.Add(keySign, signStr)
  79. enc := params.Encode()
  80. ru := uri
  81. if enc != "" {
  82. ru = uri + "?" + enc
  83. }
  84. if method == http.MethodGet {
  85. req, err = http.NewRequest(http.MethodGet, ru, nil)
  86. } else {
  87. req, err = http.NewRequest(http.MethodPost, uri, strings.NewReader(enc))
  88. }
  89. if err != nil {
  90. err = pkgerr.Wrapf(err, "method:%s,uri:%s", method, ru)
  91. return
  92. }
  93. const (
  94. _contentType = "Content-Type"
  95. _urlencoded = "application/x-www-form-urlencoded"
  96. )
  97. if method == http.MethodPost {
  98. req.Header.Set(_contentType, _urlencoded)
  99. }
  100. return
  101. }
  102. // Do sends an HTTP request and returns an HTTP json response.
  103. func (client *HttpClient) Do(c context.Context, req *http.Request, res interface{}, v ...string) (err error) {
  104. var bs []byte
  105. if bs, err = client.Raw(c, req, v...); err != nil {
  106. return
  107. }
  108. if res != nil {
  109. if err = json.Unmarshal(bs, res); err != nil {
  110. err = pkgerr.Wrapf(err, "host:%s, url:%s, response:%s", req.URL.Host, realURL(req), string(bs))
  111. }
  112. }
  113. return
  114. }
  115. //Raw get from url
  116. func (client *HttpClient) Raw(c context.Context, req *http.Request, v ...string) (bs []byte, err error) {
  117. var resp *http.Response
  118. var uri = fmt.Sprintf("%s://%s%s", req.URL.Scheme, req.Host, req.URL.Path)
  119. var now = time.Now()
  120. var code string
  121. defer func() {
  122. clientStats.Timing(uri, int64(time.Since(now)/time.Millisecond))
  123. if code != "" {
  124. clientStats.Incr(uri, code)
  125. }
  126. }()
  127. req = req.WithContext(c)
  128. if resp, err = client.client.Do(req); err != nil {
  129. err = pkgerr.Wrapf(err, "host:%s, url:%s", req.URL.Host, realURL(req))
  130. code = "failed"
  131. return
  132. }
  133. defer resp.Body.Close()
  134. if resp.StatusCode >= http.StatusBadRequest {
  135. err = pkgerr.Errorf("incorrect http status:%d host:%s, url:%s", resp.StatusCode, req.URL.Host, realURL(req))
  136. code = strconv.Itoa(resp.StatusCode)
  137. return
  138. }
  139. if bs, err = readAll(resp.Body, 16*1024); err != nil {
  140. err = pkgerr.Wrapf(err, "host:%s, url:%s", req.URL.Host, realURL(req))
  141. return
  142. }
  143. if client.Debug {
  144. log.Info("reqeust: host:%s, url:%s, response body:%s", req.URL.Host, realURL(req), string(bs))
  145. }
  146. return
  147. }
  148. // sign calc appkey and appsecret sign.
  149. // see http://info.bilibili.co/pages/viewpage.action?pageId=5410881#id-%E6%95%B0%E6%8D%AE%E7%9B%98%EF%BC%8D%E5%AE%89%E5%85%A8%E8%AE%A4%E8%AF%81-%E4%BA%8C%E7%AD%BE%E5%90%8D%E7%AE%97%E6%B3%95
  150. func (client *HttpClient) sign(params url.Values) (sign string, err error) {
  151. key := client.conf.Key
  152. secret := client.conf.Secret
  153. if params == nil {
  154. params = url.Values{}
  155. }
  156. params.Set(keyAppKey, key)
  157. params.Set(keyVersion, "1.0")
  158. if params.Get(keyTimeStamp) == "" {
  159. params.Set(keyTimeStamp, time.Now().Format(TimeStampFormat))
  160. }
  161. params.Set(keySignMethod, "md5")
  162. var needSignParams = url.Values{}
  163. needSignParams.Add(keyAppKey, key)
  164. needSignParams.Add(keyTimeStamp, params.Get(keyTimeStamp))
  165. needSignParams.Add(keyVersion, params.Get(keyVersion))
  166. //tmp := params.Encode()
  167. var valueMap = map[string][]string(needSignParams)
  168. var buf bytes.Buffer
  169. // 开头与结尾加secret
  170. buf.Write([]byte(secret))
  171. keys := make([]string, 0, len(valueMap))
  172. for k := range valueMap {
  173. keys = append(keys, k)
  174. }
  175. sort.Strings(keys)
  176. for _, k := range keys {
  177. vs := valueMap[k]
  178. prefix := k
  179. buf.WriteString(prefix)
  180. for _, v := range vs {
  181. buf.WriteString(v)
  182. break
  183. }
  184. }
  185. buf.Write([]byte(secret))
  186. var md5 = md5.New()
  187. md5.Write(buf.Bytes())
  188. sign = fmt.Sprintf("%X", md5.Sum(nil))
  189. return
  190. }
  191. // readAll reads from r until an error or EOF and returns the data it read
  192. // from the internal buffer allocated with a specified capacity.
  193. func readAll(r io.Reader, capacity int64) (b []byte, err error) {
  194. buf := bytes.NewBuffer(make([]byte, 0, capacity))
  195. // If the buffer overflows, we will get bytes.ErrTooLarge.
  196. // Return that as an error. Any other panic remains.
  197. defer func() {
  198. e := recover()
  199. if e == nil {
  200. return
  201. }
  202. if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
  203. err = panicErr
  204. } else {
  205. panic(e)
  206. }
  207. }()
  208. _, err = buf.ReadFrom(r)
  209. return buf.Bytes(), err
  210. }
  211. // realUrl return url with http://host/params.
  212. func realURL(req *http.Request) string {
  213. if req.Method == http.MethodGet {
  214. return req.URL.String()
  215. } else if req.Method == http.MethodPost {
  216. ru := req.URL.Path
  217. if req.Body != nil {
  218. rd, ok := req.Body.(io.Reader)
  219. if ok {
  220. buf := bytes.NewBuffer([]byte{})
  221. buf.ReadFrom(rd)
  222. ru = ru + "?" + buf.String()
  223. }
  224. }
  225. return ru
  226. }
  227. return req.URL.Path
  228. }
  229. // SetTransport set client transport
  230. func (client *HttpClient) SetTransport(t http.RoundTripper) {
  231. client.client.Transport = t
  232. }