web-dev-qa-db-fra.com

'+' (signe plus) non encodé avec RestTemplate en utilisant l'url de chaîne, mais interprété comme '' (espace)

Nous passons de Java 8 à Java 11, et donc de Spring Boot 1.5.6 à 2.1.2. Nous avons remarqué que lors de l'utilisation de RestTemplate, le signe '+' n'est plus codé en '% 2B' (modifications par SPR-14828). Ce serait correct, car RFC3986 ne répertorie pas '+' en tant que caractère réservé, mais il est toujours interprété comme un '' (espace) lorsqu'il est reçu dans un point de terminaison Spring Boot.

Nous avons une requête de recherche qui peut prendre des horodatages facultatifs comme paramètres de requête. La requête ressemble à http://example.com/search?beforeTimestamp=2019-01-21T14:56:50%2B00:00.

Nous ne pouvons pas comprendre comment envoyer un signe plus codé, sans qu'il soit codé en double. Paramètre de requête 2019-01-21T14:56:50+00:00 serait interprété comme 2019-01-21T14:56:50 00:00. Si nous devions encoder le paramètre nous-mêmes (2019-01-21T14:56:50%2B00:00), il serait alors reçu et interprété comme 2019-01-21T14:56:50%252B00:00.

Une contrainte supplémentaire est, que nous voulons définir l'URL de base ailleurs, lors de la configuration du restTemplate, pas là où la requête est exécutée.

Sinon, existe-t-il un moyen de forcer "+" à ne pas être interprété comme "" par le point final?

J'ai écrit un court exemple montrant quelques façons d'obtenir un codage plus strict avec leurs inconvénients expliqués comme commentaires:

package com.example.clientandserver;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;

import Java.nio.charset.StandardCharsets;
import Java.util.HashMap;
import Java.util.Map;

@SpringBootApplication
@RestController
public class ClientAndServerApp implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(ClientAndServerApp.class, args);
    }

    @Override
    public void run(String... args) {
        String beforeTimestamp = "2019-01-21T14:56:50+00:00";

        // Previously - base url and raw params (encoded automatically). 
        // This worked in the earlier version of Spring Boot
        {
            RestTemplate restTemplate = new RestTemplateBuilder()
               .rootUri("http://localhost:8080").build();
            UriComponentsBuilder b = UriComponentsBuilder.fromPath("/search");
            if (beforeTimestamp != null) {
                b.queryParam("beforeTimestamp", beforeTimestamp);
            }
            restTemplate.getForEntity(b.toUriString(), Object.class);
            // Received: 2019-01-21T14:56:50 00:00
            //       Plus sign missing here ^
        }

        // Option 1 - no base url and encoding the param ourselves.
        {
            RestTemplate restTemplate = new RestTemplate();
            UriComponentsBuilder b = UriComponentsBuilder
                .fromHttpUrl("http://localhost:8080/search");
            if (beforeTimestamp != null) {
                b.queryParam(
                    "beforeTimestamp",
                    UriUtils.encode(beforeTimestamp, StandardCharsets.UTF_8)
                );
            }
            restTemplate.getForEntity(
                b.build(true).toUri(), Object.class
            ).getBody();
            // Received: 2019-01-21T14:56:50+00:00
        }

        // Option 2 - with templated base url, query parameter is not optional.
        {
            RestTemplate restTemplate = new RestTemplateBuilder()
                .rootUri("http://localhost:8080")
                .uriTemplateHandler(new DefaultUriBuilderFactory())
                .build();
            Map<String, String> params = new HashMap<>();
            params.put("beforeTimestamp", beforeTimestamp);
            restTemplate.getForEntity(
                "/search?beforeTimestamp={beforeTimestamp}",
                Object.class,
                params);
            // Received: 2019-01-21T14:56:50+00:00
        }
    }

    @GetMapping("/search")
    public void search(@RequestParam String beforeTimestamp) {
        System.out.println("Received: " + beforeTimestamp);
    }
}
20
Gregor Eesmaa

La question a également été discutée ici.

Encodage des variables URI sur RestTemplate [SPR-16202]

Une solution plus simple consiste à définir le mode de codage sur le générateur d'URI sur VALUES_ONLY.

    DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
    builderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
    RestTemplate restTemplate = new RestTemplateBuilder()
            .rootUri("http://localhost:8080")
            .uriTemplateHandler(builderFactory)
            .build();

Cela a produit le même résultat que l'utilisation de PlusEncodingInterceptor lors de l'utilisation des paramètres de requête.

0
Matt Garner