httputils.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package httputils
  15. import (
  16. "bytes"
  17. "compress/flate"
  18. "compress/gzip"
  19. "context"
  20. "crypto/tls"
  21. "fmt"
  22. "io"
  23. "io/ioutil"
  24. "net"
  25. "net/http"
  26. "net/http/httputil"
  27. "net/url"
  28. "os"
  29. "strconv"
  30. "strings"
  31. "syscall"
  32. "time"
  33. "github.com/fatih/color"
  34. "moul.io/http2curl/v2"
  35. "yunion.io/x/jsonutils"
  36. "yunion.io/x/pkg/appctx"
  37. "yunion.io/x/pkg/errors"
  38. "yunion.io/x/pkg/gotypes"
  39. "yunion.io/x/pkg/trace"
  40. "yunion.io/x/pkg/utils"
  41. )
  42. type THttpMethod string
  43. const (
  44. USER_AGENT = "yunioncloud-go/201708"
  45. GET = THttpMethod("GET")
  46. HEAD = THttpMethod("HEAD")
  47. POST = THttpMethod("POST")
  48. PUT = THttpMethod("PUT")
  49. PATCH = THttpMethod("PATCH")
  50. DELETE = THttpMethod("DELETE")
  51. OPTION = THttpMethod("OPTION")
  52. ConnectionTimeoutSeconds = 120
  53. IdleConnTimeoutSeconds = 60
  54. TLSHandshakeTimeoutSeconds = 10
  55. ResponseHeaderTimeoutSeconds = 30
  56. )
  57. var (
  58. red = color.New(color.FgRed, color.Bold).PrintlnFunc()
  59. green = color.New(color.FgGreen, color.Bold).PrintlnFunc()
  60. yellow = color.New(color.FgYellow, color.Bold).PrintlnFunc()
  61. cyan = color.New(color.FgHiCyan, color.Bold).PrintlnFunc()
  62. )
  63. type Error struct {
  64. Id string `json:"id,omitempty"`
  65. Fields []interface{} `json:"fields,omitempty"`
  66. }
  67. type JSONClientError struct {
  68. Request struct {
  69. Method string `json:"method,omitempty"`
  70. Url string `json:"url,omitempty"`
  71. Body jsonutils.JSONObject `json:"body,omitempty"`
  72. Headers map[string]string `json:"headers,omitempty"`
  73. } `json:"request,omitempty"`
  74. Code int `json:"code,omitzero"`
  75. Class string `json:"class,omitempty"`
  76. Details string `json:"details,omitempty"`
  77. Data Error `json:"data,omitempty"`
  78. }
  79. type sClient interface {
  80. Do(req *http.Request) (*http.Response, error)
  81. }
  82. // body might have been consumed, so body is provided separately
  83. func newJsonClientErrorFromRequest(req *http.Request, body string) *JSONClientError {
  84. return newJsonClientErrorFromRequest2(req.Method, req.URL.String(), req.Header, body)
  85. }
  86. func newJsonClientErrorFromRequest2(method string, urlStr string, hdrs http.Header, body string) *JSONClientError {
  87. jce := &JSONClientError{}
  88. jce.Request.Method = strings.ToUpper(method)
  89. jce.Request.Url = urlStr
  90. jce.Request.Headers = make(map[string]string)
  91. excludeHdrs := []string{
  92. "Accept",
  93. "Accept-Encoding",
  94. }
  95. authHdrs := []string{
  96. http.CanonicalHeaderKey("authorization"),
  97. http.CanonicalHeaderKey("x-auth-token"),
  98. http.CanonicalHeaderKey("x-subject-token"),
  99. }
  100. const (
  101. MAX_BODY = 128
  102. FIRST_PART = 100
  103. )
  104. switch jce.Request.Method {
  105. case "PUT", "POST", "PATCH":
  106. contType := hdrs.Get(http.CanonicalHeaderKey("content-type"))
  107. if len(body) > MAX_BODY {
  108. jce.Request.Body = jsonutils.NewString(body[:FIRST_PART] + "..." + body[len(body)-MAX_BODY+FIRST_PART+3:])
  109. } else if strings.Contains(contType, "json") {
  110. jce.Request.Body, _ = jsonutils.ParseString(body)
  111. } else if strings.Contains(contType, "xml") ||
  112. strings.Contains(contType, "x-www-form-urlencoded") {
  113. jce.Request.Body = jsonutils.NewString(body)
  114. }
  115. default:
  116. excludeHdrs = append(excludeHdrs, http.CanonicalHeaderKey("content-type"), http.CanonicalHeaderKey("content-length"))
  117. }
  118. for h := range hdrs {
  119. ch := http.CanonicalHeaderKey(h)
  120. if utils.IsInStringArray(ch, excludeHdrs) {
  121. continue
  122. }
  123. if utils.IsInStringArray(ch, authHdrs) {
  124. jce.Request.Headers[ch] = "*"
  125. } else {
  126. jce.Request.Headers[ch] = hdrs.Get(ch)
  127. }
  128. }
  129. return jce
  130. }
  131. type JSONClientErrorMsg struct {
  132. Error *JSONClientError
  133. }
  134. type JsonClient struct {
  135. client sClient
  136. }
  137. type JsonRequest interface {
  138. GetHttpMethod() THttpMethod
  139. GetRequestBody() jsonutils.JSONObject
  140. GetUrl() string
  141. SetHttpMethod(method THttpMethod)
  142. GetHeader() http.Header
  143. SetHeader(header http.Header)
  144. }
  145. type JsonBaseRequest struct {
  146. httpMethod THttpMethod
  147. url string
  148. params interface{}
  149. header http.Header
  150. }
  151. func (req *JsonBaseRequest) GetHttpMethod() THttpMethod {
  152. return req.httpMethod
  153. }
  154. func (req *JsonBaseRequest) GetRequestBody() jsonutils.JSONObject {
  155. if !gotypes.IsNil(req.params) {
  156. return jsonutils.Marshal(req.params)
  157. }
  158. return nil
  159. }
  160. func (req *JsonBaseRequest) GetUrl() string {
  161. return req.url
  162. }
  163. func (req *JsonBaseRequest) SetHttpMethod(method THttpMethod) {
  164. req.httpMethod = method
  165. }
  166. func (req *JsonBaseRequest) GetHeader() http.Header {
  167. return req.header
  168. }
  169. func (req *JsonBaseRequest) SetHeader(header http.Header) {
  170. for k, values := range header {
  171. req.header.Del(k)
  172. for _, v := range values {
  173. req.header.Add(k, v)
  174. }
  175. }
  176. }
  177. func NewJsonRequest(method THttpMethod, url string, params interface{}) *JsonBaseRequest {
  178. return &JsonBaseRequest{
  179. httpMethod: method,
  180. url: url,
  181. params: params,
  182. header: http.Header{"Content-Type": []string{"application/json"}},
  183. }
  184. }
  185. type JsonResponse interface {
  186. ParseErrorFromJsonResponse(statusCode int, status string, body jsonutils.JSONObject) error
  187. }
  188. func (ce *JSONClientError) ParseErrorFromJsonResponse(statusCode int, status string, body jsonutils.JSONObject) error {
  189. body.Unmarshal(ce)
  190. if ce.Code == 0 {
  191. ce.Code = statusCode
  192. }
  193. if len(ce.Class) == 0 {
  194. ce.Class = http.StatusText(statusCode)
  195. }
  196. if len(ce.Details) == 0 {
  197. ce.Details = body.String()
  198. }
  199. return ce
  200. }
  201. func NewJsonClient(client sClient) *JsonClient {
  202. return &JsonClient{client: client}
  203. }
  204. func (e *JSONClientError) Error() string {
  205. if !gotypes.IsNil(e.Request.Body) {
  206. if body, ok := e.Request.Body.(*jsonutils.JSONDict); ok && body.Contains("password") {
  207. body.Set("password", jsonutils.NewString("***"))
  208. e.Request.Body = body
  209. }
  210. }
  211. errMsg := JSONClientErrorMsg{Error: e}
  212. return jsonutils.Marshal(errMsg).String()
  213. }
  214. func (err *JSONClientError) Cause() error {
  215. if len(err.Class) > 0 {
  216. return errors.Error(err.Class)
  217. } else if err.Code >= 500 {
  218. return errors.ErrServer
  219. } else if err.Code >= 400 {
  220. return errors.ErrClient
  221. } else {
  222. return errors.ErrUnclassified
  223. }
  224. }
  225. func ErrorCode(err error) int {
  226. if err == nil {
  227. return 0
  228. }
  229. switch je := err.(type) {
  230. case *JSONClientError:
  231. return je.Code
  232. }
  233. return -1
  234. }
  235. func ErrorMsg(err error) string {
  236. if err == nil {
  237. return ""
  238. }
  239. switch je := err.(type) {
  240. case *JSONClientError:
  241. return je.Details
  242. }
  243. return err.Error()
  244. }
  245. func GetAddrPort(urlStr string) (string, int, error) {
  246. parts, err := url.Parse(urlStr)
  247. if err != nil {
  248. return "", 0, errors.Wrapf(err, "url.Parse %s", urlStr)
  249. }
  250. portStr := parts.Port()
  251. if len(portStr) == 0 {
  252. switch parts.Scheme {
  253. case "http":
  254. return parts.Hostname(), 80, nil
  255. case "https":
  256. return parts.Hostname(), 443, nil
  257. default:
  258. return "", 0, errors.Errorf("Unknown schema %s", parts.Scheme)
  259. }
  260. }
  261. port, err := strconv.Atoi(portStr)
  262. if err != nil {
  263. return "", 0, errors.Wrapf(err, "strconv.Atoi port string %s", portStr)
  264. }
  265. return parts.Hostname(), port, nil
  266. }
  267. func GetTransport(insecure bool) *http.Transport {
  268. return getTransport(insecure, false, 0)
  269. }
  270. func GetAdaptiveTransport(insecure bool) *http.Transport {
  271. return getTransport(insecure, true, 0)
  272. }
  273. func adptiveDial(ctx context.Context, network, addr string) (net.Conn, error) {
  274. conn, err := net.DialTimeout(network, addr, ConnectionTimeoutSeconds*time.Second)
  275. if err != nil {
  276. return nil, err
  277. }
  278. return getConnDelegate(conn, 10*time.Second, 20*time.Second), nil
  279. }
  280. func getTransport(insecure bool, adaptive bool, timeout time.Duration) *http.Transport {
  281. tr := &http.Transport{
  282. Proxy: http.ProxyFromEnvironment,
  283. // 一个空闲连接保持连接的时间
  284. // IdleConnTimeout is the maximum amount of time an idle
  285. // (keep-alive) connection will remain idle before closing
  286. // itself.
  287. // Zero means no limit.
  288. IdleConnTimeout: IdleConnTimeoutSeconds * time.Second,
  289. // 建立TCP连接后,等待TLS握手的超时时间
  290. // TLSHandshakeTimeout specifies the maximum amount of time waiting to
  291. // wait for a TLS handshake. Zero means no timeout.
  292. TLSHandshakeTimeout: TLSHandshakeTimeoutSeconds * time.Second,
  293. // 发送请求后,等待服务端http响应的超时时间
  294. // ResponseHeaderTimeout, if non-zero, specifies the amount of
  295. // time to wait for a server's response headers after fully
  296. // writing the request (including its body, if any). This
  297. // time does not include the time to read the response body.
  298. ResponseHeaderTimeout: ResponseHeaderTimeoutSeconds * time.Second,
  299. // 当请求携带Expect: 100-continue时,等待服务端100响应的超时时间
  300. // ExpectContinueTimeout, if non-zero, specifies the amount of
  301. // time to wait for a server's first response headers after fully
  302. // writing the request headers if the request has an
  303. // "Expect: 100-continue" header. Zero means no timeout and
  304. // causes the body to be sent immediately, without
  305. // waiting for the server to approve.
  306. // This time does not include the time to send the request header.
  307. ExpectContinueTimeout: 5 * time.Second,
  308. TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure},
  309. }
  310. if adaptive {
  311. tr.DialContext = adptiveDial
  312. } else {
  313. tr.IdleConnTimeout = timeout
  314. tr.TLSHandshakeTimeout = timeout
  315. tr.ResponseHeaderTimeout = timeout
  316. tr.DialContext = (&net.Dialer{
  317. // 建立TCP连接超时时间
  318. // Timeout is the maximum amount of time a dial will wait for
  319. // a connect to complete. If Deadline is also set, it may fail
  320. // earlier.
  321. //
  322. // The default is no timeout.
  323. //
  324. // When using TCP and dialing a host name with multiple IP
  325. // addresses, the timeout may be divided between them.
  326. //
  327. // With or without a timeout, the operating system may impose
  328. // its own earlier timeout. For instance, TCP timeouts are
  329. // often around 3 minutes.
  330. Timeout: ConnectionTimeoutSeconds * time.Second,
  331. //
  332. // KeepAlive specifies the interval between keep-alive
  333. // probes for an active network connection.
  334. // If zero, keep-alive probes are sent with a default value
  335. // (currently 15 seconds), if supported by the protocol and operating
  336. // system. Network protocols or operating systems that do
  337. // not support keep-alives ignore this field.
  338. // If negative, keep-alive probes are disabled.
  339. KeepAlive: 5 * time.Second, // send keep-alive probe every 5 seconds
  340. }).DialContext
  341. }
  342. return tr
  343. }
  344. func GetClient(insecure bool, timeout time.Duration) *http.Client {
  345. adaptive := false
  346. if timeout == 0 {
  347. adaptive = true
  348. }
  349. tr := getTransport(insecure, adaptive, timeout)
  350. return &http.Client{
  351. Transport: tr,
  352. // 一个完整http request的超时时间
  353. // Timeout specifies a time limit for requests made by this
  354. // Client. The timeout includes connection time, any
  355. // redirects, and reading the response body. The timer remains
  356. // running after Get, Head, Post, or Do return and will
  357. // interrupt reading of the Response.Body.
  358. //
  359. // A Timeout of zero means no timeout.
  360. //
  361. // The Client cancels requests to the underlying Transport
  362. // as if the Request's Context ended.
  363. //
  364. // For compatibility, the Client will also use the deprecated
  365. // CancelRequest method on Transport if found. New
  366. // RoundTripper implementations should use the Request's Context
  367. // for cancellation instead of implementing CancelRequest.
  368. Timeout: timeout,
  369. }
  370. }
  371. type TransportProxyFunc func(*http.Request) (*url.URL, error)
  372. func SetClientProxyFunc(
  373. client *http.Client,
  374. proxyFunc TransportProxyFunc,
  375. ) bool {
  376. set := false
  377. if transport, ok := client.Transport.(*http.Transport); ok {
  378. transport.Proxy = proxyFunc
  379. set = true
  380. }
  381. return set
  382. }
  383. func GetTimeoutClient(timeout time.Duration) *http.Client {
  384. return GetClient(true, timeout)
  385. }
  386. func GetAdaptiveTimeoutClient() *http.Client {
  387. return GetClient(true, 0)
  388. }
  389. var defaultHttpClient *http.Client
  390. func init() {
  391. defaultHttpClient = GetDefaultClient()
  392. }
  393. func GetDefaultClient() *http.Client {
  394. return GetClient(true, time.Second*15)
  395. }
  396. func getClientErrorClass(err error) error {
  397. cause := errors.Cause(err)
  398. if urlErr, ok := cause.(*url.Error); ok {
  399. if netErr, ok := urlErr.Err.(*net.OpError); ok {
  400. switch t := netErr.Err.(type) {
  401. case *net.DNSError:
  402. return errors.ErrDNS
  403. case *os.SyscallError:
  404. if errno, ok := t.Err.(syscall.Errno); ok {
  405. switch errno {
  406. case syscall.ECONNREFUSED:
  407. return errors.ErrConnectRefused
  408. case syscall.ETIMEDOUT:
  409. return errors.ErrTimeout
  410. }
  411. }
  412. }
  413. }
  414. }
  415. return errors.ErrClient
  416. }
  417. func isHTTPReqErrorRetryable(err error) bool {
  418. if err == nil {
  419. return false
  420. }
  421. switch e := err.(type) {
  422. case *url.Error:
  423. switch e.Err.(type) {
  424. case *net.DNSError, *net.OpError, net.UnknownNetworkError:
  425. return true
  426. }
  427. if strings.Contains(err.Error(), "Connection closed by foreign host") {
  428. return true
  429. } else if strings.Contains(err.Error(), "net/http: TLS handshake timeout") {
  430. // If error is - tlsHandshakeTimeoutError, retry.
  431. return true
  432. } else if strings.Contains(err.Error(), "i/o timeout") {
  433. // If error is - tcp timeoutError, retry.
  434. return true
  435. } else if strings.Contains(err.Error(), "connection timed out") {
  436. // If err is a net.Dial timeout, retry.
  437. return true
  438. } else if strings.Contains(err.Error(), "net/http: HTTP/1.x transport connection broken") {
  439. // If error is transport connection broken, retry.
  440. return true
  441. } else if strings.Contains(err.Error(), "net/http: timeout awaiting response headers") {
  442. // Retry errors due to server not sending the response before timeout
  443. return true
  444. } else if strings.Contains(err.Error(), "dial tcp: lookup") {
  445. return true
  446. }
  447. }
  448. return false
  449. }
  450. func Request(client sClient, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body io.Reader, debug bool) (*http.Response, error) {
  451. return request(client, ctx, method, urlStr, header, body, false, debug)
  452. }
  453. func RequestWithRetry(client sClient, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body io.Reader, debug bool) (*http.Response, error) {
  454. return request(client, ctx, method, urlStr, header, body, true, debug)
  455. }
  456. func request(client sClient, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body io.Reader, retry, debug bool) (*http.Response, error) {
  457. req, resp, err := requestInternal(client, ctx, method, urlStr, header, body, retry, debug)
  458. if err != nil {
  459. var reqBody string
  460. if bodySeeker, ok := body.(io.ReadSeeker); ok {
  461. bodySeeker.Seek(0, io.SeekStart)
  462. reqBodyBytes, _ := io.ReadAll(bodySeeker)
  463. if reqBodyBytes != nil {
  464. reqBody = string(reqBodyBytes)
  465. }
  466. }
  467. if req == nil {
  468. ce := newJsonClientErrorFromRequest2(string(method), urlStr, header, reqBody)
  469. ce.Class = getClientErrorClass(err).Error()
  470. ce.Details = err.Error()
  471. ce.Code = 499
  472. return nil, ce
  473. }
  474. ce := newJsonClientErrorFromRequest(req, reqBody)
  475. ce.Class = getClientErrorClass(err).Error()
  476. ce.Details = err.Error()
  477. ce.Code = 499
  478. return nil, ce
  479. }
  480. return resp, nil
  481. }
  482. func requestInternal(client sClient, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body io.Reader, retry, debug bool) (*http.Request, *http.Response, error) {
  483. if client == nil {
  484. client = defaultHttpClient
  485. }
  486. if header == nil {
  487. header = http.Header{}
  488. }
  489. ctxData := appctx.FetchAppContextData(ctx)
  490. var clientTrace *trace.STrace
  491. if len(ctxData.ServiceName) > 0 {
  492. if !ctxData.Trace.IsZero() {
  493. clientTrace = &ctxData.Trace
  494. }
  495. addr, port, err := GetAddrPort(urlStr)
  496. if err != nil {
  497. return nil, nil, err
  498. }
  499. clientTrace = trace.StartClientTrace(clientTrace, addr, port, ctxData.ServiceName)
  500. clientTrace.AddClientRequestHeader(header)
  501. }
  502. if len(ctxData.RequestId) > 0 {
  503. header.Set("X-Request-Id", ctxData.RequestId)
  504. }
  505. req, err := http.NewRequest(string(method), urlStr, body)
  506. if err != nil {
  507. return nil, nil, err
  508. }
  509. req.Header.Set("User-Agent", USER_AGENT)
  510. req.Header.Set("Accept", "*/*")
  511. req.Header.Set("Accept-Encoding", "*")
  512. if body == nil {
  513. if method != GET && method != HEAD {
  514. req.ContentLength = 0
  515. req.Header.Set("Content-Length", "0")
  516. }
  517. } else {
  518. clen := header.Get("Content-Length")
  519. if len(clen) > 0 {
  520. req.ContentLength, _ = strconv.ParseInt(clen, 10, 64)
  521. }
  522. }
  523. if header != nil {
  524. for k, vs := range header {
  525. for i, v := range vs {
  526. if i == 0 {
  527. req.Header.Set(k, v)
  528. } else {
  529. req.Header.Add(k, v)
  530. }
  531. }
  532. }
  533. }
  534. if debug {
  535. dump, _ := httputil.DumpRequestOut(req, false)
  536. yellow(string(dump))
  537. // 忽略掉上传文件的请求,避免大量日志输出
  538. if header.Get("Content-Type") != "application/octet-stream" {
  539. curlCmd, _ := http2curl.GetCurlCommand(req)
  540. cyan("CURL:", curlCmd, "\n")
  541. }
  542. }
  543. resp, err := func() (*http.Response, error) {
  544. var resp *http.Response
  545. for i := 0; i < 3; i++ {
  546. resp, err = client.Do(req)
  547. if err == nil || !retry || !isHTTPReqErrorRetryable(err) {
  548. return resp, err
  549. }
  550. time.Sleep(time.Second * 5)
  551. }
  552. return resp, err
  553. }()
  554. if err != nil {
  555. red(err.Error())
  556. return req, nil, err
  557. }
  558. encoding := resp.Header.Get("Content-Encoding")
  559. switch encoding {
  560. case "", "identity":
  561. // do nothing
  562. case "gzip":
  563. gzipBody, err := gzip.NewReader(resp.Body)
  564. if err != nil {
  565. return req, nil, errors.Wrap(err, "gzip.NewReader")
  566. }
  567. resp.Body = gzipBody
  568. case "deflate":
  569. resp.Body = flate.NewReader(resp.Body)
  570. default:
  571. return req, nil, errors.Wrapf(errors.ErrNotSupported, "unsupported content-encoding %s", encoding)
  572. }
  573. if clientTrace != nil {
  574. clientTrace.EndClientTraceHeader(resp.Header)
  575. }
  576. return req, resp, nil
  577. }
  578. func JSONRequestWithRetry(client sClient, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body jsonutils.JSONObject, debug bool) (http.Header, jsonutils.JSONObject, error) {
  579. return jsonRequest(client, ctx, method, urlStr, header, body, true, debug)
  580. }
  581. func JSONRequest(client sClient, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body jsonutils.JSONObject, debug bool) (http.Header, jsonutils.JSONObject, error) {
  582. return jsonRequest(client, ctx, method, urlStr, header, body, false, debug)
  583. }
  584. func jsonRequest(client sClient, ctx context.Context, method THttpMethod, urlStr string, header http.Header, body jsonutils.JSONObject, retry, debug bool) (http.Header, jsonutils.JSONObject, error) {
  585. var bodystr string
  586. if !gotypes.IsNil(body) {
  587. bodystr = body.String()
  588. }
  589. jbody := strings.NewReader(bodystr)
  590. if header == nil {
  591. header = http.Header{}
  592. }
  593. header.Set("Content-Length", strconv.FormatInt(int64(len(bodystr)), 10))
  594. header.Set("Content-Type", "application/json")
  595. resp, err := request(client, ctx, method, urlStr, header, jbody, retry, debug)
  596. return ParseJSONResponse(bodystr, resp, err, debug)
  597. }
  598. // closeResponse close non nil response with any response Body.
  599. // convenient wrapper to drain any remaining data on response body.
  600. //
  601. // Subsequently this allows golang http RoundTripper
  602. // to re-use the same connection for future requests.
  603. func CloseResponse(resp *http.Response) {
  604. // Callers should close resp.Body when done reading from it.
  605. // If resp.Body is not closed, the Client's underlying RoundTripper
  606. // (typically Transport) may not be able to re-use a persistent TCP
  607. // connection to the server for a subsequent "keep-alive" request.
  608. if resp != nil && resp.Body != nil {
  609. // Drain any remaining Body and then close the connection.
  610. // Without this closing connection would disallow re-using
  611. // the same connection for future uses.
  612. // - http://stackoverflow.com/a/17961593/4465767
  613. io.Copy(ioutil.Discard, resp.Body)
  614. resp.Body.Close()
  615. }
  616. }
  617. func (client *JsonClient) Send(ctx context.Context, req JsonRequest, response JsonResponse, debug bool) (http.Header, jsonutils.JSONObject, error) {
  618. var bodystr string
  619. body := req.GetRequestBody()
  620. if !gotypes.IsNil(body) {
  621. bodystr = body.String()
  622. }
  623. jbody := strings.NewReader(bodystr)
  624. resp, err := Request(client.client, ctx, req.GetHttpMethod(), req.GetUrl(), req.GetHeader(), jbody, debug)
  625. if err != nil {
  626. return nil, nil, err
  627. }
  628. defer CloseResponse(resp)
  629. if debug {
  630. dump, _ := httputil.DumpResponse(resp, false)
  631. if resp.StatusCode < 300 {
  632. green(string(dump))
  633. } else if resp.StatusCode < 400 {
  634. yellow(string(dump))
  635. } else {
  636. red(string(dump))
  637. }
  638. }
  639. rbody, err := ioutil.ReadAll(resp.Body)
  640. if err != nil {
  641. ce := newJsonClientErrorFromRequest(resp.Request, bodystr)
  642. ce.Code = resp.StatusCode
  643. ce.Class = string(errors.ErrClient)
  644. ce.Details = fmt.Sprintf("Fail to read body: %v", err)
  645. return resp.Header, nil, ce
  646. } else if debug {
  647. fmt.Fprintf(os.Stderr, "Response body: %s\n", string(rbody))
  648. }
  649. rbody = bytes.TrimSpace(rbody)
  650. var jrbody jsonutils.JSONObject = nil
  651. if len(rbody) > 0 && (rbody[0] == '{' || rbody[0] == '[') {
  652. var err error
  653. jrbody, err = jsonutils.Parse(rbody)
  654. if err != nil {
  655. if debug {
  656. fmt.Fprintf(os.Stderr, "parsing json %s failed: %v", string(rbody), err)
  657. }
  658. ce := newJsonClientErrorFromRequest(resp.Request, bodystr)
  659. ce.Code = resp.StatusCode
  660. ce.Class = string(errors.ErrServer)
  661. ce.Details = fmt.Sprintf("jsonutils.Parse(%s) error: %v", string(rbody), err)
  662. return resp.Header, nil, ce
  663. }
  664. }
  665. if resp.StatusCode < 300 {
  666. return resp.Header, jrbody, nil
  667. } else if resp.StatusCode >= 300 && resp.StatusCode < 400 {
  668. ce := JSONClientError{}
  669. ce.Code = resp.StatusCode
  670. ce.Details = resp.Header.Get("Location")
  671. ce.Class = "redirect"
  672. return resp.Header, jrbody, &ce
  673. }
  674. return resp.Header, jrbody, response.ParseErrorFromJsonResponse(resp.StatusCode, resp.Status, jrbody)
  675. }
  676. func IsRedirectError(err error) bool {
  677. ce, ok := err.(*JSONClientError)
  678. if ok && ce.Class == "redirect" {
  679. return true
  680. }
  681. return false
  682. }
  683. func ParseResponse(reqBody string, resp *http.Response, err error, debug bool) (http.Header, []byte, error) {
  684. if err != nil {
  685. return nil, nil, err
  686. }
  687. defer CloseResponse(resp)
  688. if debug {
  689. dump, _ := httputil.DumpResponse(resp, false)
  690. if resp.StatusCode < 300 {
  691. green(string(dump))
  692. } else if resp.StatusCode < 400 {
  693. yellow(string(dump))
  694. } else {
  695. red(string(dump))
  696. }
  697. }
  698. rbody, err := ioutil.ReadAll(resp.Body)
  699. if err != nil {
  700. ce := newJsonClientErrorFromRequest(resp.Request, reqBody)
  701. ce.Code = 499
  702. ce.Details = fmt.Sprintf("Fail to read body: %s", err)
  703. ce.Class = string(errors.ErrClient)
  704. return resp.Header, nil, ce
  705. } else if debug {
  706. fmt.Fprintf(os.Stderr, "Response body: %s\n", string(rbody))
  707. }
  708. if resp.StatusCode < 300 {
  709. return resp.Header, rbody, nil
  710. } else if resp.StatusCode >= 300 && resp.StatusCode < 400 {
  711. ce := newJsonClientErrorFromRequest(resp.Request, reqBody)
  712. ce.Code = resp.StatusCode
  713. ce.Details = resp.Header.Get("Location")
  714. ce.Class = "redirect"
  715. return resp.Header, rbody, ce
  716. } else {
  717. ce := newJsonClientErrorFromRequest(resp.Request, reqBody)
  718. ce.Code = resp.StatusCode
  719. ce.Details = resp.Status
  720. if len(rbody) > 0 {
  721. ce.Details = string(rbody)
  722. }
  723. return nil, nil, ce
  724. }
  725. }
  726. func ParseJSONResponse(reqBody string, resp *http.Response, err error, debug bool) (http.Header, jsonutils.JSONObject, error) {
  727. if err != nil {
  728. return nil, nil, err
  729. }
  730. defer CloseResponse(resp)
  731. if debug {
  732. dump, _ := httputil.DumpResponse(resp, false)
  733. if resp.StatusCode < 300 {
  734. green(string(dump))
  735. } else if resp.StatusCode < 400 {
  736. yellow(string(dump))
  737. } else {
  738. red(string(dump))
  739. }
  740. }
  741. rbody, err := ioutil.ReadAll(resp.Body)
  742. if err != nil {
  743. ce := newJsonClientErrorFromRequest(resp.Request, reqBody)
  744. ce.Code = 499
  745. ce.Class = string(errors.ErrClient)
  746. ce.Details = fmt.Sprintf("Fail to read body: %s", err)
  747. return resp.Header, nil, ce
  748. } else if debug {
  749. fmt.Fprintf(os.Stderr, "Response body: %s\n", string(rbody))
  750. }
  751. rbody = bytes.TrimSpace(rbody)
  752. var jrbody jsonutils.JSONObject = nil
  753. if len(rbody) > 0 && (rbody[0] == '{' || rbody[0] == '[') {
  754. var err error
  755. jrbody, err = jsonutils.Parse(rbody)
  756. if err != nil && debug {
  757. // ignore the error
  758. fmt.Fprintf(os.Stderr, "parsing json failed: %s", err)
  759. }
  760. }
  761. if resp.StatusCode < 300 {
  762. return resp.Header, jrbody, nil
  763. } else if resp.StatusCode >= 300 && resp.StatusCode < 400 {
  764. ce := newJsonClientErrorFromRequest(resp.Request, reqBody)
  765. ce.Code = resp.StatusCode
  766. ce.Details = resp.Header.Get("Location")
  767. ce.Class = "redirect"
  768. return resp.Header, jrbody, ce
  769. } else {
  770. ce := newJsonClientErrorFromRequest(resp.Request, reqBody)
  771. if jrbody == nil {
  772. ce.Code = resp.StatusCode
  773. ce.Details = resp.Status
  774. if len(rbody) > 0 {
  775. ce.Details = string(rbody)
  776. }
  777. return nil, nil, ce
  778. }
  779. err = jrbody.Unmarshal(ce)
  780. if len(ce.Class) > 0 && ce.Code >= 400 && len(ce.Details) > 0 {
  781. return nil, nil, ce
  782. }
  783. jrbody1, err := jrbody.GetMap()
  784. if err != nil {
  785. err = jrbody.Unmarshal(ce)
  786. if err != nil {
  787. ce.Details = err.Error()
  788. }
  789. return nil, nil, ce
  790. }
  791. var jrbody2 jsonutils.JSONObject
  792. if len(jrbody1) > 1 {
  793. jrbody2 = jsonutils.Marshal(jrbody1)
  794. } else {
  795. for _, v := range jrbody1 {
  796. jrbody2 = v
  797. }
  798. }
  799. if jrbody2 != nil {
  800. if ecode, _ := jrbody2.GetString("code"); len(ecode) > 0 {
  801. code, err := strconv.Atoi(ecode)
  802. if err != nil {
  803. ce.Class = ecode
  804. } else {
  805. ce.Code = code
  806. }
  807. }
  808. }
  809. if ce.Code == 0 {
  810. ce.Code = resp.StatusCode
  811. }
  812. if edetail := jsonutils.GetAnyString(jrbody2, []string{"message", "detail", "details", "error_msg"}); len(edetail) > 0 {
  813. ce.Details = edetail
  814. }
  815. if eclass := jsonutils.GetAnyString(jrbody2, []string{"title", "type", "error_code"}); len(eclass) > 0 {
  816. ce.Class = eclass
  817. }
  818. return nil, nil, ce
  819. }
  820. }
  821. func JoinPath(ep string, paths ...string) string {
  822. buf := strings.Builder{}
  823. buf.WriteString(strings.TrimRight(ep, "/"))
  824. for _, path := range paths {
  825. buf.WriteByte('/')
  826. buf.WriteString(strings.Trim(path, "/"))
  827. }
  828. return buf.String()
  829. }