Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce the number of Bastion dependencies #58

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
59 changes: 26 additions & 33 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jackson.version>2.8.2</jackson.version>
<jackson.version>2.8.8</jackson.version>
<kotlin.version>1.0.6</kotlin.version>
</properties>

Expand All @@ -62,53 +62,28 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.5.RELEASE</version>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.flipkart.zjsonpatch</groupId>
<artifactId>zjsonpatch</artifactId>
<version>0.2.1</version>
<version>0.3.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>com.mashape.unirest</groupId>
<artifactId>unirest-java</artifactId>
<version>1.4.5</version>
</dependency>
<dependency>
<groupId>org.jglue.fluent-json</groupId>
<artifactId>fluent-json</artifactId>
<version>2.0.0</version>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>com.github.fge</groupId>
<artifactId>json-schema-validator</artifactId>
<version>2.2.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<version>1.13</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
Expand All @@ -124,11 +99,23 @@
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<version>1.13</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.6</version>
<version>2.4.10</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -143,6 +130,12 @@
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
Expand Down
27 changes: 1 addition & 26 deletions src/main/java/rocks/bastion/core/FileRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/**
* An HTTP request which takes any arbitrary file/resource, using the data within as its content body. The {@linkplain FileRequest} will not perform
* any conversions or validation on any user-supplied body content. Use the static factory methods, such as {@link #post(String, String)}
* or {@link #delete(String, String)} to initialise a new {@linkplain FileRequest}.
* or {@link #patch(String, String)} to initialise a new {@linkplain FileRequest}.
* <p>
* By default, this request will contain no headers (except for the content-type) and no query parameters. Use the {@link #addHeader(String, String)}
* and {@link #addQueryParam(String, String)}} to add them. Also, initially, Bastion will attempt to guess the MIME type to send as part of the
Expand Down Expand Up @@ -54,31 +54,6 @@ public static FileRequest post(String url, String resource) throws UnreadableRes
return new FileRequest(HttpMethod.POST, url, resource);
}

/**
* Construct an HTTP request, using the DELETE method, to be sent on the specified URL. The request's content will be loaded
* from the specified resource URL. Bastion will attempt to guess the MIME type to send by looking at the given file.
* <p>
* The resource source is specified as a resource URL as described in {@link ResourceLoader}. Valid resource URLs include (but
* are not limited to):
* </p>
* <ul>
* <li>{@code classpath:/rocks/bastion/json/Sushi.json}</li>
* <li>{@code file:/home/user/Sushi.json}</li>
* </ul>
* <p>
* For more information about which resource URLs are accepted see the documentation for {@link ResourceLoader}.
* </p>
*
* @param url A non-{@literal null} URL to send the request on
* @param resource A non-{@literal null} resource URL to load the data from, for this request
* @return An HTTP request using the DELETE method
* @throws UnreadableResourceException Thrown if the specified resource exists but cannot be read (because it is a directory, for example)
* @throws ResourceNotFoundException Thrown if the specified resource does not exist
*/
public static FileRequest delete(String url, String resource) throws UnreadableResourceException, ResourceNotFoundException {
return new FileRequest(HttpMethod.DELETE, url, resource);
}

