URLEncoder no puede traducir caracteres espaciales

179

Estoy esperando

System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8"));

a la salida:

Hello%20World

(20 es el código ASCII Hex para el espacio)

Sin embargo, lo que obtengo es:

Hello+World

¿Estoy usando el método incorrecto? ¿Cuál es el método correcto que debería usar?

Cheok Yan Cheng
fuente
3
el nombre de la clase es realmente confuso, y muchas personas lo han usado incorrectamente. sin embargo, no lo notan, porque cuando se aplica URLDecoder, se restaura el valor original, por lo que + o% 20 realmente no les importa.
irreputable

Respuestas:

227

Esto se comporta como se esperaba. losURLEncoder implementos de las especificaciones de HTML para cómo codificar las direcciones URL en formularios HTML.

De los javadocs :

Esta clase contiene métodos estáticos para convertir una cadena al formato MIME application / x-www-form-urlencoded.

y de la especificación HTML :

application / x-www-form-urlencoded

Los formularios enviados con este tipo de contenido deben codificarse de la siguiente manera:

  1. Los nombres y valores de control se escapan. Los caracteres de espacio se reemplazan por '+'

Tendrá que reemplazarlo, por ejemplo:

System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("+", "%20"));
dogbane
fuente
19
Bueno, esta es una respuesta, en lugar de reemplazar, ¿no hay una biblioteca de Java o una función para realizar la tarea?
co2f2e
55
El signo más necesita ser escapadot.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("\\+", "%20"));
George
26
@congliu eso es incorrecto: probablemente esté pensando en replaceAll () que funciona con expresiones regulares: replace () es un reemplazo simple de secuencia de caracteres.
CupawnTae
12
Sí @congliu, la buena manera es: URLEncoder.encode ("Myurl", "utf-8"). ReplaceAll ("\\ +", "% 20");
eento
9
@ClintEastwood Esta respuesta alienta el uso de java.net.URLEncoder, que no cumple con lo que se solicitó originalmente. Y entonces esta respuesta sugiere un parche, usando replace (), encima de él. Por qué no? Debido a que esta solución es propensa a errores y podría conducir a otras 20 preguntas similares pero con un carácter diferente. Por eso dije que esto era miope.
pyb
57

Se codifica un espacio %20en URL y+ en datos enviados de formularios (tipo de contenido application / x-www-form-urlencoded). Necesitas lo primero.

Usando guayaba :

dependencies {
     compile 'com.google.guava:guava:23.0'
     // or, for Android:
     compile 'com.google.guava:guava:23.0-android'
}

Puedes usar UrlEscapers :

String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);

No use String.replace, esto solo codificaría el espacio. Use una biblioteca en su lugar.

pyb
fuente
También funciona para Android, com.google.guava: guava: 22.0-rc1-android.
Bevor
1
@Bevor rc1 significa Candidato a la primera versión, es decir, una versión aún no aprobada para la versión general. Si puede, elija una versión sin instantánea, alfa, beta, rc, ya que se sabe que contienen errores.
pyb
1
@pyb Gracias, pero actualizaré las librerías de todos modos cuando mi proyecto esté terminado. Significa que no iré a pinchar sin las versiones finales. Y todavía lleva muchas semanas, así que supongo que hay una versión final.
Bevor
1
Desafortunadamente, Guava no proporciona un decodificador, a diferencia del URLCodec de Apache .
Benny Bottema
26

Esta clase realiza application/x-www-form-urlencodedcodificación de tipo en lugar de codificación porcentual, por lo tanto, reemplaza con+ es un comportamiento correcto.

De javadoc:

Al codificar una cadena, se aplican las siguientes reglas:

  • Los caracteres alfanuméricos "a" a "z", "A" a "Z" y "0" a "9" siguen siendo los mismos.
  • Los caracteres especiales ".", "-", "*" y "_" siguen siendo los mismos.
  • El carácter de espacio "" se convierte en un signo más "+".
  • Todos los demás caracteres no son seguros y se convierten primero en uno o más bytes usando 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.
axtavt
fuente
@axtavt Buena explicación. Pero aún tengo algunas preguntas. En el url, el espacio debe interpretarse como %20. ¿Entonces tenemos que hacer url.replaceAll("\\+", "%20")? Y si es javascript, no deberíamos usar la escapefunción. Uso encodeURIo en su encodeURIComponentlugar. Es lo que pensaba.
Alston
1
@Stallman, esto es Java, no JavaScript. Idiomas totalmente diferentes.
Charles Wood
19

Codificar parámetros de consulta

org.apache.commons.httpclient.util.URIUtil
    URIUtil.encodeQuery(input);

O si quieres escapar de los caracteres dentro de URI

