簡體   English   中英

Append 相對 URL 到 java.net.URL

[英]Append relative URL to java.net.URL

假設我有一個 java.net.URL object,指向讓我們說

http://example.com/myItemshttp://example.com/myItems/

append 的某個地方是否有一些幫手,一些親戚 URL 到這個? 例如 append ./myItemIdmyItemId得到: http://example.com/myItems/myItemId

這個不需要任何額外的庫或代碼並提供所需的結果:

//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

接受的答案僅在主機之后沒有路徑時才有效(恕我直言,接受的答案是錯誤的)

URL有一個構造函數,它接受一個基本URL和一個String規范。

或者, java.net.URI更符合標准,並且有一個resolve方法來做同樣的事情。 使用URL.toURI從您的URL創建一個URI

這是我編寫的要添加到 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();
}

我已經廣泛搜索了這個問題的答案。 我能找到的唯一實現是在 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));

更新

我相信這是最短的解決方案:

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

您可以使用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

您可以為此使用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();
}

將相對路徑連接到 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

在 Android 上,您可以使用android.net.Uri 下面允許從現有的 URL 創建一個Uri.Builder作為String ,然后附加:

Uri.parse(baseUrl) // Create Uri from String
    .buildUpon()   // Creates a "Builder"
    .appendEncodedPath("path/to/add")
    .appendQueryParameter("at_ref", "123") // To add ?at_ref=123
    .fragment("anker") // To add #anker
    .build()

請注意, appendEncodedPath不需要前導/並且只包含檢查“baseUrl”是否以 1 結尾,否則在路徑之前添加一個。

根據文檔,這支持

  • 遵循模式的絕對分層 URI 引用

    • <scheme>://<authority><absolute path>?<query>#<fragment>
  • 帶模式的相對 URI

    • <relative or absolute path>?<query>#<fragment>
    • //<authority><absolute path>?<query>#<fragment>
  • 帶有模式的不透明 URI

    • <scheme>:<opaque part>#<fragment>

使用 Apache URIBuilder http://hc.apache.org/httpcomponents-client-4.3.x/httpclient/apidocs/org/apache/http/client/utils/URIBuilder.html 的一些示例:

例1:

String url = "http://example.com/test";
URIBuilder builder = new URIBuilder(url);
builder.setPath((builder.getPath() + "/example").replaceAll("//+", "/"));
System.out.println("Result 1 -> " + builder.toString());

結果 1 -> http://example.com/test/example

例2:

String url = "http://example.com/test";
URIBuilder builder = new URIBuilder(url);
builder.setPath((builder.getPath() + "///example").replaceAll("//+", "/"));
System.out.println("Result 2 -> " + builder.toString());

結果 2 -> http://example.com/test/example

我基於 twhitbeck 回答的解決方案:

import java.net.URI;
import java.net.URISyntaxException;

public class URIBuilder extends org.apache.http.client.utils.URIBuilder {
    public URIBuilder() {
    }

    public URIBuilder(String string) throws URISyntaxException {
        super(string);
    }

    public URIBuilder(URI uri) {
        super(uri);
    }

    public org.apache.http.client.utils.URIBuilder addPath(String subPath) {
        if (subPath == null || subPath.isEmpty() || "/".equals(subPath)) {
            return this;
        }
        return setPath(appendSegmentToPath(getPath(), subPath));
    }

    private String appendSegmentToPath(String path, String segment) {
        if (path == null || path.isEmpty()) {
            path = "/";
        }

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

        return path + "/" + segment;
    }
}

測試:

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class URIBuilderTest {

    @Test
    public void testAddPath() throws Exception {
        String url = "http://example.com/test";
        String expected = "http://example.com/test/example";

        URIBuilder builder = new URIBuilder(url);
        builder.addPath("/example");
        assertEquals(expected, builder.toString());

        builder = new URIBuilder(url);
        builder.addPath("example");
        assertEquals(expected, builder.toString());

        builder.addPath("");
        builder.addPath(null);
        assertEquals(expected, builder.toString());

        url = "http://example.com";
        expected = "http://example.com/example";

        builder = new URIBuilder(url);
        builder.addPath("/");
        assertEquals(url, builder.toString());
        builder.addPath("/example");
        assertEquals(expected, builder.toString());
    }
}

