HTTP URL-адрес кодирования в Java
Мое автономное Java-приложение получает от пользователя URL-адрес (который указывает на файл), и мне нужно нажать его и загрузить. Проблема в том, что я не могу правильно закодировать URL-адрес HTTP...
Пример:
URL: http://search.barnesandnoble.com/booksearch/first book.pdf
java.net.URLEncoder.encode(url.toString(), "ISO-8859-1");
возвращает меня:
http%3A%2F%2Fsearch.barnesandnoble.com%2Fbooksearch%2Ffirst+book.pdf
Но то, что я хочу, это
http://search.barnesandnoble.com/booksearch/first%20book.pdf
(пробел заменен на%20)
Похоже URLEncoder
не предназначен для кодирования URL-адресов HTTP... JavaDoc сообщает "Класс утилит для кодирования форм HTML"... Есть ли другой способ сделать это?
27 ответов
Класс java.net.URI может помочь; в документации по URL вы найдете
Обратите внимание, что при определенных обстоятельствах класс URI выполняет экранирование своих компонентных полей. Рекомендуемый способ управления кодированием и декодированием URL-адресов - использовать URI.
Используйте один из конструкторов с более чем одним аргументом, например:
URI uri = new URI(
"http",
"search.barnesandnoble.com",
"/booksearch/first book.pdf",
null);
URL url = uri.toURL();
//or String request = uri.toString();
(конструктор URI с одним аргументом НЕ экранирует недопустимые символы)
Только недопустимые символы экранируются вышеприведенным кодом - он НЕ экранирует не-ASCII-символы (см. Комментарий Фатиха).toASCIIString
Метод может использоваться для получения строки только с символами US-ASCII:
URI uri = new URI(
"http",
"search.barnesandnoble.com",
"/booksearch/é",
null);
String request = uri.toASCIIString();
Для URL с запросом вроде http://www.google.com/ig/api?weather=São Paulo
используйте 5-параметрическую версию конструктора:
URI uri = new URI(
"http",
"www.google.com",
"/ig/api",
"weather=São Paulo",
null);
String request = uri.toASCIIString();
Пожалуйста, обратите внимание, что большинство ответов выше НЕПРАВИЛЬНЫ.
URLEncoder
класс, несмотря на имя, это НЕ то, что должно быть здесь. К сожалению, Sun назвал этот класс так досадно. URLEncoder
предназначен для передачи данных в качестве параметров, а не для кодирования самого URL.
Другими словами, "http://search.barnesandnoble.com/booksearch/first book.pdf"
это URL. Параметры будут, например, "http://search.barnesandnoble.com/booksearch/first book.pdf?parameter1=this¶m2=that"
, Параметры - это то, что вы бы использовали URLEncoder
за.
Следующие два примера подчеркивают различия между ними.
Следующее дает неправильные параметры, в соответствии со стандартом HTTP. Обратите внимание, что амперсанд (&) и плюс (+) кодируются неправильно.
uri = new URI("http", null, "www.google.com", 80,
"/help/me/book name+me/", "MY CRZY QUERY! +&+ :)", null);
// URI: http://www.google.com:80/help/me/book%20name+me/?MY%20CRZY%20QUERY!%20+&+%20:)
Далее будут получены правильные параметры с правильно закодированным запросом. Обратите внимание на пробелы, амперсанды и знаки плюс.
uri = new URI("http", null, "www.google.com", 80, "/help/me/book name+me/", URLEncoder.encode("MY CRZY QUERY! +&+ :)", "UTF-8"), null);
// URI: http://www.google.com:80/help/me/book%20name+me/?MY+CRZY+QUERY%2521+%252B%2526%252B+%253A%2529
Я собираюсь добавить одно предложение, предназначенное для пользователей Android. Вы можете сделать это, чтобы избежать необходимости получать какие-либо внешние библиотеки. Кроме того, все решения поиска / замены символов, предложенные в некоторых из приведенных выше ответов, опасны и их следует избегать.
Попробуйте это:
String urlStr = "http://abc.dev.domain.com/0007AC/ads/800x480 15sec h.264.mp4";
URL url = new URL(urlStr);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
url = uri.toURL();
Вы можете видеть, что в этом конкретном URL мне нужно закодировать эти пробелы, чтобы я мог использовать его для запроса.
Это использует пару функций, доступных вам в классах Android. Во-первых, класс URL может разбить URL на его соответствующие компоненты, поэтому вам не нужно выполнять какие-либо операции поиска / замены строк. Во-вторых, этот подход использует преимущество класса URI для правильного экранирования компонентов, когда вы создаете URI через компоненты, а не из одной строки.
Прелесть этого подхода в том, что вы можете взять любую допустимую строку URL и заставить ее работать, не требуя каких-либо специальных знаний о ней самостоятельно.
Решение, которое я разработал, и гораздо более стабильное, чем любое другое:
public class URLParamEncoder {
public static String encode(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;
}
}
Если у вас есть URL, вы можете передать в этот метод url.toString(). Сначала декодируйте, чтобы избежать двойного кодирования (например, кодирование пробела приводит к%20, а кодирование знака процента приводит к%25, поэтому двойное кодирование превратит пробел в%2520). Затем используйте URI, как описано выше, добавляя все части URL-адреса (чтобы не сбрасывать параметры запроса).
public URL convertToURLEscapingIllegalCharacters(String string){
try {
String decodedURL = URLDecoder.decode(string, "UTF-8");
URL url = new URL(decodedURL);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
return uri.toURL();
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
Да, URL-кодирование будет кодировать эту строку, чтобы она правильно передавалась по URL-адресу в конечный пункт назначения. Например, у вас не может быть http://stackru.com/?url=http://yyy.com. UrlEncoding параметра будет фиксировать значение этого параметра.
Итак, у меня есть два варианта для вас:
У вас есть доступ к пути отдельно от домена? Если это так, вы можете просто UrlEncode пути. Однако, если это не так, то вариант 2 может быть для вас.
Получите commons-httpclient-3.1. Это имеет класс URIUtil:
System.out.println (URIUtil.encodePath (" http://example.com/x y", "ISO-8859-1"));
Это выведет именно то, что вы ищете, поскольку закодирует только часть пути URI.
К вашему сведению, для работы этого метода во время выполнения вам понадобятся кодексы и регистрация.
Если кто-то не хочет добавлять зависимость в свой проект, эти функции могут быть полезны.
Мы передаем часть пути нашего URL сюда. Вы, вероятно, не хотите передавать полный URL-адрес как параметр (для строк запроса требуются разные экранированные символы и т. Д.).
/**
* Percent-encodes a string so it's suitable for use in a URL Path (not a query string / form encode, which uses + for spaces, etc)
*/
public static String percentEncode(String encodeMe) {
if (encodeMe == null) {
return "";
}
String encoded = encodeMe.replace("%", "%25");
encoded = encoded.replace(" ", "%20");
encoded = encoded.replace("!", "%21");
encoded = encoded.replace("#", "%23");
encoded = encoded.replace("$", "%24");
encoded = encoded.replace("&", "%26");
encoded = encoded.replace("'", "%27");
encoded = encoded.replace("(", "%28");
encoded = encoded.replace(")", "%29");
encoded = encoded.replace("*", "%2A");
encoded = encoded.replace("+", "%2B");
encoded = encoded.replace(",", "%2C");
encoded = encoded.replace("/", "%2F");
encoded = encoded.replace(":", "%3A");
encoded = encoded.replace(";", "%3B");
encoded = encoded.replace("=", "%3D");
encoded = encoded.replace("?", "%3F");
encoded = encoded.replace("@", "%40");
encoded = encoded.replace("[", "%5B");
encoded = encoded.replace("]", "%5D");
return encoded;
}
/**
* Percent-decodes a string, such as used in a URL Path (not a query string / form encode, which uses + for spaces, etc)
*/
public static String percentDecode(String encodeMe) {
if (encodeMe == null) {
return "";
}
String decoded = encodeMe.replace("%21", "!");
decoded = decoded.replace("%20", " ");
decoded = decoded.replace("%23", "#");
decoded = decoded.replace("%24", "$");
decoded = decoded.replace("%26", "&");
decoded = decoded.replace("%27", "'");
decoded = decoded.replace("%28", "(");
decoded = decoded.replace("%29", ")");
decoded = decoded.replace("%2A", "*");
decoded = decoded.replace("%2B", "+");
decoded = decoded.replace("%2C", ",");
decoded = decoded.replace("%2F", "/");
decoded = decoded.replace("%3A", ":");
decoded = decoded.replace("%3B", ";");
decoded = decoded.replace("%3D", "=");
decoded = decoded.replace("%3F", "?");
decoded = decoded.replace("%40", "@");
decoded = decoded.replace("%5B", "[");
decoded = decoded.replace("%5D", "]");
decoded = decoded.replace("%25", "%");
return decoded;
}
И тесты:
@Test
public void testPercentEncode_Decode() {
assertEquals("", percentDecode(percentEncode(null)));
assertEquals("", percentDecode(percentEncode("")));
assertEquals("!", percentDecode(percentEncode("!")));
assertEquals("#", percentDecode(percentEncode("#")));
assertEquals("$", percentDecode(percentEncode("$")));
assertEquals("@", percentDecode(percentEncode("@")));
assertEquals("&", percentDecode(percentEncode("&")));
assertEquals("'", percentDecode(percentEncode("'")));
assertEquals("(", percentDecode(percentEncode("(")));
assertEquals(")", percentDecode(percentEncode(")")));
assertEquals("*", percentDecode(percentEncode("*")));
assertEquals("+", percentDecode(percentEncode("+")));
assertEquals(",", percentDecode(percentEncode(",")));
assertEquals("/", percentDecode(percentEncode("/")));
assertEquals(":", percentDecode(percentEncode(":")));
assertEquals(";", percentDecode(percentEncode(";")));
assertEquals("=", percentDecode(percentEncode("=")));
assertEquals("?", percentDecode(percentEncode("?")));
assertEquals("@", percentDecode(percentEncode("@")));
assertEquals("[", percentDecode(percentEncode("[")));
assertEquals("]", percentDecode(percentEncode("]")));
assertEquals(" ", percentDecode(percentEncode(" ")));
// Get a little complex
assertEquals("[]]", percentDecode(percentEncode("[]]")));
assertEquals("a=d%*", percentDecode(percentEncode("a=d%*")));
assertEquals(") (", percentDecode(percentEncode(") (")));
assertEquals("%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25",
percentEncode("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %"));
assertEquals("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %", percentDecode(
"%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25"));
assertEquals("%23456", percentDecode(percentEncode("%23456")));
}
Плохое указание: строка, содержащая символ пробела по определению, не является URI. Итак, вы ищете код, который реализует экранирование URI, определенное в Разделе 2.1 RFC 3986.
К несчастью, org.apache.commons.httpclient.util.URIUtil
устарела, а replacement org.apache.commons.codec.net.URLCodec
Подходит ли кодирование для сообщений в форме, а не в реальных URL. Поэтому мне пришлось написать свою собственную функцию, которая выполняет один компонент (не подходит для целых строк запроса, которые имеют? S и & s)
public static String encodeURLComponent(final String s)
{
if (s == null)
{
return "";
}
final StringBuilder sb = new StringBuilder();
try
{
for (int i = 0; i < s.length(); i++)
{
final char c = s.charAt(i);
if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
((c >= '0') && (c <= '9')) ||
(c == '-') || (c == '.') || (c == '_') || (c == '~'))
{
sb.append(c);
}
else
{
final byte[] bytes = ("" + c).getBytes("UTF-8");
for (byte b : bytes)
{
sb.append('%');
int upper = (((int) b) >> 4) & 0xf;
sb.append(Integer.toHexString(upper).toUpperCase(Locale.US));
int lower = ((int) b) & 0xf;
sb.append(Integer.toHexString(lower).toUpperCase(Locale.US));
}
}
}
return sb.toString();
}
catch (UnsupportedEncodingException uee)
{
throw new RuntimeException("UTF-8 unsupported!?", uee);
}
}
Как вы, к сожалению, обнаружили, URLEncoding может прекрасно кодировать URL-адреса HTTP. Переданная вами строка " http://search.barnesandnoble.com/booksearch/first book.pdf" была правильно и полностью закодирована в виде URL-кода. Вы можете передать всю длинную строку gobbledigook, которую вы вернули, в качестве параметра в URL, и она может быть декодирована обратно в ту строку, в которой вы были переданы.
Похоже, вы хотите сделать что-то немного отличное от передачи всего URL-адреса в качестве параметра. Исходя из того, что я понял, вы пытаетесь создать поисковый URL, который выглядит как " http://search.barnesandnoble.com/booksearch/whateverTheUserPassesIn". Единственное, что вам нужно кодировать, это бит "whatTheUserPassesIn", поэтому, возможно, все, что вам нужно сделать, это что-то вроде этого:
String url = "http://search.barnesandnoble.com/booksearch/" +
URLEncoder.encode(userInput,"UTF-8");
Это должно произвести что-то более правильное для вас.
Проблема все еще существует, если в вашем URL есть закодированный символ "/" (%2F).
RFC 3986 - раздел 2.2 гласит: "Если данные для компонента URI будут конфликтовать с назначением зарезервированного символа в качестве разделителя, то конфликтующие данные должны быть закодированы в процентах до формирования URI". (RFC 3986 - раздел 2.2)
Но есть проблема с Tomcat:
http://tomcat.apache.org/security-6.html - Исправлено в Apache Tomcat 6.0.10
важно: Обратный путь в каталогах CVE-2007-0450
Tomcat разрешает "\", "% 2F" и "%5C" [...] .
Следующие системные свойства Java были добавлены в Tomcat, чтобы обеспечить дополнительный контроль над обработкой разделителей пути в URL (обе опции по умолчанию имеют значение false):
- org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH: true | false
- org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH: true | false
Из-за невозможности гарантировать, что все URL-адреса обрабатываются Tomcat, как и на прокси-серверах, Tomcat всегда должен быть защищен, как если бы прокси-сервер не ограничивал доступ к контексту.
Влияет: 6.0.0-6.0.9
Поэтому, если у вас есть URL с символом% 2F, Tomcat возвращает: "400 Invalid URI: noSlash"
Вы можете переключить исправление в скрипте запуска Tomcat:
set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG% -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
Я прочитал предыдущие ответы, чтобы написать свой собственный метод, потому что я не мог заставить что-то правильно работать, используя решение предыдущих ответов, это выглядит хорошо для меня, но если вы можете найти URL, который не работает с этим, пожалуйста, дайте мне знать.
public static URL convertToURLEscapingIllegalCharacters(String toEscape) throws MalformedURLException, URISyntaxException {
URL url = new URL(toEscape);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
//if a % is included in the toEscape string, it will be re-encoded to %25 and we don't want re-encoding, just encoding
return new URL(uri.toString().replace("%25", "%"));
}
Может быть, можете попробовать UriUtils в org.springframework.web.util
UriUtils.encodeUri(input, "UTF-8")
Вы также можете использовать GUAVA
and path escaper:UrlEscapers.urlFragmentEscaper().escape(relativePath)
Я согласен с Мэттом. На самом деле, я никогда не видел, чтобы это хорошо объяснялось в руководствах, но один вопрос заключается в том, как кодировать путь URL, а совсем другой вопрос - в том, как кодировать параметры, которые добавляются к URL (часть запроса, за "?" " условное обозначение). Они используют похожую кодировку, но не одинаковую.
Специально для кодирования символа пробела. Путь к URL должен быть закодирован как%20, тогда как часть запроса допускает%20, а также знак "+". Лучше всего протестировать его самостоятельно на нашем веб-сервере с помощью веб-браузера.
For both cases, I ALWAYS would encode COMPONENT BY COMPONENT, never the whole string. Indeed URLEncoder allows that for the query part. For the path part you can use the class URI, although in this case it asks for the entire string, not a single component.
Anyway, I believe that the best way to avoid these problems is to use a personal non-conflictive design. Как? For example, I never would name directories or parameters using other characters than aZ, AZ, 0-9 and _ . That way, the only need is to encode the value of every parameter, since it may come from an user input and the used characters are unknown.
Я взял содержание выше и немного изменил его. Сначала мне нравится позитивная логика, и я подумал, что HashSet может дать лучшую производительность, чем некоторые другие параметры, такие как поиск по строке. Хотя я не уверен, стоит ли штраф за автобокс, но если компилятор оптимизирует ASCII-символы, тогда стоимость бокса будет низкой.
/***
* Replaces any character not specifically unreserved to an equivalent
* percent sequence.
* @param s
* @return
*/
public static String encodeURIcomponent(String s)
{
StringBuilder o = new StringBuilder();
for (char ch : s.toCharArray()) {
if (isSafe(ch)) {
o.append(ch);
}
else {
o.append('%');
o.append(toHex(ch / 16));
o.append(toHex(ch % 16));
}
}
return o.toString();
}
private static char toHex(int ch)
{
return (char)(ch < 10 ? '0' + ch : 'A' + ch - 10);
}
// https://tools.ietf.org/html/rfc3986#section-2.3
public static final HashSet<Character> UnreservedChars = new HashSet<Character>(Arrays.asList(
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'0','1','2','3','4','5','6','7','8','9',
'-','_','.','~'));
public static boolean isSafe(char ch)
{
return UnreservedChars.contains(ch);
}
Используйте следующее стандартное решение Java (проходит около 100 тестовых случаев, предоставляемых Web Plattform Tests):
1. Разделить URL на структурные части. использование java.net.URL
для этого.
2. Правильно закодируйте каждую деталь конструкции!
3. Используйте IDN.toASCII(putDomainNameHere)
Punycode кодировать имя хоста!
4. Используйте java.net.URI.toASCIIString()
кодировать в процентах, кодировать NFC в юникоде (лучше будет NFKC!).
Узнайте больше здесь: /questions/13188591/java-url-kodirovanie-parametrov-stroki-zaprosa/13188621#13188621
Если вы используете Spring, вы можете попробовать
org.springframework.web.util.UriUtils#encodePath
В дополнение к ответу Карлоса Хойбергера: если требуется значение, отличное от значения по умолчанию (80), следует использовать конструктор из 7 параметров:
URI uri = new URI(
"http",
null, // this is for userInfo
"www.google.com",
8080, // port number as int
"/ig/api",
"weather=São Paulo",
null);
String request = uri.toASCIIString();
Я создал новый проект, чтобы помочь создать URL-адреса HTTP. Библиотека автоматически URL-кодирует сегменты пути и параметры запроса.
Вы можете просмотреть исходный код и загрузить бинарный файл по адресу https://github.com/Widen/urlbuilder
Пример URL в этом вопросе:
new UrlBuilder("search.barnesandnoble.com", "booksearch/first book.pdf").toString()
производит
http://search.barnesandnoble.com/booksearch/first%20book.pdf
Это скорее примечание, чем ответ. Для вышесказанного обычный http-клиент URIUtil по-прежнему является наиболее удобным и простым методом для кодирования различных частей URI. К сожалению, он устарел, причину нельзя сразу установить. Хотя URIUtil не может охватить все крайние случаи, это все же самый несложный подход.
Я искал множество библиотек / методов для этого, однако ни один из них (мое мнение здесь) не предлагал простого подхода.
Наконец, я взял URIUtil и его код зависимостей и перекомпилировал, что мне очень нравится.
Хотя я не ожидаю, что кто-то последует этому подходу, однако, если кому-то потребуется, ниже приведены зависимости для компиляции (из обычного http-клиента 3):
- org.apache.commons.httpclient.URIException
- org.apache.commons.httpclient.HttpClientError
- org.apache.commons.httpclient.NameValuePair
- org.apache.commons.httpclient.util.LangUtils
- org.apache.commons.httpclient.util.URIUtil
- org.apache.commons.httpclient.URI
- org.apache.commons.httpclient.util.EncodingUtil
У меня такая же проблема. Решил это с помощью unsing:
android.net.Uri.encode(urlString, ":/");
Кодирует строку, но пропускает ":" и "/".
Я использую это
org.apache.commons.text.StringEscapeUtils.escapeHtml4("my text % & < >");
добавить эту зависимость
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.8</version>
</dependency>
Я разрабатываю библиотеку, которая служит этой цели: galimatias. Он анализирует URL так же, как веб-браузеры. То есть, если URL работает в браузере, он будет правильно проанализирован galimatias.
В этом случае:
// Parse
io.mola.galimatias.URL.parse(
"http://search.barnesandnoble.com/booksearch/first book.pdf"
).toString()
Дам тебе: http://search.barnesandnoble.com/booksearch/first%20book.pdf
, Конечно, это самый простой случай, но он будет работать с чем угодно, далеко за пределами java.net.URI
,
Вы можете проверить это по адресу: https://github.com/smola/galimatias
Вы можете использовать такую функцию. Заполните и измените его по своему усмотрению:
/**
* Encode URL (except :, /, ?, &, =, ... characters)
* @param url to encode
* @param encodingCharset url encoding charset
* @return encoded URL
* @throws UnsupportedEncodingException
*/
public static String encodeUrl (String url, String encodingCharset) throws UnsupportedEncodingException{
return new URLCodec().encode(url, encodingCharset).replace("%3A", ":").replace("%2F", "/").replace("%3F", "?").replace("%3D", "=").replace("%26", "&");
}
Пример использования:
String urlToEncode = ""http://www.growup.com/folder/intérieur-à_vendre?o=4";
Utils.encodeUrl (urlToEncode , "UTF-8")
Результат: http://www.growup.com/folder/int%C3%A9rieur-%C3%A0_vendre?o=4
String url = "" http://search.barnesandnoble.com/booksearch/;
Я думаю, это будет константа, и только имя файла будет изменено динамически, поэтому получите имя файла
Строковое имя файла; // получить имя файла
String urlEnc=url+fileName.replace(" ","%20");
Как насчет:
public String UrlEncode (String in_) {
String retVal = "";
try {
retVal = URLEncoder.encode(in_, "UTF8");
} catch (UnsupportedEncodingException ex) {
Log.get().exception(Log.Level.Error, "urlEncode ", ex);
}
return retVal;
}