HTTP URL-адрес кодирования в Java - PullRequest
350 голосов
/ 07 апреля 2009

Мое автономное 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" ... Есть ли другой способ сделать это?

Ответы [ 24 ]

288 голосов
/ 07 апреля 2009

Класс 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();
85 голосов
/ 08 апреля 2010

Пожалуйста, имейте в виду, что большинство ответов выше НЕПРАВИЛЬНЫ.

Класс URLEncoder, несмотря на имя, НЕ является тем, что должно быть здесь. К сожалению, Sun назвал этот класс так досадно. URLEncoder предназначен для передачи данных в качестве параметров, а не для кодирования самого URL.

Другими словами, "http://search.barnesandnoble.com/booksearch/first book.pdf" - это URL. Параметры будут, например, "http://search.barnesandnoble.com/booksearch/first book.pdf?parameter1=this&param2=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
76 голосов
/ 22 января 2012

Я собираюсь добавить одно предложение, предназначенное для пользователей 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-адреса и заставить ее работать, не требуя каких-либо специальных знаний о себе.

48 голосов
/ 05 января 2011

решение, которое я разработал и гораздо более стабильное, чем любое другое:

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

}
35 голосов
/ 03 марта 2012

Если у вас есть 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;
    }
}
26 голосов
/ 07 апреля 2009

Да, кодировка URL будет кодировать эту строку, чтобы она правильно передавалась в URL-адресе в конечный пункт назначения. Например, у вас не может быть http://stackoverflow.com? Url = http://yyy.com. UrlEncoding для параметра будет фиксировать это значение параметра.

Так что у меня есть два варианта для вас:

  1. Есть ли у вас доступ к пути отдельно от домена? Если это так, вы можете просто UrlEncode пути. Однако, если это не так, то вариант 2 может быть для вас.

  2. Get commons-httpclient-3.1. Это имеет класс URIUtil:

    System.out.println (URIUtil.encodePath ("http://example.com/x y", "ISO-8859-1"));

Это выведет именно то, что вы ищете, поскольку закодирует только часть пути URI.

К вашему сведению, для работы этого метода во время выполнения вам понадобятся кодексы и журналы общего доступа.

11 голосов
/ 07 апреля 2009

Nitpicking: строка, содержащая символ пробела по определению, не является URI. Итак, вы ищете код, который реализует экранирование URI, определенное в Разделе 2.1 RFC 3986 .

11 голосов
/ 30 июня 2011

К сожалению, 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);
  }
}
8 голосов
/ 19 мая 2017

Если кто-то не хочет добавлять зависимость в свой проект, эти функции могут быть полезны.

Мы передаем часть пути нашего 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")));

}
7 голосов
/ 07 апреля 2009

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

Это должно произвести что-то более правильное для вас.

...