Skip to content

Commit

Permalink
Add possibility to set request method and body
Browse files Browse the repository at this point in the history
Fixes #303
  • Loading branch information
michel-kraemer committed Mar 18, 2023
1 parent 82e70ca commit 472920e
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 8 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,12 @@ ETags</a>. Possible values are:
<dt>cachedETagsFile</dt>
<dd>The location of the file that keeps entity tags (ETags) received
from the server. <em>(default: <code>${downloadTaskDir}/etags.json</code>)</em></dd>
<dt>method</dt>
<dd>The HTTP method to use <em>(default: <code>GET</code>)</em></dd>
<dt>body</dt>
<dd>An optional request body. As gradle-download-task is meant for downloading
and not for uploading, only simple strings are supported.
<em>(optional)</em></dd>
</dl>

Verify task
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/de/undercouch/gradle/tasks/download/Download.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<Action<? super DownloadDetails>> eachFileActions = new ArrayList<>();
Expand Down Expand Up @@ -794,16 +799,20 @@ private <T> 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()
.setConnectionRequestTimeout(Timeout.ofMilliseconds(connectTimeoutMs))
.setResponseTimeout(Timeout.ofMilliseconds(readTimeoutMs))
.setContentCompressionEnabled(compress)
.build();
get.setConfig(config);
req.setConfig(config);

// add authentication information for proxy
String scheme = httpHost.getSchemeName();
Expand All @@ -825,24 +834,24 @@ private <T> 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<String, String> 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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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();
}
113 changes: 113 additions & 0 deletions src/test/java/de/undercouch/gradle/tasks/download/MethodTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 472920e

Please sign in to comment.