简体   繁体   中英

Append relative URL to java.net.URL

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

http://example.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

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)

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 .

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

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

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

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

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

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

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

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

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

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>

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

    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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM