github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/http/http.go (about) 1 package http 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "github.com/isyscore/isc-gobase/config" 8 "github.com/isyscore/isc-gobase/logger" 9 gourl "net/url" 10 11 //"github.com/isyscore/isc-gobase/goid" 12 "io" 13 "log" 14 "net" 15 "net/http" 16 "strconv" 17 "strings" 18 "time" 19 ) 20 21 var httpClient = createHTTPClient() 22 23 const ( 24 MaxIdleConns int = 100 25 MaxIdleConnsPerHost int = 100 26 IdleConnTimeout int = 90 27 ContentTypeJson string = "application/json; charset=utf-8" 28 ContentTypeHtml string = "text/html; charset=utf-8" 29 ContentTypeText string = "text/plain; charset=utf-8" 30 ContentTypeCss string = "text/css; charset=utf-8" 31 ContentTypeJavaScript string = "application/x-javascript; charset=utf-8" 32 ContentTypeJpeg string = "image/jpeg" 33 ContentTypePng string = "image/png" 34 ContentTypeGif string = "image/gif" 35 ContentTypeAll string = "*/*" 36 ContentPostForm string = "application/x-www-form-urlencoded" 37 ) 38 39 var NetHttpHooks []GobaseHttpHook 40 41 func init() { 42 NetHttpHooks = []GobaseHttpHook{} 43 } 44 45 type GobaseHttpHook interface { 46 Before(ctx context.Context, req *http.Request) (context.Context, http.Header) 47 After(ctx context.Context, rsp *http.Response, rspCode int, rspData any, err error) 48 } 49 50 func AddHook(httpHook GobaseHttpHook) { 51 NetHttpHooks = append(NetHttpHooks, httpHook) 52 } 53 54 type NetError struct { 55 ErrMsg string 56 } 57 58 func (error *NetError) Error() string { 59 return error.ErrMsg 60 } 61 62 type DataResponse[T any] struct { 63 Code int `json:"code"` 64 Message string `json:"message"` 65 Data T `json:"data"` 66 } 67 68 // createHTTPClient for connection re-use 69 func createHTTPClient() *http.Client { 70 config.LoadConfig() 71 client := &http.Client{} 72 73 // 从配置文件中载入配置 74 loadClientFromConfig(client) 75 76 return client 77 } 78 79 func loadClientFromConfig(client *http.Client) { 80 if config.GetValueString("base.http.timeout") != "" { 81 t, err := time.ParseDuration(config.GetValueString("base.http.timeout")) 82 if err != nil { 83 logger.Warn("读取配置【base.http.timeout】异常", err) 84 } else { 85 client.Timeout = t 86 } 87 } 88 89 transport := &http.Transport{} 90 if config.GetValueString("base.http.transport.tls-handshake-timeout") != "" { 91 t, err := time.ParseDuration(config.GetValueString("base.http.transport.tls-handshake-timeout")) 92 if err != nil { 93 logger.Warn("读取配置【base.http.transport.tls-handshake-timeout】异常", err) 94 } else { 95 transport.TLSHandshakeTimeout = t 96 } 97 } 98 99 if config.GetValueString("base.http.transport.disable-keep-alives") != "" { 100 transport.DisableKeepAlives = config.GetValueBool("base.http.transport.disable-keep-alives") 101 } 102 103 if config.GetValueString("base.http.transport.disable-compression") != "" { 104 transport.DisableCompression = config.GetValueBool("base.http.transport.disable-compression") 105 } 106 107 if config.GetValueString("base.http.transport.max-idle-conns") != "" { 108 transport.MaxIdleConns = config.GetValueInt("base.http.transport.max-idle-conns") 109 } 110 111 if config.GetValueString("base.http.transport.max-idle-conns-per-host") != "" { 112 transport.MaxIdleConnsPerHost = config.GetValueInt("base.http.transport.max-idle-conns-per-host") 113 } 114 115 if config.GetValueString("base.http.transport.max-conns-per-host") != "" { 116 transport.MaxConnsPerHost = config.GetValueInt("base.http.transport.max-conns-per-host") 117 } 118 119 if config.GetValueString("base.http.transport.idle-conn-timeout") != "" { 120 t, err := time.ParseDuration(config.GetValueString("base.http.transport.idle-conn-timeout")) 121 if err != nil { 122 logger.Warn("读取配置【base.http.transport.idle-conn-timeout】异常", err) 123 } else { 124 transport.IdleConnTimeout = t 125 } 126 } 127 128 if config.GetValueString("base.http.transport.response-header-timeout") != "" { 129 t, err := time.ParseDuration(config.GetValueString("base.http.transport.response-header-timeout")) 130 if err != nil { 131 logger.Warn("读取配置【base.http.transport.response-header-timeout】异常", err) 132 } else { 133 transport.ResponseHeaderTimeout = t 134 } 135 } 136 137 if config.GetValueString("base.http.transport.expect-continue-timeout") != "" { 138 t, err := time.ParseDuration(config.GetValueString("base.http.transport.expect-continue-timeout")) 139 if err != nil { 140 logger.Warn("读取配置【base.http.transport.expect-continue-timeout】异常", err) 141 } else { 142 transport.ExpectContinueTimeout = t 143 } 144 } 145 146 if config.GetValueString("base.http.transport.max-response-header-bytes") != "" { 147 transport.MaxResponseHeaderBytes = config.GetValueInt64("base.http.transport.max-response-header-bytes") 148 } 149 150 if config.GetValueString("base.http.transport.write-buffer-size") != "" { 151 transport.WriteBufferSize = config.GetValueInt("base.http.transport.write-buffer-size") 152 } 153 154 if config.GetValueString("base.http.transport.read-buffer-size") != "" { 155 transport.ReadBufferSize = config.GetValueInt("base.http.transport.read-buffer-size") 156 } 157 158 if config.GetValueString("base.http.transport.force-attempt-HTTP2") != "" { 159 transport.ForceAttemptHTTP2 = config.GetValueBool("base.http.transport.force-attempt-HTTP2") 160 } 161 162 transport.DialContext = loadConfigOfDialContext() 163 client.Transport = transport 164 } 165 166 func loadConfigOfDialContext() func(ctx context.Context, network, addr string) (net.Conn, error) { 167 dialer := &net.Dialer{} 168 if config.GetValueString("base.http.transport.dial-context.timeout") != "" { 169 t, err := time.ParseDuration(config.GetValueString("base.http.transport.dial-context.timeout")) 170 if err != nil { 171 logger.Warn("读取配置【base.http.transport.dial-context.timeout】异常", err) 172 } else { 173 dialer.Timeout = t 174 } 175 } 176 177 if config.GetValueString("base.http.transport.dial-context.keep-alive") != "" { 178 t, err := time.ParseDuration(config.GetValueString("base.http.transport.dial-context.keep-alive")) 179 if err != nil { 180 logger.Warn("读取配置【base.http.transport.dial-context.keep-alive】异常", err) 181 } else { 182 dialer.KeepAlive = t 183 } 184 } 185 return dialer.DialContext 186 } 187 188 func SetHttpClient(httpClientOuter *http.Client) { 189 httpClient = httpClientOuter 190 } 191 192 func GetClient() *http.Client { 193 return httpClient 194 } 195 196 func Do(httpRequest *http.Request) (int, http.Header, any, error) { 197 ctx := context.Background() 198 for _, hook := range NetHttpHooks { 199 _ctx, httpHeader := hook.Before(ctx, httpRequest) 200 httpRequest.Header = httpHeader 201 ctx = _ctx 202 } 203 204 resp, err := httpClient.Do(httpRequest) 205 rspCode, rspHead, rspData, err := doParseResponse(resp, err) 206 for _, hook := range NetHttpHooks { 207 hook.After(ctx, resp, rspCode, rspData, err) 208 } 209 return rspCode, rspHead, rspData, err 210 } 211 212 // ------------------ get ------------------ 213 214 func GetSimple(url string) (int, http.Header, any, error) { 215 return Get(url, nil, nil) 216 } 217 218 func GetSimpleOfStandard(url string) (int, http.Header, any, error) { 219 return GetOfStandard(url, nil, nil) 220 } 221 222 func Get(url string, header http.Header, parameterMap map[string]string) (int, http.Header, any, error) { 223 httpRequest, err := http.NewRequest("GET", UrlWithParameter(url, parameterMap), nil) 224 if err != nil { 225 log.Printf("NewRequest error(%v)\n", err) 226 return -1, nil, nil, err 227 } 228 229 if header != nil { 230 httpRequest.Header = header 231 } 232 233 return call(httpRequest, url) 234 } 235 236 func GetOfStandard(url string, header http.Header, parameterMap map[string]string) (int, http.Header, any, error) { 237 httpRequest, err := http.NewRequest("GET", UrlWithParameter(url, parameterMap), nil) 238 if err != nil { 239 log.Printf("NewRequest error(%v)\n", err) 240 return -1, nil, nil, err 241 } 242 243 if header != nil { 244 httpRequest.Header = header 245 } 246 247 return callToStandard(httpRequest, url) 248 } 249 250 // ------------------ head ------------------ 251 252 func HeadSimple(url string) error { 253 return Head(url, nil, nil) 254 } 255 256 func Head(url string, header http.Header, parameterMap map[string]string) error { 257 httpRequest, err := http.NewRequest("GET", UrlWithParameter(url, parameterMap), nil) 258 if err != nil { 259 log.Printf("NewRequest error(%v)\n", err) 260 return err 261 } 262 263 if header != nil { 264 httpRequest.Header = header 265 } 266 267 return callIgnoreReturn(httpRequest, url) 268 } 269 270 // ------------------ post ------------------ 271 272 func PostSimple(url string, body any) (int, http.Header, any, error) { 273 return Post(url, nil, nil, body) 274 } 275 276 func PostSimpleOfStandard(url string, body any) (int, http.Header, any, error) { 277 return PostOfStandard(url, nil, nil, body) 278 } 279 280 func Post(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) { 281 bytes, _ := json.Marshal(body) 282 payload := strings.NewReader(string(bytes)) 283 httpRequest, err := http.NewRequest("POST", UrlWithParameter(url, parameterMap), payload) 284 if err != nil { 285 log.Printf("NewRequest error(%v)\n", err) 286 return -1, nil, nil, err 287 } 288 289 if header != nil { 290 httpRequest.Header = header 291 } 292 httpRequest.Header.Add("Content-Type", ContentTypeJson) 293 return call(httpRequest, url) 294 } 295 296 func PostOfStandard(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) { 297 bytes, _ := json.Marshal(body) 298 payload := strings.NewReader(string(bytes)) 299 httpRequest, err := http.NewRequest("POST", UrlWithParameter(url, parameterMap), payload) 300 if err != nil { 301 log.Printf("NewRequest error(%v)\n", err) 302 return -1, nil, nil, err 303 } 304 305 if header != nil { 306 httpRequest.Header = header 307 } 308 httpRequest.Header.Add("Content-Type", ContentTypeJson) 309 return callToStandard(httpRequest, url) 310 } 311 312 func PostForm(url string, header http.Header, parameterMap map[string]any) (int, http.Header, any, error) { 313 // resolve parameterMap 314 var httpRequest http.Request 315 _ = httpRequest.ParseForm() 316 if parameterMap != nil { 317 for k, v := range parameterMap { 318 httpRequest.Form.Add(k, fmt.Sprintf("%v", v)) 319 } 320 } 321 body := strings.NewReader(httpRequest.Form.Encode()) 322 // 简单封装一下 323 httpReq, err := http.NewRequest("POST", url, body) 324 if err != nil { 325 return 0, nil, nil, err 326 } 327 // resolve header 328 if header != nil { 329 httpReq.Header = header 330 } 331 httpReq.Header.Set("Content-Type", ContentPostForm) 332 333 ctx := context.Background() 334 for _, hook := range NetHttpHooks { 335 _ctx, httpHeader := hook.Before(ctx, httpReq) 336 httpReq.Header = httpHeader 337 ctx = _ctx 338 } 339 340 resp, err := httpClient.Do(httpReq) 341 rspCode, rspHead, rspData, err := doParseResponse(resp, err) 342 for _, hook := range NetHttpHooks { 343 hook.After(ctx, resp, rspCode, rspData, err) 344 } 345 346 return rspCode, rspHead, rspData, err 347 } 348 349 // ------------------ put ------------------ 350 351 func PutSimple(url string, body any) (int, http.Header, any, error) { 352 return Put(url, nil, nil, body) 353 } 354 355 func PutSimpleOfStandard(url string, body any) (int, http.Header, any, error) { 356 return PutOfStandard(url, nil, nil, body) 357 } 358 359 func Put(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) { 360 bytes, _ := json.Marshal(body) 361 payload := strings.NewReader(string(bytes)) 362 httpRequest, err := http.NewRequest("PUT", UrlWithParameter(url, parameterMap), payload) 363 if err != nil { 364 log.Printf("NewRequest error(%v)\n", err) 365 return -1, nil, nil, err 366 } 367 368 if header != nil { 369 httpRequest.Header = header 370 } 371 httpRequest.Header.Add("Content-Type", ContentTypeJson) 372 return call(httpRequest, url) 373 } 374 375 func PutOfStandard(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) { 376 bytes, _ := json.Marshal(body) 377 payload := strings.NewReader(string(bytes)) 378 httpRequest, err := http.NewRequest("PUT", UrlWithParameter(url, parameterMap), payload) 379 if err != nil { 380 log.Printf("NewRequest error(%v)\n", err) 381 return -1, nil, nil, err 382 } 383 384 if header != nil { 385 httpRequest.Header = header 386 } 387 httpRequest.Header.Add("Content-Type", ContentTypeJson) 388 return callToStandard(httpRequest, url) 389 } 390 391 // ------------------ delete ------------------ 392 393 func DeleteSimple(url string) (int, http.Header, any, error) { 394 return Get(url, nil, nil) 395 } 396 397 func DeleteSimpleOfStandard(url string) (int, http.Header, any, error) { 398 return GetOfStandard(url, nil, nil) 399 } 400 401 func Delete(url string, header http.Header, parameterMap map[string]string) (int, http.Header, any, error) { 402 httpRequest, err := http.NewRequest("DELETE", UrlWithParameter(url, parameterMap), nil) 403 if err != nil { 404 log.Printf("NewRequest error(%v)\n", err) 405 return -1, nil, nil, err 406 } 407 408 if header != nil { 409 httpRequest.Header = header 410 } 411 412 return call(httpRequest, url) 413 } 414 415 func DeleteOfStandard(url string, header http.Header, parameterMap map[string]string) (int, http.Header, any, error) { 416 httpRequest, err := http.NewRequest("DELETE", UrlWithParameter(url, parameterMap), nil) 417 if err != nil { 418 log.Printf("NewRequest error(%v)\n", err) 419 return -1, nil, nil, err 420 } 421 422 if header != nil { 423 httpRequest.Header = header 424 } 425 426 return callToStandard(httpRequest, url) 427 } 428 429 // ------------------ patch ------------------ 430 431 func PatchSimple(url string, body any) (int, http.Header, any, error) { 432 return Post(url, nil, nil, body) 433 } 434 435 func PatchSimpleOfStandard(url string, body any) (int, http.Header, any, error) { 436 return PostOfStandard(url, nil, nil, body) 437 } 438 439 func Patch(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) { 440 bytes, _ := json.Marshal(body) 441 payload := strings.NewReader(string(bytes)) 442 httpRequest, err := http.NewRequest("PATCH", UrlWithParameter(url, parameterMap), payload) 443 if err != nil { 444 log.Printf("NewRequest error(%v)\n", err) 445 return -1, nil, nil, err 446 } 447 448 if header != nil { 449 httpRequest.Header = header 450 } 451 httpRequest.Header.Add("Content-Type", ContentTypeJson) 452 return call(httpRequest, url) 453 } 454 455 func PatchOfStandard(url string, header http.Header, parameterMap map[string]string, body any) (int, http.Header, any, error) { 456 bytes, _ := json.Marshal(body) 457 payload := strings.NewReader(string(bytes)) 458 httpRequest, err := http.NewRequest("PATCH", UrlWithParameter(url, parameterMap), payload) 459 if err != nil { 460 log.Printf("NewRequest error(%v)\n", err) 461 return -1, nil, nil, err 462 } 463 464 if header != nil { 465 httpRequest.Header = header 466 } 467 httpRequest.Header.Add("Content-Type", ContentTypeJson) 468 return callToStandard(httpRequest, url) 469 } 470 471 func call(httpRequest *http.Request, url string) (int, http.Header, any, error) { 472 ctx := context.Background() 473 474 for _, hook := range NetHttpHooks { 475 _ctx, httpHeader := hook.Before(ctx, httpRequest) 476 httpRequest.Header = httpHeader 477 ctx = _ctx 478 } 479 480 httpResponse, err := httpClient.Do(httpRequest) 481 rspCode, rspHead, rspData, err := doParseResponse(httpResponse, err) 482 483 for _, hook := range NetHttpHooks { 484 hook.After(ctx, httpResponse, rspCode, rspData, err) 485 } 486 return rspCode, rspHead, rspData, err 487 } 488 489 func doParseResponse(httpResponse *http.Response, err error) (int, http.Header, any, error) { 490 if err != nil && httpResponse == nil { 491 log.Printf("Error sending request to API endpoint. %+v", err) 492 return -1, nil, nil, &NetError{ErrMsg: "Error sending request, err" + err.Error()} 493 } else { 494 if httpResponse == nil { 495 log.Printf("httpResponse is nil\n") 496 return -1, nil, nil, nil 497 } 498 defer func(Body io.ReadCloser) { 499 err := Body.Close() 500 if err != nil { 501 log.Printf("Body close error(%v)", err) 502 } 503 }(httpResponse.Body) 504 505 code := httpResponse.StatusCode 506 headers := httpResponse.Header 507 if code != http.StatusOK { 508 body, _ := io.ReadAll(httpResponse.Body) 509 return code, headers, nil, &NetError{ErrMsg: "remote error, url: code " + strconv.Itoa(code) + ", message: " + string(body)} 510 } 511 512 // We have seen inconsistencies even when we get 200 OK response 513 body, err := io.ReadAll(httpResponse.Body) 514 if err != nil { 515 log.Printf("Couldn't parse response body(%v)", err) 516 return code, headers, nil, &NetError{ErrMsg: "Couldn't parse response body, err: " + err.Error()} 517 } 518 519 return code, headers, body, nil 520 } 521 } 522 523 // ------------------ trace ------------------ 524 // ------------------ options ------------------ 525 // 暂时先不处理 526 527 func callIgnoreReturn(httpRequest *http.Request, url string) error { 528 ctx := context.Background() 529 530 for _, hook := range NetHttpHooks { 531 _ctx, httpHeader := hook.Before(ctx, httpRequest) 532 httpRequest.Header = httpHeader 533 ctx = _ctx 534 } 535 536 httpResponse, err := httpClient.Do(httpRequest) 537 rspCode, _, rspData, err := doParseResponse(httpResponse, err) 538 539 for _, hook := range NetHttpHooks { 540 hook.After(ctx, httpResponse, rspCode, rspData, err) 541 } 542 return err 543 } 544 545 func callToStandard(httpRequest *http.Request, url string) (int, http.Header, any, error) { 546 return parseStandard(call(httpRequest, url)) 547 } 548 549 func parseStandard(statusCode int, headers http.Header, responseResult any, errs error) (int, http.Header, any, error) { 550 if errs != nil { 551 return statusCode, headers, nil, errs 552 } 553 var standRsp DataResponse[any] 554 err := json.Unmarshal(responseResult.([]byte), &standRsp) 555 if err != nil { 556 return statusCode, headers, nil, err 557 } 558 559 // 判断业务的失败信息 560 if standRsp.Code != 0 && standRsp.Code != 200 { 561 return statusCode, headers, nil, &NetError{ErrMsg: fmt.Sprintf("remote err, bizCode=%d, message=%s", standRsp.Code, standRsp.Message)} 562 } 563 564 return statusCode, headers, standRsp.Data, nil 565 } 566 567 func UrlWithParameter(url string, parameterMap map[string]string) string { 568 goUrl, err := gourl.Parse(url) 569 if err != nil { 570 //Here, in order to be compatible with the old logic, if parsing fails, it will call the old method. 571 return oldUrlWithParameter(url, parameterMap) 572 } 573 queryValue := goUrl.Query() 574 for key, value := range parameterMap { 575 queryValue.Set(key, value) 576 } 577 goUrl.RawQuery = queryValue.Encode() 578 return goUrl.String() 579 } 580 581 // Deprecated 582 func oldUrlWithParameter(url string, parameterMap map[string]string) string { 583 if parameterMap == nil || len(parameterMap) == 0 { 584 return url 585 } 586 587 url += "?" 588 589 var parameters []string 590 for key, value := range parameterMap { 591 parameters = append(parameters, key+"="+value) 592 } 593 594 return url + strings.Join(parameters, "&") 595 }