草庐IT

java - 当相对 URI 包含空路径时,Java 的 URI.resolve 是否与 RFC 3986 不兼容?

coder 2023-08-30 原文

我认为 Java 的 URI.resolve 方法的定义和实现与 RFC 3986 section 5.2.2 不兼容。 .我知道 Java API 定义了该方法的工作原理,如果现在更改它会破坏现有的应用程序,但我的问题是:谁能证实我对这种方法与 RFC 3986 不兼容的理解?

我正在使用这个问题的例子:java.net.URI resolve against only query string ,我将在此处复制:


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

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

理论(或者我的想法)是 resolve 应该返回:

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 section 5.2.2的理解就是,如果相对 URI 的路径为空,那么将使用基本 URI 的整个路径:

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

并且仅当指定了路径时,相对路径才会与基本路径合并:

        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 实现总是进行合并,即使路径为空也是如此:

    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 作为网页链接的经验我希望:

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

但我希望在网页中,“http://example.com/something/more/long”页面上到“?query”的链接会转到“http://example.com/something/more/long?query”,而不是“http://example.com/something/more/?query”——换句话说,与 RFC 一致,但与 Java 实现不一致。

我对 RFC 的阅读是否正确,Java 方法与其不一致,还是我遗漏了什么?

最佳答案

是的,我同意 URI.resolve(URI) 方法与 RFC 3986 不兼容。最初的问题本身提供了大量极好的研究,有助于得出这个结论.首先,让我们澄清一下所有的困惑。

正如 Raedwald 所解释的(在现已删除的答案中),/ 结尾或不以 fizz 结尾的基本路径之间存在区别:

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

虽然正确,但它不是一个完整的答案,因为原始问题不是询问 path (即上面的“fizz”)。相反,问题与相对 URI 引用的单独 query component 有关。 URI 类 constructor used in the example code 接受五个不同的字符串参数,并且除了 null 参数之外的所有参数都作为 java.net.URI 传递。 (请注意,Java 接受空字符串作为路径参数,这在逻辑上会导致“空”路径组件,因为“the path component is never undefined ”虽然它是“may be empty (zero length) ”。)这在以后很重要。

earlier comment 中,Sajan Chandran 指出 / class 被记录为实现 RFC 2396不是问题的主题 RFC 3986 。前者在 2005 年被后者淘汰。URI 类 Javadoc 没有提到较新的 RFC 可以解释为它不兼容的更多证据。让我们再补充一些:

  • JDK-6791060 建议此类“应该针对 RFC 3986 进行更新”。那里的评论警告说“RFC3986 并不完全落后 与 2396 兼容”。它于 2018 年作为 JDK-8019345 的副本关闭(截至 2022 年 10 月仍然开放且未解决,自 2013 年以来没有显着 Activity )。

  • 之前曾尝试更新 URI 类的部分内容以符合 RFC 3986,例如 JDK-6348622 ,但随后 rolled back 破坏了向后兼容性。 (另请参阅 JDK 邮件列表中的 this discussion。)

  • 虽然路径“合并”逻辑听起来与 noted by SubOptimal 相似,但较新的 RFC 中指定的伪代码与 actual implementation 不匹配。在伪代码中,当相对 URI 的路径为时,生成的目标路径将按原样从基本 URI 复制。伪代码的“合并”逻辑不会在这些条件下执行。与该规范相反,Java 的 URI 实现在最后一个 javax.ws.rs.core.UriBuilder 字符之后修剪基本路径,如问题中所观察到的。

如果您想要 RFC 3986 行为,可以使用 URI 类的替代方法。 Java EE 6 到 EE 8 实现提供了 jakarta.ws.rs.core.UriBuilder ,它(在 Jersey 1.18 中)的行为似乎符合您的预期(见下文)。就编码不同的 URI 组件而言,它至少声称了解 RFC。随着从 JavaEE 到 JakartaEE 9 的切换(大约 2020 年),此类转移到 ojit_code

在 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

关于java - 当相对 URI 包含空路径时,Java 的 URI.resolve 是否与 RFC 3986 不兼容?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22203111/

有关java - 当相对 URI 包含空路径时,Java 的 URI.resolve 是否与 RFC 3986 不兼容?的更多相关文章

  1. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  2. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  3. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  4. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  5. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  6. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  9. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  10. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

随机推荐