当相对URI包含空路径时,Java的URI.resolve是否与RFC 3986不兼容? [英] Is Java's URI.resolve incompatible with RFC 3986 when the relative URI contains an empty path?

查看:198
本文介绍了当相对URI包含空路径时,Java的URI.resolve是否与RFC 3986不兼容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我认为Java的URI.resolve方法的定义和实现与不兼容RFC 3986第5.2.2节。我知道Java API定义了该方法的工作方式,如果它现在已经更改,它会破坏现有的应用程序,但我的问题是:任何人都可以确认我的理解这个方法与RFC 3986不兼容吗?

I believe the definition and implementation of Java's URI.resolve method is incompatible with RFC 3986 section 5.2.2. I understand that the Java API defines how that method works, and if it were changed now it would break existing apps, but my question is this: Can anyone confirm my understanding that this method is incompatible with RFC 3986?

我正在使用这个问题的例子: java.net.URI仅针对查询字符串解析,我将在此处复制:

I'm using the example from this question: java.net.URI resolve against only query string, which I will copy here:

我正在尝试使用JDK java.net.URI构建URI。
我想附加一个绝对URI对象,一个查询(在String中)。例如:

I'm trying to build URI's using the JDK java.net.URI. I want to append to an absolute URI object, a query (in String). In example:

URI base = new URI("http://example.com/something/more/long");
String queryString = "query=http://local:282/rand&action=aaaa";
URI query = new URI(null, null, null, queryString, null);
URI result = base.resolve(query);

理论(或我认为)是解决方案应该返回:

Theory (or what I think) is that resolve should return:

http://example.com/something/more/long?query=http://local:282/rand&action=aaaa

但我得到的是:

http://example.com/something/more/?query=http://local:282/rand&action=aaaa






我对 RFC 3986第5.2.2节是如果相对URI的路径为空,则使用基URI的整个路径:


My understanding of RFC 3986 section 5.2.2 is that if the path of the relative URI is empty, then the entire path of the base URI is to be used:

        if (R.path == "") then
           T.path = Base.path;
           if defined(R.query) then
              T.query = R.query;
           else
              T.query = Base.query;
           endif;

且仅当指定了路径时才是要与基本路径合并的相对路径:

and only if a path is specified is the relative path to be merged against the base path:

        else
           if (R.path starts-with "/") then
              T.path = remove_dot_segments(R.path);
           else
              T.path = merge(Base.path, R.path);
              T.path = remove_dot_segments(T.path);
           endif;
           T.query = R.query;
        endif;

但是Java实现总是进行合并,即使路径是空的:

but the Java implementation always does the merge, even if the path is empty:

    String cp = (child.path == null) ? "" : child.path;
    if ((cp.length() > 0) && (cp.charAt(0) == '/')) {
      // 5.2 (5): Child path is absolute
      ru.path = child.path;
    } else {
      // 5.2 (6): Resolve relative path
      ru.path = resolvePath(base.path, cp, base.isAbsolute());
    }

如果我的读数正确,要从RFC伪代码中获取此行为,可以在查询字符串之前在相对URI中放置一个点作为路径,根据我的经验,使用相对URI作为网页中的链接是我所期望的:

If my reading is correct, to get this behaviour from the RFC pseudocode, you could put a dot as the path in the relative URI, before the query string, which from my experience using relative URIs as links in web pages is what I would expect:

transform(Base="http://example.com/something/more/long", R=".?query")
    => T="http://example.com/something/more/?query"

但我会期望在网页中显示页面上的链接 http://example.com/something/更多/更长到?查询将转到 http://示例.com / something / more / long?query ,而不是 http:// example .com / something / more /?query - 换句话说,与RFC一致,但不与Java实现一致。

But I would expect, in a web page, that a link on the page "http://example.com/something/more/long" to "?query" would go to "http://example.com/something/more/long?query", not "http://example.com/something/more/?query" - in other words, consistent with the RFC, but not with the Java implementation.

我的阅读是RFC正确,Java方法与它不一致,或者我错过了什么?

Is my reading of the RFC correct, and the Java method inconsistent with it, or am I missing something?

推荐答案

是的,我同意 URI.resolve(URI)方法与RFC 3986不兼容。原始问题本身就提供了一个梦幻般的贡献的研究数量得出这个结论。首先,让我们清除任何混淆。

Yes, I agree that the URI.resolve(URI) method is incompatible with RFC 3986. The original question, on its own, presents a fantastic amount of research that contributes to this conclusion. First, let's clear up any confusion.

正如Raedwald所解释的那样(在一个现已删除的答案中),区分基础路径的结束或者不以 / 结尾:

