Skip to content

Commit a563728

Browse files
author
Luke Sikina
committed
[ALS-6880] HttpClient with proxy
- Add proxy config to http client - Refactor rest template to use client - Fix SLF4J duplicate impls
1 parent 0061638 commit a563728

File tree

7 files changed

+104
-33
lines changed

7 files changed

+104
-33
lines changed

pic-sure-auth-services/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@
1515
<xml.bind.version>2.3.0</xml.bind.version>
1616
</properties>
1717
<dependencies>
18+
<dependency>
19+
<groupId>org.apache.httpcomponents.client5</groupId>
20+
<artifactId>httpclient5</artifactId>
21+
<version>5.2.1</version>
22+
<exclusions>
23+
<exclusion>
24+
<groupId>org.slf4j</groupId>
25+
<artifactId>slf4j-api</artifactId>
26+
</exclusion>
27+
</exclusions>
28+
</dependency>
29+
1830
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
1931
<!-- Web version has been explicitly set to fix security finding in spring-boot-starter-web -->
2032
<dependency>

pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/Auth0AuthenticationService.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import org.springframework.http.HttpHeaders;
2525
import org.springframework.http.MediaType;
2626
import org.springframework.http.ResponseEntity;
27-
import org.springframework.http.client.ClientHttpRequestFactory;
28-
import org.springframework.http.client.SimpleClientHttpRequestFactory;
2927
import org.springframework.stereotype.Service;
3028