要點: https : //gist.github.com/enginer/230e2dc2f1d213a825d5

我在 URI 的編碼方面遇到了一些困難。 追加對我不起作用,因為它是 content:// 類型並且它不喜歡“/”。 這個解決方案假設沒有查詢,也沒有片段(畢竟我們正在使用路徑):

科特林代碼:

  val newUri = Uri.parse(myUri.toString() + Uri.encode("/$relPath"))

下面給出了一個沒有任何外部庫的實用解決方案。

(評論:在閱讀了迄今為止給出的所有答案后,我對所提供的解決方案真的不滿意 - 特別是因為這個問題已經有八年歷史了。沒有解決方案可以正確處理查詢、片段等。)

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

對於 android,請確保您使用android.net.Uri .appendPath()

public String joinUrls(String baseUrl, String extraPath) {
        try {
            URI uri = URI.create(baseUrl+"/");//added additional slash in case there is no slash at either sides
            URI newUri = uri.resolve(extraPath);
            return newUri.toURL().toString();
        } catch (IllegalArgumentException | MalformedURLException e) {
            //exception
        }
}

一個手工制作的uri段木匠

public static void main(String[] args) {
    System.out.println(concatURISegments(
            "http://abc/",
            "/dfg/",
            "/lmn",
            "opq"
    ));
}

public static String concatURISegments(String... segmentArray) {
    if (segmentArray.length == 0) {
        return null;
    } else if (segmentArray.length == 1) {
        return segmentArray[0];
    } else {
        boolean previousEnds;
        boolean currentStarts;
        StringBuilder sb = new StringBuilder();
        sb.append(segmentArray[0]);
        for (int i = 1; i < segmentArray.length; i++) {
            previousEnds = segmentArray[i - 1].endsWith("/");
            currentStarts = segmentArray[i].startsWith("/");
            if (!previousEnds && !currentStarts) {
                sb.append("/").append(segmentArray[i]);
            } else if (previousEnds && currentStarts) {
                sb.append(segmentArray[i].substring(1));
            } else {
                sb.append(segmentArray[i]);
            }
        }
        return sb.toString();
    }
}

在 Apache HttpClient 5.1 中使用appendPath方法向URIBuilder添加了對附加路徑的支持:

import org.apache.hc.core5.net.URIBuilder;
..
URI uri = new URIBuilder("https://stackoverflow.com/questions")
  .appendPath("7498030")
  .appendPath("append-relative-url")
  .build();

// https://stackoverflow.com/questions/7498030/append-relative-url

Maven 依賴:

<dependency>
  <groupId>org.apache.httpcomponents.client5</groupId>
  <artifactId>httpclient5</artifactId>
  <version>5.1</version>
</dependency>

我無法相信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;
    else if (uri.getPath().endsWith("/")) newPath = uri.getPath() + path;
    else newPath = uri.getPath() + "/" + path;

    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

這只需要一行, normalize()是你的朋友,並且總是在連接之間添加一個額外的/

當 baseUrl 以/結尾時,normalize() 將刪除多余的。 如果它不以/結尾,那么我們已經通過故意添加一個來覆蓋它。

String unknownBaseUrl = "https://example.com/apples/";
String result = URI.create(unknownBaseUrl + "/" + "1209").normalize().toString();
System.out.println(result);

output: https://example.com/apples/1209

根據RFC 2396 ,帶有許多額外/的示例將被標准化為正常路徑

String unknownBaseUrl = "https://example.com/apples/";
String result = URI.create(unknownBaseUrl + "/" + "/1209").normalize().toString();
System.out.println(result);

output: https://example.com/apples/1209

要繞過所有邊緣情況,最好的辦法是組合兩個標准類 - 來自UriBuilderapache.httpclientjava.nio.file.Paths

String rootUrl = "http://host:80/root/url";
String relativePath = "relative/path";

URIBuilder builder = new URIBuilder(rootUrl);
String combinedPath = Paths.get(builder.getPath(), relativePath).toString();
builder.setPath(combinedPath);

URL targetUrl = builder.build().toURL();

結果為: http://host:80/root/url/relative/path

這適用於任意數量的前導和尾隨/以及不存在/的情況。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM