URLEncoder не умеет переводить символы пробела

179

Я ожидаю

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

вывести:

Hello%20World

(20 - шестнадцатеричный код ASCII для пробела)

Однако, что я получаю, это:

Hello+World

Я использую неправильный метод? Какой правильный метод я должен использовать?

Чеок Ян Ченг
источник
3
название класса действительно сбивает с толку, и многие люди использовали его неправильно. однако они этого не замечают, потому что когда применяется URLDecoder, восстанавливается исходное значение, поэтому + или% 20 для них не имеет значения.
неопровержимый

Ответы:

227

Это ведет себя как ожидалось. В URLEncoderРеализует HTML спецификации о том , как кодировать URL - адреса в HTML - формах.

Из Javadocs :

Этот класс содержит статические методы для преобразования строки в формат MIME application / x-www-form-urlencoded.

и из спецификации HTML :

применение / х-WWW-форм-urlencoded

Формы, представленные с этим типом содержимого, должны быть закодированы следующим образом:

  1. Имена и значения элементов управления экранированы. Символы пробела заменяются на «+»

Вам придется заменить его, например:

System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("+", "%20"));
кендырь
источник
19
Это действительно ответ, а не замена. Разве нет библиотеки Java или функции для выполнения задачи /?
co2f2e
5
Знак плюс должен быть экранированt.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("\\+", "%20"));
Джордж
26
@congliu неверно - вы, вероятно, думаете о replaceAll (), который работает с регулярным выражением - replace () - это простая замена последовательности символов.
CupawnTae,
12
Да, @congliu хороший способ: URLEncoder.encode ("Myurl", "utf-8"). ReplaceAll ("\\ +", "% 20");
13
9
@ClintEastwood Этот ответ поощряет использование java.net.URLEncoder, который не выполняет то, о чем первоначально просили. И поэтому этот ответ предлагает патч, использующий replace (), поверх него. Почему нет? Потому что это решение подвержено ошибкам и может привести к 20 другим подобным вопросам, но с другим характером. Вот почему я сказал, что это было недальновидно.
pyb
57

Пробел кодируется %20в URL-адресах и +в формах, в которые передаются данные (тип контента application / x-www-form-urlencoded). Вам нужен первый.

Использование гуавы :

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

Вы можете использовать UrlEscapers :

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

Не используйте String.replace, это только кодирует пространство. Используйте библиотеку вместо.

PYB
источник
Это также работает для Android, com.google.guava: guava: 22.0-rc1-android.
бувигер
1
@Bevor rc1 означает кандидата на первую версию, то есть версию, еще не утвержденную для общего выпуска. Если вы можете, выберите версию без снимка, альфа, бета, rc, поскольку они, как известно, содержат ошибки.
Pyb
1
@pyb Спасибо, но я все равно обновлю библиотеки, когда мой проект будет закончен. Значит, я не пойду в prod без окончательных версий. И это все еще занимает много недель, поэтому я думаю, что есть окончательная версия.
бувигер
1
К сожалению, в отличие от Apache URLCodec , Guava не предоставляет декодер .
Бенни Боттема
26

Этот класс выполняет application/x-www-form-urlencoded-тип , кодирующим , а не проценты кодирования, поэтому замену с +является правильным поведением.

Из Javadoc:

При кодировании строки применяются следующие правила:

  • Буквенно-цифровые символы от «a» до «z», от «A» до «Z» и от «0» до «9» остаются неизменными.
  • Специальные символы ".", "-", "*" и "_" остаются прежними.
  • Символ пробела "" преобразуется в знак плюс "+".
  • Все остальные символы небезопасны и сначала преобразуются в один или несколько байтов с использованием некоторой схемы кодирования. Затем каждый байт представлен трехсимвольной строкой «% xy», где xy - шестнадцатеричное представление байта из двух цифр. Рекомендуемая схема кодирования - UTF-8. Однако из соображений совместимости, если кодировка не указана, используется кодировка платформы по умолчанию.
axtavt
источник
@axtavt Хорошее объяснение. Но у меня все еще есть некоторые вопросы. В url, пространство должно интерпретироваться как %20. Так что нам нужно сделать url.replaceAll("\\+", "%20")? И если это javascript, мы не должны использовать escapeфункцию. Используйте encodeURIили encodeURIComponentвместо. Это то, о чем я думал.
Олстон
1
@ Stallman это Java, а не JavaScript. Совершенно разные языки.
Чарльз Вуд
19

Кодировать параметры запроса

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

ИЛИ если вы хотите экранировать символы в 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
источник
3
Использование, org.apache.commons.httpclient.util.URIUtilкажется, самый эффективный способ решить проблему!
Стефан Аммар
11

Hello+Worldэто то, как браузер будет кодировать данные формы ( application/x-www-form-urlencoded) для GETзапроса, и это общепринятая форма для части запроса URI.

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

Если вы отправите этот запрос сервлету Java, сервлет будет правильно декодировать значение параметра. Обычно здесь возникают проблемы только в том случае, если кодировка не совпадает.

