github.com/aldelo/common@v1.5.1/rest/rest.go (about) 1 package rest 2 3 /* 4 * Copyright 2020-2023 Aldelo, LP 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 import ( 20 "bytes" 21 "crypto/tls" 22 "errors" 23 "github.com/aldelo/common/tlsconfig" 24 "google.golang.org/protobuf/proto" 25 "io/ioutil" 26 "net/http" 27 "strconv" 28 "strings" 29 ) 30 31 // server ca pems stores list of self-signed CAs for client tls config 32 var serverCaPems []string 33 34 // client tls config stores the current client tls root CA config object 35 var clientTlsConfig *tls.Config 36 37 // AppendServerCAPemFiles adds self-signed server ca pems to local cache, 38 // and then recreates the clientTlsConfig object based on the new list of CAs 39 func AppendServerCAPemFiles(caPemFilePath ...string) error { 40 if len(caPemFilePath) > 0 { 41 serverCaPems = append(serverCaPems, caPemFilePath...) 42 return newClientTlsCAsConfig() 43 } else { 44 return nil 45 } 46 } 47 48 // ResetServerCAPemFiles first clears serverCaPems cache, 49 // then adds self-signed server ca pems to local cache, 50 // then recreates the clientTlsConfig object based on the new list of CAs 51 func ResetServerCAPemFiles(caPemFilePath ...string) error { 52 serverCaPems = []string{} 53 clientTlsConfig = nil 54 55 if len(caPemFilePath) > 0 { 56 serverCaPems = append(serverCaPems, caPemFilePath...) 57 return newClientTlsCAsConfig() 58 } else { 59 return nil 60 } 61 } 62 63 // newClientTlsCAsConfig creates new ClientTlsConfig object for the serverCaPems in cache 64 func newClientTlsCAsConfig() error { 65 if len(serverCaPems) == 0 { 66 clientTlsConfig = nil 67 return nil 68 } 69 70 t := &tlsconfig.TlsConfig{} 71 72 if c, e := t.GetClientTlsConfig(serverCaPems, "", ""); e != nil { 73 return e 74 } else { 75 clientTlsConfig = c 76 return nil 77 } 78 } 79 80 // HeaderKeyValue is struct used for containing http header element key value pair 81 type HeaderKeyValue struct { 82 Key string 83 Value string 84 } 85 86 // GET sends url get request to host and retrieve the body response in string 87 func GET(url string, headers []*HeaderKeyValue) (statusCode int, body string, err error) { 88 // create http client 89 var client *http.Client 90 91 if clientTlsConfig == nil { 92 client = &http.Client{} 93 } else { 94 tr := &http.Transport{ 95 TLSClientConfig: clientTlsConfig, 96 } 97 98 client = &http.Client{ 99 Transport: tr, 100 } 101 } 102 103 // create http request from client 104 var req *http.Request 105 106 if req, err = http.NewRequest("GET", url, nil); err != nil { 107 return 0, "", errors.New("Create New Http GET Request Failed: " + err.Error()) 108 } 109 110 // add headers to request if any 111 if len(headers) > 0 { 112 for _, v := range headers { 113 req.Header.Add(v.Key, v.Value) 114 } 115 } 116 117 // execute http request and assign response 118 var resp *http.Response 119 120 if resp, err = client.Do(req); err != nil { 121 return 500, "", errors.New("[500 - Http Get Error] " + err.Error()) 122 } 123 124 // evaluate response 125 statusCode = resp.StatusCode 126 127 var respBytes []byte 128 129 respBytes, err = ioutil.ReadAll(resp.Body) 130 _ = resp.Body.Close() 131 resp.Close = true 132 133 // clean up stale connections 134 client.CloseIdleConnections() 135 136 if err != nil && statusCode == 400 { 137 return statusCode, "", err 138 } 139 140 if statusCode != 200 { 141 return statusCode, "", errors.New("[" + strconv.Itoa(statusCode) + " - Get Resp] " + string(respBytes)) 142 } 143 144 // success 145 return statusCode, string(respBytes), nil 146 } 147 148 // POST sends url post request to host and retrieve the body response in string 149 // 150 // Default Header = Content-Type: application/x-www-form-urlencoded 151 // 152 // JSON Content-Type Header: 153 // 154 // Content-Type: application/json 155 func POST(url string, headers []*HeaderKeyValue, requestBody string) (statusCode int, responseBody string, err error) { 156 // create http client 157 var client *http.Client 158 159 if clientTlsConfig == nil { 160 client = &http.Client{} 161 } else { 162 tr := &http.Transport{ 163 TLSClientConfig: clientTlsConfig, 164 } 165 166 client = &http.Client{ 167 Transport: tr, 168 } 169 } 170 171 // create http request from client 172 var req *http.Request 173 174 if req, err = http.NewRequest("POST", url, bytes.NewBuffer([]byte(requestBody))); err != nil { 175 return 0, "", errors.New("Create New Http Post Request Failed: " + err.Error()) 176 } 177 178 // add headers to request if any 179 contentTypeConfigured := false 180 181 if len(headers) > 0 { 182 for _, v := range headers { 183 req.Header.Add(v.Key, v.Value) 184 185 if strings.ToUpper(v.Key) == "CONTENT-TYPE" { 186 contentTypeConfigured = true 187 } 188 } 189 } 190 191 if !contentTypeConfigured { 192 req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 193 } 194 195 // execute http request and assign response 196 var resp *http.Response 197 198 if resp, err = client.Do(req); err != nil { 199 return 500, "", errors.New("[500 - Http Post Error] " + err.Error()) 200 } 201 202 // evaluate response 203 statusCode = resp.StatusCode 204 205 var respBytes []byte 206 207 respBytes, err = ioutil.ReadAll(resp.Body) 208 _ = resp.Body.Close() 209 resp.Close = true 210 211 // clean up stale connections 212 client.CloseIdleConnections() 213 214 if err != nil && statusCode == 400 { 215 return statusCode, "", err 216 } 217 218 if statusCode != 200 { 219 return statusCode, "", errors.New("[" + strconv.Itoa(statusCode) + " - Post Resp] " + string(respBytes)) 220 } 221 222 return statusCode, string(respBytes), nil 223 } 224 225 // PUT sends url put request to host and retrieve the body response in string 226 // 227 // Default Header = Content-Type: application/x-www-form-urlencoded 228 // 229 // JSON Content-Type Header: 230 // 231 // Content-Type: application/json 232 func PUT(url string, headers []*HeaderKeyValue, requestBody string) (statusCode int, responseBody string, err error) { 233 // create http client 234 var client *http.Client 235 236 if clientTlsConfig == nil { 237 client = &http.Client{} 238 } else { 239 tr := &http.Transport{ 240 TLSClientConfig: clientTlsConfig, 241 } 242 243 client = &http.Client{ 244 Transport: tr, 245 } 246 } 247 248 // create http request from client 249 var req *http.Request 250 251 if req, err = http.NewRequest("PUT", url, bytes.NewBuffer([]byte(requestBody))); err != nil { 252 return 0, "", errors.New("Create New Http Put Request Failed: " + err.Error()) 253 } 254 255 // add headers to request if any 256 contentTypeConfigured := false 257 258 if len(headers) > 0 { 259 for _, v := range headers { 260 req.Header.Add(v.Key, v.Value) 261 262 if strings.ToUpper(v.Key) == "CONTENT-TYPE" { 263 contentTypeConfigured = true 264 } 265 } 266 } 267 268 if !contentTypeConfigured { 269 req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 270 } 271 272 // execute http request and assign response 273 var resp *http.Response 274 275 if resp, err = client.Do(req); err != nil { 276 return 500, "", errors.New("[500 - Http Put Error] " + err.Error()) 277 } 278 279 // evaluate response 280 statusCode = resp.StatusCode 281 282 var respBytes []byte 283 284 respBytes, err = ioutil.ReadAll(resp.Body) 285 _ = resp.Body.Close() 286 resp.Close = true 287 288 // clean up stale connections 289 client.CloseIdleConnections() 290 291 if err != nil && statusCode == 400 { 292 return statusCode, "", err 293 } 294 295 if statusCode != 200 { 296 return statusCode, "", errors.New("[" + strconv.Itoa(statusCode) + " - Put Resp] " + string(respBytes)) 297 } 298 299 return statusCode, string(respBytes), nil 300 } 301 302 // DELETE sends url delete request to host and performs delete action (no body expected) 303 // 304 // Default Header = Content-Type: application/x-www-form-urlencoded 305 // 306 // JSON Content-Type Header: 307 // 308 // Content-Type: application/json 309 func DELETE(url string, headers []*HeaderKeyValue) (statusCode int, body string, err error) { 310 // create http client 311 var client *http.Client 312 313 if clientTlsConfig == nil { 314 client = &http.Client{} 315 } else { 316 tr := &http.Transport{ 317 TLSClientConfig: clientTlsConfig, 318 } 319 320 client = &http.Client{ 321 Transport: tr, 322 } 323 } 324 325 // create http request from client 326 var req *http.Request 327 328 if req, err = http.NewRequest("DELETE", url, nil); err != nil { 329 return 0, "", errors.New("Create New Http Delete Request Failed: " + err.Error()) 330 } 331 332 // add headers to request if any 333 if len(headers) > 0 { 334 for _, v := range headers { 335 req.Header.Add(v.Key, v.Value) 336 } 337 } 338 339 // execute http request and assign response 340 var resp *http.Response 341 342 if resp, err = client.Do(req); err != nil { 343 return 500, "", errors.New("[500 - Http Delete Error] " + err.Error()) 344 } 345 346 // evaluate response 347 statusCode = resp.StatusCode 348 349 var respBytes []byte 350 351 respBytes, err = ioutil.ReadAll(resp.Body) 352 _ = resp.Body.Close() 353 resp.Close = true 354 355 // clean up stale connections 356 client.CloseIdleConnections() 357 358 if err != nil && statusCode == 400 { 359 return statusCode, "", err 360 } 361 362 if statusCode != 200 { 363 return statusCode, "", errors.New("[" + strconv.Itoa(statusCode) + " - Delete Resp] " + string(respBytes)) 364 } 365 366 // success 367 return statusCode, string(respBytes), nil 368 } 369 370 // GETProtoBuf sends url get request to host, and retrieves response via protobuf object as an output pointer parameter 371 // 372 // default header if not specified: 373 // 374 // Content-Type: application/x-protobuf 375 func GETProtoBuf(url string, headers []*HeaderKeyValue, outResponseProtoBufObjectPtr proto.Message) (statusCode int, err error) { 376 // create http client 377 var client *http.Client 378 379 if clientTlsConfig == nil { 380 client = &http.Client{} 381 } else { 382 tr := &http.Transport{ 383 TLSClientConfig: clientTlsConfig, 384 } 385 386 client = &http.Client{ 387 Transport: tr, 388 } 389 } 390 391 // create http request from client 392 var req *http.Request 393 394 if req, err = http.NewRequest("GET", url, nil); err != nil { 395 outResponseProtoBufObjectPtr = nil 396 return 0, errors.New("Create New Http GET ProtoBuf Request Failed: " + err.Error()) 397 } 398 399 // add headers to request if any 400 contentTypeConfigured := false 401 402 if len(headers) > 0 { 403 for _, v := range headers { 404 req.Header.Add(v.Key, v.Value) 405 406 if strings.ToUpper(v.Key) == "CONTENT-TYPE" { 407 contentTypeConfigured = true 408 } 409 } 410 } 411 412 if !contentTypeConfigured { 413 req.Header.Add("Content-Type", "application/x-protobuf") 414 } 415 416 // execute http request and assign response 417 var resp *http.Response 418 419 if resp, err = client.Do(req); err != nil { 420 outResponseProtoBufObjectPtr = nil 421 return 500, errors.New("[500 - Http Get ProtoBuf Error] " + err.Error()) 422 } 423 424 // evaluate response 425 statusCode = resp.StatusCode 426 427 var respBytes []byte 428 429 respBytes, err = ioutil.ReadAll(resp.Body) 430 _ = resp.Body.Close() 431 resp.Close = true 432 433 // clean up stale connections 434 client.CloseIdleConnections() 435 436 if err != nil && statusCode == 400 { 437 outResponseProtoBufObjectPtr = nil 438 return statusCode, err 439 } 440 441 if statusCode != 200 { 442 outResponseProtoBufObjectPtr = nil 443 return statusCode, errors.New("[" + strconv.Itoa(statusCode) + " - Get ProtoBuf Not 200] Response ProtoBuf Bytes Length = " + strconv.Itoa(len(respBytes))) 444 } 445 446 // unmarshal bytes to protobuf object 447 if outResponseProtoBufObjectPtr != nil { 448 if err = proto.Unmarshal(respBytes, outResponseProtoBufObjectPtr); err != nil { 449 outResponseProtoBufObjectPtr = nil 450 return 500, errors.New("[500 - Http Get ProtoBuf Error] Unmarshal ProtoBuf Response Failed: " + err.Error()) 451 } 452 } 453 454 // success if outResponseProtoBufObjectPtr is not nil 455 if outResponseProtoBufObjectPtr != nil { 456 return statusCode, nil 457 } else { 458 return 500, errors.New("[500 - Http Get ProtoBuf Error] Expected ProtoBuf Response Object Nil") 459 } 460 } 461 462 // POSTProtoBuf sends url post request to host, with body content in protobuf pointer object, 463 // and retrieves response in protobuf object as output pointer parameter 464 // 465 // default header if not specified: 466 // 467 // Content-Type: application/x-protobuf 468 func POSTProtoBuf(url string, headers []*HeaderKeyValue, requestProtoBufObjectPtr proto.Message, outResponseProtoBufObjectPtr proto.Message) (statusCode int, err error) { 469 // create http client 470 var client *http.Client 471 472 if clientTlsConfig == nil { 473 client = &http.Client{} 474 } else { 475 tr := &http.Transport{ 476 TLSClientConfig: clientTlsConfig, 477 } 478 479 client = &http.Client{ 480 Transport: tr, 481 } 482 } 483 484 // marshal proto message to bytes 485 if requestProtoBufObjectPtr == nil { 486 outResponseProtoBufObjectPtr = nil 487 return 0, errors.New("Request ProtoBuf Object is Nil") 488 } 489 490 reqBytes, err2 := proto.Marshal(requestProtoBufObjectPtr) 491 492 if err2 != nil { 493 outResponseProtoBufObjectPtr = nil 494 return 0, errors.New("Request ProtoBuf Object Marshaling Failed: " + err2.Error()) 495 } 496 497 // create http request from client 498 req, err3 := http.NewRequest("POST", url, bytes.NewReader(reqBytes)) 499 500 if err3 != nil { 501 outResponseProtoBufObjectPtr = nil 502 return 0, errors.New("Create New Http Post ProtoBuf Request Failed: " + err3.Error()) 503 } 504 505 // add headers to request if any 506 contentTypeConfigured := false 507 508 if len(headers) > 0 { 509 for _, v := range headers { 510 req.Header.Add(v.Key, v.Value) 511 512 if strings.ToUpper(v.Key) == "CONTENT-TYPE" { 513 contentTypeConfigured = true 514 } 515 } 516 } 517 518 if !contentTypeConfigured { 519 req.Header.Add("Content-Type", "application/x-protobuf") 520 } 521 522 // execute http request and assign response 523 var resp *http.Response 524 525 if resp, err = client.Do(req); err != nil { 526 outResponseProtoBufObjectPtr = nil 527 return 500, errors.New("[500 - Http Post ProtoBuf Error] " + err.Error()) 528 } 529 530 // evaluate response 531 statusCode = resp.StatusCode 532 533 var respBytes []byte 534 535 respBytes, err = ioutil.ReadAll(resp.Body) 536 _ = resp.Body.Close() 537 resp.Close = true 538 539 // clean up stale connections 540 client.CloseIdleConnections() 541 542 if err != nil && statusCode == 400 { 543 outResponseProtoBufObjectPtr = nil 544 return statusCode, err 545 } 546 547 if statusCode != 200 { 548 return statusCode, errors.New("[" + strconv.Itoa(statusCode) + " - Post ProtoBuf Not 200] Response ProtoBuf Bytes Length = " + strconv.Itoa(len(respBytes))) 549 } 550 551 // unmarshal response bytes into protobuf object message 552 if outResponseProtoBufObjectPtr != nil { 553 if err = proto.Unmarshal(respBytes, outResponseProtoBufObjectPtr); err != nil { 554 outResponseProtoBufObjectPtr = nil 555 return 500, errors.New("[500 - Http Post ProtoBuf Error] Unmarshal ProtoBuf Response Failed: " + err.Error()) 556 } 557 } 558 559 // success if outResponseProtoBufObjectPtr is not nil 560 if outResponseProtoBufObjectPtr != nil { 561 return statusCode, nil 562 } else { 563 return 500, errors.New("[500 - Http Post ProtoBuf Error] Expected ProtoBuf Response Object Nil") 564 } 565 } 566 567 // PUTProtoBuf sends url put request to host, with body content in protobuf pointer object, 568 // and retrieves response in protobuf object as output pointer parameter 569 // 570 // default header if not specified: 571 // 572 // Content-Type: application/x-protobuf 573 func PUTProtoBuf(url string, headers []*HeaderKeyValue, requestProtoBufObjectPtr proto.Message, outResponseProtoBufObjectPtr proto.Message) (statusCode int, err error) { 574 // create http client 575 var client *http.Client 576 577 if clientTlsConfig == nil { 578 client = &http.Client{} 579 } else { 580 tr := &http.Transport{ 581 TLSClientConfig: clientTlsConfig, 582 } 583 584 client = &http.Client{ 585 Transport: tr, 586 } 587 } 588 589 // marshal proto message to bytes 590 if requestProtoBufObjectPtr == nil { 591 outResponseProtoBufObjectPtr = nil 592 return 0, errors.New("Request ProtoBuf Object is Nil") 593 } 594 595 reqBytes, err2 := proto.Marshal(requestProtoBufObjectPtr) 596 597 if err2 != nil { 598 outResponseProtoBufObjectPtr = nil 599 return 0, errors.New("Request ProtoBuf Object Marshaling Failed: " + err2.Error()) 600 } 601 602 // create http request from client 603 req, err3 := http.NewRequest("PUT", url, bytes.NewReader(reqBytes)) 604 605 if err3 != nil { 606 outResponseProtoBufObjectPtr = nil 607 return 0, errors.New("Create New Http PUT ProtoBuf Request Failed: " + err3.Error()) 608 } 609 610 // add headers to request if any 611 contentTypeConfigured := false 612 613 if len(headers) > 0 { 614 for _, v := range headers { 615 req.Header.Add(v.Key, v.Value) 616 617 if strings.ToUpper(v.Key) == "CONTENT-TYPE" { 618 contentTypeConfigured = true 619 } 620 } 621 } 622 623 if !contentTypeConfigured { 624 req.Header.Add("Content-Type", "application/x-protobuf") 625 } 626 627 // execute http request and assign response 628 var resp *http.Response 629 630 if resp, err = client.Do(req); err != nil { 631 outResponseProtoBufObjectPtr = nil 632 return 500, errors.New("[500 - Http Put ProtoBuf Error] " + err.Error()) 633 } 634 635 // evaluate response 636 statusCode = resp.StatusCode 637 638 var respBytes []byte 639 640 respBytes, err = ioutil.ReadAll(resp.Body) 641 _ = resp.Body.Close() 642 resp.Close = true 643 644 // clean up stale connections 645 client.CloseIdleConnections() 646 647 if err != nil && statusCode == 400 { 648 outResponseProtoBufObjectPtr = nil 649 return statusCode, err 650 } 651 652 if statusCode != 200 { 653 return statusCode, errors.New("[" + strconv.Itoa(statusCode) + " - Put ProtoBuf Not 200] Response ProtoBuf Bytes Length = " + strconv.Itoa(len(respBytes))) 654 } 655 656 // unmarshal response bytes into protobuf object message 657 if outResponseProtoBufObjectPtr != nil { 658 if err = proto.Unmarshal(respBytes, outResponseProtoBufObjectPtr); err != nil { 659 outResponseProtoBufObjectPtr = nil 660 return 500, errors.New("[500 - Http Put ProtoBuf Error] Unmarshal ProtoBuf Response Failed: " + err.Error()) 661 } 662 } 663 664 // success if outResponseProtoBufObjectPtr is not nil 665 if outResponseProtoBufObjectPtr != nil { 666 return statusCode, nil 667 } else { 668 return 500, errors.New("[500 - Http Put ProtoBuf Error] Expected ProtoBuf Response Object Nil") 669 } 670 } 671 672 // DELETEProtoBuf sends url delete request to host, and retrieves response via protobuf object as an output pointer parameter 673 // 674 // default header if not specified: 675 // 676 // Content-Type: application/x-protobuf 677 func DELETEProtoBuf(url string, headers []*HeaderKeyValue, outResponseProtoBufObjectPtr proto.Message) (statusCode int, err error) { 678 // create http client 679 var client *http.Client 680 681 if clientTlsConfig == nil { 682 client = &http.Client{} 683 } else { 684 tr := &http.Transport{ 685 TLSClientConfig: clientTlsConfig, 686 } 687 688 client = &http.Client{ 689 Transport: tr, 690 } 691 } 692 693 // create http request from client 694 var req *http.Request 695 696 if req, err = http.NewRequest("DELETE", url, nil); err != nil { 697 outResponseProtoBufObjectPtr = nil 698 return 0, errors.New("Create New Http Delete ProtoBuf Request Failed: " + err.Error()) 699 } 700 701 // add headers to request if any 702 contentTypeConfigured := false 703 704 if len(headers) > 0 { 705 for _, v := range headers { 706 req.Header.Add(v.Key, v.Value) 707 708 if strings.ToUpper(v.Key) == "CONTENT-TYPE" { 709 contentTypeConfigured = true 710 } 711 } 712 } 713 714 if !contentTypeConfigured { 715 req.Header.Add("Content-Type", "application/x-protobuf") 716 } 717 718 // execute http request and assign response 719 var resp *http.Response 720 721 if resp, err = client.Do(req); err != nil { 722 outResponseProtoBufObjectPtr = nil 723 return 500, errors.New("[500 - Http Delete ProtoBuf Error] " + err.Error()) 724 } 725 726 // evaluate response 727 statusCode = resp.StatusCode 728 729 var respBytes []byte 730 731 respBytes, err = ioutil.ReadAll(resp.Body) 732 _ = resp.Body.Close() 733 resp.Close = true 734 735 // clean up stale connections 736 client.CloseIdleConnections() 737 738 if err != nil && statusCode == 400 { 739 outResponseProtoBufObjectPtr = nil 740 return statusCode, err 741 } 742 743 if statusCode != 200 { 744 outResponseProtoBufObjectPtr = nil 745 return statusCode, errors.New("[" + strconv.Itoa(statusCode) + " - Delete ProtoBuf Not 200] Response ProtoBuf Bytes Length = " + strconv.Itoa(len(respBytes))) 746 } 747 748 // unmarshal bytes to protobuf object 749 if outResponseProtoBufObjectPtr != nil { 750 if err = proto.Unmarshal(respBytes, outResponseProtoBufObjectPtr); err != nil { 751 outResponseProtoBufObjectPtr = nil 752 return 500, errors.New("[500 - Http Delete ProtoBuf Error] Unmarshal ProtoBuf Response Failed: " + err.Error()) 753 } 754 } 755 756 // success if outResponseProtoBufObjectPtr is not nil 757 if outResponseProtoBufObjectPtr != nil { 758 return statusCode, nil 759 } else { 760 return 500, errors.New("[500 - Http Delete ProtoBuf Error] Expected ProtoBuf Response Object Nil") 761 } 762 }