github.com/pavlo67/common@v0.5.3/common/httplib/request.go (about) 1 package httplib 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "regexp" 11 12 "github.com/pavlo67/common/common" 13 "github.com/pavlo67/common/common/errors" 14 "github.com/pavlo67/common/common/logger" 15 "github.com/pavlo67/common/common/server_http" 16 ) 17 18 const bodyLogLimit = 2048 19 20 const onRequest = "on httplib.Request()" 21 22 type ResponseBinary struct { 23 MIMEType string 24 Data []byte 25 } 26 27 var reBinary = regexp.MustCompile(`^image/`) 28 29 func Request(client *http.Client, serverURL, method string, header http.Header, requestData, responseData interface{}, l logger.Operator, logFile string) error { 30 if client == nil { 31 client = &http.Client{} 32 } 33 34 var err error 35 //for _, doReAuth := range reAuthTries { 36 37 // start of single try 38 39 var requestBody []byte 40 var requestBodyReader io.Reader 41 42 if requestData != nil { 43 switch v := requestData.(type) { 44 case []byte: 45 requestBody = v 46 case *[]byte: 47 requestBody = *v 48 case string: 49 requestBody = []byte(v) 50 case *string: 51 requestBody = []byte(*v) 52 default: 53 if requestBody, err = json.Marshal(requestData); err != nil { 54 return fmt.Errorf(onRequest+": can't marshal request responseData (%#v): %s", requestData, err) 55 } 56 } 57 58 // must be checked for nil instead direct write 59 // the external for GET requests expected nil body, but nil-requestData after json.Marshal return not empty responseData 60 61 requestBodyReader = bytes.NewBuffer(requestBody) 62 } 63 64 req, err := http.NewRequest(method, serverURL, requestBodyReader) 65 if err != nil || req == nil { 66 logger.LogRequest(method, serverURL, nil, requestBody, nil, nil, err, 0, logFile) 67 return fmt.Errorf(onRequest+": can't create request %s %s, got %#v, %s", method, serverURL, req, err) 68 } else if req.Body != nil { 69 defer Close(req.Body, client, nil) 70 } 71 req.Header = header 72 var responseBody []byte 73 74 resp, err := client.Do(req) 75 if resp != nil && resp.Body != nil { 76 defer Close(resp.Body, client, nil) 77 } 78 79 if err != nil { 80 var statusCode int 81 var responseHeaders http.Header 82 if resp != nil { 83 statusCode = resp.StatusCode 84 responseHeaders = resp.Header 85 responseBody, _ = ioutil.ReadAll(resp.Body) 86 } 87 88 logger.LogRequest(method, serverURL, req.Header, requestBody, responseHeaders, responseBody, err, statusCode, logFile) 89 return fmt.Errorf(onRequest+": can't %s %s, got %s", method, serverURL, err) 90 } 91 92 responseBody, err = ioutil.ReadAll(resp.Body) 93 if err != nil { 94 l.Error("can't read response body: ", err) 95 } 96 97 var responseBodyToLog []byte 98 99 if !reBinary.MatchString(resp.Header.Get("Content-Type")) { 100 responseBodyToLog = responseBody 101 } 102 logger.LogRequest(method, serverURL, req.Header, requestBody, resp.Header, responseBodyToLog, err, resp.StatusCode, logFile) 103 if err != nil { 104 return fmt.Errorf(onRequest+": can't read body from %s %s, got %s", method, serverURL, err) 105 } 106 107 if resp.StatusCode == http.StatusUnauthorized { // && doReAuth 108 //if identity.Token = reAuthJWT(*identity); identity.Token != "" { 109 // continue 110 //} 111 } 112 113 if resp.StatusCode != http.StatusOK { 114 // TODO!!! be careful writing server_http handlers, http.StatusOK is the only success code accepted here 115 116 if len(responseBody) > bodyLogLimit { 117 responseBody = responseBody[:bodyLogLimit] 118 } 119 120 var data common.Map 121 if err = json.Unmarshal(responseBody, &data); err != nil { 122 if len(responseBody) > bodyLogLimit { 123 responseBody = responseBody[:bodyLogLimit] 124 } 125 return fmt.Errorf(onRequest+": can't unmarshal body from %s %s: status = %d, body = %s, got %s", method, serverURL, resp.StatusCode, responseBody, err) 126 } 127 128 errCommon := fmt.Sprintf("can't %s %s: status = %d, body = %s", method, serverURL, resp.StatusCode, responseBody) 129 if data["error"] != nil { 130 data["error"] = errors.CommonError(data["error"], errCommon) 131 } else { 132 data["error"] = errCommon 133 } 134 errorKey := errors.Key(data.StringDefault(server_http.ErrorKey, "")) 135 return errors.CommonError(errorKey, data) 136 } 137 138 if responseData != nil { 139 switch v := responseData.(type) { 140 case *[]byte: 141 if v != nil { 142 *v = responseBody 143 } 144 case *string: 145 if v != nil { 146 *v = string(responseBody) 147 } 148 case *ResponseBinary: 149 v.MIMEType = resp.Header.Get("Content-Type") 150 v.Data = responseBody 151 default: 152 if err = json.Unmarshal(responseBody, responseData); err != nil { 153 if len(responseBody) > bodyLogLimit { 154 responseBody = responseBody[:bodyLogLimit] 155 } 156 return fmt.Errorf(onRequest+": can't unmarshal body from %s %s %s, got %s", method, serverURL, responseBody, err) 157 } 158 } 159 } 160 161 // break // end of each try means the end of all tries if something other wasn't managed before 162 //} 163 164 return nil 165 } 166 167 //func RequestJSON(method, url string, data []byte, headers map[string]string) (common.Map, error) { 168 // client := &http.Client{} 169 // 170 // req, err := http.NewRequest(method, url, bytes.NewReader(data)) 171 // if err != nil { 172 // return nil, err 173 // } 174 // 175 // for k, v := range headers { 176 // req.Header.Add(k, v) 177 // } 178 // 179 // resp, err := client.Do(req) 180 // if err != nil { 181 // return nil, err 182 // } 183 // 184 // defer resp.Body.Close() 185 // body, err := ioutil.ReadAll(resp.Body) 186 // if err != nil { 187 // return nil, err 188 // } 189 // 190 // // log.Printf("%s", body) 191 // 192 // result := common.Map{} 193 // err = json.Unmarshal(body, &result) 194 // if err != nil { 195 // return result, errors.Wrapf(err, "can't unmarsal: %s", body) 196 // } 197 // 198 // return result, nil 199 //}