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 }