3129
/**
@@ -156,7 +154,7 @@ public JsonNode retrieveUserInfo(String accessToken) throws IOException {
156154
ResponseEntity<String> response = this.restClientUtil.retrieveGetResponseWithRequestConfiguration(
157155
auth0UserInfoURI,
158156
headers,
159-
RestClientUtil.createRequestConfigWithCustomTimeout(2000)
157+
2000
160158
);
161159

162160
auth0Response = objectMapper.readTree(response.getBody());

pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/FENCEAuthenticationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ private JsonNode getFENCEUserProfile(String access_token) {
188188
ResponseEntity<String> fence_user_profile_response = this.restClientUtil.retrieveGetResponseWithRequestConfiguration(
189189
this.idp_provider_uri + "/user/user",
190190
headers,
191-
RestClientUtil.createRequestConfigWithCustomTimeout(10000)
191+
10000
192192
);
193193

194194
// Map the response to a JsonNode object
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package edu.harvard.hms.dbmi.avillach.auth.utils;
2+
3+
4+
import org.apache.hc.client5.http.auth.AuthScope;
5+
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
6+
import org.apache.hc.client5.http.classic.HttpClient;
7+
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
8+
import org.apache.hc.client5.http.impl.classic.HttpClients;
9+
import org.apache.hc.core5.http.HttpHost;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
import org.springframework.beans.factory.annotation.Autowired;
13+
import org.springframework.beans.factory.annotation.Value;
14+
import org.springframework.context.annotation.Bean;
15+
import org.springframework.context.annotation.Configuration;
16+
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
17+
import org.springframework.util.StringUtils;
18+
import org.springframework.web.client.RestTemplate;
19+
20+
@Configuration
21+
public class RestClientConfig {
22+
23+
private static final Logger LOG = LoggerFactory.getLogger(RestClientConfig.class);
24+
25+
@Value("${http.proxyHost:}")
26+
private String proxyHost;
27+
28+
@Value("${http.proxyPort:}")
29+
private int proxyPort;
30+
31+
@Value("${http.proxyUser:}")
32+
private String proxyUser;
33+
34+
@Value("${http.proxyPassword:}")
35+
private String proxyPassword;
36+
37+
@Bean
38+
public HttpClient getHttpClient() {
39+
if (!StringUtils.hasLength(proxyHost)) {
40+
return HttpClients.createDefault();
41+
} else if (!StringUtils.hasLength(proxyUser)) {
42+
LOG.info("Utilizing unauthenticated proxy: host={}", proxyHost);
43+
return HttpClients.custom()
44+
.setProxy(new HttpHost(proxyHost, proxyPort))
45+
.build();
46+
} else {
47+
LOG.info("Utilizing authenticated proxy: host={}, user={}", proxyHost, proxyUser);
48+
49+
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
50+
credentialsProvider.setCredentials(
51+
new AuthScope(proxyHost, proxyPort),
52+
new UsernamePasswordCredentials(proxyUser, proxyPassword.toCharArray()));
53+
54+
return HttpClients.custom()
55+
.setDefaultCredentialsProvider(credentialsProvider)
56+
.setProxy(new HttpHost(proxyHost, proxyPort))
57+
.build();
58+
}
59+
}
60+
61+
@Bean
62+
public RestTemplate getRestTemplate(@Autowired HttpClient client) {
63+
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
64+
factory.setHttpClient(client);
65+
return new RestTemplate(factory);
66+
}
67+
}

pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@
22

33
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;
5+
import org.springframework.beans.factory.annotation.Autowired;
56
import org.springframework.http.*;
6-
import org.springframework.http.client.ClientHttpRequestFactory;
7-
import org.springframework.http.client.SimpleClientHttpRequestFactory;
7+
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
88
import org.springframework.stereotype.Component;
99
import org.springframework.util.MultiValueMap;
1010
import org.springframework.web.client.HttpClientErrorException;
11-
import org.springframework.util.MultiValueMap;
1211
import org.springframework.web.client.RestTemplate;
1312

1413
@Component
1514
public class RestClientUtil {
1615

1716
private static final Logger logger = LoggerFactory.getLogger(RestClientUtil.class);
18-
private final RestTemplate restTemplate = new RestTemplate();
17+
private final RestTemplate restTemplate;
18+
19+
@Autowired
20+
public RestClientUtil(RestTemplate restTemplate) {
21+
this.restTemplate = restTemplate;
22+
}
1923

2024
public ResponseEntity<String> retrieveGetResponse(String uri, HttpHeaders headers) {
2125
try {
@@ -29,16 +33,14 @@ public ResponseEntity<String> retrieveGetResponse(String uri, HttpHeaders header
2933
}
3034

3135
// Implement: The ability to set the timeout on the rest template for a given request.
32-
public ResponseEntity<String> retrieveGetResponseWithRequestConfiguration(String uri, HttpHeaders headers, ClientHttpRequestFactory requestFactory) {
33-
if (requestFactory == null) {
34-
return retrieveGetResponse(uri, headers);
35-
}
36-
RestTemplate localRestTemplate = new RestTemplate(requestFactory);
37-
36+
public ResponseEntity<String> retrieveGetResponseWithRequestConfiguration(String uri, HttpHeaders headers, int timeoutMs) {
3837
try {
3938
HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
39+
// Set timeout settings
40+
((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setConnectTimeout(timeoutMs);
41+
((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setConnectionRequestTimeout(timeoutMs);
4042
// Pass custom configuration to the RestTemplate
41-
return localRestTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
43+
return restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
4244
} catch (HttpClientErrorException ex) {
4345
logger.error("HttpClientErrorException: {}", ex.getMessage());
4446
throw ex;
@@ -55,12 +57,4 @@ public ResponseEntity<String> retrievePostResponse(String uri, HttpHeaders heade
5557
public ResponseEntity<String> retrievePostResponse(String uri, HttpEntity<MultiValueMap<String, String>> requestEntity) throws HttpClientErrorException {
5658
return restTemplate.postForEntity(uri, requestEntity, String.class);
5759
}
58-
59-
public static ClientHttpRequestFactory createRequestConfigWithCustomTimeout(int timeoutMs) {
60-
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
61-
requestFactory.setConnectTimeout(timeoutMs);
62-
requestFactory.setReadTimeout(timeoutMs);
63-
return requestFactory;
64-
}
65-
6660
}

pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import org.springframework.http.HttpHeaders;
1616
import org.springframework.http.HttpStatus;
1717
import org.springframework.http.ResponseEntity;
18-
import org.springframework.http.client.ClientHttpRequestFactory;
1918

2019
import java.io.IOException;
2120
import java.util.HashMap;
@@ -25,6 +24,7 @@
2524

2625
import static org.junit.Assert.*;
2726
import static org.mockito.ArgumentMatchers.any;
27+
import static org.mockito.ArgumentMatchers.anyInt;
2828
import static org.mockito.ArgumentMatchers.anyString;
2929
import static org.mockito.Mockito.*;
3030

@@ -70,15 +70,15 @@ public void testGetToken_MissingParameters() throws IOException {
7070
// Tests the failure in retrieving user information, expecting an IOException to be converted into a NotAuthorizedException
7171
@Test(expected = NotAuthorizedException.class)
7272
public void testGetToken_UserInfoRetrievalFails() throws IOException {
73-
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class)))
73+
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), anyInt()))
7474
.thenThrow(new NotAuthorizedException("Failed to retrieve user info"));
7575
authenticationService.authenticate(authRequest, "localhost");
7676
}
7777

7878
// Tests the scenario where the user ID is not found in the user info retrieved
7979
@Test(expected = NotAuthorizedException.class)
8080
public void testGetToken_NoUserIdInUserInfo() throws IOException {
81-
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any()))
81+
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), anyInt()))
8282
.thenReturn(new ResponseEntity<>("{}", HttpStatus.OK));
8383
authenticationService.authenticate(authRequest, "localhost");
8484
}
@@ -98,7 +98,7 @@ public void testGetToken_Successful() throws Exception {
9898
// Additional test to handle retries in user info retrieval
9999
@Test
100100
public void testRetrieveUserInfo_WithRetries() throws Exception {
101-
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any()))
101+
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), anyInt()))
102102
.thenThrow(new RuntimeException("Network error"))
103103
.thenReturn(new ResponseEntity<>("{}", HttpStatus.OK));
104104
// Assuming retrieveUserInfo is accessible, or using reflection if it is private
@@ -142,7 +142,7 @@ private void setupSuccessfulTokenRetrievalScenario() throws IOException {
142142
+ "]"
143143
+ "}";
144144

145-
when(restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class)))
145+
when(restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), anyInt()))
146146
.thenReturn(new ResponseEntity<>(validJson, HttpStatus.OK));
147147

148148
// Create a test user

pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/authentication/AuthenticationServiceTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.springframework.http.HttpHeaders;
1818
import org.springframework.http.HttpStatus;
1919
import org.springframework.http.ResponseEntity;
20-
import org.springframework.http.client.ClientHttpRequestFactory;
2120

2221
import java.io.IOException;
2322
import java.util.HashMap;
@@ -27,6 +26,7 @@
2726

2827
import static org.junit.Assert.*;
2928
import static org.mockito.ArgumentMatchers.any;
29+
import static org.mockito.ArgumentMatchers.anyInt;
3030
import static org.mockito.ArgumentMatchers.anyString;
3131
import static org.mockito.Mockito.*;
3232

@@ -72,15 +72,15 @@ public void testGetToken_MissingParameters() throws IOException {
7272
// Tests the failure in retrieving user information, expecting an IOException to be converted into a NotAuthorizedException
7373
@Test(expected = NotAuthorizedException.class)
7474
public void testGetToken_UserInfoRetrievalFails() throws IOException {
75-
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class)))
75+
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), anyInt()))
7676
.thenThrow(new NotAuthorizedException("Failed to retrieve user info"));
7777
authenticationService.authenticate(authRequest, "localhost");
7878
}
7979

8080
// Tests the scenario where the user ID is not found in the user info retrieved
8181
@Test(expected = NotAuthorizedException.class)
8282
public void testGetToken_NoUserIdInUserInfo() throws IOException {
83-
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any()))
83+
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), anyInt()))
8484
.thenReturn(new ResponseEntity<>("{}", HttpStatus.OK));
8585
authenticationService.authenticate(authRequest, "localhost");
8686
}
@@ -100,7 +100,7 @@ public void testGetToken_Successful() throws Exception {
100100
// Additional test to handle retries in user info retrieval
101101
@Test
102102
public void testRetrieveUserInfo_WithRetries() throws Exception {
103-
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any()))
103+
when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), anyInt()))
104104
.thenThrow(new RuntimeException("Network error"))
105105
.thenReturn(new ResponseEntity<>("{}", HttpStatus.OK));
106106
// Assuming retrieveUserInfo is accessible, or using reflection if it is private
@@ -144,7 +144,7 @@ private void setupSuccessfulTokenRetrievalScenario() throws IOException {
144144
+ "]"
145145
+ "}";
146146

147-
when(restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class)))
147+
when(restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), anyInt()))
148148
.thenReturn(new ResponseEntity<>(validJson, HttpStatus.OK));
149149

150150
// Create a test user

0 commit comments

Comments
 (0)