Codificação de URL Java dos parâmetros da string de consulta

710

Digamos que eu tenho um URL

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

e eu tenho uma consulta inserida pelo usuário, como:

palavra aleatória £ 500 bank $

Quero que o resultado seja um URL codificado corretamente:

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

Qual é a melhor maneira de conseguir isso? Eu tentei URLEncodercriar objetos URI / URL, mas nenhum deles saiu bem.

user1277546
fonte
25
O que você quer dizer com "nenhum deles sai muito bem"?
Mark Elliot
2
Eu usei URI.create e substitui espaços por + na querystring. No site do cliente, ele converteu + de volta para espaços quando selecionei as cadeias de consulta. Isso funcionou para mim.
ND27 17/06/2014
Por que você espera que $ seja codificado em porcentagem?
jschnasse

Respostas:

1151

URLEncoderé o caminho a percorrer. Você só precisa codificar apenas o nome e / ou o valor do parâmetro da sequência de consulta individual, e não o URL inteiro, com certeza não o caractere separador do parâmetro da sequência de consultas &nem o caractere separador nome-valor do parâmetro =.

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

Observe que os espaços nos parâmetros de consulta são representados por +, não %20, o que é legitimamente válido. O %20geralmente é para ser usado para representar espaços em si mesmo (a parte antes do caractere separador seqüência URI-consulta URI ?), e não na string de consulta (a parte depois ?).

Observe também que existem três encode()métodos. Um sem Charsetcomo segundo argumento e outro com Stringcomo segundo argumento que lança uma exceção verificada. O sem Charsetargumento está obsoleto. Nunca use e sempre especifique o Charsetargumento. O javadoc ainda recomenda explicitamente o uso da codificação UTF-8, conforme exigido pelo RFC3986 e W3C .

Todos os outros caracteres são inseguros 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.

Veja também:

BalusC
fonte
Pode haver 2 tipos de parâmetros no URL. Cadeia de caracteres de consulta (seguida por?) E parâmetro do caminho (normalmente parte da própria URL). Então, e os parâmetros do caminho. O URLEncoder produz + para o espaço, mesmo para os parâmetros do caminho. Na verdade, ele simplesmente não lida com nada além de string de consulta. Além disso, esse comportamento não está sincronizado com os servidores js do nó. Então, para mim, essa classe é um desperdício e não pode ser usada a não ser em cenários muito específicos / especiais.
Sharadendu sinha
2
@sharadendusinha: conforme documentado e respondido, URLEncoderé para parâmetros de consulta codificados em URL, conforme application/x-www-form-urlencodedregras. Os parâmetros do caminho não se enquadram nessa categoria. Você precisa de um codificador URI.
BalusC
Como previ, os usuários ficarão confusos porque, obviamente, o problema é que as pessoas precisam codificar mais do que apenas o valor do parâmetro. É um caso muito raro que você só precisa codificar um valor de parâmetro. É por isso que forneci minha resposta wiki "confusa" para ajudar pessoas como @sharadendusinha.
Adam Gent
1
@WijaySharma: porque os caracteres específicos da URL também seriam codificados. Você só deve fazer isso quando quiser passar o URL inteiro como um parâmetro de consulta de outro URL.
BalusC
1
"+, não% 20" é o que eu precisava ouvir. Muito obrigado.
wetjosh 8/08/19
173

Eu não usaria URLEncoder. Além de ter um nome incorreto ( URLEncodernão tem nada a ver com URLs), é ineficiente (usa um em StringBuffervez do Builder e faz algumas outras coisas que são lentas) Também é muito fácil estragar tudo.

Em vez disso eu usaria URIBuilderou Primavera do org.springframework.web.util.UriUtils.encodeQueryou Commons ApacheHttpClient . A razão é que você precisa escapar do nome dos parâmetros da consulta (ou seja, a resposta do BalusC q) de maneira diferente do valor do parâmetro.

A única desvantagem do que foi mencionado acima (que eu descobri dolorosamente) é que os URLs não são um verdadeiro subconjunto dos URIs .

Código de amostra:

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 estou apenas ligando para outras respostas, marquei isso como um wiki da comunidade. Sinta-se livre para editar.

Adam Gent
fonte
2
Por que isso não tem nada a ver com URLs?
26415 Sep Luis
15
@Luis: URLEncoderé como o javadoc diz que pretende codificar os parâmetros da string de consulta conforme application/x-www-form-urlencodeddescrito na especificação HTML: w3.org/TR/html4/interact/… . Alguns usuários realmente o confundem / abusam por codificar URIs inteiros, como aparentemente o atendedor atual.
precisa saber é o seguinte
8
@LuisSep em resumo URLEncoder é para codificação para envio de formulários. Não é para escapar. Não é exatamente o mesmo escape que você usaria para criar URLs a serem colocados em sua página da web, mas é semelhante o suficiente para que as pessoas abusem dela. O único momento em que você deve usar o URLEncoder é se estiver escrevendo um cliente HTTP (e mesmo assim existem opções muito superiores para codificação).
Adam Gent
1
@BalusC " Alguns usuários realmente confundem / abusam dele por codificar URIs inteiros, como aparentemente o atendente atual fez. ". Você assumiu errado. Eu nunca disse que estraguei tudo. Acabei de ver outros que fizeram isso, quem são os bugs que tenho que corrigir. A parte que estraguei é que a classe de URL Java aceitará colchetes sem escape, mas não a classe URI. Há muitas maneiras de estragar a construção de URLs e nem todo mundo é brilhante como você. Eu diria que a maioria dos usuários que estão procurando SO em URLEncoding provavelmente são " usuários realmente confundem / abusam " de escape de URI.
Adam Gent
1
A pergunta não era sobre isso, mas sua resposta implica isso.
precisa saber é o seguinte
99

