Refactor PrestashopClient and PrestashopProxyController to support raw query handling and simplify proxy GET requests
This commit is contained in:
@@ -1,15 +1,9 @@
|
|||||||
// File: src/main/java/fr/gameovergne/api/controller/PrestashopProxyController.java
|
|
||||||
package fr.gameovergne.api.controller;
|
package fr.gameovergne.api.controller;
|
||||||
|
|
||||||
import fr.gameovergne.api.service.PrestashopClient;
|
import fr.gameovergne.api.service.PrestashopClient;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/ps")
|
@RequestMapping("/api/ps")
|
||||||
@@ -21,37 +15,13 @@ public class PrestashopProxyController {
|
|||||||
this.prestashopClient = prestashopClient;
|
this.prestashopClient = prestashopClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/**")
|
@GetMapping("/{resource}")
|
||||||
public ResponseEntity<String> proxyGet(HttpServletRequest request) {
|
public ResponseEntity<String> proxyGet(
|
||||||
// Ex: fullPath = /api/ps/categories
|
@PathVariable String resource,
|
||||||
String fullPath = (String) request.getAttribute(
|
HttpServletRequest request
|
||||||
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
|
) {
|
||||||
String bestMatchPattern = (String) request.getAttribute(
|
String rawQuery = request.getQueryString(); // ex: "display=%5Bid,name,active%5D&output_format=JSON"
|
||||||
HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
|
String body = prestashopClient.getWithRawQuery(resource, rawQuery);
|
||||||
|
return ResponseEntity.ok(body);
|
||||||
String relativePath = new AntPathMatcher()
|
|
||||||
.extractPathWithinPattern(bestMatchPattern, fullPath);
|
|
||||||
|
|
||||||
String path = relativePath.isEmpty() ? "/" : "/" + relativePath;
|
|
||||||
|
|
||||||
// Query string brute, déjà encodée (display=%5Bid,name,active%5D&output_format=JSON)
|
|
||||||
String rawQuery = request.getQueryString();
|
|
||||||
|
|
||||||
var prestaResponse = prestashopClient.getWithRawQuery(path, rawQuery);
|
|
||||||
|
|
||||||
// Deux options :
|
|
||||||
// 1) Propager les headers d’origine (dont Content-Type) :
|
|
||||||
/*
|
|
||||||
return ResponseEntity
|
|
||||||
.status(prestaResponse.getStatusCode())
|
|
||||||
.headers(prestaResponse.getHeaders())
|
|
||||||
.body(prestaResponse.getBody());
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 2) Forcer JSON (si tu veux être sûr du Content-Type côté front) :
|
|
||||||
return ResponseEntity
|
|
||||||
.status(prestaResponse.getStatusCode())
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.body(prestaResponse.getBody());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,179 +1,88 @@
|
|||||||
// File: src/main/java/fr/gameovergne/api/service/PrestashopClient.java
|
|
||||||
package fr.gameovergne.api.service;
|
package fr.gameovergne.api.service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.http.MediaType;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
|
||||||
public class PrestashopClient {
|
public class PrestashopClient {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PrestashopClient.class);
|
||||||
|
|
||||||
private final RestClient client;
|
private final RestClient client;
|
||||||
private final String baseUrl;
|
private final String baseUrl;
|
||||||
|
private final String apiKey;
|
||||||
|
|
||||||
public PrestashopClient(
|
public PrestashopClient(
|
||||||
@Value("${prestashop.base-url}") String baseUrl,
|
@Value("${prestashop.api.base-url}") String baseUrl,
|
||||||
@Value("${prestashop.api-key}") String apiKey
|
@Value("${prestashop.api.key}") String apiKey
|
||||||
) {
|
) {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl; // ex: https://shop.gameovergne.fr/api
|
||||||
|
this.apiKey = apiKey;
|
||||||
String basicAuth = java.util.Base64.getEncoder()
|
|
||||||
.encodeToString((apiKey + ":").getBytes(StandardCharsets.UTF_8));
|
|
||||||
|
|
||||||
this.client = RestClient.builder()
|
|
||||||
.baseUrl(baseUrl)
|
|
||||||
.defaultHeaders(headers -> {
|
|
||||||
// Authentification PrestaShop via Basic Auth : key en user, mdp vide
|
|
||||||
headers.setBasicAuth(apiKey, "");
|
|
||||||
|
|
||||||
// IMPORTANT : User-Agent “neutre”
|
|
||||||
headers.set(HttpHeaders.USER_AGENT, "curl/8.10.1");
|
|
||||||
|
|
||||||
// Accept JSON
|
|
||||||
headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.ALL));
|
|
||||||
})
|
|
||||||
.build();
|
|
||||||
|
|
||||||
log.info("[PrestaShop] Base URL = {}", baseUrl);
|
log.info("[PrestaShop] Base URL = {}", baseUrl);
|
||||||
log.info("[PrestaShop] API key length = {}", apiKey.length());
|
log.info("[PrestaShop] API key length = {}", apiKey.length());
|
||||||
|
|
||||||
|
this.client = RestClient.builder()
|
||||||
|
.defaultHeaders(headers -> {
|
||||||
|
// PLUS de Basic Auth => on force l’auth par ws_key
|
||||||
|
headers.set(HttpHeaders.USER_AGENT, "curl/8.10.1");
|
||||||
|
headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.ALL));
|
||||||
|
})
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construit l’URL complète en encodant proprement les query params.
|
* Appel "proxy" brut : on reçoit la query string telle quelle et on injecte ws_key proprement.
|
||||||
* Utilisé par les services "admin" (listSimple, listProducts, etc.).
|
|
||||||
*/
|
*/
|
||||||
private String buildUri(String path, MultiValueMap<String, String> params) {
|
public String getWithRawQuery(String resource, String rawQuery) {
|
||||||
StringBuilder sb = new StringBuilder();
|
try {
|
||||||
sb.append(baseUrl);
|
// resource = "categories", "products", ...
|
||||||
|
StringBuilder fullUrl = new StringBuilder();
|
||||||
if (path != null && !path.isEmpty()) {
|
fullUrl.append(baseUrl);
|
||||||
if (path.charAt(0) == '/') sb.append(path);
|
if (!baseUrl.endsWith("/")) {
|
||||||
else sb.append('/').append(path);
|
fullUrl.append("/");
|
||||||
}
|
}
|
||||||
|
fullUrl.append(resource);
|
||||||
|
|
||||||
if (params != null && !params.isEmpty()) {
|
// On construit la query à la main, sans que Spring y touche
|
||||||
boolean first = true;
|
String encodedKey = URLEncoder.encode(apiKey, StandardCharsets.UTF_8);
|
||||||
for (Map.Entry<String, List<String>> entry : params.entrySet()) {
|
fullUrl.append("?ws_key=").append(encodedKey);
|
||||||
String key = entry.getKey();
|
|
||||||
List<String> values = entry.getValue();
|
|
||||||
if (values == null || values.isEmpty()) continue;
|
|
||||||
|
|
||||||
for (String rawValue : values) {
|
|
||||||
if (first) {
|
|
||||||
sb.append('?');
|
|
||||||
first = false;
|
|
||||||
} else sb.append('&');
|
|
||||||
|
|
||||||
// encode la clé
|
|
||||||
sb.append(URLEncoder.encode(key, StandardCharsets.UTF_8));
|
|
||||||
sb.append('=');
|
|
||||||
|
|
||||||
// on encode seulement les parties sensibles, PAS les crochets ni virgules
|
|
||||||
String safeValue = rawValue
|
|
||||||
.replace("[", "%5B")
|
|
||||||
.replace("]", "%5D")
|
|
||||||
.replace(" ", "")
|
|
||||||
.replace(",", ","); // laisse la virgule brute
|
|
||||||
|
|
||||||
sb.append(safeValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String finalUri = sb.toString();
|
|
||||||
log.info("[PrestaShop] built URI = {}", finalUri);
|
|
||||||
return finalUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJson(String path, MultiValueMap<String, String> params) {
|
|
||||||
String uri = buildUri(path, params);
|
|
||||||
log.info("[PrestaShop] GET JSON {}", uri);
|
|
||||||
return client.get()
|
|
||||||
.uri(uri)
|
|
||||||
.accept(MediaType.APPLICATION_JSON)
|
|
||||||
.retrieve()
|
|
||||||
.body(String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getXml(String path, MultiValueMap<String, String> params) {
|
|
||||||
String uri = buildUri(path, params);
|
|
||||||
log.info("[PrestaShop] GET XML {}", uri);
|
|
||||||
return client.get()
|
|
||||||
.uri(uri)
|
|
||||||
.accept(MediaType.APPLICATION_XML)
|
|
||||||
.retrieve()
|
|
||||||
.body(String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String postXml(String path, MultiValueMap<String, String> params, String xmlBody) {
|
|
||||||
String uri = buildUri(path, params);
|
|
||||||
log.info("[PrestaShop] POST XML {}", uri);
|
|
||||||
return client.post()
|
|
||||||
.uri(uri)
|
|
||||||
.contentType(MediaType.APPLICATION_XML)
|
|
||||||
.body(xmlBody)
|
|
||||||
.retrieve()
|
|
||||||
.body(String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String putXml(String path, MultiValueMap<String, String> params, String xmlBody) {
|
|
||||||
String uri = buildUri(path, params);
|
|
||||||
log.info("[PrestaShop] PUT XML {}", uri);
|
|
||||||
return client.put()
|
|
||||||
.uri(uri)
|
|
||||||
.contentType(MediaType.APPLICATION_XML)
|
|
||||||
.body(xmlBody)
|
|
||||||
.retrieve()
|
|
||||||
.body(String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(String path, MultiValueMap<String, String> params) {
|
|
||||||
String uri = buildUri(path, params);
|
|
||||||
log.info("[PrestaShop] DELETE {}", uri);
|
|
||||||
client.delete()
|
|
||||||
.uri(uri)
|
|
||||||
.retrieve()
|
|
||||||
.toBodilessEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Méthode spéciale pour le proxy brut :
|
|
||||||
* - on reçoit la query string déjà encodée (rawQuery : "display=%5Bid,name,active%5D&output_format=JSON")
|
|
||||||
* - on NE REENCODE PAS cette query
|
|
||||||
* - on renvoie un ResponseEntity<String> complet (status + headers + body)
|
|
||||||
*/
|
|
||||||
public ResponseEntity<String> getWithRawQuery(String path, String rawQuery) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(baseUrl);
|
|
||||||
|
|
||||||
if (path != null && !path.isEmpty()) {
|
|
||||||
if (path.charAt(0) == '/') {
|
|
||||||
sb.append(path);
|
|
||||||
} else {
|
|
||||||
sb.append('/').append(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawQuery != null && !rawQuery.isBlank()) {
|
if (rawQuery != null && !rawQuery.isBlank()) {
|
||||||
sb.append('?').append(rawQuery); // déjà encodé
|
// ATTENTION : rawQuery vient de request.getQueryString() donc déjà encodée.
|
||||||
|
// On l’ajoute telle quelle pour ne pas casser les [%5B ... %5D]
|
||||||
|
fullUrl.append("&").append(rawQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
String uri = sb.toString();
|
String urlString = fullUrl.toString();
|
||||||
log.info("[PrestaShop] RAW GET {}", uri);
|
log.info("[PrestaShop] RAW GET via ws_key = {}", urlString);
|
||||||
|
|
||||||
return client.get()
|
return client.get()
|
||||||
.uri(uri)
|
.uri(URI.create(urlString)) // IMPORTANT : on donne l’URI complète => pas de ré-encodage
|
||||||
.accept(MediaType.APPLICATION_JSON)
|
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.toEntity(String.class);
|
.body(String.class);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[PrestaShop] getWithRawQuery error for resource={} rawQuery={}", resource, rawQuery, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Optionnel) pour tes autres méthodes listSimple/listProducts,
|
||||||
|
// tu peux aussi passer par ws_key plutôt que BasicAuth, même principe :
|
||||||
|
public String getJson(String resource, String fieldsQuery) {
|
||||||
|
// ex fieldsQuery = "display=[id,name,active]&output_format=JSON"
|
||||||
|
String rawQuery = fieldsQuery; // tu peux garder le nom si tu veux
|
||||||
|
return getWithRawQuery(resource, rawQuery);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user