Строго говоря, в спецификациях HTTP или URI не требуется кодировать часть запроса с использованием application/x-www-form-urlencodedпар ключ-значение; часть запроса просто должна быть в форме, которую принимает веб-сервер. На практике это вряд ли будет проблемой.

Как правило, было бы неправильно использовать эту кодировку для других частей URI (например, путь). В этом случае вы должны использовать схему кодирования, как описано в RFC 3986 .

http://host/Hello%20World

Больше здесь .

Макдауэлл
источник
5

Другие ответы представляют либо ручную замену строк, URLEncoder, который фактически кодирует для формата HTML, заброшенный URIUtil Apache , либо использование UrlEscapers в Guava . Последнее хорошо, за исключением того, что оно не обеспечивает декодер.

Apache Commons Lang предоставляет URLCodec , который кодирует и декодирует в соответствии с форматом URL rfc3986 .

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

Если вы уже используете Spring, вы также можете использовать его класс UriUtils .

Бенни Боттема
источник
6
URLCodec не является хорошим решением здесь, потому что он кодирует пробелы как плюсы, но вопрос заключается в том, чтобы пробелы кодировались как% 20.
davidwebster48
4

Только что боролся с этим и на Android, сумел наткнуться на Uri.encode (String, String), в то время как специфический для android (android.net.Uri) может быть полезен для некоторых.

статическое строковое кодирование (String s, String allow)

https://developer.android.com/reference/android/net/Uri.html#encode(java.lang.String, java.lang.String)

Chrispix
источник
3

«+» - это правильно. Если вам действительно нужен% 20, то замените Plusses потом.

Даниил
источник
5
Может возникнуть проблема, если начальная строка действительно содержит символ +.
Алексис Дафреной,
17
@ Traroth - Не совсем. +Символ в исходном тексте должен быть закодирован как %2B.
Тед Хопп
говорить, что +это правильно, не зная контекста, по крайней мере, педантично. Downvoted. Прочтите другие ответы, чтобы узнать, когда следует использовать + или% 20.
Клинт Иствуд
@ClintEastwood: Можете ли вы рассказать мне о каком-либо случае использования, в котором символ + для пробелов неверен в URL? За исключением случаев, когда на другой стороне есть несоответствующий парсер URL?
Даниил
@ Даниил, конечно, не говоря "неправильно", но не подходит? да. Инструменты аналитики часто используют параметры запроса со значениями, разделенными определенным символом, например, «+». В этом случае использование «+» вместо «% 20» будет неправильным. «+» используется для экранирования пробелов в форме, в то время как «процентное кодирование» (иначе кодирование URL) больше ориентировано на URL-адреса.
Клинт Иствуд
2

Это сработало для меня

org.apache.catalina.util.URLEncoder ul = new org.apache.catalina.util.URLEncoder().encode("MY URL");
Хитеш Кумар
источник
1

Хотя довольно старый, тем не менее быстрый ответ:

Spring предоставляет UriUtils - с этим вы можете указать, как кодировать и с какой частью это связано с URI, например

encodePathSegment
encodePort
encodeFragment
encodeUriVariables
....

Я использую их, потому что мы уже используем Spring, т.е. никакой дополнительной библиотеки не требуется!

Лео
источник
0

Проверьте класс java.net.URI.

Фредрик Видерберг
источник
0

Я использую неправильный метод? Какой правильный метод я должен использовать?

Да, этот метод java.net.URLEncoder.encode не был создан для преобразования "" в "20%" в соответствии со спецификацией ( источник ).

Символ пробела "" преобразуется в знак плюс "+".

Даже если это не правильный метод, вы можете изменить это следующим образом: System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replaceAll("\\+", "%20"));хорошего дня =).

Pregunton
источник
Вы предлагаете использовать метод, который не является адекватным ( URLEncoder.encode), и исправлять его, используя, replaceAllкоторый будет работать только в этом конкретном случае. Вместо этого используйте правильный класс и метод, смотрите другие ответы.
pyb
@pyb похоже, что вы не можете понять, что я написал. Я никогда не говорил «я предлагаю использовать это», я говорил «вы можете». Пожалуйста, прочитайте и поймите, прежде чем писать.
Pregunton
Это сайт вопросов и ответов, а не обычная доска объявлений, где люди общаются. Если у вас есть побочные комментарии, используйте комментарии. Дольше говорить? Используйте чат. Не публикуйте код, с которым вы не согласны, в качестве ответа. Пожалуйста, прочитайте и поймите правила этого сайта, прежде чем вносить свой вклад и читать лекции другим.
pyb
1
Я отказываюсь от него, потому что большинство других решений дают такой же совет. Не было предоставлено никаких «конкретных случаев», чтобы доказать, что этот метод ошибочен. Использование общих Apache с блоками или зависимостями try-catch - слишком сложная задача для метода, который можно эффективно исправить с помощью replaceAll.
Евгений Картоев
-2

ИСПОЛЬЗУЙТЕ MyUrlEncode.URLencoding (String url, String enc) для решения проблемы

    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
источник
9
заново изобретать колесо, добавляя супер-склонный к ошибкам код в базу кода, почти всегда плохое решение.
Клинт Иствуд
-6

используйте набор символов " ISO-8859-1" для URLEncoder

Ахил Сикри
источник