Tp 55555/integrate document service client (#5)
* TP-55555 | document client and kafka integration * TP-55555 | introduce service concept refactor code
This commit is contained in:
139
pkg/httpClient/httpClient.go
Normal file
139
pkg/httpClient/httpClient.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"cybertron/configs"
|
||||
"cybertron/pkg/log"
|
||||
"cybertron/pkg/metrics"
|
||||
"cybertron/pkg/utils"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HttpClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type CircuitBreaker interface {
|
||||
ExecuteRequest(req func() (interface{}, error)) (interface{}, error)
|
||||
}
|
||||
|
||||
var logger = log.Log
|
||||
|
||||
// todo - custom configs for all clients
|
||||
func NewHttpClient(httpConfig configs.HttpConfig) HttpClient {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: httpConfig.MaxIdleConnectionPool,
|
||||
MaxConnsPerHost: httpConfig.MaxConnection,
|
||||
},
|
||||
Timeout: time.Duration(httpConfig.MaxTimeoutInSeconds * time.Second.Nanoseconds()),
|
||||
}
|
||||
}
|
||||
|
||||
// GetHttpRequest is a generic function to create a http request with the given method, url and body.
|
||||
// Accepts the body as a proto message. Adds the necessary headers.
|
||||
func GetHttpRequest(requestMetadata *context.Context, method string, url string, body proto.Message) (*http.Request, error) {
|
||||
requestBody, _ := proto.Marshal(body)
|
||||
req, err := http.NewRequest(method, url, bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = addHeadersToRequest(req, requestMetadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// addHeadersToRequest adds the necessary headers to the request.
|
||||
func addHeadersToRequest(req *http.Request, requestMetadata *context.Context) error {
|
||||
//correlationId := metadata.GetRequestMetadata(requestMetadata, utils.CORRELATION_ID_HEADER)
|
||||
//if correlationId == nil {
|
||||
// return fmt.Errorf("correlation id not found in request metadata")
|
||||
//}
|
||||
//req.Header.Add(utils.CORRELATION_ID_HEADER, *correlationId)
|
||||
//
|
||||
//saCustomerId := metadata.GetRequestMetadata(requestMetadata, utils.CUSTOMER_ID_HEADER)
|
||||
//if saCustomerId != nil {
|
||||
// req.Header.Add(utils.CUSTOMER_ID_HEADER, *saCustomerId)
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateUrl builds a url for the given endpoint and query params. To append path params to the url,
|
||||
// pass each part of the path as a parameter. For example, if the endpoint is /billers/{billerId}/bills,
|
||||
// then pass "billers", "{billerId}" and "bills" as parameters, in the same order.
|
||||
func GenerateUrl(baseUrl, apiVersion string, queryParams map[string]string, endpoint ...string) (*url.URL, error) {
|
||||
url, err := url.Parse(baseUrl)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
url.Path = path.Join(url.Path, apiVersion, path.Join(endpoint...))
|
||||
if queryParams != nil {
|
||||
query := url.Query()
|
||||
for key, value := range queryParams {
|
||||
query.Add(key, value)
|
||||
}
|
||||
url.RawQuery = query.Encode()
|
||||
}
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// HttpCallToClientWithCB is a generic function to make an api call to client service with circuit breaker pattern.
|
||||
// Returns error only when http response is nil. This is because, in case of internal failures, downstream services are expected
|
||||
// to share the error details in the response body.
|
||||
func HttpCallToClientWithCB(requestMetadata *context.Context, req *http.Request, httpClient *HttpClient, circuitBreaker CircuitBreaker) (*http.Response, error) {
|
||||
resp, err := circuitBreaker.ExecuteRequest(func() (interface{}, error) {
|
||||
return HttpCallToClient(req, httpClient)
|
||||
})
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if httpResponse, ok := resp.(*http.Response); ok && httpResponse != nil {
|
||||
if utils.IsErrorStatusCode(httpResponse.StatusCode) {
|
||||
logger.ErrorWithCtx(requestMetadata, "api call failed", zap.String("url", req.URL.String()), zap.Int("status_code", httpResponse.StatusCode))
|
||||
if httpResponse.Body == nil {
|
||||
return nil, errors.New("api call failed: response body is nil")
|
||||
}
|
||||
} else {
|
||||
logger.InfoWithCtx(requestMetadata, "api call successful", zap.String("url", req.URL.String()), zap.Int("status_code", httpResponse.StatusCode))
|
||||
}
|
||||
return httpResponse, nil
|
||||
}
|
||||
|
||||
log.Log.ErrorWithCtx(requestMetadata, "unexpected response", zap.String("response", utils.ConvertToString(resp)))
|
||||
return nil, errors.New("unexpected response type")
|
||||
}
|
||||
|
||||
// HttpCallToClient is a generic function to make an api call to client service. It expects
|
||||
// all the headers to be set in the request. Also records the metrics for the api call.
|
||||
// In error scenario, downstream service is expected to return 4xx or 5xx status code with error details in the response body.
|
||||
// Errors returned by this method will increase the failure counter in circuit breaker. Error is only returned if status code is 5XX.
|
||||
func HttpCallToClient(req *http.Request, httpClient *HttpClient) (*http.Response, error) {
|
||||
httpMethodCall := func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := (*httpClient).Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only returning error in cases we want CB to consider failure.
|
||||
if resp.StatusCode >= 500 {
|
||||
return resp, errors.New("api call failed with status code: " + fmt.Sprintf("%d", resp.StatusCode))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
return metrics.RecordClientHttpCallMetrics(req, httpMethodCall)
|
||||
}
|
||||
Reference in New Issue
Block a user