Ответ 1
Да, я согласен с тем, что метод URI.resolve(URI)
несовместим с RFC 3986. Оригинальный вопрос сам по себе представляет собой фантастическое исследование, которое способствует этому выводу. Во-первых, проясните любую путаницу.
Как объяснил Раэдвальд (в удаленном ответе), существует различие между базовыми путями, которые заканчиваются или не заканчиваются на /
:
-
fizz
относительно/foo/bar
:/foo/fizz
-
fizz
относительно/foo/bar/
:/foo/bar/fizz
Правильно, это не полный ответ, потому что исходный вопрос не спрашивает о путь (т.е. "fizz", выше). Вместо этого возникает вопрос об отдельном компоненте относительной ссылки URI. Конструктор URI класса используемый в примере кода, принимает пять различных аргументов String, и все, кроме аргумента queryString
, были переданы как null
. (Обратите внимание, что Java принимает пустую строку в качестве параметра пути, и это логически приводит к "пустым" компонентам пути, потому что " компонент пути никогда не undefined" хотя он " может быть пустым (нулевая длина)".) Это будет важно позже.
В предыдущем комментарии Саян Чандран указал, что java.net.URI
class документируется для реализации RFC 2396, а не тема вопроса RFC 3986. Первый был устарел последним в 2005 году. Что класс URI Javadoc не упоминает, что новый RFC можно интерпретировать как еще одно доказательство его несовместимости. Пусть куча еще:
-
JDK-6791060 - это открытая проблема, которая предполагает, что этот класс "должен быть обновлен для RFC 3986". Комментарий там предупреждает, что "RFC3986 не полностью назад совместимый с 2396 ".
-
Были предприняты попытки обновить части класса URI, чтобы они соответствовали RFC 3986, например JDK-6348622, но затем откат для устранения обратной совместимости. (Также см. это обсуждение в списке рассылки JDK.)
-
Хотя логика "merge" пути похожа, как отмеченная SubOptimal, псевдокод, указанный в новом RFC, не соответствует фактическая реализация. В псевдокоде, когда относительный путь URI пуст, результирующий целевой путь копируется как-из базового URI. Логика "слияния" не выполняется в этих условиях. Вопреки этой спецификации реализация Java URI обрезает базовый путь после последнего символа
/
, как это было отмечено в вопросе.
Существуют альтернативы классу URI, если вы хотите, чтобы поведение RFC 3986. Реализации Java EE 6 обеспечивают javax.ws.rs.core.UriBuilder
, который (на Джерси 1.18), кажется, ведет себя так, как вы ожидали (см. Ниже). Он, по меньшей мере, утверждает, что осведомленность о RFC касается кодирования различных компонентов URI.
Вне J2EE Spring 3.0 введен UriUtils, специально документированный для "кодирования и декодирования на основе RFC 3986". Spring 3.1 отказался от некоторых из этих функций и представил UriComponentsBuilder, но, к сожалению, он не документирует присоединение к какому-либо конкретному RFC.
Программа тестирования, демонстрирующая разные типы поведения:
import java.net.*;
import java.util.*;
import java.util.function.*;
import javax.ws.rs.core.UriBuilder; // using Jersey 1.18
public class StackOverflow22203111 {
private URI withResolveURI(URI base, String targetQuery) {
URI reference = queryOnlyURI(targetQuery);
return base.resolve(reference);
}
private URI withUriBuilderReplaceQuery(URI base, String targetQuery) {
UriBuilder builder = UriBuilder.fromUri(base);
return builder.replaceQuery(targetQuery).build();
}
private URI withUriBuilderMergeURI(URI base, String targetQuery) {
URI reference = queryOnlyURI(targetQuery);
UriBuilder builder = UriBuilder.fromUri(base);
return builder.uri(reference).build();
}
public static void main(String... args) throws Exception {
final URI base = new URI("http://example.com/something/more/long");
final String queryString = "query=http://local:282/rand&action=aaaa";
final String expected =
"http://example.com/something/more/long?query=http://local:282/rand&action=aaaa";
StackOverflow22203111 test = new StackOverflow22203111();
Map<String, BiFunction<URI, String, URI>> strategies = new LinkedHashMap<>();
strategies.put("URI.resolve(URI)", test::withResolveURI);
strategies.put("UriBuilder.replaceQuery(String)", test::withUriBuilderReplaceQuery);
strategies.put("UriBuilder.uri(URI)", test::withUriBuilderMergeURI);
strategies.forEach((name, method) -> {
System.out.println(name);
URI result = method.apply(base, queryString);
if (expected.equals(result.toString())) {
System.out.println(" MATCHES: " + result);
}
else {
System.out.println(" EXPECTED: " + expected);
System.out.println(" but WAS: " + result);
}
});
}
private URI queryOnlyURI(String queryString)
{
try {
String scheme = null;
String authority = null;
String path = null;
String fragment = null;
return new URI(scheme, authority, path, queryString, fragment);
}
catch (URISyntaxException syntaxError) {
throw new IllegalStateException("unexpected", syntaxError);
}
}
}
Выходы:
URI.resolve(URI)
EXPECTED: http://example.com/something/more/long?query=http://local:282/rand&action=aaaa
but WAS: http://example.com/something/more/?query=http://local:282/rand&action=aaaa
UriBuilder.replaceQuery(String)
MATCHES: http://example.com/something/more/long?query=http://local:282/rand&action=aaaa
UriBuilder.uri(URI)
MATCHES: http://example.com/something/more/long?query=http://local:282/rand&action=aaaa