Java URL кодирование параметров строки запроса

710

Скажи у меня есть URL

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

и у меня есть запрос, введенный пользователем, такой как:

случайное слово £ 500 банк $

Я хочу, чтобы результат был правильно закодированным URL:

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

Какой лучший способ достичь этого? Я попытался URLEncoderсоздать объекты URI / URL, но ни один из них не получился совершенно правильным.

user1277546
источник
25
Что вы подразумеваете под "ни один из них не выходит совершенно верно"?
Марк Эллиот
2
Я использовал URI.create и заменил пробелы на + в строке запроса. На сайте клиента он преобразовал + обратно в пробелы, когда я выбрал строки запроса. Это сработало для меня.
ND27
Почему вы ожидаете, что $ будет кодироваться в процентах?
Jschnasse

Ответы:

1151

URLEncoderэто путь Вам нужно только помнить, что нужно кодировать только имя и / или значение отдельного параметра строки запроса, а не весь URL-адрес, чтобы не быть &ни символа разделителя параметра строки запроса, ни символа разделителя имени-значения параметра =.

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

Обратите внимание, что пробелы в параметрах запроса представлены +, а не %20, что является допустимым. %20, Как правило , будет использоваться для представления пробелов в самом (часть перед URI-запрос строки символа - разделителя URI ?), а не в строке запроса (часть после ?).

Также обратите внимание, что есть три encode()метода. Один без Charsetвторого аргумента и другой со Stringвторым аргументом, который генерирует проверенное исключение. Тот без Charsetаргументов устарел. Никогда не используйте его и всегда указывайте Charsetаргумент. Javadoc даже явно рекомендует использовать кодировку UTF-8, как это предусмотрено в RFC3986 и W3C .

Все остальные символы небезопасны и сначала преобразуются в один или несколько байтов с использованием некоторой схемы кодирования. Затем каждый байт представлен трехсимвольной строкой «% xy», где xy - шестнадцатеричное представление байта из двух цифр. Рекомендуемая схема кодирования - UTF-8 . Однако из соображений совместимости, если кодировка не указана, используется кодировка платформы по умолчанию.

Смотрите также:

BalusC
источник
В URL может быть 2 типа параметров. Строка запроса (сопровождаемая?) И параметр пути (как правило, часть самого URL). Итак, что насчет параметров пути. URLEncoder производит + для пространства даже для параметров пути. Фактически он просто не обрабатывает ничего, кроме строки запроса. Кроме того, это поведение не синхронизировано с серверами js узла. Так что для меня этот класс - пустая трата времени, и его нельзя использовать иначе, как для очень специфических / специальных сценариев.
Шараденду Синха
2
@sharadendusinha: как задокументировано и дано ответ, URLEncoderдля параметров запроса в кодировке URL соответствует application/x-www-form-urlencodedправилам. Параметры пути не вписываются в эту категорию. Вам нужен кодировщик URI вместо этого.
BalusC
Как я и предсказывал, случится ... пользователи запутаются, потому что, очевидно, проблема в том, что людям нужно кодировать больше, чем просто значение параметра. Это очень редкий случай, когда вам нужно только закодировать значение параметра. Вот почему я предоставил свой «запутанный» вики-ответ, чтобы помочь людям вроде @sharadendusinha.
Адам Гент
1
@WijaySharma: потому что специфичные для URL символы также будут закодированы. Это следует делать только в том случае, если вы хотите передать весь URL-адрес в качестве параметра запроса другого URL-адреса.
BalusC
1
«+, а не% 20» - это то, что мне нужно было услышать. Огромное спасибо.
wetjosh
173

Я бы не использовал URLEncoder. Помимо неправильного имени (не URLEncoderимеет ничего общего с URL-адресами), он неэффективен (он использует StringBufferвместо Builder и выполняет несколько медленных действий). Его также слишком легко облажать.

Вместо этого я хотел бы использовать URIBuilderили Spring , org.springframework.web.util.UriUtils.encodeQueryили Commons ApacheHttpClient . Причина в том, что вы должны избегать имени параметров запроса (т.е. ответа BalusC q) иначе, чем значение параметра.

Единственный недостаток вышеизложенного (который я обнаружил до боли) - это то, что URL не являются истинным подмножеством URI .

Образец кода:

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

Поскольку я просто ссылаюсь на другие ответы, я отметил это как вики сообщества. Не стесняйтесь редактировать.

Adam Gent
источник
2
Почему это не имеет ничего общего с URL?
Луис Сен
15
@Luis: URLEncoderэто, как говорит его javadoc, намерение кодировать параметры строки запроса, application/x-www-form-urlencodedкак описано в спецификации HTML: w3.org/TR/html4/interact/… . Некоторые пользователи действительно путают / злоупотребляют им для кодирования целых URI, как, по-видимому, сделал нынешний ответчик.
BalusC
8
@LuisSep короче URLEncoder для кодирования для отправки формы. Это не для побега. Его не точная же маскирование , что вы будете использовать для создания URL - адресов , чтобы положить в вашем веб - страницы , но случается достаточно схожи , что люди злоупотребляют его. Единственный раз, когда вы должны использовать URLEncoder, это если вы пишете HTTP-клиент (и даже тогда есть гораздо лучшие варианты для кодирования).
Адам Гент
1
@BalusC « Некоторые пользователи действительно путают / злоупотребляют им для кодирования целых URI, как, очевидно, сделал нынешний ответчик ». Вы ошиблись. Я никогда не говорил, что облажался с этим. Я только что видел других, которые сделали это, чьи ошибки я должен исправить. Часть, которую я облажался, состоит в том, что класс URL Java будет принимать неэкранированные скобки, но не класс URI. Существует множество способов испортить создание URL-адресов, и не все такие блестящие, как вы. Я бы сказал, что большинство пользователей, которые ищут SOR для кодирования URLE, вероятно, являются пользователями, которые действительно путают / злоупотребляют экранированием URI.
Адам Гент
1
Вопрос не об этом, но ваш ответ подразумевает это.
BalusC
99

Вам нужно сначала создать URI, например:

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

Затем преобразуйте этот Uri в строку ASCII:

urlStr=uri.toASCIIString();

Теперь ваша строка URL полностью закодирована. Сначала мы сделали простое кодирование URL, а затем преобразовали ее в строку ASCII, чтобы убедиться, что в строке не осталось символов вне US-ASCII. Именно так и поступают браузеры.

М Абдул Сами
источник
7
Спасибо! Глупо, что ваше решение работает, а встроенное - URL.toURI()нет.
user11153
2
К сожалению, это не похоже на работу с «file: ///» (например: «file: /// some / directory / a file with spaces.html»); бомба с MalformedURLException в "новом URL ()"; есть идеи как это исправить?
ZioByte
Вам нужно сделать что-то вроде этого: String urlStr = " some / directory / a file with spaces.html"; URL url = новый URL (urlStr); URI uri = новый URI (url.getProtocol (), url.getUserInfo (), url.getHost (), url.getPort (), url.getPath (), url.getQuery (), url.getRef ()); urlStr = uri.toASCIIString (); urlStr.replace ( "HTTP: //", "Файл: ///"); Я не проверял это, но я думаю, что это будет работать .... :)
М Абдул Сами
1
@tibi вы можете просто использовать метод uri.toString (), чтобы преобразовать его в строку вместо строки Ascii.
М Абдул Сами
1
API, с которым я работал, не принимал +замену пробелов, но принял% 20, так что это решение работало лучше, чем BalusC, спасибо!
Джулиан Хонма
35

В Guava 15 добавлен ряд простых экранировщиков URL .

Эммануэль Тузери
источник
1
Они страдают от тех же самых глупых правил побега, что и URLEncoder.
2rs2ts
3
не уверен, что у них есть проблема. они различают, например, "+" или "% 20", чтобы убежать "" (образуют параметр или путь), который URLEncoderне делает.
Эммануэль Тузери
1
Это сработало для меня, я просто заменил вызов URLEncoder () для вызова UrlEscapers.urlFragmentEscaper (), и это сработало, не ясно, должен ли я использовать вместо этого UrlEscapers.urlPathSegmentEscaper ().
Пол Тейлор
2
На самом деле это не работает для меня, потому что в отличие от URLEncoder он не кодирует '+', он оставляет его в покое, сервер декодирует '+' как пробел, тогда как если я использую URLEncoder '+, они преобразуются в% 2B и корректно декодируются обратно в +
Пол Тейлор
2
Обновление ссылки: UrlEscapers
mgaert
6

Библиотека Apache Http Components предоставляет удобную опцию для построения и кодирования параметров запроса -

С использованием HttpComponents 4.x - URLEncodedUtils

Для использования HttpClient 3.x - EncodingUtil

Sashi
источник
6

Вот метод, который вы можете использовать в своем коде для преобразования строки URL-адреса и сопоставления параметров в допустимую кодированную строку URL-адреса, содержащую параметры запроса.

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;
}
гранула
источник
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);

Печать

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

Что здесь происходит?

1. Разделить URL на структурные части. Используйте java.net.URL для этого.

2. Правильно закодируйте каждую деталь конструкции!

3. Используйте IDN.toASCII(putDomainNameHere)для Punycode кодирования имени хоста!

4. Используйте java.net.URI.toASCIIString()для кодирования в процентах, кодированный в NFC Unicode - (лучше было бы NFKC!). Для получения дополнительной информации см .: Как правильно закодировать этот URL

В некоторых случаях рекомендуется проверить, не закодирован ли уже URL . Также замените кодированные пробелом «+» пробелами «% 20».

Вот несколько примеров, которые также будут работать правильно

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

Решение проходит около 100 тестовых случаев, предоставленных Web Plattform Tests .

jschnasse
источник
1

В Android я бы использовал этот код:

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

Где Uriнаходитсяandroid.net.Uri

Шарджил Лашари
источник
10
Это не использование стандартного Java API. Поэтому, пожалуйста, укажите используемую библиотеку.
rmuller
1

В моем случае мне просто нужно было передать весь URL и кодировать только значение каждого параметра. Я не нашел общий код для этого (!!), поэтому я создал этот небольшой метод для выполнения этой работы:

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

Он использует org.apache.commons.lang3.StringUtils

Laurent
источник
-2
  1. Используйте это: URLEncoder.encode (query, StandardCharsets.UTF_8.displayName ()); или это: URLEncoder.encode (запрос, "UTF-8");
  2. Вы можете использовать следующий код.

    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);
Ксюэльский хан
источник
4
Неправильно. Вы должны кодировать имена параметров и значения отдельно. Кодирование всего строки запроса будет также кодировать =и &разделители, которые не является правильным.
маркиз Лорн