/**
* Construct an HTTP request, using the PUT method, to be sent on the specified URL. The request's content will be loaded
* from the specified resource URL. Bastion will attempt to guess the MIME type to send by looking at the given file.
Expand Down
146 changes: 88 additions & 58 deletions src/main/java/rocks/bastion/core/RequestExecutor.java
Original file line number Diff line number Diff line change
@@ -1,42 +1,59 @@
package rocks.bastion.core;

import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import com.mashape.unirest.request.HttpRequestWithBody;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import rocks.bastion.core.configuration.Configuration;

import java.io.InputStream;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import rocks.bastion.core.configuration.Configuration;

/**
* Responsible for executing a Bastion remote request built using the {@link BastionBuilderImpl} builder and prepare a response object.
*/
public class RequestExecutor {

private Configuration configuration;
private HttpRequest bastionHttpRequest;
private com.mashape.unirest.request.HttpRequest executableHttpRequest;
private HttpUriRequest executableHttpRequest;
private Collection<ApiHeader> headers;
private String resolvedUrl;
private URIBuilder uriBuilder;

public RequestExecutor(HttpRequest bastionHttpRequest, Configuration configuration) {
Objects.requireNonNull(bastionHttpRequest);
this.bastionHttpRequest = bastionHttpRequest;
this.configuration = configuration;
executableHttpRequest = prepareHttpRequest();
applyHeaders();
applyQueryParameters();
applyRouteParameters();
applyBody();
try {
Objects.requireNonNull(bastionHttpRequest);
this.bastionHttpRequest = bastionHttpRequest;
this.configuration = configuration;
uriBuilder = new URIBuilder(applyRouteParameters());
applyQueryParameters();
executableHttpRequest = prepareHttpRequest();
applyHeaders();
applyBody();
} catch (URISyntaxException exception) {
throw new IllegalStateException("Could not compute URI", exception);
}
}

public String getMethod() {
Expand All @@ -58,96 +75,109 @@ public Collection<ApiHeader> getHeaders() {
*/
public Response execute() {
try {
HttpResponse<InputStream> httpResponse = performRequest();
CloseableHttpResponse httpResponse = performRequest();
return convertToRawResponse(httpResponse);
} catch (UnirestException exception) {
if (exception.getCause() instanceof SocketTimeoutException) {
throw new AssertionError(String.format("Failed to receive response before timeout of [%s] ms", resolveTimeoutOrFallbackToGlobal(bastionHttpRequest, configuration)));
}
} catch (SocketTimeoutException exception) {
throw new AssertionError(String.format("Failed to receive response before timeout of [%s] ms",
resolveTimeoutOrFallbackToGlobal(bastionHttpRequest, configuration)), exception);
} catch (IOException exception) {
throw new IllegalStateException("Failed executing request", exception);
}
}

private com.mashape.unirest.request.HttpRequest prepareHttpRequest() {
private HttpUriRequest prepareHttpRequest() throws URISyntaxException {

long timeout = resolveTimeoutOrFallbackToGlobal(bastionHttpRequest, configuration);
Unirest.setTimeouts(timeout, timeout);
com.mashape.unirest.request.HttpRequest request;
HttpRequestBase request;
resolvedUrl = uriBuilder.build().toString();
switch (bastionHttpRequest.method().getValue()) {
case "GET":
request = Unirest.get(bastionHttpRequest.url());
request = new HttpGet(resolvedUrl);
break;
case "POST":
request = Unirest.post(bastionHttpRequest.url());
request = new HttpPost(resolvedUrl);
break;
case "PATCH":
request = Unirest.patch(bastionHttpRequest.url());
request = new HttpPost(resolvedUrl);
break;
case "DELETE":
request = Unirest.delete(bastionHttpRequest.url());
request = new HttpDelete(resolvedUrl);
break;
case "PUT":
request = Unirest.put(bastionHttpRequest.url());
request = new HttpPut(resolvedUrl);
break;
case "OPTIONS":
request = Unirest.options(bastionHttpRequest.url());
request = new HttpOptions(resolvedUrl);
break;
case "HEAD":
request = Unirest.head(bastionHttpRequest.url());
request = new HttpHead(resolvedUrl);
break;
default:
throw new UnsupportedOperationException(String.format("We cannot perform a request of type %s.", bastionHttpRequest.method().getValue()));
throw new UnsupportedOperationException(String.format("We cannot perform a request of type %s.",
bastionHttpRequest.method().getValue()));
}
request.setConfig(RequestConfig.custom()
.setConnectTimeout((int) timeout)
.setSocketTimeout((int) timeout)
.setConnectionRequestTimeout((int) timeout)
.build());
return request;
}

private static long resolveTimeoutOrFallbackToGlobal(HttpRequest request, Configuration configuration) {
if (request.timeout() == HttpRequest.USE_GLOBAL_TIMEOUT) {
return configuration.getGlobalRequestAttributes().getGlobalRequestTimeout();
} else {
return request.timeout();
}
}

private void applyHeaders() {
headers = new LinkedList<>(configuration.getGlobalRequestAttributes().getGlobalHeaders());
headers.addAll(bastionHttpRequest.headers());
if (headers.stream().noneMatch(header -> header.getName().equalsIgnoreCase("content-type")) && bastionHttpRequest.contentType().isPresent()) {
if (headers.stream().noneMatch(header -> header.getName().equalsIgnoreCase("content-type")) && bastionHttpRequest.contentType()
.isPresent()) {
headers.add(new ApiHeader("Content-Type", bastionHttpRequest.contentType().get().toString()));
}
headers.forEach(header -> executableHttpRequest.header(header.getName(), header.getValue()));
headers.forEach(header -> executableHttpRequest.addHeader(header.getName(), header.getValue()));
}

private void applyQueryParameters() {
List<ApiQueryParam> apiQueryParams = new ArrayList<>(configuration.getGlobalRequestAttributes().getGlobalQueryParams());
apiQueryParams.addAll(bastionHttpRequest.queryParams());
apiQueryParams.forEach(queryParam -> executableHttpRequest.queryString(queryParam.getName(), queryParam.getValue()));
resolvedUrl = executableHttpRequest.getUrl();
apiQueryParams.forEach(queryParam -> uriBuilder.addParameter(queryParam.getName(), queryParam.getValue()));
}

private void applyRouteParameters() {
private String applyRouteParameters() {
List<RouteParam> routeParams = new ArrayList<>(configuration.getGlobalRequestAttributes().getGlobalRouteParams());
routeParams.addAll(bastionHttpRequest.routeParams());
routeParams.forEach(routeParam -> executableHttpRequest.routeParam(routeParam.getName(), routeParam.getValue()));
resolvedUrl = executableHttpRequest.getUrl();
String urlWithPlaceholders = bastionHttpRequest.url();
for (RouteParam routeParam : routeParams) {
urlWithPlaceholders = urlWithPlaceholders.replaceAll("\\Q{" + routeParam.getName() + "}\\E", routeParam.getValue());
}
return urlWithPlaceholders;
}

private void applyBody() {
if (executableHttpRequest instanceof HttpRequestWithBody) {
((HttpRequestWithBody) executableHttpRequest).body(bastionHttpRequest.body().toString());
if (executableHttpRequest instanceof HttpEntityEnclosingRequest) {
((HttpEntityEnclosingRequest) executableHttpRequest).setEntity(new StringEntity(
bastionHttpRequest.body().toString(),
bastionHttpRequest.contentType().orElse(null)
));
}
}

private HttpResponse<InputStream> performRequest() throws UnirestException {
return executableHttpRequest.asBinary();
private CloseableHttpResponse performRequest() throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
return httpClient.execute(executableHttpRequest);
}

private Response convertToRawResponse(HttpResponse<InputStream> httpResponse) {
return new RawResponse(httpResponse.getStatus(),
httpResponse.getStatusText(),
httpResponse.getHeaders().entrySet().stream().flatMap(header ->
header.getValue().stream().map(headerValue ->
new ApiHeader(header.getKey(), headerValue))).collect(Collectors.toList()),
httpResponse.getBody());
private Response convertToRawResponse(CloseableHttpResponse httpResponse) throws IOException {
return new RawResponse(httpResponse.getStatusLine().getStatusCode(),
httpResponse.getStatusLine().getReasonPhrase(),
Arrays.stream(httpResponse.getAllHeaders())
.map(header -> new ApiHeader(header.getName(), header.getValue()))
.collect(Collectors.toList()),
httpResponse.getEntity().getContent());
}

private static long resolveTimeoutOrFallbackToGlobal(HttpRequest request, Configuration configuration) {
if (request.timeout() == HttpRequest.USE_GLOBAL_TIMEOUT) {
return configuration.getGlobalRequestAttributes().getGlobalRequestTimeout();
} else {
return request.timeout();
}
}
}
Loading