diff --git a/README.md b/README.md
index bb6e1c9c..4d3dee69 100644
--- a/README.md
+++ b/README.md
@@ -327,6 +327,12 @@ ETags. Possible values are:
cachedETagsFile
The location of the file that keeps entity tags (ETags) received
from the server. (default: ${downloadTaskDir}/etags.json
)
+method
+The HTTP method to use (default: GET
)
+body
+An optional request body. As gradle-download-task is meant for downloading
+and not for uploading, only simple strings are supported.
+(optional)
Verify task
diff --git a/src/main/java/de/undercouch/gradle/tasks/download/Download.java b/src/main/java/de/undercouch/gradle/tasks/download/Download.java
index 947ddc6a..d9b7cbd3 100644
--- a/src/main/java/de/undercouch/gradle/tasks/download/Download.java
+++ b/src/main/java/de/undercouch/gradle/tasks/download/Download.java
@@ -200,6 +200,16 @@ public void eachFile(Action super DownloadDetails> action) {
this.action.eachFile(action);
}
+ @Override
+ public void method(String method) {
+ action.method(method);
+ }
+
+ @Override
+ public void body(String body) {
+ action.body(body);
+ }
+
@Input
@Override
public Object getSrc() {
@@ -322,4 +332,18 @@ public Object getUseETag() {
public File getCachedETagsFile() {
return action.getCachedETagsFile();
}
+
+ @Input
+ @Optional
+ @Override
+ public String getMethod() {
+ return this.action.getMethod();
+ }
+
+ @Input
+ @Optional
+ @Override
+ public String getBody() {
+ return this.action.getBody();
+ }
}
diff --git a/src/main/java/de/undercouch/gradle/tasks/download/DownloadAction.java b/src/main/java/de/undercouch/gradle/tasks/download/DownloadAction.java
index f0977821..5bf562fc 100644
--- a/src/main/java/de/undercouch/gradle/tasks/download/DownloadAction.java
+++ b/src/main/java/de/undercouch/gradle/tasks/download/DownloadAction.java
@@ -17,7 +17,7 @@
import org.apache.hc.client5.http.auth.CredentialsProvider;
import org.apache.hc.client5.http.auth.CredentialsStore;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
-import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
@@ -33,6 +33,7 @@
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.util.Timeout;
import org.gradle.api.Action;
import org.gradle.api.JavaVersion;
@@ -58,6 +59,7 @@
import java.io.Serializable;
import java.lang.reflect.Array;
import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
@@ -71,6 +73,7 @@
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -121,6 +124,8 @@ public class DownloadAction implements DownloadSpec, Serializable {
private File downloadTaskDir;
private boolean tempAndMove = false;
private UseETag useETag = UseETag.FALSE;
+ private String method = "GET";
+ private String body;
private File cachedETagsFile;
private transient Lock cachedETagsFileLock = new ReentrantLock();
private final List> eachFileActions = new ArrayList<>();
@@ -794,8 +799,12 @@ private void openConnection(HttpHost httpHost, String file,
addAuthentication(httpHost, c, context);
}
- //c reate request
- HttpGet get = new HttpGet(file);
+ // create request
+ HttpUriRequestBase req = new HttpUriRequestBase(
+ this.method.toUpperCase(Locale.ROOT), URI.create(file));
+ if (body != null) {
+ req.setEntity(new StringEntity(body));
+ }
// configure timeouts
RequestConfig config = RequestConfig.custom()
@@ -803,7 +812,7 @@ private void openConnection(HttpHost httpHost, String file,
.setResponseTimeout(Timeout.ofMilliseconds(readTimeoutMs))
.setContentCompressionEnabled(compress)
.build();
- get.setConfig(config);
+ req.setConfig(config);
// add authentication information for proxy
String scheme = httpHost.getSchemeName();
@@ -825,24 +834,24 @@ private void openConnection(HttpHost httpHost, String file,
// set If-Modified-Since header
if (timestamp > 0) {
- get.setHeader("If-Modified-Since", DateUtils.formatStandardDate(
+ req.setHeader("If-Modified-Since", DateUtils.formatStandardDate(
Instant.ofEpochMilli(timestamp)));
}
// set If-None-Match header
if (etag != null) {
- get.setHeader("If-None-Match", etag);
+ req.setHeader("If-None-Match", etag);
}
// set headers
if (headers != null) {
for (Map.Entry headerEntry : headers.entrySet()) {
- get.addHeader(headerEntry.getKey(), headerEntry.getValue());
+ req.addHeader(headerEntry.getKey(), headerEntry.getValue());
}
}
// execute request
- client.execute(httpHost, get, context, response -> {
+ client.execute(httpHost, req, context, response -> {
// handle response
int code = response.getCode();
if ((code < 200 || code > 299) && code != HttpStatus.SC_NOT_MODIFIED) {
@@ -1147,6 +1156,19 @@ public void eachFile(Action super DownloadDetails> action) {
eachFileActions.add(action);
}
+ @Override
+ public void method(String method) {
+ if (method == null) {
+ throw new IllegalArgumentException("HTTP method must not be null");
+ }
+ this.method = method;
+ }
+
+ @Override
+ public void body(String body) {
+ this.body = body;
+ }
+
/**
* Recursively convert the given source to a list of URLs
* @param src the source to convert
@@ -1353,6 +1375,16 @@ public File getCachedETagsFile() {
}
}
+ @Override
+ public String getMethod() {
+ return method;
+ }
+
+ @Override
+ public String getBody() {
+ return body;
+ }
+
/**
* In order to support Gradle's configuration cache, we need to make some
* fields transient. This method re-initializes these fields after the
diff --git a/src/main/java/de/undercouch/gradle/tasks/download/DownloadSpec.java b/src/main/java/de/undercouch/gradle/tasks/download/DownloadSpec.java
index c3cdec48..80f757cc 100644
--- a/src/main/java/de/undercouch/gradle/tasks/download/DownloadSpec.java
+++ b/src/main/java/de/undercouch/gradle/tasks/download/DownloadSpec.java
@@ -200,6 +200,19 @@ public interface DownloadSpec {
*/
void eachFile(Action super DownloadDetails> action);
+ /**
+ * Sets the HTTP method to use
+ * @param method the HTTP method (default: {@code GET})
+ */
+ void method(String method);
+
+ /**
+ * Sets an optional request body to send to the server before the download.
+ * By default, the request will not have a body.
+ * @param body the request body ({@code null} to send no body)
+ */
+ void body(String body);
+
/**
* @return the download source(s), either a URL or a list of URLs
*/
@@ -311,4 +324,15 @@ public interface DownloadSpec {
* from the server
*/
File getCachedETagsFile();
+
+ /**
+ * @return the HTTP method to use (default: {@code GET})
+ */
+ String getMethod();
+
+ /**
+ * @return an optional request body to send to the server before
+ * downloading (default: {@code null})
+ */
+ String getBody();
}
diff --git a/src/test/java/de/undercouch/gradle/tasks/download/MethodTest.java b/src/test/java/de/undercouch/gradle/tasks/download/MethodTest.java
new file mode 100644
index 00000000..5523e535
--- /dev/null
+++ b/src/test/java/de/undercouch/gradle/tasks/download/MethodTest.java
@@ -0,0 +1,113 @@
+// Copyright 2013-2023 Michel Kraemer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package de.undercouch.gradle.tasks.download;
+
+import com.github.tomakehurst.wiremock.matching.MatchResult;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.put;
+import static com.github.tomakehurst.wiremock.client.WireMock.requestMatching;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests if another HTTP method can be used
+ * @author Michel Kraemer
+ */
+public class MethodTest extends TestBaseWithMockServer {
+ private void testWithMethod(String method) throws Exception {
+ Download t = makeProjectAndTask();
+ t.src(wireMock.url(TEST_FILE_NAME));
+ File dst = newTempFile();
+ t.dest(dst);
+ t.method(method);
+ execute(t);
+
+ assertThat(dst).usingCharset(StandardCharsets.UTF_8).hasContent(CONTENTS);
+ }
+
+ /**
+ * Tests that we can download a file using HTTP POST
+ * @throws Exception if anything goes wrong
+ */
+ @Test
+ public void postMethod() throws Exception {
+ stubFor(post(urlEqualTo("/" + TEST_FILE_NAME))
+ .willReturn(aResponse()
+ .withBody(CONTENTS)));
+ testWithMethod("POST");
+ }
+
+ /**
+ * Tests that we can download a file using HTTP PUT
+ * @throws Exception if anything goes wrong
+ */
+ @Test
+ public void putMethod() throws Exception {
+ stubFor(put(urlEqualTo("/" + TEST_FILE_NAME))
+ .willReturn(aResponse()
+ .withBody(CONTENTS)));
+ testWithMethod("PUT");
+ }
+
+ /**
+ * Tests that we can download a file using HTTP POST even if we specify
+ * the method in mixed case
+ * @throws Exception if anything goes wrong
+ */
+ @Test
+ public void caseInsensitive() throws Exception {
+ stubFor(post(urlEqualTo("/" + TEST_FILE_NAME))
+ .willReturn(aResponse()
+ .withBody(CONTENTS)));
+ testWithMethod("poSt");
+ }
+
+ /**
+ * Tests that we can download a file using a custom HTTP method
+ * the method in mixed case
+ * @throws Exception if anything goes wrong
+ */
+ @Test
+ public void customMethod() throws Exception {
+ stubFor(requestMatching(r -> {
+ if (r.getMethod().getName().equals("CUSTOM") &&
+ r.getUrl().equals("/" + TEST_FILE_NAME)) {
+ return MatchResult.exactMatch();
+ }
+ return MatchResult.noMatch();
+ }).willReturn(aResponse().withBody(CONTENTS)));
+ testWithMethod("custom");
+ }
+
+ /**
+ * Makes sure {@code null} cannot be used as method
+ * @throws Exception if anything goes wrong
+ */
+ @Test
+ public void nullMethod() throws Exception {
+ Download t = makeProjectAndTask();
+ assertThat(t.getMethod()).isEqualTo("GET");
+ assertThatThrownBy(() -> t.method(null)).isInstanceOf(
+ IllegalArgumentException.class);
+ }
+}
diff --git a/src/test/java/de/undercouch/gradle/tasks/download/PostWithBodyTest.java b/src/test/java/de/undercouch/gradle/tasks/download/PostWithBodyTest.java
new file mode 100644
index 00000000..f54865b9
--- /dev/null
+++ b/src/test/java/de/undercouch/gradle/tasks/download/PostWithBodyTest.java
@@ -0,0 +1,57 @@
+// Copyright 2013-2023 Michel Kraemer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package de.undercouch.gradle.tasks.download;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests if another we can POST a request body
+ * @author Michel Kraemer
+ */
+public class PostWithBodyTest extends TestBaseWithMockServer {
+ /**
+ * Tests that we can download a file using HTTP POST and a body
+ * @throws Exception if anything goes wrong
+ */
+ @Test
+ public void postWithBody() throws Exception {
+ String body = "This is a body";
+
+ stubFor(post(urlEqualTo("/" + TEST_FILE_NAME))
+ .withRequestBody(equalTo(body))
+ .willReturn(aResponse()
+ .withBody(CONTENTS)));
+
+ Download t = makeProjectAndTask();
+ t.src(wireMock.url(TEST_FILE_NAME));
+ File dst = newTempFile();
+ t.dest(dst);
+ t.method("POST");
+ t.body(body);
+ execute(t);
+
+ assertThat(dst).usingCharset(StandardCharsets.UTF_8).hasContent(CONTENTS);
+ }
+}