Codificación de URL de Java de parámetros de cadena de consulta

710

Digamos que tengo una URL

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

y tengo una consulta ingresada por el usuario como:

palabra al azar £ 500 banco $

Quiero que el resultado sea una URL correctamente codificada:

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

¿Cuál es la mejor manera de lograr esto? Traté de URLEncodercrear objetos URI / URL pero ninguno de ellos salió del todo bien.

usuario1277546
fuente
25
¿Qué quiere decir con "ninguno de ellos sale del todo bien"?
Mark Elliot
2
He usado URI.create y he reemplazado espacios con + en querystring. En el sitio del cliente, se convirtió + de nuevo en espacios cuando seleccioné las cadenas de consulta. Eso ha funcionado para mi.
ND27
¿Por qué esperas que $ esté codificado en porcentaje?
jschnasse

Respuestas:

1151

URLEncoderEs el camino a seguir. Solo debe tener en cuenta para codificar solo el nombre y / o el valor del parámetro de la cadena de consulta individual, no la URL completa, con seguridad no el carácter separador del parámetro de la cadena de consulta &ni el carácter separador de nombre-valor del parámetro =.

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

Tenga en cuenta que los espacios en los parámetros de consulta están representados por +, no %20, que es legítimamente válido. El %20suele usarse para representar espacios en URI en sí (la parte antes del carácter URI-consulta cadena de separación ?), no en cadena de consulta (la parte que sigue? ).

También tenga en cuenta que hay tres encode()métodos. Uno sin un Charsetsegundo argumento y otro con un Stringsegundo argumento que arroja una excepción marcada. El que no tiene Charsetargumento está en desuso. Nunca lo use y siempre especifique el Charsetargumento. El javadoc incluso recomienda explícitamente utilizar la codificación UTF-8, según lo dispuesto por RFC3986 y W3C .

Todos los demás caracteres no son seguros y se convierten primero en uno o más bytes utilizando algún esquema de codificación. Luego, cada byte está representado por la cadena de 3 caracteres "% xy", donde xy es la representación hexadecimal de dos dígitos del byte. El esquema de codificación recomendado para usar es UTF-8 . Sin embargo, por razones de compatibilidad, si no se especifica una codificación, se utiliza la codificación predeterminada de la plataforma.

Ver también:

BalusC
fuente
Puede haber 2 tipos de parámetros en la URL. Cadena de consulta (seguida de?) Y parámetro de ruta (normalmente, parte de la URL). Entonces, ¿qué pasa con los parámetros de ruta? URLEncoder produce + para espacio incluso para parámetros de ruta. De hecho, no maneja nada más que una cadena de consulta. Además, este comportamiento no está sincronizado con los servidores del nodo js. Entonces, para mí, esta clase es un desperdicio y no se puede usar más que para escenarios muy específicos / especiales.
sharadendu sinha
2
@sharadendusinha: según lo documentado y respondido, URLEncoderes para parámetros de consulta codificados en URL que cumplen con las application/x-www-form-urlencodedreglas. Los parámetros de ruta no encajan en esta categoría. Necesita un codificador URI en su lugar.
BalusC
Como predije que sucedería ... los usuarios se confunden porque obviamente el problema es que las personas necesitan codificar más que solo el valor del parámetro. Es un caso muy raro que solo necesite codificar un valor de parámetro. Es por eso que proporcioné mi respuesta wiki "confundida" para ayudar a personas como @sharadendusinha.
Adam Gent
1
@WijaySharma: Porque los caracteres específicos de URL también se codificarían. Solo debe hacerlo cuando desee pasar la URL completa como parámetro de consulta de otra URL.
BalusC
1
"+, no% 20" es lo que necesitaba escuchar. Muchas gracias.
wetjosh
173

No lo usaría URLEncoder. Además de ser nombrado incorrectamente ( URLEncoderno tiene nada que ver con las URL), ineficiente (utiliza unStringBuffer lugar de Builder y hace un par de otras cosas que son lentas) También es demasiado fácil arruinarlo.

En cambio, usaría URIBuilder o primavera del org.springframework.web.util.UriUtils.encodeQueryo de los Comunes ApacheHttpClient . La razón es que tiene que escapar del nombre de los parámetros de consulta (es decir, la respuesta de BalusCq ) de manera diferente al valor del parámetro.

El único inconveniente de lo anterior (que descubrí dolorosamente) es que las URL no son un verdadero subconjunto de URI .

Código de muestra:

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

Como solo estoy enlazando a otras respuestas, marqué esto como wiki comunitario. Siéntase libre de editar.

Adam Gent
fuente
2
¿Por qué no tiene nada que ver con las URL?
Luis Sep
15
@Luis: URLEncoderes como su javadoc dice que tiene la intención de codificar los parámetros de la cadena de consulta conforme a application/x-www-form-urlencodedlo descrito en la especificación HTML: w3.org/TR/html4/interact/… . Algunos usuarios lo confunden / abusan para codificar URI completos, como aparentemente hizo el respondedor actual.
BalusC
8
En resumen, @LuisSep URLEncoder es para codificar el envío de formularios. No es para escapar. No es exactamente el mismo escape que usaría para crear URL para poner en su página web, pero resulta ser lo suficientemente similar como para que las personas abusen de él. El único momento en que debería usar URLEncoder es si está escribiendo un cliente HTTP (e incluso entonces hay opciones muy superiores para la codificación).
Adam Gent
1
@BalusC " Algunos usuarios lo confunden / abusan por codificar URI completos, como aparentemente hizo el respondedor actual ". Asumiste mal. Nunca dije que lo arruiné. Acabo de ver a otros que lo han hecho, y los errores que tengo que corregir. La parte que arruiné es que la clase URL de Java aceptará corchetes sin escape, pero no la clase URI. Hay muchas maneras de arruinar la construcción de URL y no todos son brillantes como tú. Diría que la mayoría de los usuarios que están buscando SO para URLEncoding probablemente son " usuarios que realmente confunden / abusan " del escape de URI.
Adam Gent
1
La pregunta no era sobre eso, pero su respuesta implica eso.
BalusC
99

