将相对URL添加到java.net.URL

61

假设我有一个指向 http://example.com/myItems 或者 http://example.com/myItems/的java.net.URL对象,是否有某种助手方法可以将一些相对URL添加到它的末尾?例如添加 ./myItemId myItemId 以获取: http://example.com/myItems/myItemId


可能是在Java servlet中从相对URL构建绝对URL的重复问题。 - Don Roby
似乎没有简单的解决方案。无论是URL还是URI类都不支持此功能。Apache HttpClient也不支持此功能。 - Sri
21个回答

37

URL有一个constructor,它需要一个基本的URL和一个String规范。

或者,java.net.URI更加符合标准,并且有一个resolve方法来完成相同的操作。使用URL.toURI从你的URL创建一个URI


29
URL有一个构造函数,接受一个基础URL和一个字符串规范。示例代码为:URL url1 = new URL("http://petstore.swagger.wordnik.com/api/api-docs"); URL url2 = new URL(url1, "/pet"); System.out.println(url2.toString()) 输出结果为:http://petstore.swagger.wordnik.com/pet,而不是http://petstore.swagger.wordnik.com/api/api-docs/pet。 - user2585038
2
为了让其他人受益:第一个URL构造方法并没有按照我预期的方式工作(RTFM,嗯?)。只有baseURL的非路径部分被使用。 - Jasper Blues
10
第二个参数不能以斜杠开头。斜杠表示“绝对”链接,因此 url1 的路径会被忽略。 - harsel
2
URI解析在这里实际上只是错误的。请参见下面@Christoph Henkelmann的答案。 - Ittai
2
正如@user2585038所提到的,此答案未考虑RFC2396第5.2节(6a)中的规定:“将基本URI路径组件的除最后一段之外的所有内容复制到缓冲区。换句话说,如果有任何斜杠字符,则排除最后(最右侧)斜杠字符之后的任何字符。”在我看来,这使得URI解析在大多数情况下都无法使用,包括OP的情况。 - steinybot
显示剩余2条评论

36

这个不需要任何额外的库或代码,就可以得到所需的结果:

//import java.net.URL;
URL url1 = new URL("http://petstore.swagger.wordnik.com/api/api-docs?foo=1&bar=baz");
URL url2 = new URL(url1.getProtocol(), url1.getHost(), url1.getPort(), url1.getPath() + "/pet" + "?" + url1.getQuery(), null);
System.out.println(url1);
System.out.println(url2);

这将打印:

http://petstore.swagger.wordnik.com/api/api-docs?foo=1&bar=baz
http://petstore.swagger.wordnik.com/api/api-docs/pet?foo=1&bar=baz

在主机名后面没有路径时,才能使用被接受的答案(我认为被接受的答案是错误的)。


1
MalformedUrlException为什么他们不抛出未经检查的异常 :'( - gkiko
是的,那很糟糕...而且它不一致:解析数字会抛出未经检查的异常^^ - Christoph Henkelmann
2
此解决方案无法处理尾部斜杠。该问题提供了两个示例。 - rrhrg
1
url1.getFile().endsWith("/") ? url1.getFile() + "pet" : url1.getFile() + "/pet"?请注意,这是一段编程相关的内容,翻译时需要保留其专业性和准确性。 - Christoph Henkelmann
1
@MartinSenne 很好发现,我调整了示例。 - Christoph Henkelmann
显示剩余3条评论

15

你可以使用 URI 类来实现这个功能:

import java.net.URI;
import org.apache.http.client.utils.URIBuilder;

URI uri = URI.create("http://example.com/basepath/");
URI uri2 = uri.resolve("./relative");
// => http://example.com/basepath/relative

请注意基本路径的末尾斜线和正在附加的段的基于基本路径的格式。您还可以使用Apache HTTP客户端的URIBuilder类:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.3</version>
</dependency>

...

import java.net.URI;
import org.apache.http.client.utils.URIBuilder;

