From 47f8fba08dab4715bbb6de41f9a8e8d887b2d129 Mon Sep 17 00:00:00 2001 From: mkslofstra Date: Fri, 21 Jun 2024 15:09:51 +0200 Subject: [PATCH 1/4] refactor(RockConnection): replace resttemplate with restclient (WIP) --- .../org/molgenis/r/rock/RockConnection.java | 144 ++++++++++++------ 1 file changed, 98 insertions(+), 46 deletions(-) diff --git a/r/src/main/java/org/molgenis/r/rock/RockConnection.java b/r/src/main/java/org/molgenis/r/rock/RockConnection.java index d77c4b6a5..b5be12331 100644 --- a/r/src/main/java/org/molgenis/r/rock/RockConnection.java +++ b/r/src/main/java/org/molgenis/r/rock/RockConnection.java @@ -17,6 +17,7 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseExtractor; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; @@ -24,65 +25,86 @@ public class RockConnection implements RServerConnection { private static final Logger logger = LoggerFactory.getLogger(RockConnection.class); + public static final String FILE_DOWNLOAD_FAILED = "File download failed: "; + public static final MediaType MEDIATYPE_APPLICATION_RSCRIPT = + MediaType.valueOf("application/x-rscript"); + private static final String UPLOAD_FAILED = "File upload failed: "; + private static final String UPLOAD_ENDPOINT = "/_upload"; + private static final String EVAL_ENDPOINT = "/_eval"; + public static final String DOWNLOAD_ENDPOINT = "/_download"; + private static final String PATH = "path"; + private static final String OVERWRITE = "overwrite"; + private static final String FILE = "file"; private String rockSessionId; private final RockApplication application; + private final RestClient restClient; public RockConnection(RockApplication application) throws RServerException { this.application = application; + this.restClient = getRestClient(); openSession(); } @Override - public RServerResult eval(String expr, boolean serialized) throws RServerException { - RestTemplate restTemplate = new RestTemplate(); - HttpHeaders headers = createHeaders(); - headers.setContentType(MediaType.valueOf("application/x-rscript")); - - String serverUrl = getRSessionResourceUrl("/_eval"); + public RServerResult eval(String expr, boolean serialized) { + String serverUrl = getRSessionResourceUrl(EVAL_ENDPOINT); if (serialized) { - // accept application/octet-stream - headers.setAccept( - Lists.newArrayList(MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON)); - ResponseEntity response = - restTemplate.exchange( - serverUrl, HttpMethod.POST, new HttpEntity<>(expr, headers), byte[].class); - return new RockResult(response.getBody()); + RestClient.RequestBodySpec request = + createRequestForPost(serverUrl, expr, MEDIATYPE_APPLICATION_RSCRIPT); + byte[] responseBody = + request + .headers( + httpHeaders -> { + httpHeaders.setAccept( + Lists.newArrayList( + MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON)); + }) + .retrieve() + .body(byte[].class); + return new RockResult(responseBody); } else { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = createHeaders(); + headers.setContentType(MEDIATYPE_APPLICATION_RSCRIPT); headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); ResponseEntity response = restTemplate.exchange( serverUrl, HttpMethod.POST, new HttpEntity<>(expr, headers), String.class); String jsonSource = response.getBody(); return new RockResult(jsonSource); + // RestClient.RequestBodySpec request = createRequestForPost(serverUrl, expr, + // MEDIATYPE_APPLICATION_RSCRIPT); + // String responseBody = request.headers(httpHeaders -> { + // httpHeaders.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); + // }).retrieve().body(String.class); + // return new RockResult(responseBody); } } @Override public void writeFile(String fileName, InputStream in) throws RServerException { try { - HttpHeaders headers = createHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("file", new MultiPartInputStreamResource(in, fileName)); - HttpEntity> requestEntity = new HttpEntity<>(body, headers); + body.add(FILE, new MultiPartInputStreamResource(in, fileName)); - String serverUrl = getRSessionResourceUrl("/_upload"); + String serverUrl = getRSessionResourceUrl(UPLOAD_ENDPOINT); UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(serverUrl) - .queryParam("path", fileName) - .queryParam("overwrite", true); + .queryParam(PATH, fileName) + .queryParam(OVERWRITE, true); + + ResponseEntity response = + postToRestClient(builder.toUriString(), body, MediaType.MULTIPART_FORM_DATA) + .toBodilessEntity(); - RestTemplate restTemplate = new RestTemplate(); - ResponseEntity response = - restTemplate.postForEntity(builder.toUriString(), requestEntity, String.class); if (!response.getStatusCode().is2xxSuccessful()) { logger.error("File upload to {} failed: {}", serverUrl, response.getStatusCode()); - throw new RockServerException("File upload failed: " + response.getStatusCode()); + throw new RockServerException(UPLOAD_FAILED + response.getStatusCode()); } } catch (RestClientException e) { - throw new RockServerException("File upload failed", e); + throw new RockServerException(UPLOAD_FAILED, e); } } @@ -93,9 +115,15 @@ public void readFile(String fileName, Consumer inputStreamConsumer) HttpHeaders headers = createHeaders(); headers.setAccept(Collections.singletonList(MediaType.ALL)); - String serverUrl = getRSessionResourceUrl("/_download"); + String serverUrl = getRSessionResourceUrl(DOWNLOAD_ENDPOINT); + UriComponentsBuilder builder = - UriComponentsBuilder.fromHttpUrl(serverUrl).queryParam("path", fileName); + UriComponentsBuilder.fromHttpUrl(serverUrl).queryParam(PATH, fileName); + // RestClient.ResponseSpec response = restClient.get() + // .uri(builder.build().toUri()) + // .headers(httpHeaders -> { + // headers.setAccept(Collections.singletonList(MediaType.ALL)); + // }).retrieve(); RestTemplate restTemplate = new RestTemplate(); restTemplate.execute( @@ -103,18 +131,17 @@ public void readFile(String fileName, Consumer inputStreamConsumer) HttpMethod.GET, request -> request.getHeaders().putAll(headers), (ResponseExtractor) - response -> { - if (!response.getStatusCode().is2xxSuccessful()) { - logger.error( - "File download from {} failed: {}", serverUrl, response.getStatusCode()); - throw new RuntimeException("File download failed: " + response.getStatusText()); + resp -> { + if (!resp.getStatusCode().is2xxSuccessful()) { + logger.error("File download from {} failed: {}", serverUrl, resp.getStatusCode()); + throw new RuntimeException(FILE_DOWNLOAD_FAILED + resp.getStatusText()); } else { - inputStreamConsumer.accept(response.getBody()); + inputStreamConsumer.accept(resp.getBody()); } return null; }); } catch (RestClientException e) { - throw new RockServerException("File download failed", e); + throw new RockServerException(FILE_DOWNLOAD_FAILED, e); } } @@ -141,14 +168,13 @@ public boolean close() { private void openSession() throws RServerException { try { - RestTemplate restTemplate = new RestTemplate(); - ResponseEntity response = - restTemplate.exchange( - getRSessionsResourceUrl(), - HttpMethod.POST, - new HttpEntity<>(createHeaders()), - RockSessionInfo.class); - RockSessionInfo info = response.getBody(); + RockSessionInfo info = + postToRestClient( + getRSessionsResourceUrl(), + new LinkedMultiValueMap<>(), + MediaType.APPLICATION_JSON) + .body(RockSessionInfo.class); + assert info != null; this.rockSessionId = info.getId(); } catch (RestClientException e) { throw new RockServerException("Failure when opening a Rock R session", e); @@ -163,17 +189,43 @@ private String getRSessionResourceUrl(String path) { return String.format("%s/r/session/%s%s", application.getUrl(), rockSessionId, path); } + private String getAuthHeader() { + String auth = application.getUser() + ":" + application.getPassword(); + byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8)); + return "Basic " + new String(encodedAuth); + } + private HttpHeaders createHeaders() { return new HttpHeaders() { { - String auth = application.getUser() + ":" + application.getPassword(); - byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8)); - String authHeader = "Basic " + new String(encodedAuth); - add("Authorization", authHeader); + add("Authorization", getAuthHeader()); } }; } + private RestClient getRestClient() { + String serverUrl = getRSessionResourceUrl(UPLOAD_ENDPOINT); + String authHeader = getAuthHeader(); + return RestClient.builder() + .baseUrl(serverUrl) + .defaultHeaders( + httpHeaders -> { + httpHeaders.setBasicAuth(authHeader); + httpHeaders.set(HttpHeaders.AUTHORIZATION, authHeader); + }) + .build(); + } + + private RestClient.RequestBodySpec createRequestForPost( + String uriString, Object body, MediaType contentType) { + return restClient.post().uri(uriString).contentType(contentType).body(body); + } + + private RestClient.ResponseSpec postToRestClient( + String uriString, Object body, MediaType contentType) { + return createRequestForPost(uriString, body, contentType).retrieve(); + } + private static class MultiPartInputStreamResource extends InputStreamResource { private final String fileName; From d009b0508af2abb835292118bfdacb0b90052967 Mon Sep 17 00:00:00 2001 From: mkslofstra Date: Tue, 25 Jun 2024 15:58:56 +0200 Subject: [PATCH 2/4] refactor(RockConnection): Replace RestTemplate with RestClient --- .../org/molgenis/r/rock/RockConnection.java | 70 ++++++++----------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/r/src/main/java/org/molgenis/r/rock/RockConnection.java b/r/src/main/java/org/molgenis/r/rock/RockConnection.java index b5be12331..5dd8fb082 100644 --- a/r/src/main/java/org/molgenis/r/rock/RockConnection.java +++ b/r/src/main/java/org/molgenis/r/rock/RockConnection.java @@ -4,6 +4,7 @@ import com.google.common.collect.Lists; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Base64; import java.util.Collections; import java.util.function.Consumer; @@ -12,14 +13,14 @@ import org.molgenis.r.RServerResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.web.client.ClientHttpRequestFactories; +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; import org.springframework.core.io.InputStreamResource; import org.springframework.http.*; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.ResponseExtractor; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; public class RockConnection implements RServerConnection { @@ -65,21 +66,17 @@ public RServerResult eval(String expr, boolean serialized) { .body(byte[].class); return new RockResult(responseBody); } else { - RestTemplate restTemplate = new RestTemplate(); - HttpHeaders headers = createHeaders(); - headers.setContentType(MEDIATYPE_APPLICATION_RSCRIPT); - headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); - ResponseEntity response = - restTemplate.exchange( - serverUrl, HttpMethod.POST, new HttpEntity<>(expr, headers), String.class); - String jsonSource = response.getBody(); - return new RockResult(jsonSource); - // RestClient.RequestBodySpec request = createRequestForPost(serverUrl, expr, - // MEDIATYPE_APPLICATION_RSCRIPT); - // String responseBody = request.headers(httpHeaders -> { - // httpHeaders.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); - // }).retrieve().body(String.class); - // return new RockResult(responseBody); + RestClient.RequestBodySpec request = + createRequestForPost(serverUrl, expr, MEDIATYPE_APPLICATION_RSCRIPT); + RestClient.ResponseSpec resp = + request + .headers( + httpHeaders -> { + httpHeaders.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); + }) + .retrieve(); + String responseBody = resp.body(String.class); + return new RockResult(responseBody); } } @@ -119,24 +116,17 @@ public void readFile(String fileName, Consumer inputStreamConsumer) UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(serverUrl).queryParam(PATH, fileName); - // RestClient.ResponseSpec response = restClient.get() - // .uri(builder.build().toUri()) - // .headers(httpHeaders -> { - // headers.setAccept(Collections.singletonList(MediaType.ALL)); - // }).retrieve(); - - RestTemplate restTemplate = new RestTemplate(); - restTemplate.execute( - builder.build().toUri(), - HttpMethod.GET, - request -> request.getHeaders().putAll(headers), - (ResponseExtractor) - resp -> { - if (!resp.getStatusCode().is2xxSuccessful()) { - logger.error("File download from {} failed: {}", serverUrl, resp.getStatusCode()); - throw new RuntimeException(FILE_DOWNLOAD_FAILED + resp.getStatusText()); + restClient + .get() + .uri(builder.build().toUri()) + .exchange( + (request, response) -> { + if (!response.getStatusCode().is2xxSuccessful()) { + logger.error( + "File download from {} failed: {}", serverUrl, response.getStatusCode()); + throw new RuntimeException(FILE_DOWNLOAD_FAILED + response.getStatusText()); } else { - inputStreamConsumer.accept(resp.getBody()); + inputStreamConsumer.accept(response.getBody()); } return null; }); @@ -150,12 +140,7 @@ public boolean close() { if (Strings.isNullOrEmpty(rockSessionId)) return true; try { - RestTemplate restTemplate = new RestTemplate(); - restTemplate.exchange( - getRSessionResourceUrl(""), - HttpMethod.DELETE, - new HttpEntity<>(createHeaders()), - Void.class); + restClient.delete().uri(getRSessionResourceUrl("")).retrieve().toBodilessEntity(); this.rockSessionId = null; return true; } catch (RestClientException e) { @@ -206,8 +191,13 @@ private HttpHeaders createHeaders() { private RestClient getRestClient() { String serverUrl = getRSessionResourceUrl(UPLOAD_ENDPOINT); String authHeader = getAuthHeader(); + ClientHttpRequestFactorySettings settings = + ClientHttpRequestFactorySettings.DEFAULTS + .withConnectTimeout(Duration.ofSeconds(300L)) + .withReadTimeout(Duration.ofSeconds(900L)); return RestClient.builder() .baseUrl(serverUrl) + .requestFactory(ClientHttpRequestFactories.get(settings)) .defaultHeaders( httpHeaders -> { httpHeaders.setBasicAuth(authHeader); From f68a3f0d2dc16b7873fc0588b52e958deca52068 Mon Sep 17 00:00:00 2001 From: mkslofstra Date: Tue, 25 Jun 2024 15:59:38 +0200 Subject: [PATCH 3/4] chore: use apache to do http calls to rock --- r/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/r/build.gradle b/r/build.gradle index 75202bc72..15a5e9154 100644 --- a/r/build.gradle +++ b/r/build.gradle @@ -35,6 +35,7 @@ dependencies { implementation 'com.google.auto.value:auto-value-annotations:1.10.4' implementation 'org.apache.commons:commons-text:1.11.0' implementation 'org.json:json:20240303' + implementation 'org.apache.httpcomponents.client5:httpclient5:5.3.1' //test testImplementation('org.springframework.boot:spring-boot-starter-test') { From d4e186807271a5225aa1406ea1d42c2022c867ba Mon Sep 17 00:00:00 2001 From: mkslofstra Date: Tue, 25 Jun 2024 16:20:08 +0200 Subject: [PATCH 4/4] chore: remove unused function --- .../java/org/molgenis/r/rock/RockConnection.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/r/src/main/java/org/molgenis/r/rock/RockConnection.java b/r/src/main/java/org/molgenis/r/rock/RockConnection.java index 5dd8fb082..171f17891 100644 --- a/r/src/main/java/org/molgenis/r/rock/RockConnection.java +++ b/r/src/main/java/org/molgenis/r/rock/RockConnection.java @@ -6,7 +6,6 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Base64; -import java.util.Collections; import java.util.function.Consumer; import org.molgenis.r.RServerConnection; import org.molgenis.r.RServerException; @@ -109,9 +108,6 @@ public void writeFile(String fileName, InputStream in) throws RServerException { public void readFile(String fileName, Consumer inputStreamConsumer) throws RServerException { try { - HttpHeaders headers = createHeaders(); - headers.setAccept(Collections.singletonList(MediaType.ALL)); - String serverUrl = getRSessionResourceUrl(DOWNLOAD_ENDPOINT); UriComponentsBuilder builder = @@ -180,14 +176,6 @@ private String getAuthHeader() { return "Basic " + new String(encodedAuth); } - private HttpHeaders createHeaders() { - return new HttpHeaders() { - { - add("Authorization", getAuthHeader()); - } - }; - } - private RestClient getRestClient() { String serverUrl = getRSessionResourceUrl(UPLOAD_ENDPOINT); String authHeader = getAuthHeader();