URLEncoder não consegue converter caracteres de espaço

179

Estou esperando

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

para saída:

Hello%20World

(20 é código hexadecimal ASCII para espaço)

No entanto, o que recebo é:

Hello+World

Estou usando o método errado? Qual é o método correto que devo usar?

Cheok Yan Cheng
fonte
3
o nome da classe é realmente confuso, e muitas pessoas o usaram incorretamente. no entanto, eles não percebem, porque quando URLDecoder é aplicado, o valor original é restaurado; portanto, + ou% 20 realmente não importa para eles.
irreputable

Respostas:

227

Isso se comporta conforme o esperado. oURLEncoder implementa as especificações HTML para saber como codificar URLs em formulários HTML.

Dos javadocs :

Esta classe contém métodos estáticos para converter uma String no formato MIME application / x-www-form-urlencoded.

e da especificação HTML :

application / x-www-form-urlencoded

Os formulários enviados com esse tipo de conteúdo devem ser codificados da seguinte maneira:

  1. Os nomes e valores de controle são escapados. Caracteres de espaço são substituídos por `+ '

Você precisará substituí-lo, por exemplo:

System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("+", "%20"));
dogbane
fonte
19
bem Esta é realmente uma resposta, ao invés de substituir, não há uma biblioteca java ou uma função para executar a tarefa /?
co2f2e
5
As necessidades mais sinal a ser escapout.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("\\+", "%20"));
George
26
@congliu que está incorreto - você provavelmente está pensando em replaceAll () que funciona com regex - replace () é uma simples substituição de sequência de caracteres.
CupawnTae
12
Sim @congliu, o bom é: URLEncoder.encode ("Myurl", "utf-8"). ReplaceAll ("\\ +", "% 20");
eento
9
@ClintEastwood Esta resposta incentiva o uso do java.net.URLEncoder, que não funciona do que foi originalmente solicitado. E, portanto, esta resposta sugere um patch, usando replace (), em cima dele. Por que não? Porque esta solução é propensa a erros e pode levar a outras 20 questões semelhantes, mas com um caráter diferente. Por isso disse que isso era míope.
PYB
57

Um espaço é codificado %20em URLs e +em dados enviados por formulários (tipo de conteúdo application / x-www-form-urlencoded). Você precisa do primeiro.

Usando a goiaba :

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

Você pode usar UrlEscapers :

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

Não use String.replace, isso codificaria apenas o espaço. Use uma biblioteca.

pyb
fonte
Também funciona para Android, com.google.guava: goiaba: 22.0-rc1-android.
Bevor
1
@Bevor rc1 significa Candidato à 1ª Versão, ou seja, uma versão ainda não aprovada para liberação geral. Se você puder, escolha uma versão sem snapshot, alpha, beta, rc, pois eles são conhecidos por conter bugs.
PYB
1
@pyb Obrigado, mas atualizarei as bibliotecas de qualquer maneira quando meu projeto for concluído. Significa, não irei produzir sem as versões finais. E ainda leva muitas semanas, então acho que existe uma versão final.
Bevor
1
Infelizmente, o Guava não fornece um decodificador, ao contrário do URLCodec do Apache .
9118 Benny Bottema
26

Essa classe executa application/x-www-form-urlencodedcodificação de tipo em vez de codificação de porcentagem, portanto, substituir por +é um comportamento correto.

Partida javadoc:

Ao codificar uma String, as seguintes regras se aplicam:

  • Os caracteres alfanuméricos "a" a "z", "A" a "Z" e "0" a "9" permanecem os mesmos.
  • Os caracteres especiais ".", "-", "*" e "_" permanecem os mesmos.
  • O caractere de espaço "" é convertido em um sinal de mais "+".
  • Todos os outros caracteres não são seguros e são primeiro convertidos em um ou mais bytes usando algum esquema de codificação. Cada byte é representado pela sequência de três caracteres "% xy", em que xy é a representação hexadecimal de dois dígitos do byte. O esquema de codificação recomendado para usar é UTF-8. No entanto, por motivos de compatibilidade, se uma codificação não for especificada, a codificação padrão da plataforma será usada.
axtavt
fonte
@axtavt Boa explicação. Mas ainda tenho algumas perguntas. No url, o espaço deve ser interpretado como %20. Então, precisamos fazer url.replaceAll("\\+", "%20")? E se for javascript, não devemos usar a escapefunção Use encodeURIou em encodeURIComponentvez disso. Isso foi o que eu pensei.
Alston
1
@Stallman é Java, não JavaScript. Idiomas totalmente diferentes.
Charles Wood
19

Parâmetros de consulta de codificação

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

OU se você quiser escapar de caracteres dentro do 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
fonte
3
Usar org.apache.commons.httpclient.util.URIUtilparece ser a maneira mais eficiente de resolver o problema!
Stéphane Ammar
11

Hello+Worldé como um navegador codifica os dados do formulário ( application/x-www-form-urlencoded) para uma GETsolicitação e esse é o formulário geralmente aceito para a parte da consulta de um URI.

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

Se você enviou essa solicitação a um servlet Java, o servlet decodificaria corretamente o valor do parâmetro. Normalmente, a única vez que há problemas aqui é se a codificação não corresponder.

A rigor, não há exigência nas especificações HTTP ou URI de que a parte da consulta seja codificada usando application/x-www-form-urlencodedpares de valores-chave; a parte da consulta precisa estar no formato que o servidor da web aceita. Na prática, é improvável que isso seja um problema.

Geralmente, seria incorreto usar essa codificação para outras partes do URI (o caminho, por exemplo). Nesse caso, você deve usar o esquema de codificação conforme descrito na RFC 3986 .

http://host/Hello%20World

Mais aqui .

McDowell
fonte
5

As outras respostas apresentam uma substituição manual de cadeias de caracteres, o URLEncoder , que realmente codifica para o formato HTML, o URIUtil abandonado do Apache ou usando os UrlEscapers da Guava . O último está bom, exceto que não fornece um decodificador.

O Apache Commons Lang fornece o URLCodec , que codifica e decodifica de acordo com o formato de URL rfc3986 .

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

Se você já usa o Spring, também pode optar por usar a classe UriUtils .

Benny Bottema
fonte
6
O URLCodec não é uma boa solução aqui, porque codifica espaços como vantagens, mas a pergunta é solicitar que os espaços sejam codificados como% 20.
precisa saber é o seguinte
3

"+" está correto. Se você realmente precisar de% 20, substitua os plusses depois.

Daniel
fonte
5
Pode haver um problema se a sequência inicial realmente contiver um caractere +.
Alexis Dufrenoy
17
@Traroth - Na verdade não. Um +caractere no texto original deve ser codificado como %2B.
Ted Hopp
dizer que +está correto sem conhecer o contexto é, pelo menos, pedante. Votado. Leia outras respostas para saber quando + ou% 20 deve ser usado.
Clint Eastwood
@ClintEastwood: Você pode me contar sobre qualquer caso de uso, pois o caractere + para espaços não está correto nos URLs? Exceto quando existe um analisador de URL não conforme no outro lado?
1818 Daniel
@ Daniel com certeza, não dizendo "incorreto", mas inadequado? sim. As ferramentas de análise geralmente usam parâmetros de consulta com valores separados por um determinado caractere, por exemplo "+". Nesse caso, usar "+" em vez de "% 20" estaria errado. "+" é usado para escapar de espaços em um formulário, enquanto a "porcentagem de codificação" (também conhecida como codificação de URL) é mais orientada para URLs.
Clint Eastwood
2

Isso funcionou para mim

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

Embora bastante antigo, no entanto, uma resposta rápida:

O Spring fornece UriUtils - com isso você pode especificar como codificar e qual parte está relacionada a um URI, por exemplo

encodePathSegment
encodePort
encodeFragment
encodeUriVariables
....

Eu os uso porque já estamos usando o Spring, ou seja, nenhuma biblioteca adicional é necessária!

LeO
fonte
0

Confira a classe java.net.URI.

Fredrik Widerberg
fonte
0

Estou usando o método errado? Qual é o método correto que devo usar?

Sim, este método java.net.URLEncoder.encode não foi criado para converter "" para "20%" de acordo com a especificação ( fonte ).

O caractere de espaço "" é convertido em um sinal de mais "+".

Mesmo que este não seja o método correto, você pode modificá-lo para: System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replaceAll("\\+", "%20"));tenha um bom dia =).

Pregunton
fonte
Você está sugerindo usar um método que não seja adequado ( URLEncoder.encode) e corrigi-lo usando o replaceAllque só funcionaria nesse caso específico. Use a classe e o método corretos, veja outras respostas.
PYB
@pyb parece que você não entende o que escrevi. Eu nunca disse "eu sugiro usá-lo", eu disse "você pode". Por favor, leia e entenda antes de escrever.
Pregunton
Este é um site de perguntas e respostas, não um quadro de mensagens comum em que as pessoas conversam. Se você tiver comentários colaterais, use os comentários. Conversa mais longa? Use o chat. Não poste o código que você discorda como resposta. Leia e compreenda as regras deste site antes de contribuir e dar palestras a outros.
PYB
1
Estou votando de volta, porque a maioria das outras soluções fornece os mesmos conselhos. Não foram fornecidos "casos específicos" para provar que esse método estava errado. Usar o apache commons com blocos ou dependências try-catch é muito trabalhoso para um método que pode ser efetivamente corrigido com replaceAll.
Eugene Kartoyev 15/07
-2

USE MyUrlEncode.URLencoding (URL da string, String enc) para lidar com o 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
fonte
9
reinventar a roda, adicionar código super propenso a erros a uma base de código é quase sempre uma má decisão.
Clint Eastwood
-6

use o conjunto de caracteres " ISO-8859-1" para o URLEncoder

Akhil Sikri
fonte