Refactor PrestashopProxyController to enhance request forwarding and error handling

This commit is contained in:
Vincent Guillet
2025-11-26 14:46:37 +01:00
parent f9a9e81713
commit 8cdfab9596
3 changed files with 115 additions and 305 deletions

View File

@@ -1,101 +1,141 @@
package fr.gameovergne.api.controller.prestashop; package fr.gameovergne.api.controller.prestashop;
import fr.gameovergne.api.service.prestashop.PrestashopClient;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType; import org.slf4j.Logger;
import org.springframework.http.ResponseEntity; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping("/api/ps") @RequestMapping("/api/ps")
public class PrestashopProxyController { public class PrestashopProxyController {
private final PrestashopClient prestashopClient; private static final Logger log = LoggerFactory.getLogger(PrestashopProxyController.class);
public PrestashopProxyController(PrestashopClient prestashopClient) { private final RestTemplate restTemplate = new RestTemplate();
this.prestashopClient = prestashopClient;
private final String baseUrl; // ex : https://shop.gameovergne.fr
private final String basicAuth; // base64 SANS le "Basic "
public PrestashopProxyController(
@Value("${prestashop.base-url}") String baseUrl,
@Value("${prestashop.basic-auth}") String basicAuth
) {
this.baseUrl = baseUrl;
this.basicAuth = basicAuth;
} }
private String extractPath(HttpServletRequest request) { // =============== Helpers ===============
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 private String extractPath(HttpServletRequest request) {
// /api/ps/products/446 -> /products/446
String fullPath = request.getRequestURI();
String contextPath = request.getContextPath(); // souvent ""
String relative = fullPath.substring(contextPath.length());
return relative.replaceFirst("^/api/ps", ""); return relative.replaceFirst("^/api/ps", "");
} }
/* =========================== private String readBody(HttpServletRequest request) throws IOException {
* GET try (BufferedReader reader = request.getReader()) {
* =========================== */ return reader.lines().collect(Collectors.joining(System.lineSeparator()));
}
}
private ResponseEntity<String> forward(HttpServletRequest request,
HttpMethod method,
String bodyIfAny) {
String path = extractPath(request);
String rawQuery = request.getQueryString();
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path);
if (rawQuery != null && !rawQuery.isBlank()) {
builder.query(rawQuery); // on garde EXACTEMENT la query envoyée par Angular
}
String url = builder.build(true).toUriString();
log.info("[PrestaProxy] {} {}", method.name(), url);
HttpHeaders headers = new HttpHeaders();
// Auth Presta (cachée côté backend)
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
// On propage Accept/Content-Type du front pour garder le même comportement quen dev
String accept = request.getHeader(HttpHeaders.ACCEPT);
if (accept != null) {
headers.set(HttpHeaders.ACCEPT, accept);
}
String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
if (contentType != null) {
headers.set(HttpHeaders.CONTENT_TYPE, contentType);
}
HttpEntity<String> entity = new HttpEntity<>(bodyIfAny, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
url,
method,
entity,
String.class
);
// On renvoie EXACTEMENT le status + headers + body de Presta au front
return ResponseEntity
.status(response.getStatusCode())
.headers(response.getHeaders())
.body(response.getBody());
} catch (HttpStatusCodeException ex) {
// On propage aussi les erreurs Presta (4xx/5xx) sans les transformer en 500 Spring
HttpHeaders respHeaders = ex.getResponseHeaders() != null
? ex.getResponseHeaders()
: new HttpHeaders();
log.error("[PrestaProxy] {} {} -> HTTP {}",
method.name(), url, ex.getStatusCode().value());
return ResponseEntity
.status(ex.getStatusCode())
.headers(respHeaders)
.body(ex.getResponseBodyAsString());
}
}
// =============== Routes ===============
@GetMapping("/**") @GetMapping("/**")
public ResponseEntity<String> proxyGet(HttpServletRequest request) { public ResponseEntity<String> proxyGet(HttpServletRequest request) {
String path = extractPath(request); return forward(request, HttpMethod.GET, null);
String rawQuery = request.getQueryString(); // on laisse Angular décider des filtres
String body = prestashopClient.get(path, rawQuery);
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(body);
} }
/* ===========================
* POST
* =========================== */
@PostMapping("/**")
public ResponseEntity<String> proxyPost(
HttpServletRequest request,
@RequestBody String rawBody
) {
String path = extractPath(request);
String rawQuery = request.getQueryString();
String body = prestashopClient.post(path, rawQuery, rawBody);
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(body);
}
/* ===========================
* PUT
* =========================== */
@PutMapping("/**")
public ResponseEntity<String> proxyPut(
HttpServletRequest request,
@RequestBody String rawBody
) {
String path = extractPath(request);
String rawQuery = request.getQueryString();
String body = prestashopClient.put(path, rawQuery, rawBody);
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(body);
}
/* ===========================
* DELETE
* =========================== */
@DeleteMapping("/**") @DeleteMapping("/**")
public ResponseEntity<String> proxyDelete(HttpServletRequest request) { public ResponseEntity<String> proxyDelete(HttpServletRequest request) {
String path = extractPath(request); return forward(request, HttpMethod.DELETE, null);
String rawQuery = request.getQueryString(); }
String body = prestashopClient.delete(path, rawQuery); @PostMapping("/**")
public ResponseEntity<String> proxyPost(HttpServletRequest request) throws IOException {
String body = readBody(request); // XML ou JSON, on ne touche à rien
return forward(request, HttpMethod.POST, body);
}
return ResponseEntity @PutMapping("/**")
.ok() public ResponseEntity<String> proxyPut(HttpServletRequest request) throws IOException {
.contentType(MediaType.APPLICATION_JSON) String body = readBody(request);
.body(body); return forward(request, HttpMethod.PUT, body);
} }
} }

