Refactor PrestashopClient and PrestashopProxyController for improved error handling and response management

This commit is contained in:
Vincent Guillet
2025-11-26 14:08:33 +01:00
parent d94ce06d95
commit f4696b5f5b
2 changed files with 149 additions and 114 deletions

View File

@@ -16,92 +16,70 @@ public class PrestashopProxyController {
this.prestashopClient = prestashopClient;
}
/**
* Extrait le path Presta à partir de la requête entrante.
* Exemple :
* - requestURI = /api/ps/products/446
* - retourne /products/446
*/
// Utilitaire pour extraire /products, /products/446, etc.
private String extractPath(HttpServletRequest request) {
String fullPath = request.getRequestURI(); // ex: /api/ps/products/446
String contextPath = request.getContextPath(); // souvent ""
String relative = fullPath.substring(contextPath.length()); // /api/ps/products/446
// On enlève le préfixe /api/ps => /products/446
return relative.replaceFirst("^/api/ps", "");
return relative.replaceFirst("^/api/ps", ""); // => /products/446
}
// ---------- GET ----------
/* ---------------- GET ---------------- */
@GetMapping("/**")
public ResponseEntity<String> proxyGet(HttpServletRequest request) {
String path = extractPath(request);
String query = request.getQueryString(); // ex: display=full&filter[id_product]=123
String query = request.getQueryString(); // on laisse tel quel
String body = prestashopClient.get(path, query);
// On sait qu'on force output_format=JSON côté client.get(...)
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(body);
}
// ---------- POST ----------
/* ---------------- POST ---------------- */
@PostMapping("/**")
public ResponseEntity<String> proxyPost(
HttpServletRequest request,
@RequestBody String bodyFromFront
) {
public ResponseEntity<String> proxyPost(HttpServletRequest request,
@RequestBody String xmlBody) {
String path = extractPath(request);
String query = request.getQueryString();
String body = prestashopClient.post(path, query, bodyFromFront);
// Si Angular demande output_format=JSON dans la query, on peut renvoyer JSON,
// sinon ce sera typiquement du XML. Dans tous les cas on renvoie le raw body.
MediaType mediaType = (query != null && query.contains("output_format=JSON"))
? MediaType.APPLICATION_JSON
: MediaType.APPLICATION_XML;
String responseBody = prestashopClient.post(path, query, xmlBody);
// Presta peut renvoyer JSON ou XML, mais côté front tu traites en JSON
return ResponseEntity
.ok()
.contentType(mediaType)
.body(body);
.contentType(MediaType.APPLICATION_JSON)
.body(responseBody);
}
// ---------- PUT ----------
/* ---------------- PUT ---------------- */
@PutMapping("/**")
public ResponseEntity<String> proxyPut(
HttpServletRequest request,
@RequestBody String bodyFromFront
) {
public ResponseEntity<String> proxyPut(HttpServletRequest request,
@RequestBody String xmlBody) {
String path = extractPath(request);
String query = request.getQueryString();
String body = prestashopClient.put(path, query, bodyFromFront);
MediaType mediaType = (query != null && query.contains("output_format=JSON"))
? MediaType.APPLICATION_JSON
: MediaType.APPLICATION_XML;
String responseBody = prestashopClient.put(path, query, xmlBody);
return ResponseEntity
.ok()
.contentType(mediaType)
.body(body);
.contentType(MediaType.APPLICATION_JSON)
.body(responseBody);
}
// ---------- DELETE ----------
/* ---------------- DELETE ---------------- */
@DeleteMapping("/**")
public ResponseEntity<Void> proxyDelete(HttpServletRequest request) {
public ResponseEntity<String> proxyDelete(HttpServletRequest request) {
String path = extractPath(request);
String query = request.getQueryString();
prestashopClient.delete(path, query);
String responseBody = prestashopClient.delete(path, query);
return ResponseEntity.noContent().build();
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(responseBody);
}
}

View File

@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@@ -29,50 +30,49 @@ public class PrestashopClient {
this.basicAuth = basicAuth;
}
/**
* Construit l'URL finale vers PrestaShop en propageant la query d'origine.
*
* @param path ex: "/products", "/products/446"
* @param query queryString brute reçue côté API (peut être null)
* @param addDefaultFormatAndDisplay si true, ajoute output_format=JSON et display=full
*/
private String buildUrl(String path, String query, boolean addDefaultFormatAndDisplay) {
/* ------------------------------------------------------------------
* Helpers communs
* ------------------------------------------------------------------ */
private HttpHeaders baseHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
return headers;
}
private String normalizePath(String path) {
if (path == null || path.isBlank()) {
return "/";
}
return path.startsWith("/") ? path : "/" + path;
}
/* ------------------------------------------------------------------
* GET : force JSON + display=full
* - on garde tous les autres paramètres (filter[..], limit, etc.)
* - on écrase/normalise seulement output_format et display
* => équivalent à ce que tu faisais en front avec output_format=JSON&display=full
* ------------------------------------------------------------------ */
public String get(String path, String query) {
String normalizedPath = normalizePath(path);
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path);
.path(normalizedPath);
// On propage TOUT ce que le front a mis (display, filter[...], etc.)
if (query != null && !query.isBlank()) {
builder.query(query);
builder.query(query); // on garde tous les filtres venus du front
}
if (addDefaultFormatAndDisplay) {
// Si le front n'a pas mis output_format, on force JSON
if (query == null || !query.contains("output_format=")) {
builder.queryParam("output_format", "JSON");
}
// Si le front n'a pas mis display, on force full
if (query == null || !query.contains("display=")) {
builder.queryParam("display", "full");
}
}
return builder.build(true).toUriString();
}
/**
* GET générique vers PrestaShop.
* - Propage la queryString Angular (display, filter[...], etc.)
* - Ajoute output_format=JSON & display=full si absents.
*/
public String get(String path, String query) {
String url = buildUrl(path, query, true);
// on force ces deux-là pour corriger les soucis des crochets + id seuls
builder.replaceQueryParam("output_format", "JSON");
builder.replaceQueryParam("display", "full");
String url = builder.build(true).toUriString();
log.info("[PrestaShop] GET {}", url);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
HttpHeaders headers = baseHeaders();
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
HttpEntity<Void> entity = new HttpEntity<>(headers);
@@ -85,34 +85,50 @@ public class PrestashopClient {
String.class
);
log.info("[PrestaShop] Réponse GET {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("PrestaShop returned non-2xx status on GET: "
throw new RuntimeException("PrestaShop returned non-2xx status: "
+ response.getStatusCode() + " for URL " + url);
}
return response.getBody();
} catch (HttpStatusCodeException e) {
log.error("[PrestaShop] Erreur GET {} status={} body={}",
url, e.getStatusCode(), e.getResponseBodyAsString(), e);
throw new RuntimeException("Erreur GET PrestaShop", e);
} catch (RestClientException e) {
log.error("[PrestaShop] Erreur GET {}", url, e);
throw new RuntimeException("Erreur GET PrestaShop", e);
}
}
/**
* POST générique vers PrestaShop.
* On propage la query telle quelle (si Angular met output_format=JSON, etc.).
* Le body est du XML (templates construits côté Angular).
*/
public String post(String path, String query, String body) {
String url = buildUrl(path, query, false);
/* ------------------------------------------------------------------
* POST : XML vers Presta
* - on transmet la query EXACTE venant du front (price[use_tax], filter, etc.)
* - PAS de output_format / display forcés ici
* ------------------------------------------------------------------ */
public String post(String path, String query, String xmlBody) {
String normalizedPath = normalizePath(path);
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(normalizedPath);
if (query != null && !query.isBlank()) {
builder.query(query);
}
String url = builder.build(true).toUriString();
log.info("[PrestaShop] POST {}", url);
log.debug("[PrestaShop] POST body=\n{}", xmlBody);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
headers.setContentType(MediaType.APPLICATION_XML);
HttpHeaders headers = baseHeaders();
headers.setContentType(MediaType.APPLICATION_XML); // Presta attend du XML sur POST/PUT
headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML));
HttpEntity<String> entity = new HttpEntity<>(body, headers);
HttpEntity<String> entity = new HttpEntity<>(xmlBody, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
@@ -122,34 +138,50 @@ public class PrestashopClient {
String.class
);
log.info("[PrestaShop] Réponse POST {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("PrestaShop returned non-2xx status on POST: "
throw new RuntimeException("PrestaShop returned non-2xx status: "
+ response.getStatusCode() + " for URL " + url
+ " - response: " + response.getBody());
+ " body=" + response.getBody());
}
return response.getBody();
} catch (HttpStatusCodeException e) {
log.error("[PrestaShop] Erreur POST {} status={} body={}",
url, e.getStatusCode(), e.getResponseBodyAsString(), e);
throw new RuntimeException("Erreur POST PrestaShop", e);
} catch (RestClientException e) {
log.error("[PrestaShop] Erreur POST {}", url, e);
throw new RuntimeException("Erreur POST PrestaShop", e);
}
}
/**
* PUT générique vers PrestaShop.
* Le body est du XML (templates construits côté Angular).
*/
public String put(String path, String query, String body) {
String url = buildUrl(path, query, false);
/* ------------------------------------------------------------------
* PUT : XML vers Presta
* - pareil que POST : on garde la query telle quelle, pas de display=full ici
* ------------------------------------------------------------------ */
public String put(String path, String query, String xmlBody) {
String normalizedPath = normalizePath(path);
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(normalizedPath);
if (query != null && !query.isBlank()) {
builder.query(query);
}
String url = builder.build(true).toUriString();
log.info("[PrestaShop] PUT {}", url);
log.debug("[PrestaShop] PUT body=\n{}", xmlBody);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
HttpHeaders headers = baseHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML));
HttpEntity<String> entity = new HttpEntity<>(body, headers);
HttpEntity<String> entity = new HttpEntity<>(xmlBody, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
@@ -159,29 +191,46 @@ public class PrestashopClient {
String.class
);
log.info("[PrestaShop] Réponse PUT {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("PrestaShop returned non-2xx status on PUT: "
throw new RuntimeException("PrestaShop returned non-2xx status: "
+ response.getStatusCode() + " for URL " + url
+ " - response: " + response.getBody());
+ " body=" + response.getBody());
}
return response.getBody();
} catch (HttpStatusCodeException e) {
log.error("[PrestaShop] Erreur PUT {} status={} body={}",
url, e.getStatusCode(), e.getResponseBodyAsString(), e);
throw new RuntimeException("Erreur PUT PrestaShop", e);
} catch (RestClientException e) {
log.error("[PrestaShop] Erreur PUT {}", url, e);
throw new RuntimeException("Erreur PUT PrestaShop", e);
}
}
/**
* DELETE générique vers PrestaShop.
*/
public void delete(String path, String query) {
String url = buildUrl(path, query, false);
/* ------------------------------------------------------------------
* DELETE
* - idem : on relaie la query telle quelle
* ------------------------------------------------------------------ */
public String delete(String path, String query) {
String normalizedPath = normalizePath(path);
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(normalizedPath);
if (query != null && !query.isBlank()) {
builder.query(query);
}
String url = builder.build(true).toUriString();
log.info("[PrestaShop] DELETE {}", url);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
HttpHeaders headers = baseHeaders();
headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML));
HttpEntity<Void> entity = new HttpEntity<>(headers);
@@ -193,11 +242,19 @@ public class PrestashopClient {
String.class
);
log.info("[PrestaShop] Réponse DELETE {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("PrestaShop returned non-2xx status on DELETE: "
throw new RuntimeException("PrestaShop returned non-2xx status: "
+ response.getStatusCode() + " for URL " + url
+ " - response: " + response.getBody());
+ " body=" + response.getBody());
}
return response.getBody();
} catch (HttpStatusCodeException e) {
log.error("[PrestaShop] Erreur DELETE {} status={} body={}",
url, e.getStatusCode(), e.getResponseBodyAsString(), e);
throw new RuntimeException("Erreur DELETE PrestaShop", e);
} catch (RestClientException e) {
log.error("[PrestaShop] Erreur DELETE {}", url, e);
throw new RuntimeException("Erreur DELETE PrestaShop", e);