diff --git a/README.md b/README.md index ea8c37f..8c07a04 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ import java.io.IOException; public class Compress { public static void main(String[] args) throws java.io.IOException { Tinify.setKey("YOUR_API_KEY"); + Tinify.setProxy("http://proxy.internal.example.com") // If applicable Tinify.fromFile("input.png").toFile("output.png"); } } diff --git a/src/main/java/com/tinify/AccountException.java b/src/main/java/com/tinify/AccountException.java index ca1e328..3265b66 100644 --- a/src/main/java/com/tinify/AccountException.java +++ b/src/main/java/com/tinify/AccountException.java @@ -4,10 +4,15 @@ public class AccountException extends Exception { public AccountException() { super(); } + public AccountException(final Throwable t) { super(t); } - public AccountException(final String message) { super(message); } + + public AccountException(final String message) { + super(message); + } + public AccountException(final String message, final String type, final int status) { super(message, type, status); } diff --git a/src/main/java/com/tinify/Client.java b/src/main/java/com/tinify/Client.java index 69a97b4..4043302 100644 --- a/src/main/java/com/tinify/Client.java +++ b/src/main/java/com/tinify/Client.java @@ -3,6 +3,10 @@ import com.google.gson.Gson; import com.squareup.okhttp.*; import java.io.IOException; +import java.net.Proxy; +import java.net.InetSocketAddress; +import java.net.Proxy.Type; +import java.net.URL; import java.util.concurrent.TimeUnit; public class Client { @@ -24,8 +28,34 @@ public enum Method { GET } + public Client(final String key) { + this(key, null, null); + } + public Client(final String key, final String appIdentifier) { + this(key, appIdentifier, null); + } + + public Client(final String key, final String appIdentifier, final String proxy) { client = new OkHttpClient(); + + if (proxy != null) { + try { + URL url = new URL(proxy); + Proxy proxyAddress = createProxyAddress(url); + Authenticator proxyAuthenticator = createProxyAuthenticator(url); + + if (proxyAddress != null) { + client.setProxy(proxyAddress); + if (proxyAuthenticator != null) { + client.setAuthenticator(proxyAuthenticator); + } + } + } catch (java.lang.Exception e) { + throw new ConnectionException("Invalid proxy: " + e.getMessage(), e); + } + } + client.setSslSocketFactory(SSLContext.getSocketFactory()); client.setConnectTimeout(0, TimeUnit.SECONDS); client.setReadTimeout(0, TimeUnit.SECONDS); @@ -60,6 +90,47 @@ public final Response request(final Method method, final String endpoint, final return request(method, endpoint, RequestBody.create(null, body)); } + private Proxy createProxyAddress(final URL proxy) { + if (proxy == null) return null; + + String host = proxy.getHost(); + int port = proxy.getPort(); + + if (port < 0) { + port = proxy.getDefaultPort(); + } + + return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)); + } + + private Authenticator createProxyAuthenticator(final URL proxy) { + if (proxy == null) return null; + + String user = proxy.getUserInfo(); + if (user == null) return null; + + final String username, password; + int c = user.indexOf(':'); + if (0 < c) { + username = user.substring(0, c); + password = user.substring(c + 1); + } else { + username = user; + password = null; + } + + return new Authenticator() { + @Override public Request authenticate(Proxy proxy, Response response) throws IOException { + return null; + } + + @Override public Request authenticateProxy(Proxy proxy, Response response) throws IOException { + String credential = Credentials.basic(username, password); + return response.request().newBuilder().header("Proxy-Authorization", credential).build(); + } + }; + } + private Response request(final Method method, final String endpoint, final RequestBody body) throws Exception { HttpUrl url; if (endpoint.startsWith("https")) { diff --git a/src/main/java/com/tinify/ClientException.java b/src/main/java/com/tinify/ClientException.java index cd1b998..f759f46 100644 --- a/src/main/java/com/tinify/ClientException.java +++ b/src/main/java/com/tinify/ClientException.java @@ -4,9 +4,11 @@ public class ClientException extends Exception { public ClientException() { super(); } + public ClientException(final Throwable t) { super(t); } + public ClientException(final String message, final String type, final int status) { super(message, type, status); } diff --git a/src/main/java/com/tinify/ConnectionException.java b/src/main/java/com/tinify/ConnectionException.java index 91df83f..d5e41fd 100644 --- a/src/main/java/com/tinify/ConnectionException.java +++ b/src/main/java/com/tinify/ConnectionException.java @@ -4,8 +4,10 @@ public class ConnectionException extends Exception { public ConnectionException() { super(); } + public ConnectionException(final Throwable t) { super(t); } + public ConnectionException(final String message, final Throwable t) { super(message, t); } } diff --git a/src/main/java/com/tinify/ServerException.java b/src/main/java/com/tinify/ServerException.java index 7864224..248f3a3 100644 --- a/src/main/java/com/tinify/ServerException.java +++ b/src/main/java/com/tinify/ServerException.java @@ -4,9 +4,11 @@ public class ServerException extends Exception { public ServerException() { super(); } + public ServerException(final Throwable t) { super(t); } + public ServerException(final String message, final String type, final int status) { super(message, type, status); } diff --git a/src/main/java/com/tinify/Tinify.java b/src/main/java/com/tinify/Tinify.java index 62d3d8a..5ed8f85 100644 --- a/src/main/java/com/tinify/Tinify.java +++ b/src/main/java/com/tinify/Tinify.java @@ -1,10 +1,12 @@ package com.tinify; import java.io.IOException; +import java.net.URL; public class Tinify { private static String key; private static String appIdentifier; + private static String proxy; private static int compressionCount = 0; private static Client client; @@ -15,9 +17,9 @@ public static Client client() { if (client != null) { return client; } else { - synchronized(Tinify.class) { + synchronized (Tinify.class) { if (client == null) { - client = new Client(key(), appIdentifier()); + client = new Client(key, appIdentifier, proxy); } } return client; @@ -29,6 +31,11 @@ public static void setKey(final String key) { client = null; } + public static void setProxy(final String proxy) { + Tinify.proxy = proxy; + client = null; + } + public static void setAppIdentifier(final String identifier) { Tinify.appIdentifier = identifier; client = null; @@ -62,6 +69,10 @@ public static String key() { return key; } + public static String proxy() { + return proxy; + } + public static String appIdentifier() { return appIdentifier; } diff --git a/src/test/java/com/tinify/ClientEndpointTest.java b/src/test/java/com/tinify/ClientEndpointTest.java index de2315d..bc728e7 100644 --- a/src/test/java/com/tinify/ClientEndpointTest.java +++ b/src/test/java/com/tinify/ClientEndpointTest.java @@ -22,7 +22,7 @@ public class ClientEndpointTest { @Before public void setup() throws IOException { - subject = new Client(key, null); + subject = new Client(key); } @Test diff --git a/src/test/java/com/tinify/ClientErrorTest.java b/src/test/java/com/tinify/ClientErrorTest.java index d34eb30..32b6d2a 100644 --- a/src/test/java/com/tinify/ClientErrorTest.java +++ b/src/test/java/com/tinify/ClientErrorTest.java @@ -29,7 +29,7 @@ public class ClientErrorTest { public void setup() throws IOException { Logger.getLogger(MockWebServer.class.getName()).setLevel(Level.WARNING); server = new MockWebServer(); - subject = new Client(key, null); + subject = new Client(key); server.start(); } @@ -43,7 +43,7 @@ public void requestWithTimeoutShouldThrowConnectionException() throws Exception new Expectations() {{ httpClient.newCall((Request) any); result = new java.net.SocketTimeoutException("SocketTimeoutException"); }}; - new Client(key, null).request(Client.Method.POST, "http://shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "http://shrink", new byte[] {}); } @Test @@ -52,7 +52,7 @@ public void requestWithTimeoutShouldThrowExceptionWithMessage() throws Exception httpClient.newCall((Request) any); result = new java.net.SocketTimeoutException("SocketTimeoutException"); }}; try { - new Client(key, null).request(Client.Method.POST, "http://shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "http://shrink", new byte[] {}); fail("Expected an Exception to be thrown"); } catch (ConnectionException e) { assertEquals("Error while connecting: SocketTimeoutException", e.getMessage()); @@ -64,7 +64,7 @@ public void requestWithSocketErrorShouldThrowConnectionException() throws Except new Expectations() {{ httpClient.newCall((Request) any); result = new java.net.UnknownHostException("UnknownHostException"); }}; - new Client(key, null).request(Client.Method.POST, "http://shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "http://shrink", new byte[] {}); } @Test @@ -73,7 +73,7 @@ public void requestWithSocketErrorShouldThrowExceptionWithMessage() throws Excep httpClient.newCall((Request) any); result = new java.net.UnknownHostException("UnknownHostException"); }}; try { - new Client(key, null).request(Client.Method.POST, "http://shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "http://shrink", new byte[] {}); fail("Expected an Exception to be thrown"); } catch (ConnectionException e) { assertEquals("Error while connecting: UnknownHostException", e.getMessage()); @@ -85,7 +85,7 @@ public void requestWithUnexpectedExceptionShouldThrowConnectionException() throw new Expectations() {{ httpClient.newCall((Request) any); result = new java.lang.Exception("Some exception"); }}; - new Client(key, null).request(Client.Method.POST, "http://shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "http://shrink", new byte[] {}); } @Test @@ -94,7 +94,7 @@ public void requestWithUnexpectedExceptionShouldThrowExceptionWithMessage() thro httpClient.newCall((Request) any); result = new java.lang.Exception("Some exception"); }}; try { - new Client(key, null).request(Client.Method.POST, "http://shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "http://shrink", new byte[] {}); fail("Expected an Exception to be thrown"); } catch (ConnectionException e) { assertEquals("Error while connecting: Some exception", e.getMessage()); diff --git a/src/test/java/com/tinify/ClientTest.java b/src/test/java/com/tinify/ClientTest.java index 085d95c..214702a 100644 --- a/src/test/java/com/tinify/ClientTest.java +++ b/src/test/java/com/tinify/ClientTest.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.lang.*; import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; @@ -35,7 +36,7 @@ public void setup() throws IOException { server = new MockWebServer(); server.start(); - subject = new Client(key, null); + subject = new Client(key); new MockUp() { @Mock @@ -152,6 +153,17 @@ public void requestWhenValidWithAppIdShouldIssueRequestWithUserAgent() throws Ex assertEquals(Client.USER_AGENT + " TestApp/0.1", request.getHeader("User-Agent")); } + @Test + public void requestWhenValidWithProxyShouldIssueRequestWithProxyAuthorization() throws Exception, InterruptedException { + server.enqueue(new MockResponse().setResponseCode(407)); + enqueuShrink(); + Client client = new Client(key, null, "http://user:pass@" + server.getHostName() + ":" + server.getPort()); + client.request(Client.Method.POST, "/shrink", new byte[] {}); + RecordedRequest request1 = server.takeRequest(5, TimeUnit.SECONDS); + RecordedRequest request2 = server.takeRequest(5, TimeUnit.SECONDS); + assertEquals("Basic dXNlcjpwYXNz", request2.getHeader("Proxy-Authorization")); + } + @SuppressWarnings("unused") public void requestWithTimeout() { // See ClientErrorTest.java @@ -172,7 +184,7 @@ public void requestWithServerErrorShouldThrowServerException() throws Exception server.enqueue(new MockResponse() .setResponseCode(584) .setBody("{'error':'InternalServerError','message':'Oops!'}")); - new Client(key, null).request(Client.Method.POST, "/shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "/shrink", new byte[] {}); } @Test @@ -181,7 +193,7 @@ public void requestWithServerErrorShouldThrowExceptionWithMessage() throws Excep .setResponseCode(584) .setBody("{'error':'InternalServerError','message':'Oops!'}")); try { - new Client(key, null).request(Client.Method.POST, "/shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "/shrink", new byte[] {}); fail("Expected an Exception to be thrown"); } catch (Exception e) { assertEquals("Oops! (HTTP 584/InternalServerError)", e.getMessage()); @@ -193,7 +205,7 @@ public void requestWithBadServerResponseShouldThrowServerException() throws Exce server.enqueue(new MockResponse() .setResponseCode(543) .setBody("")); - new Client(key, null).request(Client.Method.POST, "/shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "/shrink", new byte[] {}); } @Test @@ -202,7 +214,7 @@ public void requestWithBlankServerResponseShouldThrowServerException() throws Ex .setResponseCode(543) .setBody("")); try { - new Client(key, null).request(Client.Method.POST, "/shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "/shrink", new byte[] {}); fail("Expected an Exception to be thrown"); } catch (Exception e) { assertEquals("Error while parsing response: received empty body (HTTP 543/ParseError)", e.getMessage()); @@ -215,7 +227,7 @@ public void requestWithBadServerResponseShouldThrowExceptionWithMessage() throws .setResponseCode(543) .setBody("")); try { - new Client(key, null).request(Client.Method.POST, "/shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "/shrink", new byte[] {}); fail("Expected an Exception to be thrown"); } catch (Exception e) { assertEquals("Error while parsing response: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $ (HTTP 543/ParseError)", e.getMessage()); @@ -227,7 +239,7 @@ public void requestWithClientErrorShouldThrowClientException() throws Exception server.enqueue(new MockResponse() .setResponseCode(492) .setBody("{'error':'BadRequest','message':'Oops!'}")); - new Client(key, null).request(Client.Method.POST, "/shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "/shrink", new byte[] {}); } @Test @@ -236,7 +248,7 @@ public void requestWithClientErrorShouldThrowExceptionWithMessage() throws Excep .setResponseCode(492) .setBody("{'error':'BadRequest','message':'Oops!'}")); try { - new Client(key, null).request(Client.Method.POST, "/shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "/shrink", new byte[] {}); fail("Expected an Exception to be thrown"); } catch (Exception e) { assertEquals("Oops! (HTTP 492/BadRequest)", e.getMessage()); @@ -248,7 +260,7 @@ public void requestWithBadCredentialsShouldThrowAccountException() throws Except server.enqueue(new MockResponse() .setResponseCode(401) .setBody("{'error':'Unauthorized','message':'Oops!'}")); - new Client(key, null).request(Client.Method.POST, "/shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "/shrink", new byte[] {}); } @Test @@ -257,7 +269,7 @@ public void requestWithBadCredentialsShouldThrowExceptionWithMessage() throws Ex .setResponseCode(401) .setBody("{'error':'Unauthorized','message':'Oops!'}")); try { - new Client(key, null).request(Client.Method.POST, "/shrink", new byte[] {}); + new Client(key).request(Client.Method.POST, "/shrink", new byte[] {}); fail("Expected an Exception to be thrown"); } catch (Exception e) { assertEquals("Oops! (HTTP 401/Unauthorized)", e.getMessage()); diff --git a/src/test/java/com/tinify/Integration.java b/src/test/java/com/tinify/Integration.java index ecc9fc0..6903221 100644 --- a/src/test/java/com/tinify/Integration.java +++ b/src/test/java/com/tinify/Integration.java @@ -20,12 +20,14 @@ public class Integration { @BeforeClass public static void setup() throws java.io.IOException, URISyntaxException { String key = System.getenv().get("TINIFY_KEY"); + String proxy = System.getenv().get("TINIFY_PROXY"); if (key == null) { System.out.println("Set the TINIFY_KEY environment variable."); System.exit(1); } Tinify.setKey(key); + Tinify.setProxy(proxy); String unoptimizedPath = Paths.get(Integration.class.getResource("/voormedia.png").toURI()).toAbsolutePath().toString(); optimized = Tinify.fromFile(unoptimizedPath); diff --git a/src/test/java/com/tinify/TinifyTest.java b/src/test/java/com/tinify/TinifyTest.java index a463e4b..029b9b4 100644 --- a/src/test/java/com/tinify/TinifyTest.java +++ b/src/test/java/com/tinify/TinifyTest.java @@ -52,6 +52,7 @@ HttpUrl parse(String url) @After public void tearDown() throws IOException { Tinify.setKey(null); + Tinify.setProxy(null); server.shutdown(); } @@ -84,6 +85,22 @@ public void appIdentifierShouldResetClientWithNewAppIdentifier() throws Interrup assertEquals(Client.USER_AGENT + " MyApp/2.0", request.getHeader("User-Agent")); } + @Test + public void proxyShouldResetClientWithNewProxy() throws InterruptedException { + server.enqueue(new MockResponse().setResponseCode(407)); + server.enqueue(new MockResponse().setResponseCode(200)); + + Tinify.setKey("abcde"); + Tinify.setProxy("http://localhost"); + Tinify.client(); + Tinify.setProxy("http://user:pass@" + server.getHostName() + ":" + server.getPort()); + Tinify.client().request(Client.Method.GET, "/"); + + RecordedRequest request1 = server.takeRequest(5, TimeUnit.SECONDS); + RecordedRequest request2 = server.takeRequest(5, TimeUnit.SECONDS); + assertEquals("Basic dXNlcjpwYXNz", request2.getHeader("Proxy-Authorization")); + } + @Test public void clientWithKeyShouldReturnClient() { Tinify.setKey("abcde"); @@ -95,6 +112,13 @@ public void clientWithoutKeyShouldThrowException() { Tinify.client(); } + @Test(expected = ConnectionException.class) + public void clientWithInvalidProxyShouldThrowException() { + Tinify.setKey("abcde"); + Tinify.setProxy("http-bad-url"); + Tinify.client(); + } + @Test public void validateWithValidKeyShouldReturnTrue() throws InterruptedException { server.enqueue(new MockResponse()