View File

@@ -1,230 +0,0 @@
package fr.gameovergne.api.service.prestashop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@Service
public class PrestashopClient {
private static final Logger log = LoggerFactory.getLogger(PrestashopClient.class);
private final RestTemplate restTemplate = new RestTemplate();
private final String baseUrl;
private final String basicAuth; // base64 SANS le "Basic "
public PrestashopClient(
@Value("${prestashop.base-url}") String baseUrl,
@Value("${prestashop.basic-auth}") String basicAuth
) {
this.baseUrl = baseUrl;
this.basicAuth = basicAuth;
}
/* ===========================
* GET (proxy)
* =========================== */
/**
* GET générique vers PrestaShop.
*
* - On part de la query envoyée par le front (filters, price[use_tax], ...).
* - On force/se remplace uniquement output_format=JSON et display=full.
*/
public String get(String path, @Nullable String rawQuery) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path);
// On remet la query EXACTEMENT comme envoyée par Angular
if (rawQuery != null && !rawQuery.isBlank()) {
builder.query(rawQuery);
}
// On force juste ces deux paramètres
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);
HttpEntity<Void> entity = new HttpEntity<>(headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.GET,
entity,
String.class
);
log.info("[PrestaShop] Réponse HTTP {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("PrestaShop returned non-2xx status: "
+ response.getStatusCode() + " for URL " + url);
}
return response.getBody();
} catch (RestClientException e) {
log.error("[PrestaShop] Erreur GET {}", url, e);
throw new RuntimeException("Erreur GET PrestaShop", e);
}
}
/* ===========================
* POST
* =========================== */
public String post(String path, @Nullable String rawQuery, String body) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path);
if (rawQuery != null && !rawQuery.isBlank()) {
builder.query(rawQuery);
}
// Pour POST, tu peux choisir de forcer output_format si tu veux la réponse en JSON
builder.replaceQueryParam("output_format", "JSON");
String url = builder.build(true).toUriString();
log.info("[PrestaShop] POST {}", url);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
headers.setContentType(MediaType.APPLICATION_XML); // Presta attend du XML
HttpEntity<String> entity = new HttpEntity<>(body, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
String.class
);
log.info("[PrestaShop] Réponse HTTP {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("PrestaShop returned non-2xx status: "
+ response.getStatusCode() + " for URL " + url);
}
return response.getBody();
} catch (RestClientException e) {
log.error("[PrestaShop] Erreur POST {}", url, e);
throw new RuntimeException("Erreur POST PrestaShop", e);
}
}
/* ===========================
* PUT
* =========================== */
public String put(String path, @Nullable String rawQuery, String body) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path);
if (rawQuery != null && !rawQuery.isBlank()) {
builder.query(rawQuery);
}
builder.replaceQueryParam("output_format", "JSON");
String url = builder.build(true).toUriString();
log.info("[PrestaShop] PUT {}", url);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<String> entity = new HttpEntity<>(body, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.PUT,
entity,
String.class
);
log.info("[PrestaShop] Réponse HTTP {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("PrestaShop returned non-2xx status: "
+ response.getStatusCode() + " for URL " + url);
}
return response.getBody();
} catch (RestClientException e) {
log.error("[PrestaShop] Erreur PUT {}", url, e);
throw new RuntimeException("Erreur PUT PrestaShop", e);
}
}
/* ===========================
* DELETE
* =========================== */
public String delete(String path, @Nullable String rawQuery) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path);
if (rawQuery != null && !rawQuery.isBlank()) {
builder.query(rawQuery);
}
builder.replaceQueryParam("output_format", "JSON");
String url = builder.build(true).toUriString();
log.info("[PrestaShop] DELETE {}", url);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
HttpEntity<Void> entity = new HttpEntity<>(headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.DELETE,
entity,
String.class
);
log.info("[PrestaShop] Réponse HTTP {} pour {}", response.getStatusCode(), url);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("PrestaShop returned non-2xx status: "
+ response.getStatusCode() + " for URL " + url);
}
return response.getBody();
} catch (RestClientException e) {
log.error("[PrestaShop] Erreur DELETE {}", url, e);
throw new RuntimeException("Erreur DELETE PrestaShop", e);
}
}
}

View File

@@ -1,5 +1,5 @@
export const environment = { export const environment = {
production: false, production: false,
apiUrl: 'http://localhost:3000/api', apiUrl: 'http://localhost:3000/api',
psUrl: '/ps' psUrl: 'http://localhost:3000/api/ps'
}; };