Skip to content

Commit

Permalink
Add optional proxy to tus client & uploader (#84)
Browse files Browse the repository at this point in the history
* 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.

* Fix PR review comments for adding optional proxy
  • Loading branch information
pdenooijer authored Mar 20, 2023
1 parent fb79d40 commit 67de05c
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 6 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ 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();
Proxy myProxy = new Proxy(...);
client.setProxy(myProxy);
```

## License

MIT
44 changes: 40 additions & 4 deletions src/main/java/io/tus/java/client/TusClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.tus.java.client;

import java.net.Proxy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -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;
Expand Down Expand Up @@ -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)}.
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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);

Expand All @@ -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);
}

/**
Expand Down
28 changes: 26 additions & 2 deletions src/main/java/io/tus/java/client/TusUploader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -21,6 +22,7 @@
*/
public class TusUploader {
private URL uploadURL;
private Proxy proxy;
private TusInputStream input;
private long offset;
private TusClient client;
Expand All @@ -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;
Expand All @@ -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");
Expand Down Expand Up @@ -268,6 +274,24 @@ 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 when uploading.
*
* @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
Expand Down
70 changes: 70 additions & 0 deletions src/test/java/io/tus/java/client/TestTusClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"))
Expand All @@ -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<String, String> metadata = new LinkedHashMap<String, String>();
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.
Expand Down Expand Up @@ -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()
Expand All @@ -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.
Expand Down
41 changes: 41 additions & 0 deletions src/test/java/io/tus/java/client/TestTusUploader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit 67de05c

Please sign in to comment.