Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add HttpUriBuilder from Galaxy

  • Loading branch information...
commit 65d12892ca6ed1e7e3e96bed09d799d737f66c5b 1 parent 8b7e507
@dain dain authored
View
299 http-client/src/main/java/io/airlift/http/client/HttpUriBuilder.java
@@ -0,0 +1,299 @@
+package io.airlift.http.client;
+
+import com.google.common.base.Ascii;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.primitives.Bytes;
+
+import java.io.ByteArrayOutputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CodingErrorAction;
+import java.util.Iterator;
+import java.util.Map;
+
+import static java.lang.Character.forDigit;
+import static java.util.Arrays.asList;
+
+/**
+ * An RFC-3986-compatible HTTP URI builder
+ */
+public class HttpUriBuilder
+{
+ private String scheme;
+ private String host;
+ private int port = -1;
+ private String path = ""; // decoded path
+ private ListMultimap<String, String> params = LinkedListMultimap.create(); // decoded query params
+
+ private static byte[] PCHAR = {
+ 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
+ '0','1','2','3','4','5','6','7','8','9',
+ '-', '.', '_', '~', '!', '$', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@',
+ };
+
+ private static byte[] ALLOWED_PATH_CHARS = Bytes.concat(PCHAR, new byte[] {'/', '&'});
+ private static byte[] ALLOWED_QUERY_CHARS = Bytes.concat(PCHAR, new byte[] {'/', '?'});
+
+ private HttpUriBuilder()
+ {
+ }
+
+ private HttpUriBuilder(URI previous)
+ {
+ scheme = previous.getScheme();
+ host = previous.getHost();
+ port = previous.getPort();
+ path = percentDecode(previous.getRawPath());
+ params.putAll(parseParams(previous.getRawQuery()));
+ }
+
+ public static HttpUriBuilder uriBuilder()
+ {
+ return new HttpUriBuilder();
+ }
+
+ public static HttpUriBuilder uriBuilderFrom(URI uri)
+ {
+ Preconditions.checkNotNull(uri, "uri is null");
+
+ return new HttpUriBuilder(uri);
+ }
+
+ public HttpUriBuilder scheme(String scheme)
+ {
+ Preconditions.checkNotNull(scheme, "scheme is null");
+
+ this.scheme = scheme;
+ return this;
+ }
+
+ public HttpUriBuilder host(String host)
+ {
+ Preconditions.checkNotNull(host, "host is null");
+ this.host = host;
+ return this;
+ }
+
+ public HttpUriBuilder port(int port)
+ {
+ Preconditions.checkArgument(port >= 1 && port <= 65535, "port must be in the range 1-65535");
+ this.port = port;
+ return this;
+ }
+
+ public HttpUriBuilder defaultPort()
+ {
+ this.port = -1;
+ return this;
+ }
+
+ /**
+ * Replace the current path with the given unencoded path
+ */
+ public HttpUriBuilder replacePath(String path)
+ {
+ Preconditions.checkNotNull(path, "path is null");
+
+ if (!path.equals("") && !path.startsWith("/")) {
+ path = "/" + path;
+ }
+
+ this.path = path;
+ return this;
+ }
+
+ /**
+ * Append an unencoded path.
+ *
+ * All reserved characters except '/' will be percent-encoded. '/' are considered as path separators and
+ * appended verbatim.
+ */
+ public HttpUriBuilder appendPath(String path)
+ {
+ Preconditions.checkNotNull(path, "path is null");
+
+ StringBuilder builder = new StringBuilder(this.path);
+ if (!this.path.endsWith("/")) {
+ builder.append('/');
+ }
+
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+
+ builder.append(path);
+
+ this.path = builder.toString();
+
+ return this;
+ }
+
+ public HttpUriBuilder replaceParameter(String name, String... values)
+ {
+ return replaceParameter(name, asList(values));
+ }
+
+ public HttpUriBuilder replaceParameter(String name, Iterable<String> values)
+ {
+ Preconditions.checkNotNull(name, "name is null");
+
+ params.removeAll(name);
+ addParameter(name, values);
+
+ return this;
+ }
+
+ public HttpUriBuilder addParameter(String name, String... values)
+ {
+ return addParameter(name, asList(values));
+ }
+
+ public HttpUriBuilder addParameter(String name, Iterable<String> values)
+ {
+ Preconditions.checkNotNull(name, "name is null");
+
+ if (Iterables.isEmpty(values)) {
+ params.put(name, null);
+ }
+
+ for (String value : values) {
+ params.put(name, value);
+ }
+
+ return this;
+ }
+
+ // return an RFC-3986-compatible URI
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(scheme);
+ builder.append("://");
+ if (host != null) {
+ builder.append(host);
+ }
+ if (port != -1) {
+ builder.append(':');
+ builder.append(port);
+ }
+
+ String path = this.path;
+ if (path.equals("") && !params.isEmpty()) {
+ path = "/";
+ }
+
+ builder.append(encode(path, ALLOWED_PATH_CHARS));
+
+ if (!params.isEmpty()) {
+ builder.append('?');
+
+ for (Iterator<Map.Entry<String, String>> iterator = params.entries().iterator(); iterator.hasNext(); ) {
+ Map.Entry<String, String> entry = iterator.next();
+
+ builder.append(encode(entry.getKey(), ALLOWED_QUERY_CHARS));
+ if (entry.getValue() != null) {
+ builder.append('=');
+ builder.append(encode(entry.getValue(), ALLOWED_QUERY_CHARS));
+ }
+
+ if (iterator.hasNext()) {
+ builder.append('&');
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ public URI build()
+ {
+ Preconditions.checkState(scheme != null, "scheme has not been set");
+ return URI.create(toString());
+ }
+
+ private ListMultimap<String, String> parseParams(String query)
+ {
+ LinkedListMultimap<String, String> result = LinkedListMultimap.create();
+
+ if (query != null) {
+ Iterable<String> pairs = Splitter.on("&")
+ .omitEmptyStrings()
+ .split(query);
+
+ for (String pair : pairs) {
+ String[] parts = pair.split("=", 2);
+ result.put(percentDecode(parts[0]), percentDecode(parts[1]));
+ }
+ }
+
+ return result;
+ }
+
+ private String encode(String input, byte... allowed)
+ {
+ StringBuilder builder = new StringBuilder();
+
+ ByteBuffer buffer = Charsets.UTF_8.encode(input);
+ while (buffer.remaining() > 0) {
+ byte b = buffer.get();
+
+ if (Bytes.contains(allowed, b)) {
+ builder.append((char) b); // b is ASCII
+ }
+ else {
+ builder.append('%');
+ builder.append(Ascii.toUpperCase(forDigit((b >>> 4) & 0xF, 16)));
+ builder.append(Ascii.toUpperCase(forDigit(b & 0xF, 16)));
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * input must be an ASCII string representing a percent-encoded UTF-8 byte sequence
+ */
+ private String percentDecode(String encoded)
+ {
+ Preconditions.checkArgument(CharMatcher.ASCII.matchesAllOf(encoded), "string must be ASCII");
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream(encoded.length());
+ for (int i = 0; i < encoded.length(); i++) {
+ char c = encoded.charAt(i);
+
+ if (c == '%') {
+ Preconditions.checkArgument(i + 2 < encoded.length(), "percent encoded value is truncated");
+
+ int high = Character.digit(encoded.charAt(i + 1), 16);
+ int low = Character.digit(encoded.charAt(i + 2), 16);
+
+ Preconditions.checkArgument(high != -1 && low != -1, "percent encoded value is not a valid hex string: ", encoded.substring(i, i + 2));
+
+ int value = (high << 4) | (low);
+ out.write(value);
+ i += 2;
+ }
+ else {
+ out.write((int) c);
+ }
+ }
+
+ try {
+ return Charsets.UTF_8.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .decode(ByteBuffer.wrap(out.toByteArray()))
+ .toString();
+ }
+ catch (CharacterCodingException e) {
+ throw new IllegalArgumentException("input does not represent a proper UTF8-encoded string");
+ }
+ }
+}
View
305 http-client/src/test/java/io/airlift/http/client/TestHttpUriBuilder.java
@@ -0,0 +1,305 @@
+package io.airlift.http.client;
+
+import org.testng.annotations.Test;
+
+import java.net.URI;
+
+import static io.airlift.http.client.HttpUriBuilder.uriBuilder;
+import static io.airlift.http.client.HttpUriBuilder.uriBuilderFrom;
+import static org.testng.Assert.assertEquals;
+
+public class TestHttpUriBuilder
+{
+ @Test
+ public void testCreateFromUri()
+ {
+ URI original = URI.create("http://www.example.com:8081/a%20/%C3%A5?k=1&k=2&%C3%A5=3");
+ assertEquals(HttpUriBuilder.uriBuilderFrom(original).build(), original);
+ }
+
+ @Test
+ public void testBasic()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com");
+ }
+
+
+ @Test
+ public void testWithPath()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .replacePath("/a/b/c")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/a/b/c");
+ }
+
+ @Test
+ public void testReplacePathWithRelative()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .replacePath("a/b/c")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/a/b/c");
+ }
+
+ @Test
+ public void testAppendToDefaultPath()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .appendPath("/a/b/c")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/a/b/c");
+ }
+
+ @Test
+ public void testAppendRelativePathToDefault()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .appendPath("a/b/c")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/a/b/c");
+ }
+
+ @Test
+ public void testAppendAbsolutePath()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .appendPath("/a/b/c")
+ .appendPath("/x/y/z")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/a/b/c/x/y/z");
+ }
+
+ @Test
+ public void testAppendRelativePath()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .appendPath("/a/b/c")
+ .appendPath("x/y/z")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/a/b/c/x/y/z");
+ }
+
+ @Test
+ public void testAppendPathElidesSlashes()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .appendPath("/a/b/c/")
+ .appendPath("/x/y/z")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/a/b/c/x/y/z");
+ }
+
+ @Test
+ public void testDoesNotStripTrailingSlash()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .appendPath("/a/b/c/")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/a/b/c/");
+ }
+
+ @Test
+ public void testFull()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .port(8081)
+ .replacePath("/a/b/c")
+ .replaceParameter("k", "1")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com:8081/a/b/c?k=1");
+ }
+
+ @Test
+ public void testAddParameter()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .replacePath("/")
+ .addParameter("k1", "1")
+ .addParameter("k1", "2")
+ .addParameter("k1", "0")
+ .addParameter("k2", "3")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/?k1=1&k1=2&k1=0&k2=3");
+ }
+
+ @Test
+ public void testAddParameterMultivalued()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .replacePath("/")
+ .addParameter("k1", "1", "2", "0")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/?k1=1&k1=2&k1=0");
+ }
+
+ @Test
+ public void testAddEmptyParameter()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .addParameter("pretty")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/?pretty");
+ }
+
+ @Test
+ public void testAddMultipleEmptyParameters()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .addParameter("pretty")
+ .addParameter("pretty")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/?pretty&pretty");
+ }
+
+ @Test
+ public void testAddMixedEmptyAndNonEmptyParameters()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .addParameter("pretty")
+ .addParameter("pretty", "true")
+ .addParameter("pretty")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/?pretty&pretty=true&pretty");
+ }
+
+ @Test
+ public void testReplaceParameters()
+ {
+ URI uri = HttpUriBuilder.uriBuilderFrom(URI.create("http://www.example.com:8081/?k1=1&k1=2&k2=3"))
+ .replaceParameter("k1", "4")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com:8081/?k2=3&k1=4");
+ }
+
+ @Test
+ public void testReplaceParameterMultivalued()
+ {
+ URI uri = HttpUriBuilder.uriBuilderFrom(URI.create("http://www.example.com/?k1=1&k1=2&k2=3"))
+ .replaceParameter("k1", "a", "b", "c")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/?k2=3&k1=a&k1=b&k1=c");
+ }
+
+ @Test
+ public void testReplacePort()
+ {
+ URI uri = HttpUriBuilder.uriBuilderFrom(URI.create("http://www.example.com:8081/"))
+ .port(80)
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com:80/");
+ }
+
+ @Test
+ public void testDefaultPort()
+ {
+ URI uri = HttpUriBuilder.uriBuilderFrom(URI.create("http://www.example.com:8081"))
+ .defaultPort()
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com");
+ }
+
+ @Test
+ public void testEncodesPath()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .replacePath("/`#%^{}|[]<>?áéíóú")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/%60%23%25%5E%7B%7D%7C%5B%5D%3C%3E%3F%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA");
+ }
+
+
+ @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = ".*scheme.*")
+ public void testVerifiesSchemeIsSet()
+ {
+ uriBuilder().build();
+ }
+
+ @Test
+ public void testQueryParametersNoPath()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .addParameter("a", "1")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/?a=1");
+ }
+
+ @Test
+ public void testEncodesQueryParameters()
+ {
+ URI uri = uriBuilder()
+ .scheme("http")
+ .host("www.example.com")
+ .replaceParameter("a", "&")
+ .build();
+
+ assertEquals(uri.toASCIIString(), "http://www.example.com/?a=%26");
+ }
+
+ @Test
+ public void testAcceptsHttpAndHttpScheme()
+ {
+ uriBuilderFrom(URI.create("http://example.com"));
+ uriBuilderFrom(URI.create("https://example.com"));
+ uriBuilderFrom(URI.create("HTTP://example.com"));
+ uriBuilderFrom(URI.create("HTTPS://example.com"));
+ }
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.