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  }