dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts@v1.0.2/clients/http/utils/common.go (about)

     1  //
     2  // Copyright (C) 2020-2023 IOTech Ltd
     3  // Copyright (C) 2023 Intel Corporation
     4  //
     5  // SPDX-License-Identifier: Apache-2.0
     6  
     7  package utils
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"encoding/json"
    13  	"fmt"
    14  	"io"
    15  	"mime/multipart"
    16  	"net/http"
    17  	"net/url"
    18  	"os"
    19  	"path"
    20  	"path/filepath"
    21  
    22  	"dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts/clients/interfaces"
    23  	"dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts/common"
    24  	"dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts/errors"
    25  
    26  	"github.com/google/uuid"
    27  )
    28  
    29  // FromContext allows for the retrieval of the specified key's value from the supplied Context.
    30  // If the value is not found, an empty string is returned.
    31  func FromContext(ctx context.Context, key string) string {
    32  	hdr, ok := ctx.Value(key).(string)
    33  	if !ok {
    34  		hdr = ""
    35  	}
    36  	return hdr
    37  }
    38  
    39  // correlatedId gets Correlation ID from supplied context. If no Correlation ID header is
    40  // present in the supplied context, one will be created along with a value.
    41  func correlatedId(ctx context.Context) string {
    42  	correlation := FromContext(ctx, common.CorrelationHeader)
    43  	if len(correlation) == 0 {
    44  		correlation = uuid.New().String()
    45  	}
    46  	return correlation
    47  }
    48  
    49  // Helper method to get the body from the response after making the request
    50  func getBody(resp *http.Response) ([]byte, errors.EdgeX) {
    51  	body, err := io.ReadAll(resp.Body)
    52  	if err != nil {
    53  		return body, errors.NewCommonEdgeX(errors.KindIOError, "failed to get the body from the response", err)
    54  	}
    55  	return body, nil
    56  }
    57  
    58  // Helper method to make the request and return the response
    59  func makeRequest(req *http.Request, authInjector interfaces.AuthenticationInjector) (*http.Response, errors.EdgeX) {
    60  	if authInjector != nil {
    61  		if err := authInjector.AddAuthenticationData(req); err != nil {
    62  			return nil, errors.NewCommonEdgeXWrapper(err)
    63  		}
    64  	}
    65  
    66  	client := &http.Client{}
    67  	resp, err := client.Do(req)
    68  	if err != nil {
    69  		return nil, errors.NewCommonEdgeX(errors.KindServiceUnavailable, "failed to send a http request", err)
    70  	}
    71  	if resp == nil {
    72  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "the response should not be a nil", nil)
    73  	}
    74  	return resp, nil
    75  }
    76  
    77  func createRequest(ctx context.Context, httpMethod string, baseUrl string, requestPath string, requestParams url.Values) (*http.Request, errors.EdgeX) {
    78  	u, err := parseBaseUrlAndRequestPath(baseUrl, requestPath)
    79  	if err != nil {
    80  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to parse baseUrl and requestPath", err)
    81  	}
    82  	if requestParams != nil {
    83  		u.RawQuery = requestParams.Encode()
    84  	}
    85  	req, err := http.NewRequest(httpMethod, u.String(), nil)
    86  	if err != nil {
    87  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to create a http request", err)
    88  	}
    89  	req.Header.Set(common.CorrelationHeader, correlatedId(ctx))
    90  	username := FromContext(ctx, string(common.Username))
    91  	if username != "" {
    92  		req.Header.Set(string(common.Username), username)
    93  	}
    94  	return req, nil
    95  }
    96  
    97  func createRequestWithRawDataAndParams(ctx context.Context, httpMethod string, baseUrl string, requestPath string, requestParams url.Values, data interface{}) (*http.Request, errors.EdgeX) {
    98  	u, err := parseBaseUrlAndRequestPath(baseUrl, requestPath)
    99  	if err != nil {
   100  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to parse baseUrl and requestPath", err)
   101  	}
   102  	if requestParams != nil {
   103  		u.RawQuery = requestParams.Encode()
   104  	}
   105  	jsonEncodedData, err := json.Marshal(data)
   106  	if err != nil {
   107  		return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "failed to encode input data to JSON", err)
   108  	}
   109  
   110  	content := FromContext(ctx, common.ContentType)
   111  	if content == "" {
   112  		content = common.ContentTypeJSON
   113  	}
   114  
   115  	req, err := http.NewRequest(httpMethod, u.String(), bytes.NewReader(jsonEncodedData))
   116  	if err != nil {
   117  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to create a http request", err)
   118  	}
   119  	req.Header.Set(common.ContentType, content)
   120  	req.Header.Set(common.CorrelationHeader, correlatedId(ctx))
   121  	username := FromContext(ctx, string(common.Username))
   122  	if username != "" {
   123  		req.Header.Set(string(common.Username), username)
   124  	}
   125  	return req, nil
   126  }
   127  
   128  func createRequestWithRawData(ctx context.Context, httpMethod string, baseUrl string, requestPath string, requestParams url.Values, data interface{}) (*http.Request, errors.EdgeX) {
   129  	u, err := parseBaseUrlAndRequestPath(baseUrl, requestPath)
   130  	if err != nil {
   131  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to parse baseUrl and requestPath", err)
   132  	}
   133  	if requestParams != nil {
   134  		u.RawQuery = requestParams.Encode()
   135  	}
   136  
   137  	jsonEncodedData, err := json.Marshal(data)
   138  	if err != nil {
   139  		return nil, errors.NewCommonEdgeX(errors.KindContractInvalid, "failed to encode input data to JSON", err)
   140  	}
   141  
   142  	content := FromContext(ctx, common.ContentType)
   143  	if content == "" {
   144  		content = common.ContentTypeJSON
   145  	}
   146  
   147  	req, err := http.NewRequest(httpMethod, u.String(), bytes.NewReader(jsonEncodedData))
   148  	if err != nil {
   149  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to create a http request", err)
   150  	}
   151  	req.Header.Set(common.ContentType, content)
   152  	req.Header.Set(common.CorrelationHeader, correlatedId(ctx))
   153  
   154  	username := FromContext(ctx, string(common.Username))
   155  	if username != "" {
   156  		req.Header.Set(string(common.Username), username)
   157  	}
   158  	return req, nil
   159  }
   160  
   161  func createRequestWithEncodedData(ctx context.Context, httpMethod string, baseUrl string, requestPath string, data []byte, encoding string) (*http.Request, errors.EdgeX) {
   162  	u, err := parseBaseUrlAndRequestPath(baseUrl, requestPath)
   163  	if err != nil {
   164  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to parse baseUrl and requestPath", err)
   165  	}
   166  
   167  	content := encoding
   168  	if content == "" {
   169  		content = FromContext(ctx, common.ContentType)
   170  	}
   171  
   172  	req, err := http.NewRequest(httpMethod, u.String(), bytes.NewReader(data))
   173  	if err != nil {
   174  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to create a http request", err)
   175  	}
   176  	req.Header.Set(common.ContentType, content)
   177  	req.Header.Set(common.CorrelationHeader, correlatedId(ctx))
   178  	username := FromContext(ctx, string(common.Username))
   179  	if username != "" {
   180  		req.Header.Set(string(common.Username), username)
   181  	}
   182  	return req, nil
   183  }
   184  
   185  // createRequestFromFilePath creates multipart/form-data request with the specified file
   186  func createRequestFromFilePath(ctx context.Context, httpMethod string, baseUrl string, requestPath string, filePath string) (*http.Request, errors.EdgeX) {
   187  	u, err := parseBaseUrlAndRequestPath(baseUrl, requestPath)
   188  	if err != nil {
   189  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to parse baseUrl and requestPath", err)
   190  	}
   191  
   192  	fileContents, err := os.ReadFile(filePath)
   193  	if err != nil {
   194  		return nil, errors.NewCommonEdgeX(errors.KindIOError, fmt.Sprintf("fail to read file from %s", filePath), err)
   195  	}
   196  
   197  	body := &bytes.Buffer{}
   198  	writer := multipart.NewWriter(body)
   199  	formFileWriter, err := writer.CreateFormFile("file", filepath.Base(filePath))
   200  	if err != nil {
   201  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "fail to create form data", err)
   202  	}
   203  	_, err = io.Copy(formFileWriter, bytes.NewReader(fileContents))
   204  	if err != nil {
   205  		return nil, errors.NewCommonEdgeX(errors.KindIOError, "fail to copy file to form data", err)
   206  	}
   207  	writer.Close()
   208  
   209  	req, err := http.NewRequest(httpMethod, u.String(), body)
   210  	if err != nil {
   211  		return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to create a http request", err)
   212  	}
   213  	req.Header.Set(common.ContentType, writer.FormDataContentType())
   214  	req.Header.Set(common.CorrelationHeader, correlatedId(ctx))
   215  	username := FromContext(ctx, string(common.Username))
   216  	if username != "" {
   217  		req.Header.Set(string(common.Username), username)
   218  	}
   219  	return req, nil
   220  }
   221  
   222  // sendRequest will make a request with raw data to the specified URL.
   223  // It returns the body as a byte array if successful and an error otherwise.
   224  func sendRequest(ctx context.Context, req *http.Request, authInjector interfaces.AuthenticationInjector) ([]byte, errors.EdgeX) {
   225  	resp, err := makeRequest(req, authInjector)
   226  	if err != nil {
   227  		return nil, errors.NewCommonEdgeXWrapper(err)
   228  	}
   229  	defer resp.Body.Close()
   230  
   231  	bodyBytes, err := getBody(resp)
   232  	if err != nil {
   233  		return nil, errors.NewCommonEdgeXWrapper(err)
   234  	}
   235  
   236  	if resp.StatusCode <= http.StatusMultiStatus {
   237  		return bodyBytes, nil
   238  	}
   239  
   240  	// Handle error response
   241  	msg := fmt.Sprintf("request failed, status code: %d, err: %s", resp.StatusCode, string(bodyBytes))
   242  	errKind := errors.KindMapping(resp.StatusCode)
   243  	return nil, errors.NewCommonEdgeX(errKind, msg, nil)
   244  }
   245  
   246  // EscapeAndJoinPath escape and join the path variables
   247  func EscapeAndJoinPath(apiRoutePath string, pathVariables ...string) string {
   248  	elements := make([]string, len(pathVariables)+1)
   249  	elements[0] = apiRoutePath // we don't need to escape the route path like /device, /reading, ...,etc.
   250  	for i, e := range pathVariables {
   251  		elements[i+1] = common.URLEncode(e)
   252  	}
   253  	return path.Join(elements...)
   254  }
   255  
   256  func parseBaseUrlAndRequestPath(baseUrl, requestPath string) (*url.URL, error) {
   257  	fullPath, err := url.JoinPath(baseUrl, requestPath)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	return url.Parse(fullPath)
   262  }