URI uri = URI.create("http://example.com/basepath");
URI uri2 = appendPath(uri, "relative");
// => http://example.com/basepath/relative

public URI appendPath(URI uri, String path) {
    URIBuilder builder = new URIBuilder(uri);
    builder.setPath(URI.create(builder.getPath() + "/").resolve("./" + path).getPath());
    return builder.build();
}

6
第一段代码的结果不是http://example.com/basepath/relative,而是http://example.com/relative,很遗憾这使得我之前回答的第一部分是错误的。 - Luciano
实际上,答案的第一部分是正确的;如果在第一个URI中有多个/,并且未提供./,则它也可以工作。例如:http://www.x.com:300/z/y/f.txt//////// ~~~ http://www.x.com:300/z/y/f.txt/zzccvr.tyt - Ironluca

13

我简直不敢相信 URI.resolve() 有多糟糕,它充满了许多恶心的边角情况。

new URI("http://localhost:80").resolve("foo") => "http://localhost:80foo"
new URI("http://localhost:80").resolve("//foo") => "http://foo"
new URI("http://localhost:80").resolve(".//foo") => "http://foo"

我见过的处理这些边缘情况最为整洁且可预测的解决方案是:

URI addPath(URI uri, String path) {
    String newPath;
    if (path.startsWith("/")) newPath = path.replaceAll("//+", "/");
    else if (uri.getPath().endsWith("/")) newPath = uri.getPath() + path.replaceAll("//+", "/");
    else newPath = uri.getPath() + "/" + path.replaceAll("//+", "/");

    return uri.resolve(newPath).normalize();
}

结果:

jshell> addPath(new URI("http://localhost"), "sub/path")
$3 ==> http://localhost/sub/path

jshell> addPath(new URI("http://localhost/"), "sub/path")
$4 ==> http://localhost/sub/path

jshell> addPath(new URI("http://localhost/"), "/sub/path")
$5 ==> http://localhost/sub/path

jshell> addPath(new URI("http://localhost/random-path"), "/sub/path")
$6 ==> http://localhost/sub/path

jshell> addPath(new URI("http://localhost/random-path"), "./sub/path")
$7 ==> http://localhost/random-path/sub/path

jshell> addPath(new URI("http://localhost/random-path"), "../sub/path")
$8 ==> http://localhost/sub/path

jshell> addPath(new URI("http://localhost"), "../sub/path")
$9 ==> http://localhost/../sub/path

jshell> addPath(new URI("http://localhost/"), "//sub/path")
$10 ==> http://localhost/sub/path

jshell> addPath(new URI("http://localhost/"), "//sub/./path")
$11 ==> http://localhost/sub/path

1
URI.resolve 函数在前面加上双斜杠的结果是正确的,这被称为“协议相对 URL”,曾经很常见,但现在不再鼓励使用。请参见 维基百科。如果第二个参数始终是路径,则您的 addPath 函数是正确的,但是 URI.resolve 处理更一般的情况,其中第二个参数可能是完整的 URL。 - Kyle Pittman

8

这是我编写的一个辅助函数,用于添加到URL路径中:

public static URL concatenate(URL baseUrl, String extraPath) throws URISyntaxException, 
                                                                    MalformedURLException {
    URI uri = baseUrl.toURI();
    String newPath = uri.getPath() + '/' + extraPath;
    URI newUri = uri.resolve(newPath);
    return newUri.toURL();
}

7
如果 baseUrl/ 结尾或 extraPath/ 开始,则最终的 URL 中会出现重复的 /。在最后一个 toURL() 前应该添加 .normalize() - Simão Martins
这对于本地文件URL无效,本地文件URL应该以file://开头。 - Sundar Rajan
如果baseUrl.getPath()"/",这将修改主机!URI("http://localhost:80").resolve("//foo") => "http://foo"我将在下面展示如何解决这些问题。 - iain