public static String escapeURIPathParam(String input) {
  StringBuilder resultStr = new StringBuilder();
  for (char ch : input.toCharArray()) {
   if (isUnsafe(ch)) {
    resultStr.append('%');
    resultStr.append(toHex(ch / 16));
    resultStr.append(toHex(ch % 16));
   } else{
    resultStr.append(ch);
   }
  }
  return resultStr.toString();
 }

 private static char toHex(int ch) {
  return (char) (ch < 10 ? '0' + ch : 'A' + ch - 10);
 }

 private static boolean isUnsafe(char ch) {
  if (ch > 128 || ch < 0)
   return true;
  return " %$&+,/:;=?@<>#%".indexOf(ch) >= 0;
 }
fmucar
fuente
3
¡Usar org.apache.commons.httpclient.util.URIUtilparece ser la forma más eficiente de resolver el problema!
Stéphane Ammar
11

Hello+Worldes cómo un navegador codificará los datos del formulario ( application/x-www-form-urlencoded) para una GETsolicitud y este es el formulario generalmente aceptado para la parte de consulta de un URI.

http://host/path/?message=Hello+World

Si envió esta solicitud a un servlet Java, el servlet decodificará correctamente el valor del parámetro. Por lo general, la única vez que hay problemas aquí es si la codificación no coincide.

Estrictamente hablando, no hay ningún requisito en las especificaciones HTTP o URI de que la parte de la consulta se codifique utilizando application/x-www-form-urlencodedpares clave-valor; la parte de la consulta solo debe estar en la forma que acepta el servidor web. En la práctica, es poco probable que esto sea un problema.

En general, sería incorrecto utilizar esta codificación para otras partes del URI (la ruta, por ejemplo). En ese caso, debe usar el esquema de codificación como se describe en RFC 3986 .

http://host/Hello%20World

Más aquí .

McDowell
fuente
5

Las otras respuestas presentan un reemplazo de cadena manual, URLEncoder , que en realidad codifica para el formato HTML, el URIUtil abandonado de Apache , o el uso de UrlEscapers de Guava . El último está bien, excepto que no proporciona un decodificador.

Apache Commons Lang proporciona el URLCodec , que codifica y decodifica de acuerdo con el formato URL rfc3986 .

String encoded = new URLCodec().encode(str);
String decoded = new URLCodec().decode(str);

Si ya está utilizando Spring, también puede optar por usar su clase UriUtils .

Benny Bottema
fuente
66
URLCodec no es una buena solución aquí porque codifica espacios como ventajas, pero la pregunta es pedir que los espacios se codifiquen como% 20.
davidwebster48
3

"+" es correcto. Si realmente necesita% 20, reemplace los Plus usted mismo después.

Daniel
fuente
55
Puede haber un problema si la cadena inicial realmente contiene un carácter +.
Alexis Dufrenoy
17
@Traroth: en realidad no. Se +supone que un carácter en el texto original se codifica como %2B.
Ted Hopp
decir que +es correcto sin conocer el contexto es, al menos, pedante. Voto negativo Lea otras respuestas para saber cuándo se utilizará + o% 20.
Clint Eastwood
@ClintEastwood: ¿Puede contarme sobre algún caso de uso en el que el carácter + para espacios no sea correcto en las URL? ¿Excepto cuando hay un analizador de URL no conforme en el otro lado?
Daniel
@Daniel seguro, ¿no dice "incorrecto" pero inadecuado? si. Las herramientas de análisis a menudo usan parámetros de consulta con valores separados por un determinado carácter, por ejemplo "+". En ese caso, usar "+" en lugar de "% 20" estaría mal. "+" se utiliza para escapar espacios en un formulario, mientras que la "codificación porcentual" (también conocida como codificación URL) está más orientada a las URL.
Clint Eastwood
2

Esto funciono para mi

org.apache.catalina.util.URLEncoder ul = new org.apache.catalina.util.URLEncoder().encode("MY URL");
Hitesh Kumar
fuente
1

Aunque bastante viejo, sin embargo, una respuesta rápida:

Spring proporciona UriUtils: con esto puede especificar cómo codificar y qué parte está relacionada desde un URI, por ejemplo

encodePathSegment
encodePort
encodeFragment
encodeUriVariables
....

Los uso porque ya estamos usando Spring, es decir, ¡no se requiere una biblioteca adicional!

León
fuente
0

Echa un vistazo a la clase java.net.URI.

Fredrik Widerberg
fuente
0

¿Estoy usando el método incorrecto? ¿Cuál es el método correcto que debería usar?

Sí, este método java.net.URLEncoder.encode no se hizo para convertir "" a "20%" de acuerdo con las especificaciones ( fuente ).

