From afdf0adaed35dcdacbc473b3fdad75d4258903bc Mon Sep 17 00:00:00 2001 From: Piet de Nooijer Date: Tue, 7 Mar 2023 17:22:59 +0100 Subject: [PATCH 1/2] Add optional proxy to tus client & uploader By default the NO_PROXY proxy is used which is the same as a direct request. Adding the possibility to override the proxy with an own instance pointing to the proxy to use when uploading. --- README.md | 9 +++ .../java/io/tus/java/client/TusClient.java | 44 ++++++++++-- .../java/io/tus/java/client/TusUploader.java | 22 +++++- .../io/tus/java/client/TestTusClient.java | 70 +++++++++++++++++++ .../io/tus/java/client/TestTusUploader.java | 41 +++++++++++ 5 files changed, 180 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cc70fc2..c0db0e5 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,15 @@ public void prepareConnection(@NotNull HttpURLConnection connection) { } ``` +### Can I use a proxy that will be used for uploading files? + +Yes, just add a proxy to the TusClient as shown below (1 line added to the above [usage](#usage)): + +```java +TusClient client = new TusClient(); +client.setProxy(myProxy); +``` + ## License MIT diff --git a/src/main/java/io/tus/java/client/TusClient.java b/src/main/java/io/tus/java/client/TusClient.java index 1e2d65a..a5cac6a 100644 --- a/src/main/java/io/tus/java/client/TusClient.java +++ b/src/main/java/io/tus/java/client/TusClient.java @@ -1,5 +1,6 @@ package io.tus.java.client; +import java.net.Proxy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -19,6 +20,7 @@ public class TusClient { public static final String TUS_VERSION = "1.0.0"; private URL uploadCreationURL; + private Proxy proxy; private boolean resumingEnabled; private boolean removeFingerprintOnSuccessEnabled; private TusURLStore urlStore; @@ -52,6 +54,24 @@ public URL getUploadCreationURL() { return uploadCreationURL; } + /** + * Set the proxy that will be used for all requests. + * + * @param proxy Proxy to use + */ + public void setProxy(Proxy proxy) { + this.proxy = proxy; + } + + /** + * Get the current proxy used for all requests. + * + * @return Current proxy + */ + public Proxy getProxy() { + return proxy; + } + /** * Enable resuming already started uploads. This step is required if you want to use * {@link #resumeUpload(TusUpload)}. @@ -174,7 +194,7 @@ public int getConnectTimeout() { * @throws IOException Thrown if an exception occurs while issuing the HTTP request. */ public TusUploader createUpload(@NotNull TusUpload upload) throws ProtocolException, IOException { - HttpURLConnection connection = (HttpURLConnection) uploadCreationURL.openConnection(); + HttpURLConnection connection = openConnection(uploadCreationURL); connection.setRequestMethod("POST"); prepareConnection(connection); @@ -206,7 +226,23 @@ public TusUploader createUpload(@NotNull TusUpload upload) throws ProtocolExcept urlStore.set(upload.getFingerprint(), uploadURL); } - return new TusUploader(this, upload, uploadURL, upload.getTusInputStream(), 0); + return createUploader(upload, uploadURL, 0L); + } + + @NotNull + private HttpURLConnection openConnection(@NotNull URL uploadURL) throws IOException { + if (proxy != null) { + return (HttpURLConnection) uploadURL.openConnection(proxy); + } + return (HttpURLConnection) uploadURL.openConnection(); + } + + @NotNull + private TusUploader createUploader(@NotNull TusUpload upload, @NotNull URL uploadURL, long offset) + throws IOException { + TusUploader uploader = new TusUploader(this, upload, uploadURL, upload.getTusInputStream(), offset); + uploader.setProxy(proxy); + return uploader; } /** @@ -259,7 +295,7 @@ public TusUploader resumeUpload(@NotNull TusUpload upload) throws */ public TusUploader beginOrResumeUploadFromURL(@NotNull TusUpload upload, @NotNull URL uploadURL) throws ProtocolException, IOException { - HttpURLConnection connection = (HttpURLConnection) uploadURL.openConnection(); + HttpURLConnection connection = openConnection(uploadURL); connection.setRequestMethod("HEAD"); prepareConnection(connection); @@ -277,7 +313,7 @@ public TusUploader beginOrResumeUploadFromURL(@NotNull TusUpload upload, @NotNul } long offset = Long.parseLong(offsetStr); - return new TusUploader(this, upload, uploadURL, upload.getTusInputStream(), offset); + return createUploader(upload, uploadURL, offset); } /** diff --git a/src/main/java/io/tus/java/client/TusUploader.java b/src/main/java/io/tus/java/client/TusUploader.java index f4e0067..faaceb1 100644 --- a/src/main/java/io/tus/java/client/TusUploader.java +++ b/src/main/java/io/tus/java/client/TusUploader.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.net.URLConnection; @@ -21,6 +22,7 @@ */ public class TusUploader { private URL uploadURL; + private Proxy proxy; private TusInputStream input; private long offset; private TusClient client; @@ -44,7 +46,7 @@ public class TusUploader { * @throws IOException Thrown if an exception occurs while issuing the HTTP request. */ public TusUploader(TusClient client, TusUpload upload, URL uploadURL, TusInputStream input, long offset) - throws IOException { + throws IOException { this.uploadURL = uploadURL; this.input = input; this.offset = offset; @@ -65,7 +67,11 @@ private void openConnection() throws IOException, ProtocolException { bytesRemainingForRequest = requestPayloadSize; input.mark(requestPayloadSize); - connection = (HttpURLConnection) uploadURL.openConnection(); + if (proxy != null) { + connection = (HttpURLConnection) uploadURL.openConnection(proxy); + } else { + connection = (HttpURLConnection) uploadURL.openConnection(); + } client.prepareConnection(connection); connection.setRequestProperty("Upload-Offset", Long.toString(offset)); connection.setRequestProperty("Content-Type", "application/offset+octet-stream"); @@ -268,6 +274,18 @@ public URL getUploadURL() { return uploadURL; } + public void setProxy(Proxy proxy) { + this.proxy = proxy; + } + + /** + * This methods returns the proxy used with the upload. + * @return The {@link Proxy} used for the upload or null when not set. + */ + public Proxy getProxy() { + return proxy; + } + /** * Finish the request by closing the HTTP connection and the InputStream. * You can call this method even before the entire file has been uploaded. Use this behavior to diff --git a/src/test/java/io/tus/java/client/TestTusClient.java b/src/test/java/io/tus/java/client/TestTusClient.java index ec51032..4cfe140 100644 --- a/src/test/java/io/tus/java/client/TestTusClient.java +++ b/src/test/java/io/tus/java/client/TestTusClient.java @@ -8,7 +8,10 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.HttpURLConnection; +import java.net.InetSocketAddress; import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.Proxy.Type; import java.net.URL; import java.util.HashMap; import java.util.LinkedHashMap; @@ -80,6 +83,7 @@ public void testCreateUpload() throws IOException, ProtocolException { mockServer.when(new HttpRequest() .withMethod("POST") .withPath("/files") + .withHeader("Connection", "keep-alive") .withHeader("Tus-Resumable", TusClient.TUS_VERSION) .withHeader("Upload-Metadata", "foo aGVsbG8=,bar d29ybGQ=") .withHeader("Upload-Length", "10")) @@ -102,6 +106,40 @@ public void testCreateUpload() throws IOException, ProtocolException { assertEquals(uploader.getUploadURL(), new URL(mockServerURL + "/foo")); } + /** + * Verifies if uploads can be created with the tus client through a proxy. + * @throws IOException if upload data cannot be read. + * @throws ProtocolException if the upload cannot be constructed. + */ + @Test + public void testCreateUploadWithProxy() throws IOException, ProtocolException { + mockServer.when(new HttpRequest() + .withMethod("POST") + .withPath("/files") + .withHeader("Proxy-Connection", "keep-alive") + .withHeader("Tus-Resumable", TusClient.TUS_VERSION) + .withHeader("Upload-Metadata", "foo aGVsbG8=,bar d29ybGQ=") + .withHeader("Upload-Length", "11")) + .respond(new HttpResponse() + .withStatusCode(201) + .withHeader("Tus-Resumable", TusClient.TUS_VERSION) + .withHeader("Location", mockServerURL + "/foo")); + + Map metadata = new LinkedHashMap(); + metadata.put("foo", "hello"); + metadata.put("bar", "world"); + + TusClient client = new TusClient(); + client.setUploadCreationURL(mockServerURL); + client.setProxy(new Proxy(Type.HTTP, new InetSocketAddress("localhost", mockServer.getPort()))); + TusUpload upload = new TusUpload(); + upload.setSize(11); + upload.setInputStream(new ByteArrayInputStream(new byte[11])); + upload.setMetadata(metadata); + TusUploader uploader = client.createUpload(upload); + + assertEquals(uploader.getUploadURL(), new URL(mockServerURL + "/foo")); + } /** * Tests if a missing location header causes an exception as expected. @@ -239,6 +277,7 @@ public void testResumeOrCreateUpload() throws IOException, ProtocolException { mockServer.when(new HttpRequest() .withMethod("POST") .withPath("/files") + .withHeader("Connection", "keep-alive") .withHeader("Tus-Resumable", TusClient.TUS_VERSION) .withHeader("Upload-Length", "10")) .respond(new HttpResponse() @@ -256,6 +295,37 @@ public void testResumeOrCreateUpload() throws IOException, ProtocolException { assertEquals(uploader.getUploadURL(), new URL(mockServerURL + "/foo")); } + /** + * Tests if an upload gets started when {@link TusClient#resumeOrCreateUpload(TusUpload)} gets called with + * a proxy set. + * @throws IOException + * @throws ProtocolException + */ + @Test + public void testResumeOrCreateUploadWithProxy() throws IOException, ProtocolException { + mockServer.when(new HttpRequest() + .withMethod("POST") + .withPath("/files") + .withHeader("Proxy-Connection", "keep-alive") + .withHeader("Tus-Resumable", TusClient.TUS_VERSION) + .withHeader("Upload-Length", "11")) + .respond(new HttpResponse() + .withStatusCode(201) + .withHeader("Tus-Resumable", TusClient.TUS_VERSION) + .withHeader("Location", mockServerURL + "/foo")); + + TusClient client = new TusClient(); + client.setUploadCreationURL(mockServerURL); + Proxy proxy = new Proxy(Type.HTTP, new InetSocketAddress("localhost", mockServer.getPort())); + client.setProxy(proxy); + TusUpload upload = new TusUpload(); + upload.setSize(11); + upload.setInputStream(new ByteArrayInputStream(new byte[11])); + TusUploader uploader = client.resumeOrCreateUpload(upload); + + assertEquals(proxy, client.getProxy()); + assertEquals(uploader.getUploadURL(), new URL(mockServerURL + "/foo")); + } /** * Checks if a new upload attempt is started in case of a serverside 404-error, without having an Exception thrown. diff --git a/src/test/java/io/tus/java/client/TestTusUploader.java b/src/test/java/io/tus/java/client/TestTusUploader.java index ef743a8..b81e2b7 100644 --- a/src/test/java/io/tus/java/client/TestTusUploader.java +++ b/src/test/java/io/tus/java/client/TestTusUploader.java @@ -11,7 +11,10 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; +import java.net.InetSocketAddress; import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.Proxy.Type; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; @@ -44,6 +47,7 @@ public void testTusUploader() throws IOException, ProtocolException { .withHeader("Tus-Resumable", TusClient.TUS_VERSION) .withHeader("Upload-Offset", "3") .withHeader("Content-Type", "application/offset+octet-stream") + .withHeader("Connection", "keep-alive") .withBody(Arrays.copyOfRange(content, 3, 11))) .respond(new HttpResponse() .withStatusCode(204) @@ -70,6 +74,43 @@ public void testTusUploader() throws IOException, ProtocolException { uploader.finish(); } + /** + * Tests if the {@link TusUploader} actually uploads files through a proxy. + * @throws IOException + * @throws ProtocolException + */ + @Test + public void testTusUploaderWithProxy() throws IOException, ProtocolException { + byte[] content = "hello world with proxy".getBytes(); + + mockServer.when(new HttpRequest() + .withPath("/files/foo") + .withHeader("Tus-Resumable", TusClient.TUS_VERSION) + .withHeader("Upload-Offset", "0") + .withHeader("Content-Type", "application/offset+octet-stream") + .withHeader("Proxy-Connection", "keep-alive") + .withBody(Arrays.copyOf(content, content.length))) + .respond(new HttpResponse() + .withStatusCode(204) + .withHeader("Tus-Resumable", TusClient.TUS_VERSION) + .withHeader("Upload-Offset", "22")); + + TusClient client = new TusClient(); + URL uploadUrl = new URL(mockServerURL + "/foo"); + Proxy proxy = new Proxy(Type.HTTP, new InetSocketAddress("localhost", mockServer.getPort())); + TusInputStream input = new TusInputStream(new ByteArrayInputStream(content)); + long offset = 0; + + TusUpload upload = new TusUpload(); + + TusUploader uploader = new TusUploader(client, upload, uploadUrl, input, offset); + uploader.setProxy(proxy); + + assertEquals(proxy, uploader.getProxy()); + assertEquals(22, uploader.uploadChunk()); + uploader.finish(); + } + /** * Verifies, that {@link TusClient#uploadFinished(TusUpload)} gets called after a proper upload has been finished. * @throws IOException From 681c4213719ec189ee9d83de9c7a151c141868c6 Mon Sep 17 00:00:00 2001 From: Piet de Nooijer Date: Fri, 17 Mar 2023 09:14:37 +0100 Subject: [PATCH 2/2] Fix PR review comments for adding optional proxy --- README.md | 1 + src/main/java/io/tus/java/client/TusUploader.java | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c0db0e5..83f2bf2 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ Yes, just add a proxy to the TusClient as shown below (1 line added to the above ```java TusClient client = new TusClient(); +Proxy myProxy = new Proxy(...); client.setProxy(myProxy); ``` diff --git a/src/main/java/io/tus/java/client/TusUploader.java b/src/main/java/io/tus/java/client/TusUploader.java index faaceb1..d84bd8b 100644 --- a/src/main/java/io/tus/java/client/TusUploader.java +++ b/src/main/java/io/tus/java/client/TusUploader.java @@ -274,12 +274,18 @@ public URL getUploadURL() { return uploadURL; } + /** + * Set the proxy that will be used when uploading. + * + * @param proxy Proxy to use + */ public void setProxy(Proxy proxy) { this.proxy = proxy; } /** - * This methods returns the proxy used with the upload. + * This methods returns the proxy used when uploading. + * * @return The {@link Proxy} used for the upload or null when not set. */ public Proxy getProxy() {