As Raedwald explained (in a now deleted answer), there is a distinction between base paths that end or do not end with /:


  • fizz 相对于 / foo / bar 是: / foo / fizz

  • fizz 相对于 / foo / bar / 是: / foo / bar / fizz

  • fizz relative to /foo/bar is: /foo/fizz
  • fizz relative to /foo/bar/ is: /foo/bar/fizz

虽然正确,但这不是一个完整的答案,因为原来的问题是不是询问路径 (即上面的fizz) 。相反,该问题涉及相对URI引用的单独查询组件 。 URI类示例代码中使用的构造函数接受五个不同的String参数,除了 queryString 参数作为 null 传递。 (请注意,Java接受一个空字符串作为路径参数,这在逻辑上会产生一个空路径组件,因为路径组件永远不会被定义虽然它 may是空的(零长度)。)这在以后很重要。

While correct, it's not a complete answer because the original question is not asking about a path (i.e. "fizz", above). Instead, the question is concerned with the separate query component of the relative URI reference. The URI class constructor used in the example code accepts five distinct String arguments, and all but the queryString argument were passed as null. (Note that Java accepts a null String as the path parameter and this logically results in an "empty" path component because "the path component is never undefined" though it "may be empty (zero length)".) This will be important later.

早期评论,Sajan Chandran指出 java.net.URI 类以实现 RFC 2396 问题的主题, RFC 3986 。前者在2005年被后者废弃。如果URI类Javadoc没有提及较新的RFC,则可以解释为其不兼容性的更多证据。让我们再说一些:

In an earlier comment, Sajan Chandran pointed out that the java.net.URI class is documented to implement RFC 2396 and not the subject of the question, RFC 3986. The former was obsoleted by the latter in 2005. That the URI class Javadoc does not mention the newer RFC could be interpreted as more evidence of its incompatibility. Let's pile on some more:


  • JDK-6791060 是一个未解决的问题,表明此类应针对RFC 3986进行更新。那里的评论警告说RFC3986并不是完全倒退
    与2396兼容。

  • JDK-6791060 is an open issue that suggests this class "should be updated for RFC 3986". A comment there warns that "RFC3986 is not completely backwards compatible with 2396".

之前的尝试是将URI类的部分更新为符合RFC 3986,例如 JDK-6348622 ,但当时< a href =https://bugs.openjdk.java.net/browse/JDK-6394131\"rel =nofollow noreferrer>回滚以打破向后兼容性。 (另请参阅JDK上的此讨论邮件列表。)

Previous attempts were made to update parts of the URI class to be compliant with RFC 3986, such as JDK-6348622, but were then rolled back for breaking backwards compatibility. (Also see this discussion on the JDK mailing list.)

虽然路径合并逻辑听起来类似,但 SubOptimal指出,新RFC中指定的伪代码与实际实施。在伪代码中,当相对URI的路径为空时,生成的目标路径将从基URI 中按原样复制。在这些条件下不执行合并逻辑。与该规范相反,Java的URI实现修剪了最后一个 / 字符后的基本路径,如问题所示。

Although the path "merge" logic sounds similar, as noted by SubOptimal, the pseudocode specified in the newer RFC does not match the actual implementation. In the pseudocode, when the relative URI's path is empty, then the resulting target path is copied as-is from the base URI. The "merge" logic is not executed under those conditions. Contrary to that specification, Java's URI implementation trims the base path after the last / character, as observed in the question.

如果您想要RFC 3986行为,还有URI类的替代方法。 Java EE 6实现提供 javax.ws.rs.core.UriBuilder ,(在泽西岛1.18中)似乎表现得如你所愿(见下文)。至少在编码不同的URI组件时,它至少要求了解RFC。

There are alternatives to the URI class, if you want RFC 3986 behavior. Java EE 6 implementations provide javax.ws.rs.core.UriBuilder, which (in Jersey 1.18) seems to behave as you expected (see below). It at least claims awareness of the RFC as far as encoding different URI components is concerned.

在J2EE之外,Spring 3.0引入了 UriUtils ,特别记录为基于RFC的编码和解码 3986\" 。 Spring 3.1弃用了其中的一些功能并引入了 UriComponentsBuilder ,但遗憾的是它没有记录对任何特定RFC的遵守情况。

Outside of J2EE, Spring 3.0 introduced UriUtils, specifically documented for "encoding and decoding based on RFC 3986". Spring 3.1 deprecated some of that functionality and introduced the UriComponentsBuilder, but it does not document adherence to any specific RFC, unfortunately.

测试程序,演示不同的行为:

Test program, demonstrating different behaviors:

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

这篇关于当相对URI包含空路径时,Java的URI.resolve是否与RFC 3986不兼容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