Кодування URL-адрес параметрів рядка запиту Java


710

Скажіть, у мене є URL

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

і у мене є запит, введений користувачем, наприклад:

випадкове слово £ 500 банк $

Я хочу, щоб результат був правильно закодованою URL-адресою:

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

Який найкращий спосіб досягти цього? Я спробував URLEncoderстворити URI / URL-об’єкти, але жоден з них не вийшов правильним.


24
Що ви маєте на увазі під "жоден з них не виходить цілком правильно"?
Марк Елліот

2
Я використав URI.create і замінив пробіли на + у запиті рядків. На сайті клієнта він перетворив + назад у пробіли, коли я вибрав рядки запиту. Це працювало для мене.
ND27


Чому ви очікуєте, що $ буде відсотковим?
jschnasse

Відповіді:


1150

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 .

Усі інші символи небезпечні і спочатку перетворюються в один або кілька байтів за допомогою певної схеми кодування. Тоді кожен байт представлений 3-символьним рядком "% xy", де xy - двозначне шістнадцяткове представлення байту. Рекомендована схема кодування для використання - UTF-8 . Однак з міркувань сумісності, якщо кодування не вказано, використовується кодування за замовчуванням платформи.

Дивись також:


В URL-адресі може бути 2 типи параметрів. Рядок запиту (далі -?) Та параметр шляху (як правило, частина самої URL-адреси). Отже, що про параметри шляху. URLEncoder виробляє + для простору навіть для параметрів шляху. Насправді він просто не обробляє нічого, крім рядка запиту. Крім того, така поведінка не синхронізується з вузлами js-серверів. Тож для мене цей клас є марнотратним і не може бути використаний інший, ніж для дуже конкретних / спеціальних сценаріїв.
sharadendu sinha

2
@sharadendusinha: як задокументовано та відповідено, URLEncoderпризначено для параметрів запиту, кодованих URL- адресами, відповідним application/x-www-form-urlencodedправилам. Параметри шляху не входять до цієї категорії. Натомість вам потрібен кодер URI.
BalusC

Як я передбачив, це станеться ... користувачі заплутаються, оскільки, очевидно, проблема полягає в тому, що людям потрібно кодувати більше, ніж просто значення параметра. Дуже рідкісний випадок, коли потрібно лише кодувати значення параметра. Тому я надав свою "заплутану" відповідь на вікі, щоб допомогти людям на кшталт @sharadendusinha.
Адам Гент

1
@WijaySharma: Тому що символи, що стосуються URL-адрес, також будуть закодовані. Це потрібно робити лише тоді, коли ви хочете передати всю URL-адресу як параметр запиту іншої URL-адреси.
BalusC

1
"+, не% 20" - це те, що мені потрібно було почути. Дуже дякую.
вільгот

173

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

Натомість я б використав URIBuilderабо Spring's, 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

Оскільки я просто посилаюся на інші відповіді, я позначив це вікі спільноти. Не соромтеся редагувати.


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-адрес, і не всі блискучі, як ви. Я б сказав, що більшість користувачів, які шукають SO на URLEncoding, ймовірно, " користувачі дійсно плутають / зловживають " 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 String, щоб переконатися, що жоден символ поза US-ASCII не залишається в рядку. Саме так роблять браузери.


7
Дякую! Дурно, що ваше рішення працює, але вбудований URL.toURI()не робить.
користувач11153

2
На жаль, це, здається, не працює з "файл: ///" (наприклад: "файл: /// деякий / каталог / файл, що містить пробіли.html"); це бомби з MalformedURLException у "новій URL ()"; будь-яка ідея, як це виправити?
ZioByte

Вам потрібно зробити щось на зразок цього: String urlStr = " деякий / каталог / файл, що містить простори.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

1
Вони страждають від тих же правил, що врятуються, як у гуфі URLEncoder.
2rs2ts

3
не впевнений, що у них проблема. вони розмежовують, наприклад, "+" або "% 20", щоб уникнути "" (форму парам або парам шляху), що URLEncoderне відповідає.
Еммануель Тузері

1
Це працювало для мене. Я просто замінив виклик URLEncoder (), щоб зателефонувати на UrlEscapers.urlFragmentEscaper (), і він працював, не зрозуміло, чи слід використовувати UrlEscapers.urlPathSegmentEscaper ().
Пол Тейлор

2
Насправді він не працював для мене, тому що на відміну від URLEncoder він не кодує '+', він залишає його в спокої, сервер декодує '+' як простір, тоді як якщо я використовую URLEncoder '+' і перетворюються на% 2B і правильно декодуються назад до +
Пол Тейлор

2
Оновлення посилань: UrlEscapers
понеділок

6

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

За допомогою використання HttpComponents 4.x - URLEncodedUtils

Для HttpClient 3.x використовуйте - EncodingUtil


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()для кодування відсотків NICK, кодованого NFC - (краще було б 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 тестів, наданих веб-тестами платформи .


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


-2
  1. Використовуйте це: URLEncoder.encode (запит, 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
Не вірно. Вам потрібно окремо кодувати імена параметрів та значення. Кодування всього рядка запиту також буде кодувати =і &роздільники, що невірно.
користувач207421
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.