Encodage URL Java des paramètres de chaîne de requête

710

Dis que j'ai une URL

http://example.com/query?q=

et j'ai une requête saisie par l'utilisateur telle que:

mot aléatoire £ 500 banque $

Je veux que le résultat soit une URL correctement encodée:

http://example.com/query?q=random%20word%20%A3500%20bank%20%24

Quelle est la meilleure façon d'y parvenir? J'ai essayé de URLEncodercréer des objets URI / URL mais aucun d'entre eux ne semble tout à fait correct.

user1277546
la source
25
Que voulez-vous dire par «aucun d'eux ne sort tout à fait bien»?
Mark Elliot
2
J'ai utilisé URI.create et remplacé les espaces par + dans querystring. Sur le site client, il a converti + en espaces lorsque j'ai sélectionné les chaînes de requête. Cela a fonctionné pour moi.
ND27
Duplication possible de Y a
Nick Grealy
Pourquoi vous attendez-vous à ce que $ soit codé en pourcentage?
jschnasse

Réponses:

1151

URLEncoderest la voie à suivre. Il vous suffit de garder à l'esprit pour encoder uniquement le nom et / ou la valeur du paramètre de chaîne de requête individuelle, pas l'URL entière, bien sûr pas le caractère séparateur de paramètre de chaîne de requête &ni le caractère séparateur nom-valeur de paramètre =.

String q = "random word £500 bank $";
String url = "https://example.com?q=" + URLEncoder.encode(q, StandardCharsets.UTF_8);

Notez que les espaces dans les paramètres de requête sont représentés par +, non %20, ce qui est valablement valide. Le %20est généralement utilisé pour représenter les espaces dans l'URI lui-même (la partie avant le caractère séparateur de chaîne de requête URI ?), pas dans la chaîne de requête (la partie après ?).

Notez également qu'il existe trois encode()méthodes. Un sans Charsetcomme deuxième argument et un autre avec Stringcomme deuxième argument qui lève une exception vérifiée. Celui sans Charsetargument est obsolète. Ne l'utilisez jamais et spécifiez toujours l' Charsetargument. Le javadoc recommande même explicitement d'utiliser le codage UTF-8, tel que prescrit par RFC3986 et W3C .

Tous les autres caractères ne sont pas sûrs et sont d'abord convertis en un ou plusieurs octets à l'aide d'un schéma de codage. Ensuite, chaque octet est représenté par la chaîne de 3 caractères "% xy", où xy est la représentation hexadécimale à deux chiffres de l'octet. Le schéma de codage recommandé à utiliser est UTF-8 . Cependant, pour des raisons de compatibilité, si un encodage n'est pas spécifié, alors l'encodage par défaut de la plateforme est utilisé.

Voir également:

BalusC
la source
Il peut y avoir 2 types de paramètres dans l'URL. Recherchez la chaîne (suivie de?) Et le paramètre de chemin (généralement une partie de l'URL elle-même). Alors, qu'en est-il des paramètres de chemin. URLEncoder produit + pour l'espace, même pour les paramètres de chemin. En fait, il ne gère rien d'autre que la chaîne de requête. En outre, ce comportement n'est pas synchronisé avec les serveurs de noeud js. Donc pour moi, cette classe est un gaspillage et ne peut pas être utilisée autrement que pour des scénarios très spécifiques / spéciaux.
sharadendu sinha
2
@sharadendusinha: tel que documenté et répondu, URLEncoderconcerne les application/x-www-form-urlencodedrègles de conformité des paramètres de requête encodés par URL . Les paramètres de chemin ne rentrent pas dans cette catégorie. Vous avez plutôt besoin d'un encodeur URI.
BalusC
Comme je l'avais prédit, cela se produirait ... les utilisateurs sont confus parce que le problème est évidemment que les gens doivent coder plus que la valeur du paramètre. C'est un cas très rare où il vous suffit de coder une valeur de paramètre. C'est pourquoi j'ai fourni ma réponse wiki "confuse" pour aider des gens comme @sharadendusinha.
Adam Gent
1
@WijaySharma: Parce que les caractères spécifiques aux URL seraient également encodés. Vous ne devez le faire que lorsque vous souhaitez transmettre l'URL entière en tant que paramètre de requête d'une autre URL.
BalusC
1
"+, pas% 20" est ce que j'avais besoin d'entendre. Merci beaucoup.
wetjosh
173

Je n'utiliserais pas URLEncoder. En plus d'être mal nommé ( URLEncodern'a rien à voir avec les URL), inefficace (il utilise un StringBufferau lieu de Builder et fait quelques autres choses qui sont lentes). C'est aussi beaucoup trop facile à visser.

Au lieu de cela , j'utiliser URIBuilderou de printemps org.springframework.web.util.UriUtils.encodeQueryou Apache CommonsHttpClient . La raison étant que vous devez échapper le nom des paramètres de la requête (c'est-à-dire la réponse de BalusC q) différemment de la valeur du paramètre.

Le seul inconvénient de ce qui précède (que j'ai découvert douloureusement) est que les URL ne sont pas un véritable sous-ensemble d'URI .

Exemple de code:

import org.apache.http.client.utils.URIBuilder;

URIBuilder ub = new URIBuilder("http://example.com/query");
ub.addParameter("q", "random word £500 bank \$");
String url = ub.toString();

// Result: http://example.com/query?q=random+word+%C2%A3500+bank+%24

Comme je ne fais que créer un lien vers d'autres réponses, j'ai marqué cela comme un wiki communautaire. N'hésitez pas à modifier.

Adam Gent
la source
2
Pourquoi cela n'a-t-il rien à voir avec les URL?
Luis Sep
15
@Luis: URLEncodercomme le dit son javadoc, il est destiné à coder les paramètres de la chaîne de requête conformément application/x-www-form-urlencodedà la description HTML: w3.org/TR/html4/interact/… . Certains utilisateurs le confondent / abusent pour encoder des URI entiers, comme le répondeur actuel l'a apparemment fait.
BalusC
8
@LuisSep en bref URLEncoder est destiné au codage pour la soumission de formulaire. Ce n'est pas pour s'échapper. Ce n'est pas exactement le même échappement que vous utiliseriez pour créer des URL à mettre dans votre page Web, mais il se trouve qu'il est suffisamment similaire pour que les gens en abusent. La seule fois où vous devriez utiliser URLEncoder est si vous écrivez un client HTTP (et même alors, il existe des options de codage bien supérieures).
Adam Gent
1
@BalusC " Certains utilisateurs le confondent / abusent pour encoder des URI entiers, comme le répondeur actuel l'a apparemment fait. ". Vous vous êtes trompé. Je n'ai jamais dit que j'avais foiré avec ça. Je viens d'en voir d'autres qui l'ont fait, qui sont les bugs que je dois corriger. La partie que j'ai ratée est que la classe URL Java acceptera les crochets non échappés mais pas la classe URI. Il y a beaucoup de façons de bousiller la construction d'URL et tout le monde n'est pas brillant comme vous. Je dirais que la plupart des utilisateurs qui recherchent SO pour URLEncoding sont probablement des utilisateurs " qui confondent / abusent " de l'URI qui s'échappe.
Adam Gent
1
La question n'était pas là-dessus, mais votre réponse implique cela.
BalusC
99

Vous devez d'abord créer un URI comme:

String urlStr = "http://www.example.com/CEREC® Materials & Accessories/IPS Empress® CAD.pdf"
URL url= new URL(urlStr);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());

Ensuite, convertissez cette Uri en chaîne ASCII:

urlStr=uri.toASCIIString();

Maintenant, votre chaîne d'URL est complètement codée.Nous avons d'abord effectué un simple codage d'URL, puis nous l'avons convertie en chaîne ASCII pour nous assurer qu'aucun caractère en dehors de US-ASCII ne reste dans la chaîne. C'est exactement ce que font les navigateurs.

M Abdul Sami
la source
7
Merci! C'est stupide que votre solution fonctionne, mais pas intégré URL.toURI().
user11153
2
Malheureusement, cela ne semble pas fonctionner avec "file: ///" (par exemple: "file: /// certains / répertoire / un fichier contenant des espaces.html"); il bombarde avec MalformedURLException dans "new URL ()"; une idée de comment résoudre ce problème?
ZioByte
Vous devez faire quelque chose comme ceci: String urlStr = " certains / répertoire / un fichier contenant des espaces.html"; URL url = nouvelle URL (urlStr); URI uri = new URI (url.getProtocol (), url.getUserInfo (), url.getHost (), url.getPort (), url.getPath (), url.getQuery (), url.getRef ()); urlStr = uri.toASCIIString (); urlStr.replace ("http: //", "fichier: ///"); Je ne l'ai pas testé, mais je pense que ça marchera .... :)
M Abdul Sami
1
@tibi, vous pouvez simplement utiliser la méthode uri.toString () pour la convertir en chaîne au lieu de chaîne Ascii.
M Abdul Sami
1
L'API avec laquelle je travaillais n'a pas accepté le +remplacement des espaces, mais a accepté le% 20, donc cette solution fonctionnait mieux que BalusC, merci!
Julian Honma
35

Guava 15 a maintenant ajouté un ensemble d'échappeurs d'URL simples .

Emmanuel Touzery
la source
1
Ceux-ci souffrent des mêmes règles d'échappement maladroites que URLEncoder.
2rs2ts
3
pas sûr qu'ils aient le problème. ils différencient par exemple "+" ou "% 20" pour échapper "" (param de forme ou param de chemin) qui URLEncoderne le fait pas.
Emmanuel Touzery
1
Cela a fonctionné pour moi.Je viens de remplacer l'appel à URLEncoder () pour appeler UrlEscapers.urlFragmentEscaper () et cela a fonctionné, pas clair si je devrais utiliser UrlEscapers.urlPathSegmentEscaper () à la place.
Paul Taylor
2
En fait, cela n'a pas fonctionné pour moi parce que contrairement à URLEncoder, il n'encode pas '+' il le laisse seul, le serveur décode '+' comme espace alors que si j'utilise URLEncoder '+' s sont convertis en% 2B et correctement décodés en +
Paul Taylor
2
Mise à jour du lien: UrlEscapers
mgaert
6

La bibliothèque de composants Apache Http fournit une option intéressante pour la création et le codage des paramètres de requête -

Avec HttpComponents 4.x, utilisez - URLEncodedUtils

Pour HttpClient 3.x - EncodingUtil

Sashi
la source
6

Voici une méthode que vous pouvez utiliser dans votre code pour convertir une chaîne d'URL et une carte de paramètres en une chaîne d'URL codée valide contenant les paramètres de requête.

String addQueryStringToUrlString(String url, final Map<Object, Object> parameters) throws UnsupportedEncodingException {
    if (parameters == null) {
        return url;
    }

    for (Map.Entry<Object, Object> parameter : parameters.entrySet()) {

        final String encodedKey = URLEncoder.encode(parameter.getKey().toString(), "UTF-8");
        final String encodedValue = URLEncoder.encode(parameter.getValue().toString(), "UTF-8");

        if (!url.contains("?")) {
            url += "?" + encodedKey + "=" + encodedValue;
        } else {
            url += "&" + encodedKey + "=" + encodedValue;
        }
    }

    return url;
}
Pastille
la source
6
URL url= new URL("http://example.com/query?q=random word £500 bank $");
URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
String correctEncodedURL=uri.toASCIIString(); 
System.out.println(correctEncodedURL);

Impressions

http://example.com/query?q=random%20word%20%C2%A3500%20bank%20$

Que se passe-t-il ici?

1. Fractionnez l'URL en parties structurelles. Utilisez java.net.URL pour cela.

2. Codez correctement chaque pièce structurelle!

3. Utilisez IDN.toASCII(putDomainNameHere)pour Punycode encoder le nom d'hôte!

4. Utilisez java.net.URI.toASCIIString()pour coder en pourcentage, unicode codé NFC - (mieux serait NFKC!). Pour plus d'informations, voir: Comment coder correctement cette URL

Dans certains cas, il est conseillé de vérifier si l'URL est déjà encodée . Remplacez également les espaces encodés '+' par des espaces encodés '% 20'.

Voici quelques exemples qui fonctionneront également correctement

{
      "in" : "http://نامه‌ای.com/",
     "out" : "http://xn--mgba3gch31f.com/"
},{
     "in" : "http://www.example.com/‥/foo",
     "out" : "http://www.example.com/%E2%80%A5/foo"
},{
     "in" : "http://search.barnesandnoble.com/booksearch/first book.pdf", 
     "out" : "http://search.barnesandnoble.com/booksearch/first%20book.pdf"
}, {
     "in" : "http://example.com/query?q=random word £500 bank $", 
     "out" : "http://example.com/query?q=random%20word%20%C2%A3500%20bank%20$"
}

La solution passe environ 100 des cas de test fournis par Web Plattform Tests .

jschnasse
la source
1

Dans Android, j'utiliserais ce code:

Uri myUI = Uri.parse ("http://example.com/query").buildUpon().appendQueryParameter("q","random word A3500 bank 24").build();

Uriest unandroid.net.Uri

Sharjeel Lasharie
la source
10
Cela n'utilise pas l'API Java standard. Veuillez donc spécifier la bibliothèque utilisée.
rmuller
1

Dans mon cas, j'avais juste besoin de passer toute l'URL et de coder uniquement la valeur de chaque paramètre. Je n'ai pas trouvé de code commun pour le faire, alors (!!) j'ai donc créé cette petite méthode pour faire le travail:

public static String encodeUrl(String url) throws Exception {
    if (url == null || !url.contains("?")) {
        return url;
    }

    List<String> list = new ArrayList<>();
    String rootUrl = url.split("\\?")[0] + "?";
    String paramsUrl = url.replace(rootUrl, "");
    List<String> paramsUrlList = Arrays.asList(paramsUrl.split("&"));
    for (String param : paramsUrlList) {
        if (param.contains("=")) {
            String key = param.split("=")[0];
            String value = param.replace(key + "=", "");
            list.add(key + "=" +  URLEncoder.encode(value, "UTF-8"));
        }
        else {
            list.add(param);
        }
    }

    return rootUrl + StringUtils.join(list, "&");
}

public static String decodeUrl(String url) throws Exception {
    return URLDecoder.decode(url, "UTF-8");
}

Il utilise org.apache.commons.lang3.StringUtils

Laurent
la source
-2
  1. Utilisez ceci: URLEncoder.encode (query, StandardCharsets.UTF_8.displayName ()); ou ceci: URLEncoder.encode (requête, "UTF-8");
  2. Vous pouvez utiliser le code suivant.

    String encodedUrl1 = UriUtils.encodeQuery(query, "UTF-8");//not change 
    String encodedUrl2 = URLEncoder.encode(query, "UTF-8");//changed
    String encodedUrl3 = URLEncoder.encode(query, StandardCharsets.UTF_8.displayName());//changed
    
    System.out.println("url1 " + encodedUrl1 + "\n" + "url2=" + encodedUrl2 + "\n" + "url3=" + encodedUrl3);
Xuelian Han
la source
4
Pas correcte. Vous devez encoder les noms et valeurs des paramètres séparément. Le codage de la chaîne de requête entière codera également les séparateurs =et &, ce qui n'est pas correct.
Marquis de Lorne