github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/client/controllers/requests.go (about) 1 package controllers 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "mime" 11 "mime/multipart" 12 "net/http" 13 "strconv" 14 15 "github.com/benoitkugler/goACVE/logs" 16 "github.com/benoitkugler/goACVE/server/core/apiserver" 17 ) 18 19 // QueryParams est envoyé via l'url et non encodé en JSON 20 type QueryParams map[string]string 21 22 func IdAsParams(id int64) QueryParams { 23 s := strconv.Itoa(int(id)) 24 return QueryParams{"id": s} 25 } 26 27 func (params QueryParams) setRequest(req *http.Request) { 28 query := req.URL.Query() 29 for k, v := range params { 30 query.Add(k, v) 31 } 32 req.URL.RawQuery = query.Encode() 33 } 34 35 func newJsonRequete(method, url string, params interface{}) (*http.Request, error) { 36 buf := new(bytes.Buffer) 37 err := json.NewEncoder(buf).Encode(params) 38 if err != nil { 39 return nil, fmt.Errorf("Impossible de formatter les arguments de la requête : %s", err) 40 } 41 req, err := http.NewRequest(method, url, buf) 42 if err != nil { 43 return nil, fmt.Errorf("Impossible de formatter les arguments de la requête : %s", err) 44 } 45 req.Header.Set("Content-Type", "application/json") 46 return req, nil 47 } 48 49 func newParamsRequete(method, url string, params QueryParams) (*http.Request, error) { 50 req, err := http.NewRequest(method, url, nil) 51 if err != nil { 52 return nil, fmt.Errorf("Impossible de formatter les arguments de la requête : %s", err) 53 } 54 params.setRequest(req) 55 return req, nil 56 } 57 58 func doRequete(endpoint, method string, params interface{}) (*http.Response, error) { 59 if Server.ApiURL("") == "" { 60 return nil, errors.New("Les requêtes au serveur sont désactivés. Veuillez redémarrer pour les activer.") 61 } 62 url := Server.ApiURL(endpoint) 63 64 var ( 65 req *http.Request 66 err error 67 ) 68 switch params := params.(type) { 69 case QueryParams: 70 req, err = newParamsRequete(method, url, params) 71 default: 72 req, err = newJsonRequete(method, url, params) 73 } 74 75 if err != nil { 76 return nil, err 77 } 78 79 req.SetBasicAuth(apiserver.BasicAuthUsername, logs.APIKEY) 80 resp, err := http.DefaultClient.Do(req) 81 if err != nil { 82 return nil, fmt.Errorf("Erreur pendant l'envoi de la requête : %s", err) 83 } 84 if err := checkStatusCode(resp); err != nil { 85 return nil, err 86 } 87 return resp, nil 88 } 89 90 type errorRep struct { 91 Message string `json:"message"` 92 } 93 94 func checkStatusCode(resp *http.Response) error { 95 if resp.StatusCode != 200 { 96 defer resp.Body.Close() 97 b, err := ioutil.ReadAll(resp.Body) 98 if err != nil { 99 return fmt.Errorf("Réponse du serveur invalide (code %d) : %s", resp.StatusCode, err) 100 } 101 var rep errorRep 102 if err = json.Unmarshal(b, &rep); err != nil { 103 return fmt.Errorf("Erreur du serveur, détails inconnu : %s", err) 104 } 105 return fmt.Errorf("Erreur du serveur : <br/> <i>%s</i>", rep.Message) 106 } 107 return nil 108 } 109 110 // requete envoie les arguments au format JSON (ou Query), et décode le retour JSON. 111 func requete(endpoint, method string, params interface{}, out interface{}) error { 112 resp, err := doRequete(endpoint, method, params) 113 if err != nil { 114 return err 115 } 116 defer resp.Body.Close() 117 err = json.NewDecoder(resp.Body).Decode(out) 118 if err != nil { 119 return fmt.Errorf("Réponse du serveur non décodable: %s", err) 120 } 121 return nil 122 } 123 124 // BinaryRequete envoie les arguments au format JSON (ou Query), sans décoder le retour. 125 //func BinaryRequete(endpoint, method string, params interface{}, out io.Writer) error { 126 // resp, err := doRequete(endpoint, method, params) 127 // if err != nil { 128 // return err 129 // } 130 // defer resp.Body.Close() 131 // _, err = io.Copy(out, resp.Body) 132 // if err != nil { 133 // return fmt.Errorf("Réponse du serveur illisible : %s", err) 134 // } 135 // return nil 136 //} 137 138 // ------------------------------------------------------------------------------ 139 // ------------------------------ Monitor progress ------------------------------ 140 // ------------------------------------------------------------------------------ 141 142 // requestMonitor émet une requête et stream la réponse 143 // Les paramètres sont JSONisés (ou Query). 144 func requestMonitor(endpoint string, method string, params interface{}, 145 out io.Writer) error { 146 resp, err := doRequete(endpoint, method, params) 147 if err != nil { 148 return err 149 } 150 defer resp.Body.Close() 151 if _, err = io.Copy(out, resp.Body); err != nil { 152 return err 153 } 154 return nil 155 } 156 157 // writeCounter counts the number of bytes written to it 158 // `callback` will be passed the download percentage 159 type writeCounter struct { 160 max uint64 161 current uint64 162 callback func(uint8) 163 } 164 165 func (wc *writeCounter) Write(p []byte) (int, error) { 166 n := len(p) 167 wc.current += uint64(n) 168 wc.callback(uint8(wc.current * 100 / wc.max)) 169 return n, nil 170 } 171 172 // requestDownload télécharge un contenu binaire et appelle `callback` 173 // pour suivre l'évolution du téléchargement. 174 // Les paramètres sont JSONisés (ou Query). 175 func requestDownload(endpoint string, method string, params interface{}, 176 callback func(uint8), out io.Writer) error { 177 resp, err := doRequete(endpoint, method, params) 178 if err != nil { 179 return err 180 } 181 defer resp.Body.Close() 182 max, err := strconv.Atoi(resp.Header.Get("Content-Length")) 183 if err != nil { 184 return fmt.Errorf("Réponse du serveur invalide : %s", err) 185 } 186 187 // Create our progress reporter and pass it to be used alongside our writer 188 counter := &writeCounter{max: uint64(max), callback: callback} 189 _, err = io.Copy(out, io.TeeReader(resp.Body, counter)) 190 if err != nil { 191 return err 192 } 193 return nil 194 } 195 196 type fileUpload struct { 197 name string 198 content io.ReadCloser 199 } 200 201 type urlModifer interface { 202 url() string 203 setRequest(*http.Request) 204 } 205 206 func requeteUploadWithMeta(file fileUpload, meta interface{}, method string, url urlModifer, callback func(uint8), out interface{}) error { 207 defer file.content.Close() 208 body := &bytes.Buffer{} 209 writer := multipart.NewWriter(body) 210 211 // content 212 part, err := writer.CreateFormFile("file", file.name) 213 if err != nil { 214 return err 215 } 216 if _, err = io.Copy(part, file.content); err != nil { 217 return err 218 } 219 220 // meta 221 metaJson, err := json.Marshal(meta) 222 if err != nil { 223 return err 224 } 225 err = writer.WriteField("meta", string(metaJson)) 226 if err != nil { 227 return err 228 } 229 230 if err = writer.Close(); err != nil { 231 return err 232 } 233 234 counter := &writeCounter{max: uint64(len(body.Bytes())), callback: callback} 235 customReader := io.TeeReader(body, counter) 236 237 req, err := http.NewRequest(method, url.url(), customReader) 238 if err != nil { 239 return err 240 } 241 242 req.Header.Set("Content-Type", writer.FormDataContentType()) 243 req.SetBasicAuth(apiserver.BasicAuthUsername, logs.APIKEY) 244 url.setRequest(req) 245 246 resp, err := http.DefaultClient.Do(req) 247 if err != nil { 248 return err 249 } 250 if err := checkStatusCode(resp); err != nil { 251 return err 252 } 253 defer resp.Body.Close() 254 err = json.NewDecoder(resp.Body).Decode(out) 255 if err != nil { 256 return fmt.Errorf("Réponse du serveur non décodable: %s", err) 257 } 258 return nil 259 } 260 261 type urlDocumentWithIdParam int64 262 263 func (urlDocumentWithIdParam) url() string { return Server.ApiURL(apiserver.UrlDocumentsTransfert) } 264 265 func (id urlDocumentWithIdParam) setRequest(req *http.Request) { 266 IdAsParams(int64(id)).setRequest(req) 267 } 268 269 type urlBasique string 270 271 func (u urlBasique) url() string { return string(u) } 272 273 func (urlBasique) setRequest(req *http.Request) {} // no-op 274 275 // requeteUpload émet une requête multipart, appelle callback pendant l'upload, 276 // et unmarshal la réponse dans out 277 func requeteUpload(params apiserver.ParamsUploadDocument, callback func(uint8), out interface{}) error { 278 return requeteUploadWithMeta(fileUpload{name: params.Filename, content: params.Content}, nil, http.MethodPost, urlDocumentWithIdParam(params.Id), callback, out) 279 } 280 281 type ParamsNewDocument struct { 282 fileUpload 283 Meta apiserver.CreateDocumentIn 284 } 285 286 // requeteCreateDocument émet une requête multipart et appelle callback pendant l'upload 287 func requeteCreateDocument(params ParamsNewDocument, callback func(uint8)) (apiserver.CreateDocumentIn, error) { 288 var out apiserver.CreateDocumentIn // le server renvoie les mêmes objets, mis à jour 289 url := Server.ApiURL(apiserver.UrlDocuments) 290 err := requeteUploadWithMeta(params.fileUpload, params.Meta, http.MethodPut, urlBasique(url), callback, &out) 291 return out, err 292 } 293 294 // requeteResponseMultipart envoie une requete JSON (ou Query) et recois une réponse multipart. 295 func requeteResponseMultipart(endpoint, method string, params interface{}) (map[string][]byte, error) { 296 resp, err := doRequete(endpoint, method, params) 297 if err != nil { 298 return nil, err 299 } 300 defer resp.Body.Close() 301 302 _, ct, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) 303 if err != nil { 304 return nil, err 305 } 306 mr := multipart.NewReader(resp.Body, ct["boundary"]) 307 out := map[string][]byte{} 308 for { 309 p, err := mr.NextPart() 310 if err == io.EOF { 311 break 312 } 313 if err != nil { 314 return nil, err 315 } 316 partContent, err := ioutil.ReadAll(p) 317 if err != nil { 318 return nil, err 319 } 320 key := p.FormName() 321 out[key] = partContent 322 323 } 324 return out, nil 325 }