Руководство по кодированию/декодированию URL Java

Руководство по кодированию / декодированию URL Java

1. Вступление

Проще говоря,URL encoding переводит специальные символы из URL-адреса в представление, которое соответствует спецификации и может быть правильно понято и интерпретировано.

В этой статье мы сосредоточимся наhow to encode/decode the URL or form data, чтобы он соответствовал спецификации и правильно передавался по сети.

2. Проанализировать URL

Базовый синтаксисURI можно обобщить как:

scheme:[//[user:[email protected]]host[:port]][/]path[?query][#fragment]

Первым шагом в кодировании URI является проверка его частей, а затем кодирование только соответствующих частей.

Давайте посмотрим на пример URI:

String testUrl =
  "http://www.example.com?key1=value+1&key2=value%40%21%242&key3=value%253";

Один из способов анализа URI - загрузка представления String в классjava.net.URI:

@Test
public void givenURL_whenAnalyze_thenCorrect() throws Exception {
    URI uri = new URI(testUrl);

    assertThat(uri.getScheme(), is("http"));
    assertThat(uri.getHost(), is("www.example.com"));
    assertThat(uri.getRawQuery(),
      .is("key1=value+1&key2=value%40%21%242&key3=value%253"));
}

КлассURI анализирует URL-адрес строкового представления и предоставляет его части через простой API - например,getXXX.

3. Закодируйте URL

При кодировании URI одной из распространенных ошибок является кодирование полного URI. Как правило, нам нужно кодировать только часть запроса URI.

Давайте закодируем данные с помощью методаencode(data, encodingScheme) классаURLEncoder:

private String encodeValue(String value) {
    return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
}

@Test
public void givenRequestParam_whenUTF8Scheme_thenEncode() throws Exception {
    Map requestParams = new HashMap<>();
    requestParams.put("key1", "value 1");
    requestParams.put("key2", "[email protected]!$2");
    requestParams.put("key3", "value%3");

    String encodedURL = requestParams.keySet().stream()
      .map(key -> key + "=" + encodeValue(requestParams.get(key)))
      .collect(joining("&", "http://www.example.com?", ""));

    assertThat(testUrl, is(encodedURL));

Методencode принимает два параметра:

  1. data - строка для перевода

  2. encodingScheme - название кодировки символов

Этот методencode преобразует строку в форматapplication/x-www-form-urlencoded.

Схема кодирования преобразует специальные символы в двухзначное шестнадцатеричное представление из 8 бит, которое будет представлено в виде «%xy». Когда мы имеем дело с параметрами пути или добавляем параметры, которые являются динамическими, мы будем кодировать данные и затем отправлять их на сервер.

Note: В РекомендацииWorld Wide Web Consortium указано, что следует использоватьUTF-8. Несоблюдение этого требования может привести к несовместимости. (Ссылка:https://docs.oracle.com/javase/7/docs/api/java/net/URLEncoder.html)

4. Расшифровать URL

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

private String decode(String value) {
    return URLDecoder.decode(value, StandardCharsets.UTF_8.toString());
}

@Test
public void givenRequestParam_whenUTF8Scheme_thenDecodeRequestParams() {
    URI uri = new URI(testUrl);

    String scheme = uri.getScheme();
    String host = uri.getHost();
    String query = uri.getRawQuery();

    String decodedQuery = Arrays.stream(query.split("&"))
      .map(param -> param.split("=")[0] + "=" + decode(param.split("=")[1]))
      .collect(Collectors.joining("&"));

    assertEquals(
      "http://www.example.com?key1=value 1&[email protected]!$2&key3=value%3",
      scheme + "://" + host + "?" + decodedQuery);
}

Здесь есть два важных момента:

  • проанализировать URL перед декодированием

  • использовать ту же схему кодирования для кодирования и декодирования

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

5. Кодировать сегмент пути

URLEncoder нельзя использовать для кодирования сегмента путиURL. Компонент пути относится к иерархической структуре, которая представляет путь к каталогу, или служит для поиска ресурсов, разделенных“/”.

Зарезервированные символы в сегменте пути отличаются от значений параметров запроса. Например, знак «+» является допустимым символом в сегменте пути и, следовательно, не должен кодироваться.

Для кодирования сегмента пути вместо этого мы используем классUriUtils от Spring Framework. КлассUriUtils предоставляет методыencodePath иencodePathSegment для кодирования пути и сегмента пути соответственно.

Давайте посмотрим на пример:

private String encodePath(String path) {
    try {
        path = UriUtils.encodePath(path, "UTF-8");
    } catch (UnsupportedEncodingException e) {
        LOGGER.error("Error encoding parameter {}", e.getMessage(), e);
    }
    return path;
}
@Test
public void givenPathSegment_thenEncodeDecode()
  throws UnsupportedEncodingException {
    String pathSegment = "/Path 1/Path+2";
    String encodedPathSegment = encodePath(pathSegment);
    String decodedPathSegment = UriUtils.decode(encodedPathSegment, "UTF-8");

    assertEquals("/Path%201/Path+2", encodedPathSegment);
    assertEquals("/Path 1/Path+2", decodedPathSegment);
}

В приведенном выше фрагменте кода мы видим, что когда мы использовали методencodePathSegment, он возвращал закодированное значение, а + не кодируется, потому что это символ значения в компоненте пути.

Давайте добавим переменную пути к нашему тестовому URL:

String testUrl
  = "/path+1?key1=value+1&key2=value%40%21%242&key3=value%253";

и чтобы собрать и утверждать правильно закодированный URL, давайте изменим тест из раздела 2:

String path = "path+1";
String encodedURL = requestParams.keySet().stream()
  .map(k -> k + "=" + encodeValue(requestParams.get(k)))
  .collect(joining("&", "/" + encodePath(path) + "?", ""));
assertThat(testUrl, CoreMatchers.is(encodedURL));

6. Заключение

В этом руководстве мы увидели, как кодировать и декодировать данные, чтобы их можно было правильно передавать и интерпретировать. Хотя статья была посвящена кодированию / декодированию значений параметров запроса URI, этот подход применим и к параметрам HTML-формы.

Вы можете найти исходный кодover on GitHub.