El carácter de espacio "" se convierte en un signo más "+".

Incluso si este no es el método correcto, puede modificarlo para: System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replaceAll("\\+", "%20"));tener un buen día =).

Pregunton
fuente
Usted sugiere utilizar un método que no sea adecuado ( URLEncoder.encode) y parchearlo usando replaceAlllo que solo funcionaría en este caso específico. Use la clase y el método correctos en su lugar, vea otras respuestas.
pyb
Parece que @pyb no puedes entender lo que he escrito. Nunca dije "sugiero usarlo", dije "puedes". Lea y comprenda antes de escribir.
Pregunton
Este es un sitio web de preguntas y respuestas, no un tablero de mensajes normal donde la gente chatea. Si tiene comentarios secundarios, use los comentarios. ¿Hablar más? Usa el chat. No publique código con el que no esté de acuerdo como respuesta. Lea y comprenda las reglas de este sitio antes de contribuir y dar una conferencia a otros.
pyb
1
Lo vuelvo a votar porque la mayoría de las otras soluciones ofrecen el mismo consejo. No se proporcionaron "casos específicos" para demostrar que este método es incorrecto. Usar apache commons con bloques o dependencias try-catch es demasiado complicado para un método que se puede parchear efectivamente con replaceAll.
Eugene Kartoyev
-2

USE MyUrlEncode.URLencoding (String url, String enc) para manejar el problema

    public class MyUrlEncode {
    static BitSet dontNeedEncoding = null;
    static final int caseDiff = ('a' - 'A');
    static {
        dontNeedEncoding = new BitSet(256);
        int i;
        for (i = 'a'; i <= 'z'; i++) {
            dontNeedEncoding.set(i);
        }
        for (i = 'A'; i <= 'Z'; i++) {
            dontNeedEncoding.set(i);
        }
        for (i = '0'; i <= '9'; i++) {
            dontNeedEncoding.set(i);
        }
        dontNeedEncoding.set('-');
        dontNeedEncoding.set('_');
        dontNeedEncoding.set('.');
        dontNeedEncoding.set('*');
        dontNeedEncoding.set('&');
        dontNeedEncoding.set('=');
    }
    public static String char2Unicode(char c) {
        if(dontNeedEncoding.get(c)) {
            return String.valueOf(c);
        }
        StringBuffer resultBuffer = new StringBuffer();
        resultBuffer.append("%");
        char ch = Character.forDigit((c >> 4) & 0xF, 16);
            if (Character.isLetter(ch)) {
            ch -= caseDiff;
        }
        resultBuffer.append(ch);
            ch = Character.forDigit(c & 0xF, 16);
            if (Character.isLetter(ch)) {
            ch -= caseDiff;
        }
         resultBuffer.append(ch);
        return resultBuffer.toString();
    }
    private static String URLEncoding(String url,String enc) throws UnsupportedEncodingException {
        StringBuffer stringBuffer = new StringBuffer();
        if(!dontNeedEncoding.get('/')) {
            dontNeedEncoding.set('/');
        }
        if(!dontNeedEncoding.get(':')) {
            dontNeedEncoding.set(':');
        }
        byte [] buff = url.getBytes(enc);
        for (int i = 0; i < buff.length; i++) {
            stringBuffer.append(char2Unicode((char)buff[i]));
        }
        return stringBuffer.toString();
    }
    private static String URIEncoding(String uri , String enc) throws UnsupportedEncodingException { //对请求参数进行编码
        StringBuffer stringBuffer = new StringBuffer();
        if(dontNeedEncoding.get('/')) {
            dontNeedEncoding.clear('/');
        }
        if(dontNeedEncoding.get(':')) {
            dontNeedEncoding.clear(':');
        }
        byte [] buff = uri.getBytes(enc);
        for (int i = 0; i < buff.length; i++) {
            stringBuffer.append(char2Unicode((char)buff[i]));
        }
        return stringBuffer.toString();
    }

    public static String URLencoding(String url , String enc) throws UnsupportedEncodingException {
        int index = url.indexOf('?');
        StringBuffer result = new StringBuffer();
        if(index == -1) {
            result.append(URLEncoding(url, enc));
        }else {
            result.append(URLEncoding(url.substring(0 , index),enc));
            result.append("?");
            result.append(URIEncoding(url.substring(index+1),enc));
        }
        return result.toString();
    }

}
IloveIniesta
fuente
9
reinventar la rueda, agregar código súper propenso a errores a una base de código es casi siempre una mala decisión.
Clint Eastwood
-6

use el juego de caracteres " ISO-8859-1" para URLEncoder

Akhil Sikri
fuente