开发者

Append relative URL to java.net.URL

Provided I have a java.net.URL object, pointing to let's say

http://ex开发者_高级运维ample.com/myItems or http://example.com/myItems/

Is there some helper somewhere to append some relative URL to this? For instance append ./myItemId or myItemId to get : http://example.com/myItems/myItemId


URL has a constructor that takes a base URL and a String spec.

Alternatively, java.net.URI adheres more closely to the standards, and has a resolve method to do the same thing. Create a URI from your URL using URL.toURI.


This one does not need any extra libs or code and gives the desired result:

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

This prints:

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

The accepted answer only works if there is no path after the host (IMHO the accepted answer is wrong)


You can just use the URI class for this:

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

Note the trailing slash on the base path and the base-relative format of the segment that's being appended. You can also use the URIBuilder class from Apache HTTP client:

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


I cannot believe how nasty URI.resolve() really is its full of nasty edge cases.

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"

The tidiest solution I have seen that handles these edge cases in an predictable way is:

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

Results:

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


Here is a helper function I've written to add to the url path:

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


I've searched far and wide for an answer to this question. The only implementation I can find is in the Android SDK: Uri.Builder. I've extracted it for my own purposes.

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

This is where I found the source.

In conjunction with Apache URIBuilder, this is how I'm using it: builder.setPath(appendSegmentToPath(builder.getPath(), segment));


You can use URIBuilder and the method URI#normalize to avoid duplicate / in the 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


UPDATED

I believe this is the shortest solution:

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


Concatenate a relative path to a URI:

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

res will contain https://stackoverflow.com/questions/some/path


A pragmatical solution without any external libs is given below.

(Comment: After reading through all the answers given so far, I am really not happy with the solutions provided - especially as this question is eight years old. No solution does deal properly with queries, fragments and so on.)

Extension method on 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);
            }
        }
    }

Testing

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


On Android you can use android.net.Uri. The following allows to create an Uri.Builder from an existing URL as String and then append:

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

Note that appendEncodedPath doesn't expect a leading / and only contains a check if the "baseUrl" ends with one, otherwise one is added before the path.

According to the docs, this supports

  • Absolute hierarchical URI reference following the pattern

    • <scheme>://<authority><absolute path>?<query>#<fragment>
  • Relative URI with pattern

    • <relative or absolute path>?<query>#<fragment>
    • //<authority><absolute path>?<query>#<fragment>
  • Opaque URI with pattern

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


Some examples using the Apache URIBuilder http://hc.apache.org/httpcomponents-client-4.3.x/httpclient/apidocs/org/apache/http/client/utils/URIBuilder.html:

Ex1:

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

Result 1 -> http://example.com/test/example

Ex2:

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

Result 2 -> http://example.com/test/example


My solution based on twhitbeck answer:

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

Test:

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

Gist: https://gist.github.com/enginer/230e2dc2f1d213a825d5


I had some difficulty with the encoding of URI's. Appending was not working for me because it was of a content:// type and it was not liking the "/". This solution assumes no query, nor fragment(we are working with paths after all):

Kotlin code:

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


Support for appending paths was added to URIBuilder in Apache HttpClient 5.1 with the appendPath method:

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:

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


For android make sure you use .appendPath() from android.net.Uri


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


An handmade uri segments joiner

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 "";
    } else if (segmentArray.length == 1) {
        return segmentArray[0];
    }

    List<String> segmentList = new ArrayList<>();
    for (String s : segmentArray) {
        if (s != null && s.length() > 0) {
            segmentList.add(s);
        }
    }

    if (segmentList.size() == 0) {
        return "";
    } else if (segmentList.size() == 1) {
        return segmentList.get(0);
    }

    StringBuilder sb = new StringBuilder();
    sb.append(segmentList.get(0));
    String prevS;
    String currS;
    boolean prevB;
    boolean currB;
    for (int i = 1; i < segmentList.size(); i++) {
        prevS = segmentList.get(i - 1);
        currS = segmentList.get(i);
        prevB = prevS.endsWith("/");
        currB = currS.startsWith("/");
        if (!prevB && !currB) {
            sb.append("/").append(currS);
        } else if (prevB && currB) {
            sb.append(currS.substring(1));
        } else {
            sb.append(currS);
        }
    }
    return sb.toString();
}


This takes only one line, normalize() is your friend here, and always add an extra / inbetween the concatenation

When baseUrl ends with / the normalize() would remove the extra ones. If it doesn't end with / then we've covered it by adding one deliberately.

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

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

Sample with many extra / will be normalized to a sane path as per the 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


To get around all the edge cases the best would be to combine two standard classes - UriBuilder from apache.httpclient and java.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();

It results in: http://host:80/root/url/relative/path

This works with any number of leading and trailing / and also when / are absent.


With Spring framework:

import org.springframework.web.util.DefaultUriBuilderFactory;
...
var url = "http://localhost"
var uriBuilderFactory = new DefaultUriBuilderFactory(url)
var target_uri = uriBuilderFactory.builder().pathSegment("path1").pathSegment("path2").build().toString()
//
// result: http://localhost/path1/path2
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