Primero debe crear un URI como:

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());

Luego convierta ese Uri a una cadena ASCII:

urlStr=uri.toASCIIString();

Ahora su cadena de url está completamente codificada, primero hicimos una codificación de url simple y luego la convertimos en una cadena ASCII para asegurarnos de que ningún carácter fuera de US-ASCII permanezca en la cadena. Así es exactamente como lo hacen los navegadores.

M Abdul Sami
fuente
77
¡Gracias! Es una estupidez que su solución funcione, pero el incorporado URL.toURI()no.
user11153
2
Desafortunadamente, esto no parece funcionar con "file: ///" (por ejemplo: "file: /// some / directory / un archivo que contiene espacios.html"); bombardea con MalformedURLException en "nueva URL ()"; ¿Algúna idea de cómo arreglar esto?
ZioByte
Debe hacer algo como esto: String urlStr = " some / directory / a file with spaces.html"; URL url = nueva URL (urlStr); URI uri = nuevo URI (url.getProtocol (), url.getUserInfo (), url.getHost (), url.getPort (), url.getPath (), url.getQuery (), url.getRef ()); urlStr = uri.toASCIIString (); urlStr.replace ("http: //", "archivo: ///"); No lo he probado, pero creo que funcionará .... :)
M Abdul Sami
1
@tibi, simplemente puede usar el método uri.toString () para convertirlo en cadena en lugar de cadena Ascii.
M Abdul Sami
1
La API con la que estaba trabajando no aceptó el +reemplazo de espacios, pero aceptó el% 20, por lo que esta solución funcionó mejor que BalusC, ¡gracias!
Julian Honma
35

Guava 15 ahora ha agregado un conjunto de escapes de URL sencillos .

Emmanuel Touzery
fuente
1
Estos sufren las mismas reglas de escape tontas que URLEncoder.
2rs2ts
3
No estoy seguro de que tengan el problema. diferencian, por ejemplo, "+" o "% 20" para escapar "" (forma param o path param) que URLEncoderno lo hace.
Emmanuel Touzery
1
Esto funcionó para mí, solo reemplacé la llamada a URLEncoder () para llamar a UrlEscapers.urlFragmentEscaper () y funcionó, no está claro si debería usar UrlEscapers.urlPathSegmentEscaper () en su lugar.
Paul Taylor
2
En realidad no funcionó para mí porque, a diferencia de URLEncoder, no codifica '+', lo deja solo, el servidor decodifica '+' como espacio, mientras que si uso URLEncoder '+' se convierten a% 2B y se decodifican correctamente de nuevo a +
Paul Taylor
2
Actualización de enlace: UrlEscapers
mgaert
6

La biblioteca Apache Http Components proporciona una opción ordenada para construir y codificar parámetros de consulta:

Con el uso de HttpComponents 4.x - URLEncodedUtils

Para el uso de HttpClient 3.x - EncodingUtil

Sashi
fuente
6

Aquí hay un método que puede usar en su código para convertir una cadena de url y un mapa de parámetros en una cadena de url codificada válida que contenga los parámetros de consulta.

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;
}
Bolita
fuente
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);

Huellas dactilares

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

¿Que está sucediendo aquí?

1. Dividir URL en partes estructurales. Úselo java.net.URL para ello.

2. ¡ Codifique cada parte estructural correctamente!

3. Use IDN.toASCII(putDomainNameHere)para codificar Punycode el nombre del host!

4. Utilícelo java.net.URI.toASCIIString()para codificar en porcentaje, unicode codificado con NFC - (¡mejor sería NFKC!). Para obtener más información, consulte: Cómo codificar correctamente esta URL

En algunos casos es aconsejable verificar si la url ya está codificada . También reemplace los espacios codificados '+' con espacios codificados '% 20'.

Aquí hay algunos ejemplos que también funcionarán correctamente.

{
      "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 solución pasa alrededor de 100 de los casos de prueba proporcionados por Web Plattform Tests .

jschnasse
fuente
1

En Android usaría este código:

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

Donde Uries unandroid.net.Uri

Sharjeel Lasharie
fuente
10
Esto no está utilizando la API estándar de Java. Entonces, especifique la biblioteca utilizada.
rmuller
1

En mi caso, solo necesitaba pasar la url completa y codificar solo el valor de cada parámetro. No encontré un código común para hacerlo (¡!!) así que creé este pequeño método para hacer el trabajo:

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");
}

Utiliza org.apache.commons.lang3.StringUtils

Laurent
fuente
-2
  1. Utilice esto: URLEncoder.encode (query, StandardCharsets.UTF_8.displayName ()); o esto: URLEncoder.encode (consulta, "UTF-8");
  2. Puede usar el siguiente código.

    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
fuente
44
Incorrecto. Debe codificar los nombres y valores de los parámetros por separado. La codificación de toda la cadena de consulta también codificará los separadores =y &, lo que no es correcto.
Marqués de Lorne