Refactor PrestashopClient to use HttpClient and simplify PrestashopProxyController

This commit is contained in:
Vincent Guillet
2025-11-25 18:55:16 +01:00
parent e839aae4dd
commit c7b9b68d42
4 changed files with 91 additions and 129 deletions

View File

@@ -100,15 +100,6 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -1,14 +0,0 @@
package fr.gameovergne.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class AppConfig {
@Bean
public WebClient prestashopWebClient(WebClient.Builder builder) {
return builder.build();
}
}

View File

@@ -1,69 +1,32 @@
package fr.gameovergne.api.controller.prestashop; package fr.gameovergne.api.controller.prestashop;
import org.springframework.beans.factory.annotation.Value; import fr.gameovergne.api.service.prestashop.PrestashopClient;
import org.springframework.http.HttpHeaders; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
@RestController @RestController
@RequestMapping("/api/ps") @RequestMapping("/api/ps")
public class PrestashopProxyController { public class PrestashopProxyController {
private final WebClient webClient; private final PrestashopClient prestashopClient;
@Value("${prestashop.api.base-url}") public PrestashopProxyController(PrestashopClient prestashopClient) {
private String prestaBaseUrl; this.prestashopClient = prestashopClient;
@Value("${prestashop.api.key}")
private String prestaApiKey; // ta clé déjà encodée en Base64
public PrestashopProxyController(WebClient prestashopWebClient) {
this.webClient = prestashopWebClient;
} }
// ----------- SUPPLIERS ----------- @GetMapping("/**")
@GetMapping("/suppliers") public ResponseEntity<String> proxyGet(HttpServletRequest request) {
public ResponseEntity<String> getSuppliers(@RequestParam MultiValueMap<String, String> params) { String fullPath = request.getRequestURI(); // ex: /api/ps/suppliers
return forwardGet("/suppliers", params); String contextPath = request.getContextPath(); // souvent ""
} String relative = fullPath.substring(contextPath.length()); // /api/ps/suppliers
// ----------- CATEGORIES ----------- // On enlève le préfixe /api/ps -> /suppliers
@GetMapping("/categories") String path = relative.replaceFirst("^/api/ps", "");
public ResponseEntity<String> getCategories(@RequestParam MultiValueMap<String, String> params) {
return forwardGet("/categories", params);
}
// ----------- MANUFACTURERS ----------- String query = request.getQueryString(); // ex: display=%5Bid,name,active%5D&output_format=JSON
@GetMapping("/manufacturers")
public ResponseEntity<String> getManufacturers(@RequestParam MultiValueMap<String, String> params) {
return forwardGet("/manufacturers", params);
}
// ----------- Méthode commune de forward ----------- return prestashopClient.get(path, query);
private ResponseEntity<String> forwardGet(String resourcePath, MultiValueMap<String, String> params) {
// IMPORTANT : build(false) => ne PAS ré-encoder les crochets
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(prestaBaseUrl + resourcePath);
params.forEach((key, values) -> values.forEach(v -> builder.queryParam(key, v)));
String targetUrl = builder.build(false).toUriString(); // false => pas de double encodage
Mono<ResponseEntity<String>> monoResponse = webClient
.get()
.uri(targetUrl)
.header(HttpHeaders.AUTHORIZATION, "Basic " + prestaApiKey)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(String.class);
// On bloque car ton contrôleur est synchrone (MVC)
return monoResponse.block();
} }
} }

View File

@@ -1,72 +1,94 @@
// FILE: api/src/main/java/fr/gameovergne/api/service/prestashop/PrestashopClient.java
package fr.gameovergne.api.service.prestashop; package fr.gameovergne.api.service.prestashop;
import org.slf4j.Logger;
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.HttpHeaders;
import org.springframework.stereotype.Component; import org.springframework.http.HttpStatus;
import org.springframework.web.client.HttpStatusCodeException; import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate; import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
@Component import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Map;
@Service
public class PrestashopClient { public class PrestashopClient {
private static final Logger log = LoggerFactory.getLogger(PrestashopClient.class); private final HttpClient httpClient = HttpClient.newHttpClient();
private final RestTemplate restTemplate = new RestTemplate(); private final String baseUrl;
private final String basicAuth; // valeur déjà encodée Base64 (sans le "Basic ")
@Value("${prestashop.base-url}") public PrestashopClient(
private String baseUrl; @Value("${prestashop.base-url}") String baseUrl,
@Value("${prestashop.basic-auth}") String basicAuth
@Value("${prestashop.basic-auth}") ) {
private String basicAuth; this.baseUrl = baseUrl;
this.basicAuth = basicAuth;
}
public ResponseEntity<String> get(String path, String query) { public ResponseEntity<String> get(String path, String query) {
// path : ex "/suppliers"
// query : ex "display=%5Bid,name,active%5D&output_format=JSON"
String url = buildUrl(path, query);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Basic " + basicAuth)
.GET()
.build();
try { try {
String url = baseUrl + "/api" + path; HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
UriComponentsBuilder builder = UriComponentsBuilder // On reconstruit une ResponseEntity en gardant le status PrestaShop
.fromHttpUrl(url); HttpHeaders springHeaders = new HttpHeaders();
for (Map.Entry<String, List<String>> entry : response.headers().map().entrySet()) {
springHeaders.put(entry.getKey(), entry.getValue());
}
return new ResponseEntity<>(response.body(), springHeaders,
HttpStatus.valueOf(response.statusCode()));
} catch (IOException | InterruptedException e) {
// En prod tu pourrais logger proprement
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\":\"Failed to call PrestaShop Webservice\"}");
}
}
private String buildUrl(String path, String query) {
StringBuilder sb = new StringBuilder();
// baseUrl, ex: https://shop.gameovergne.fr
sb.append(baseUrl);
if (baseUrl.endsWith("/")) {
sb.setLength(sb.length() - 1); // on enlève le / final si présent
}
// /api
sb.append("/api");
// path, ex: "/suppliers"
if (path != null && !path.isBlank()) {
if (!path.startsWith("/")) {
sb.append('/');
}
sb.append(path);
}
// query déjà encodée par le navigateur : NE PAS la ré-encoder !
if (query != null && !query.isBlank()) { if (query != null && !query.isBlank()) {
// on réinjecte TELS QUELS les query params venant du front sb.append('?').append(query);
builder.query(query);
} }
String finalUrl = builder.build(true).toUriString(); return sb.toString();
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", basicAuth);
headers.setAccept(MediaType.parseMediaTypes("application/json"));
HttpEntity<Void> entity = new HttpEntity<>(headers);
log.debug("[Presta] GET {}", finalUrl);
ResponseEntity<String> response = restTemplate.exchange(
finalUrl,
HttpMethod.GET,
entity,
String.class
);
log.debug("[Presta] status={} body={}", response.getStatusCode(), response.getBody());
return ResponseEntity.status(response.getStatusCode()).body(response.getBody());
} catch (HttpStatusCodeException e) {
// <-- ICI on garde le vrai status + body de Prestashop
log.error("[Presta] HTTP error {} body={}", e.getStatusCode(), e.getResponseBodyAsString());
return ResponseEntity
.status(e.getStatusCode())
.body(e.getResponseBodyAsString());
} catch (Exception e) {
log.error("[Presta] Unexpected error while calling Presta", e);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\":\"Unexpected error while calling Prestashop\"}");
}
} }
} }