Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions web-clients/src/main/java/org/bahmni/webclients/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,8 @@ private String get(URI uri, HttpHeaders httpHeaders) {
try {
HttpResponse httpResponse = httpClientInternal.get(authenticator.getRequestDetails(uri), httpHeaders);

if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED ||
httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN) {
httpClientInternal.closeConnection();
httpClientInternal = httpClientInternal.createNew();
if (isAuthFailure(httpResponse)) {
reInitializeClient();
httpResponse = httpClientInternal.get(authenticator.refreshRequestDetails(uri), httpHeaders);
}

Expand All @@ -63,13 +61,41 @@ private String get(URI uri, HttpHeaders httpHeaders) {
}
}

private String post(URI uri, String body, HttpHeaders httpHeaders) {
try {
HttpResponse httpResponse = httpClientInternal.post(authenticator.getRequestDetails(uri), body, httpHeaders);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about using try with resources instead of try - catch - finally. Check HttpResponse implements Closeable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpResponse is not closeable.
There is a ClosableHttpClient used by the HttpClientInternal which can be implemented for try with resources. We can improve that later on.


if (isAuthFailure(httpResponse)) {
reInitializeClient();
httpResponse = httpClientInternal.post(authenticator.refreshRequestDetails(uri), body, httpHeaders);
}

checkSanityOfResponse(httpResponse);
return asString(httpResponse);
} catch (IOException e) {
throw new WebClientsException(e);
} finally {
httpClientInternal.closeConnection();
}
}

public <T> T get(String url, Class<T> returnType) throws IOException {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.put("Accept", "application/json");
String response = get(URI.create(url), httpHeaders);
return objectMapper.readValue(response, returnType);
}

public <T, R> R post(String url, T payload, Class<R> returnType) throws IOException {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.put("Accept", "application/json");
httpHeaders.put("Content-Type", "application/json");
String body = objectMapper.writeValueAsString(payload);
String response = post(URI.create(url), body, httpHeaders);
return objectMapper.readValue(response, returnType);

}

private void checkSanityOfResponse(HttpResponse httpResponse) {
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
Expand All @@ -82,4 +108,14 @@ private void checkSanityOfResponse(HttpResponse httpResponse) {
private String asString(HttpResponse httpResponse) throws IOException {
return EntityUtils.toString(httpResponse.getEntity());
}

private boolean isAuthFailure(HttpResponse httpResponse) {
return httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED ||
httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN;
}

private void reInitializeClient() {
httpClientInternal.closeConnection();
httpClientInternal = httpClientInternal.createNew();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class HttpClientInternal {
private int connectTimeout;
Expand All @@ -32,19 +35,8 @@ public HttpResponse get(HttpRequestDetails requestDetails) {
}

public HttpResponse get(HttpRequestDetails requestDetails, HttpHeaders httpHeaders) {

initializeClient();
HttpGet httpGet = new HttpGet(requestDetails.getUri());
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(readTimeout)
.setSocketTimeout(connectTimeout)
.build();
if(connectionManager == null) {
connectionManager = new PoolingHttpClientConnectionManager();
}
closeableHttpClient = HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.build();
requestDetails.addDetailsTo(httpGet);
httpHeaders.addTo(httpGet);

Expand Down Expand Up @@ -74,4 +66,36 @@ void closeConnection(){
public HttpClientInternal createNew() {
return new HttpClientInternal(connectTimeout, readTimeout);
}

public HttpResponse post(HttpRequestDetails requestDetails, String body, HttpHeaders httpHeaders) {
initializeClient();
HttpPost httpPost = new HttpPost(requestDetails.getUri());
requestDetails.addDetailsTo(httpPost);
httpHeaders.addTo(httpPost);
if (body != null) {
httpPost.setEntity(new StringEntity(body, StandardCharsets.UTF_8));
}
try {
return closeableHttpClient.execute(httpPost
);
} catch (IOException e) {
throw new WebClientsException("Error executing request", e);
}
}

private void initializeClient(){
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(readTimeout)
.setSocketTimeout(connectTimeout)
.build();
if (connectionManager == null) {
connectionManager = new PoolingHttpClientConnectionManager();
}
closeableHttpClient = HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.build();
}

}

156 changes: 156 additions & 0 deletions web-clients/src/test/java/org/bahmni/webclients/HttpClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import org.junit.Test;
import org.mockito.Mock;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.matchers.JUnitMatchers.containsString;
import static org.mockito.Mockito.*;
Expand Down Expand Up @@ -107,4 +109,158 @@ private BasicHttpResponse unAuthorizedResponse() {
private BasicHttpResponse basicResponse(int statusCode, String statusReasonPhrase) {
return new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion("http", 1, 1), statusCode, statusReasonPhrase));
}

private BasicHttpResponse forbiddenResponse() {
return basicResponse(HttpStatus.SC_FORBIDDEN, "Forbidden");
}

private BasicHttpResponse serverErrorResponse() {
return basicResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
}

// Test classes for JSON serialization/deserialization
static class TestRequest {
private String name;
private int value;

public TestRequest() {}

public TestRequest(String name, int value) {
this.name = name;
this.value = value;
}

public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getValue() { return value; }
public void setValue(int value) { this.value = value; }
}

static class TestResponse {
private String result;
private boolean success;

public TestResponse() {}

public TestResponse(String result, boolean success) {
this.result = result;
this.success = success;
}

public String getResult() { return result; }
public void setResult(String result) { this.result = result; }
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
}

@Test
public void shouldReturnContentFromPostRequest() throws IOException {
String requestJson = "{\"name\":\"test\",\"value\":123}";
String responseJson = "{\"result\":\"success\",\"success\":true}";
TestRequest request = new TestRequest("test", 123);

when(webClient.post(any(HttpRequestDetails.class), anyString(), any(HttpHeaders.class)))
.thenReturn(okResponse(responseJson));

HttpClient authenticatingWebClient = new HttpClient(webClient, authenticator);
TestResponse response = authenticatingWebClient.post(uri.toString(), request, TestResponse.class);

assertEquals("success", response.getResult());
assertEquals(true, response.isSuccess());
}

@Test
public void shouldSerializePayloadAndDeserializeResponse() throws IOException {
String responseJson = "{\"result\":\"processed\",\"success\":true}";
TestRequest request = new TestRequest("testData", 456);

when(webClient.post(any(HttpRequestDetails.class), anyString(), any(HttpHeaders.class)))
.thenReturn(okResponse(responseJson));

HttpClient authenticatingWebClient = new HttpClient(webClient, authenticator);
TestResponse response = authenticatingWebClient.post(uri.toString(), request, TestResponse.class);

verify(webClient).post(any(HttpRequestDetails.class), contains("testData"), any(HttpHeaders.class));
verify(webClient).post(any(HttpRequestDetails.class), contains("456"), any(HttpHeaders.class));
assertEquals("processed", response.getResult());
assertEquals(true, response.isSuccess());
}

@Test
public void shouldRefreshRequestDetailsIfUnauthorizedResponseReceivedOnPost() throws IOException {
String responseJson = "{\"result\":\"success\",\"success\":true}";
TestRequest request = new TestRequest("test", 789);
HttpClient authenticatingWebClient = new HttpClient(webClient, authenticator);
HttpRequestDetails firstRequestDetails = new HttpRequestDetails(uri);
HttpRequestDetails secondRequestDetails = new HttpRequestDetails(uri);

when(authenticator.getRequestDetails(any(URI.class))).thenReturn(firstRequestDetails);
when(authenticator.refreshRequestDetails(any(URI.class))).thenReturn(secondRequestDetails);

when(webClient.post(eq(firstRequestDetails), anyString(), any(HttpHeaders.class)))
.thenReturn(unAuthorizedResponse());
when(webClient.post(eq(secondRequestDetails), anyString(), any(HttpHeaders.class)))
.thenReturn(okResponse(responseJson));
when(webClient.createNew()).thenReturn(webClient);

TestResponse response = authenticatingWebClient.post(uri.toString(), request, TestResponse.class);

verify(webClient).post(eq(firstRequestDetails), anyString(), any(HttpHeaders.class));
verify(webClient).post(eq(secondRequestDetails), anyString(), any(HttpHeaders.class));
assertEquals("success", response.getResult());
assertEquals(true, response.isSuccess());
}

@Test
public void shouldRefreshRequestDetailsIfForbiddenResponseReceivedOnPost() throws IOException {
String responseJson = "{\"result\":\"success\",\"success\":true}";
TestRequest request = new TestRequest("test", 999);
HttpClient authenticatingWebClient = new HttpClient(webClient, authenticator);
HttpRequestDetails firstRequestDetails = new HttpRequestDetails(uri);
HttpRequestDetails secondRequestDetails = new HttpRequestDetails(uri);

when(authenticator.getRequestDetails(any(URI.class))).thenReturn(firstRequestDetails);
when(authenticator.refreshRequestDetails(any(URI.class))).thenReturn(secondRequestDetails);

when(webClient.post(eq(firstRequestDetails), anyString(), any(HttpHeaders.class)))
.thenReturn(forbiddenResponse());
when(webClient.post(eq(secondRequestDetails), anyString(), any(HttpHeaders.class)))
.thenReturn(okResponse(responseJson));
when(webClient.createNew()).thenReturn(webClient);

TestResponse response = authenticatingWebClient.post(uri.toString(), request, TestResponse.class);

verify(webClient).post(eq(firstRequestDetails), anyString(), any(HttpHeaders.class));
verify(webClient).post(eq(secondRequestDetails), anyString(), any(HttpHeaders.class));
assertEquals("success", response.getResult());
assertEquals(true, response.isSuccess());
}

@Test(expected = WebClientsException.class)
public void shouldFailIfPostRequestFailsTwiceWithUnauthorizedException() throws IOException {
TestRequest request = new TestRequest("test", 111);
HttpClient authenticatingWebClient = new HttpClient(webClient, authenticator);
HttpRequestDetails firstRequestDetails = new HttpRequestDetails(uri);
HttpRequestDetails secondRequestDetails = new HttpRequestDetails(uri);

when(authenticator.getRequestDetails(any(URI.class))).thenReturn(firstRequestDetails);
when(authenticator.refreshRequestDetails(any(URI.class))).thenReturn(secondRequestDetails);

when(webClient.post(any(HttpRequestDetails.class), anyString(), any(HttpHeaders.class)))
.thenReturn(unAuthorizedResponse());
when(webClient.createNew()).thenReturn(webClient);

authenticatingWebClient.post(uri.toString(), request, TestResponse.class);
}

@Test(expected = WebClientsException.class)
public void shouldThrowExceptionOnBadResponseCodeForPost() throws IOException {
TestRequest request = new TestRequest("test", 222);

when(webClient.post(any(HttpRequestDetails.class), anyString(), any(HttpHeaders.class)))
.thenReturn(serverErrorResponse());

HttpClient authenticatingWebClient = new HttpClient(webClient, authenticator);
authenticatingWebClient.post(uri.toString(), request, TestResponse.class);
}
}