Skip to content

Commit b1dd928

Browse files
committed
Retry class file upload to remote application that fails to connect
Closes gh-6339
1 parent 68fb578 commit b1dd928

File tree

2 files changed

+73
-22
lines changed

2 files changed

+73
-22
lines changed

spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/ClassPathChangeUploader.java

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.io.ByteArrayOutputStream;
2020
import java.io.IOException;
2121
import java.io.ObjectOutputStream;
22+
import java.net.ConnectException;
2223
import java.net.MalformedURLException;
2324
import java.net.URI;
2425
import java.net.URISyntaxException;
@@ -51,6 +52,7 @@
5152
* Listens and pushes any classpath updates to a remote endpoint.
5253
*
5354
* @author Phillip Webb
55+
* @author Andy Wilkinson
5456
* @since 1.3.0
5557
*/
5658
public class ClassPathChangeUploader
@@ -91,23 +93,45 @@ public ClassPathChangeUploader(String url, ClientHttpRequestFactory requestFacto
9193
public void onApplicationEvent(ClassPathChangedEvent event) {
9294
try {
9395
ClassLoaderFiles classLoaderFiles = getClassLoaderFiles(event);
94-
ClientHttpRequest request = this.requestFactory.createRequest(this.uri,
95-
HttpMethod.POST);
9696
byte[] bytes = serialize(classLoaderFiles);
97-
HttpHeaders headers = request.getHeaders();
98-
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
99-
headers.setContentLength(bytes.length);
100-
FileCopyUtils.copy(bytes, request.getBody());
101-
logUpload(classLoaderFiles);
102-
ClientHttpResponse response = request.execute();
103-
Assert.state(response.getStatusCode() == HttpStatus.OK, "Unexpected "
104-
+ response.getStatusCode() + " response uploading class files");
97+
performUpload(classLoaderFiles, bytes);
10598
}
10699
catch (IOException ex) {
107100
throw new IllegalStateException(ex);
108101
}
109102
}
110103

104+
private void performUpload(ClassLoaderFiles classLoaderFiles, byte[] bytes)
105+
throws IOException {
106+
try {
107+
while (true) {
108+
try {
109+
ClientHttpRequest request = this.requestFactory
110+
.createRequest(this.uri, HttpMethod.POST);
111+
HttpHeaders headers = request.getHeaders();
112+
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
113+
headers.setContentLength(bytes.length);
114+
FileCopyUtils.copy(bytes, request.getBody());
115+
ClientHttpResponse response = request.execute();
116+
Assert.state(response.getStatusCode() == HttpStatus.OK,
117+
"Unexpected " + response.getStatusCode()
118+
+ " response uploading class files");
119+
logUpload(classLoaderFiles);
120+
return;
121+
}
122+
catch (ConnectException ex) {
123+
logger.warn("Failed to connect when uploading to " + this.uri
124+
+ ". Upload will be retried in 2 seconds");
125+
Thread.sleep(2000);
126+
}
127+
}
128+
}
129+
catch (InterruptedException ex) {
130+
Thread.currentThread().interrupt();
131+
throw new IllegalStateException(ex);
132+
}
133+
}
134+
111135
private void logUpload(ClassLoaderFiles classLoaderFiles) {
112136
int size = classLoaderFiles.size();
113137
logger.info(

spring-boot-devtools/src/test/java/org/springframework/boot/devtools/remote/client/ClassPathChangeUploaderTests.java

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.io.File;
2121
import java.io.IOException;
2222
import java.io.ObjectInputStream;
23+
import java.net.ConnectException;
2324
import java.util.Collection;
2425
import java.util.Iterator;
2526
import java.util.LinkedHashSet;
@@ -45,12 +46,14 @@
4546
import org.springframework.util.FileCopyUtils;
4647

4748
import static org.hamcrest.Matchers.equalTo;
49+
import static org.hamcrest.Matchers.is;
4850
import static org.junit.Assert.assertThat;
4951

5052
/**
5153
* Tests for {@link ClassPathChangeUploader}.
5254
*
5355
* @author Phillip Webb
56+
* @author Andy Wilkinson
5457
*/
5558
public class ClassPathChangeUploaderTests {
5659

@@ -102,19 +105,28 @@ public void urlMustNotBeMalformed() throws Exception {
102105
@Test
103106
public void sendsClassLoaderFiles() throws Exception {
104107
File sourceFolder = this.temp.newFolder();
105-
Set<ChangedFile> files = new LinkedHashSet<ChangedFile>();
106-
File file1 = createFile(sourceFolder, "File1");
107-
File file2 = createFile(sourceFolder, "File2");
108-
File file3 = createFile(sourceFolder, "File3");
109-
files.add(new ChangedFile(sourceFolder, file1, Type.ADD));
110-
files.add(new ChangedFile(sourceFolder, file2, Type.MODIFY));
111-
files.add(new ChangedFile(sourceFolder, file3, Type.DELETE));
112-
Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>();
113-
changeSet.add(new ChangedFiles(sourceFolder, files));
114-
ClassPathChangedEvent event = new ClassPathChangedEvent(this, changeSet, false);
108+
ClassPathChangedEvent event = createClassPathChangedEvent(sourceFolder);
115109
this.requestFactory.willRespond(HttpStatus.OK);
116110
this.uploader.onApplicationEvent(event);
111+
assertThat(this.requestFactory.getExecutedRequests().size(), is(1));
117112
MockClientHttpRequest request = this.requestFactory.getExecutedRequests().get(0);
113+
verifyUploadRequest(sourceFolder, request);
114+
}
115+
116+
@Test
117+
public void retriesOnConnectException() throws Exception {
118+
File sourceFolder = this.temp.newFolder();
119+
ClassPathChangedEvent event = createClassPathChangedEvent(sourceFolder);
120+
this.requestFactory.willRespond(new ConnectException());
121+
this.requestFactory.willRespond(HttpStatus.OK);
122+
this.uploader.onApplicationEvent(event);
123+
assertThat(this.requestFactory.getExecutedRequests().size(), is(2));
124+
verifyUploadRequest(sourceFolder,
125+
this.requestFactory.getExecutedRequests().get(1));
126+
}
127+
128+
private void verifyUploadRequest(File sourceFolder, MockClientHttpRequest request)
129+
throws IOException, ClassNotFoundException {
118130
ClassLoaderFiles classLoaderFiles = deserialize(request.getBodyAsBytes());
119131
Collection<SourceFolder> sourceFolders = classLoaderFiles.getSourceFolders();
120132
assertThat(sourceFolders.size(), equalTo(1));
@@ -133,6 +145,21 @@ private void assertClassFile(ClassLoaderFile file, String content, Kind kind) {
133145
assertThat(file.getKind(), equalTo(kind));
134146
}
135147

148+
private ClassPathChangedEvent createClassPathChangedEvent(File sourceFolder)
149+
throws IOException {
150+
Set<ChangedFile> files = new LinkedHashSet<ChangedFile>();
151+
File file1 = createFile(sourceFolder, "File1");
152+
File file2 = createFile(sourceFolder, "File2");
153+
File file3 = createFile(sourceFolder, "File3");
154+
files.add(new ChangedFile(sourceFolder, file1, Type.ADD));
155+
files.add(new ChangedFile(sourceFolder, file2, Type.MODIFY));
156+
files.add(new ChangedFile(sourceFolder, file3, Type.DELETE));
157+
Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>();
158+
changeSet.add(new ChangedFiles(sourceFolder, files));
159+
ClassPathChangedEvent event = new ClassPathChangedEvent(this, changeSet, false);
160+
return event;
161+
}
162+
136163
private File createFile(File sourceFolder, String name) throws IOException {
137164
File file = new File(sourceFolder, name);
138165
FileCopyUtils.copy(name.getBytes(), file);

0 commit comments

Comments
 (0)