7
您可以使用URIBuilder和方法URI#normalize来避免URI中重复出现的/
URIBuilder uriBuilder = new URIBuilder("http://example.com/test");
URI uri = uriBuilder.setPath(uriBuilder.getPath() + "/path/to/add")
          .build()
          .normalize();
// expected : http://example.com/test/path/to/add

4
我已经广泛搜索了这个问题的答案。我所找到的唯一实现是在Android SDK中: Uri.Builder。我将其提取出来用于自己的目的。
private String appendSegmentToPath(String path, String segment) {
  if (path == null || path.isEmpty()) {
    return "/" + segment;
  }

  if (path.charAt(path.length() - 1) == '/') {
    return path + segment;
  }

  return path + "/" + segment;
}

这里是我找到的源代码。

结合Apache URIBuilder,这是我使用的方式:builder.setPath(appendSegmentToPath(builder.getPath(), segment));


这可以以与Jersey客户端/ http://docs.oracle.com/javaee/7/api/javax/ws/rs/core/UriBuilder.html的UriBuilder相同的方式使用。 - Optional

3

将相对路径连接到URI:

java.net.URI uri = URI.create("https://stackoverflow.com/questions")
java.net.URI res = uri.resolve(uri.getPath + "/some/path")

res将包含https://stackoverflow.com/questions/some/path


1
如果你运行这段代码,我认为你会发现结果为: - Scott Babcock
已更正代码,使用 uri.getPath 替代,谢谢! - Martin Tapp

3

更新

我相信这是最简单的解决方案:

URL url1 = new URL("http://domain.com/contextpath");
String relativePath = "/additional/relative/path";
URL concatenatedUrl = new URL(url1.toExternalForm() + relativePath);

new URL("/additional/relative/path"); 抛出 MalformedURLException。 - Konstantin Pelepelin

2
以下是没有使用任何外部库的实用解决方案。
(注:阅读了目前给出的所有答案后,我对提供的解决方案并不满意 - 尤其是因为这个问题已经存在八年了。没有一个解决方案能够正确处理查询、片段等。)
URL的扩展方法:
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

class URLHelper {
        public static URL appendRelativePathToURL(URL base, String relPath) {
            /*
              foo://example.com:8042/over/there?name=ferret#nose
              \_/   \______________/\_________/ \_________/ \__/
               |           |            |            |        |
            scheme     authority       path        query   fragment
               |   _____________________|__
              / \ /                        \
              urn:example:animal:ferret:nose

            see https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
            */
            try {

                URI baseUri = base.toURI();

                // cut initial slash of relative path
                String relPathToAdd = relPath.startsWith("/") ? relPath.substring(1) : relPath;

                // cut trailing slash of present path
                String path = baseUri.getPath();
                String pathWithoutTrailingSlash = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;

                return new URI(baseUri.getScheme(),
                        baseUri.getAuthority(),
                        pathWithoutTrailingSlash + "/" + relPathToAdd,
                        baseUri.getQuery(),
                        baseUri.getFragment()).toURL();
            } catch (URISyntaxException e) {
                throw new MalformedURLRuntimeException("Error parsing URI.", e);
            } catch (MalformedURLException e) {
                throw new MalformedURLRuntimeException("Malformed URL.", e);
            }
        }

        public static class MalformedURLRuntimeException extends RuntimeException {
            public MalformedURLRuntimeException(String msg, Throwable cause) {
                super("Malformed URL: " + msg, cause);
            }
        }
    }

测试

    private void demo() {

        try {
            URL coolURL = new URL("http://fun.de/path/a/b/c?query&another=3#asdf");
            URL notSoCoolURL = new URL("http://fun.de/path/a/b/c/?query&another=3#asdf");
            System.out.println(URLHelper.appendRelativePathToURL(coolURL, "d"));
            System.out.println(URLHelper.appendRelativePathToURL(coolURL, "/d"));
            System.out.println(URLHelper.appendRelativePathToURL(notSoCoolURL, "d"));
            System.out.println(URLHelper.appendRelativePathToURL(notSoCoolURL, "/d"));

        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接