Você precisa primeiro criar um 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());

Em seguida, converta esse Uri em string ASCII:

urlStr=uri.toASCIIString();

Agora, sua string de URL é completamente codificada. Primeiro fizemos uma codificação de URL simples e depois a convertemos em ASCII String para garantir que nenhum caractere fora do US-ASCII permaneça na string. É exatamente assim que os navegadores fazem.

M Abdul Sami
fonte
7
Obrigado! É estúpido que sua solução funcione, mas o built-in URL.toURI()não.
user11153
2
Infelizmente, isso não parece funcionar com "file: ///" (por exemplo: "file: /// some / directory / um arquivo contendo spaces.html"); ele ataca com MalformedURLException em "new URL ()"; alguma idéia de como consertar isso?
ZioByte
Você precisa fazer algo assim: String urlStr = " algum / diretório / um arquivo que contém spaces.html"; URL URL = novo URL (urlStr); URI uri = novo URI (url.getProtocol (), url.getUserInfo (), url.getHost (), url.getPort (), url.getPath (), url.getQuery (), url.getRef ()); urlStr = uri.toASCIIString (); urlStr.replace ("http: //", "arquivo: ///"); Eu não testei isso, mas eu acho que vai funcionar .... :)
M Abdul Sami
1
@ tibi, você pode simplesmente usar o método uri.toString () para convertê-lo em string em vez de em string Ascii.
M Abdul Sami
1
A API com a qual eu estava trabalhando não aceitou a +substituição de espaços, mas aceitou o% 20, portanto esta solução funcionou melhor que o BalusC, obrigado!
Julian Honma
35

O Guava 15 agora adicionou um conjunto de escapers simples de URL .

Emmanuel Touzery
fonte
1
Eles sofrem das mesmas regras de fuga patetas que URLEncoder.
2rs2ts
3
não tenho certeza se eles têm o problema. eles diferenciam, por exemplo, "+" ou "% 20" para escapar "" (formar param ou caminho param), o que URLEncodernão acontece.
Emmanuel Touzery
1
Isso funcionou para mim. Acabei de substituir a chamada para URLEncoder () para chamar UrlEscapers.urlFragmentEscaper () e funcionou, não está claro se eu deveria usar UrlEscapers.urlPathSegmentEscaper ().
Paul Taylor
2
Na verdade, ele não funcionou para mim porque, ao contrário do URLEncoder, ele não codifica '+', deixa-o em paz, o servidor decodifica '+' como espaço, enquanto que se eu usar o URLEncoder '+' s são convertidos para% 2B e decodificados corretamente para +
Paul Taylor
2
Link de atualização: UrlEscapers
mgaert
6

A biblioteca Apache Http Components fornece uma opção interessante para criar e codificar parâmetros de consulta -

Com o uso do HttpComponents 4.x - URLEncodedUtils

Para uso do HttpClient 3.x - EncodingUtil

Sashi
fonte
6

Aqui está um método que você pode usar no seu código para converter uma string de URL e um mapa de parâmetros em uma string de URL codificada válida que contenha os 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;
}
Pellet
fonte
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);

Impressões

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

O que esta acontecendo aqui?

1. Divida o URL em partes estruturais. Usarjava.net.URL para isso.

2) Codifique cada parte estrutural corretamente!

3. Use IDN.toASCII(putDomainNameHere)para Punycode codificar o nome do host!

4. Use java.net.URI.toASCIIString()para codificação percentual, unicode codificado por NFC - (melhor seria NFKC!). Para mais informações, consulte: Como codificar corretamente este URL

Em alguns casos, é aconselhável verificar se o URL já está codificado . Substitua também os espaços codificados '+' pelos espaços codificados '% 20'.

Aqui estão alguns exemplos que também funcionarão corretamente

{
      "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$"
}

A solução passa em torno de 100 dos casos de teste fornecidos pelos testes da Web Plattform .

jschnasse
fonte
1

No android, eu usaria este código:

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

Onde Uriestá umandroid.net.Uri

Sharjeel Lasharie
fonte
10
Isso não está usando a API Java padrão. Então, por favor especifique a biblioteca usada.
Rmuller
1

No meu caso, eu só precisava passar o URL inteiro e codificar apenas o valor de cada parâmetro. Não encontrei um código comum para fazer isso (!!), então criei este pequeno método para fazer o trabalho:

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

Ele usa org.apache.commons.lang3.StringUtils

Laurent
fonte
-2
  1. Use isto: URLEncoder.encode (query, StandardCharsets.UTF_8.displayName ()); ou este: URLEncoder.encode (consulta, "UTF-8");
  2. Você pode usar o código a seguir.

    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
fonte
4
Incorreto. Você deve codificar os nomes e valores dos parâmetros separadamente. A codificação de toda a cadeia de caracteres de consulta também codificará os separadores =e &, o que não está correto.
Marquês de Lorne