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; this.prestashopClient = prestashopClient;
} }
/** // Utilitaire pour extraire /products, /products/446, etc.
* Extrait le path Presta à partir de la requête entrante.
* Exemple :
* - requestURI = /api/ps/products/446
* - retourne /products/446
*/
private String extractPath(HttpServletRequest request) { private String extractPath(HttpServletRequest request) {
String fullPath = request.getRequestURI(); // ex: /api/ps/products/446 String fullPath = request.getRequestURI(); // ex: /api/ps/products/446
String contextPath = request.getContextPath(); // souvent "" String contextPath = request.getContextPath(); // souvent ""
String relative = fullPath.substring(contextPath.length()); // /api/ps/products/446 String relative = fullPath.substring(contextPath.length()); // /api/ps/products/446
return relative.replaceFirst("^/api/ps", ""); // => /products/446
// On enlève le préfixe /api/ps => /products/446
return relative.replaceFirst("^/api/ps", "");
} }
// ---------- GET ---------- /* ---------------- GET ---------------- */
@GetMapping("/**") @GetMapping("/**")
public ResponseEntity<String> proxyGet(HttpServletRequest request) { public ResponseEntity<String> proxyGet(HttpServletRequest request) {
String path = extractPath(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); String body = prestashopClient.get(path, query);
// On sait qu'on force output_format=JSON côté client.get(...)
return ResponseEntity return ResponseEntity
.ok() .ok()
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.body(body); .body(body);
} }
// ---------- POST ---------- /* ---------------- POST ---------------- */
@PostMapping("/**") @PostMapping("/**")
public ResponseEntity<String> proxyPost( public ResponseEntity<String> proxyPost(HttpServletRequest request,
HttpServletRequest request, @RequestBody String xmlBody) {
@RequestBody String bodyFromFront
) {
String path = extractPath(request); String path = extractPath(request);
String query = request.getQueryString(); String query = request.getQueryString();
String body = prestashopClient.post(path, query, bodyFromFront); String responseBody = prestashopClient.post(path, query, xmlBody);
// 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;
// Presta peut renvoyer JSON ou XML, mais côté front tu traites en JSON
return ResponseEntity return ResponseEntity
.ok() .ok()
.contentType(mediaType) .contentType(MediaType.APPLICATION_JSON)
.body(body); .body(responseBody);
} }
// ---------- PUT ---------- /* ---------------- PUT ---------------- */
@PutMapping("/**") @PutMapping("/**")
public ResponseEntity<String> proxyPut( public ResponseEntity<String> proxyPut(HttpServletRequest request,
HttpServletRequest request, @RequestBody String xmlBody) {
@RequestBody String bodyFromFront
) {
String path = extractPath(request); String path = extractPath(request);
String query = request.getQueryString(); String query = request.getQueryString();
String body = prestashopClient.put(path, query, bodyFromFront); String responseBody = prestashopClient.put(path, query, xmlBody);
MediaType mediaType = (query != null && query.contains("output_format=JSON"))
? MediaType.APPLICATION_JSON
: MediaType.APPLICATION_XML;
return ResponseEntity return ResponseEntity
.ok() .ok()
.contentType(mediaType) .contentType(MediaType.APPLICATION_JSON)
.body(body); .body(responseBody);
} }
// ---------- DELETE ---------- /* ---------------- DELETE ---------------- */
@DeleteMapping("/**") @DeleteMapping("/**")
public ResponseEntity<Void> proxyDelete(HttpServletRequest request) { public ResponseEntity<String> proxyDelete(HttpServletRequest request) {
String path = extractPath(request); String path = extractPath(request);
String query = request.getQueryString(); 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.beans.factory.annotation.Value;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
@@ -29,50 +30,49 @@ public class PrestashopClient {
this.basicAuth = basicAuth; this.basicAuth = basicAuth;
} }
/** /* ------------------------------------------------------------------
* Construit l'URL finale vers PrestaShop en propageant la query d'origine. * Helpers communs
* * ------------------------------------------------------------------ */
* @param path ex: "/products", "/products/446"
* @param query queryString brute reçue côté API (peut être null) private HttpHeaders baseHeaders() {
* @param addDefaultFormatAndDisplay si true, ajoute output_format=JSON et display=full HttpHeaders headers = new HttpHeaders();
*/ headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
private String buildUrl(String path, String query, boolean addDefaultFormatAndDisplay) { 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 UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl) .fromHttpUrl(baseUrl)
.path("/api") .path("/api")
.path(path); .path(normalizedPath);
// On propage TOUT ce que le front a mis (display, filter[...], etc.)
if (query != null && !query.isBlank()) { if (query != null && !query.isBlank()) {
builder.query(query); builder.query(query); // on garde tous les filtres venus du front
} }
if (addDefaultFormatAndDisplay) { // on force ces deux-là pour corriger les soucis des crochets + id seuls
// Si le front n'a pas mis output_format, on force JSON builder.replaceQueryParam("output_format", "JSON");
if (query == null || !query.contains("output_format=")) { builder.replaceQueryParam("display", "full");
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);
String url = builder.build(true).toUriString();
log.info("[PrestaShop] GET {}", url); log.info("[PrestaShop] GET {}", url);
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = baseHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
headers.setAccept(List.of(MediaType.APPLICATION_JSON)); headers.setAccept(List.of(MediaType.APPLICATION_JSON));
HttpEntity<Void> entity = new HttpEntity<>(headers); HttpEntity<Void> entity = new HttpEntity<>(headers);
@@ -85,34 +85,50 @@ public class PrestashopClient {
String.class String.class
); );
log.info("[PrestaShop] Réponse GET {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) { 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); + response.getStatusCode() + " for URL " + url);
} }
return response.getBody(); 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) { } catch (RestClientException e) {
log.error("[PrestaShop] Erreur GET {}", url, e); log.error("[PrestaShop] Erreur GET {}", url, e);
throw new RuntimeException("Erreur GET PrestaShop", e); throw new RuntimeException("Erreur GET PrestaShop", e);
} }
} }
/** /* ------------------------------------------------------------------
* POST générique vers PrestaShop. * POST : XML vers Presta
* On propage la query telle quelle (si Angular met output_format=JSON, etc.). * - on transmet la query EXACTE venant du front (price[use_tax], filter, etc.)
* Le body est du XML (templates construits côté Angular). * - PAS de output_format / display forcés ici
*/ * ------------------------------------------------------------------ */
public String post(String path, String query, String body) { public String post(String path, String query, String xmlBody) {
String url = buildUrl(path, query, false); 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.info("[PrestaShop] POST {}", url);
log.debug("[PrestaShop] POST body=\n{}", xmlBody);
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = baseHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); headers.setContentType(MediaType.APPLICATION_XML); // Presta attend du XML sur POST/PUT
headers.setContentType(MediaType.APPLICATION_XML);
headers.setAccept(List.of(MediaType.APPLICATION_JSON, 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 { try {
ResponseEntity<String> response = restTemplate.exchange( ResponseEntity<String> response = restTemplate.exchange(
@@ -122,34 +138,50 @@ public class PrestashopClient {
String.class String.class
); );
log.info("[PrestaShop] Réponse POST {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) { 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.getStatusCode() + " for URL " + url
+ " - response: " + response.getBody()); + " body=" + response.getBody());
} }
return 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) { } catch (RestClientException e) {
log.error("[PrestaShop] Erreur POST {}", url, e); log.error("[PrestaShop] Erreur POST {}", url, e);
throw new RuntimeException("Erreur POST PrestaShop", e); throw new RuntimeException("Erreur POST PrestaShop", e);
} }
} }
/** /* ------------------------------------------------------------------
* PUT générique vers PrestaShop. * PUT : XML vers Presta
* Le body est du XML (templates construits côté Angular). * - pareil que POST : on garde la query telle quelle, pas de display=full ici
*/ * ------------------------------------------------------------------ */
public String put(String path, String query, String body) { public String put(String path, String query, String xmlBody) {
String url = buildUrl(path, query, false); 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.info("[PrestaShop] PUT {}", url);
log.debug("[PrestaShop] PUT body=\n{}", xmlBody);
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = baseHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
headers.setContentType(MediaType.APPLICATION_XML); headers.setContentType(MediaType.APPLICATION_XML);
headers.setAccept(List.of(MediaType.APPLICATION_JSON, 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 { try {
ResponseEntity<String> response = restTemplate.exchange( ResponseEntity<String> response = restTemplate.exchange(
@@ -159,29 +191,46 @@ public class PrestashopClient {
String.class String.class
); );
log.info("[PrestaShop] Réponse PUT {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) { 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.getStatusCode() + " for URL " + url
+ " - response: " + response.getBody()); + " body=" + response.getBody());
} }
return 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) { } catch (RestClientException e) {
log.error("[PrestaShop] Erreur PUT {}", url, e); log.error("[PrestaShop] Erreur PUT {}", url, e);
throw new RuntimeException("Erreur PUT PrestaShop", e); throw new RuntimeException("Erreur PUT PrestaShop", e);
} }
} }
/** /* ------------------------------------------------------------------
* DELETE générique vers PrestaShop. * DELETE
*/ * - idem : on relaie la query telle quelle
public void delete(String path, String query) { * ------------------------------------------------------------------ */
String url = buildUrl(path, query, false); 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); log.info("[PrestaShop] DELETE {}", url);
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = baseHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML));
HttpEntity<Void> entity = new HttpEntity<>(headers); HttpEntity<Void> entity = new HttpEntity<>(headers);
@@ -193,11 +242,19 @@ public class PrestashopClient {
String.class String.class
); );
log.info("[PrestaShop] Réponse DELETE {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) { 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.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) { } catch (RestClientException e) {
log.error("[PrestaShop] Erreur DELETE {}", url, e); log.error("[PrestaShop] Erreur DELETE {}", url, e);
throw new RuntimeException("Erreur DELETE PrestaShop", e); throw new RuntimeException("Erreur DELETE PrestaShop", e);