diff --git a/README.md b/README.md
new file mode 100644
index 0000000..54b3435
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+# HTT4J
+
+## Description
+
+This is a simple, lightweight and tiny wrapper for Java's HttpURLConnection. It has no external
+dependencies and is written for Java 8.
+
+It comes with a entity mapping system (serialization and deserialization for request and response bodies)
+with optional mappings for third party libraries (currently supporting: GSON).
+
+### Rationale
+
+Most HTTP client for Java are either built for Java 11+, or have a large amount of dependencies,
+which means that in order to use them, one needs to built a fatjar that often end up being huge.
+This aims to offer a nicer way to interact with the Java 8 HTTP client, without having to double the
+size of the output artifacts.
+
+## Usage
+
+### Repository
+
+HTT4J is available from [IntellectualSites](https://intellectualsites.com)' maven repository:
+
+```xml
+
+ intellectualsites-snapshots
+ https://mvn.intellectualsites.com/content/repositories/snapshots
+
+```
+
+```xml
+
+ com.intellectualsites.http
+ HTTP4J
+ 1.0-SNAPSHOT
+
+```
+
+### Code
+
+All requests are done using an instance of `com.intellectualsites.http.HttpClient`:
+
+```java
+HttpClient client = HttpClient.newBuilder()
+ .withBaseURL("https://your.api.com")
+ .build();
+```
+
+The client also take in a `com.intellectualsites.http.EntityMapper` instance. This
+is used to map request & response bodies to Java objects. By default, it includes a mapper
+for Java strings.
+
+```java
+EntityMapper entityMapper = EntityMapper.newInstance()
+ .registerDeserializer(JsonObject.class, GsonMapper.deserializer(JsonObject.class, GSON));
+```
+
+The above snippet would create an entity mapper that maps to and from Java strings, and
+from HTTP response's to GSON json objects.
+
+This can then be included in the HTTP client by using `.withEntityMapper(mapper)` to
+be used in all requests, or added to individual requests.
+
+HTTP4J also supports request decorators, that can be used to modify each request. These are
+added by using:
+
+```java
+builder.withDecorator(request -> {
+ request.doSomething();
+});
+```
+
+The built client can then be used to make HTTP requests, like such:
+
+```java
+client.post("/some/api").withInput(() -> "Hello World")
+ .onStatus(200, response -> {
+ System.out.println("Everything is fine");
+ System.out.println("Response: " + response.getResponseEntity(String.class));
+ })
+ .onStatus(404, response -> System.err.println("Could not find the resource =("))
+ .onRemaining(response -> System.err.printf("Got status code: %d\n", response.getStatusCode()))
+ .onException(Throwable::printStackTrace)
+ .execute();
+```
+
+#### Exception Handling
+
+HTTP4J will forward all RuntimeExceptions by default, and wrap all other exceptions (that do not
+extend RuntimeException) in a RuntimeException.
+
+By using `onException(exception -> {})` you are able to modify the behaviour.
+
+#### Examples
+
+More examples can be found in [HttpClientTest.java](https://github.com/Sauilitired/HTTP4J/blob/master/src/test/java/com/intellectualsites/http/HttpClientTest.java)
diff --git a/src/main/java/com/intellectualsites/http/ClientSettings.java b/src/main/java/com/intellectualsites/http/ClientSettings.java
index fc27510..7098eda 100644
--- a/src/main/java/com/intellectualsites/http/ClientSettings.java
+++ b/src/main/java/com/intellectualsites/http/ClientSettings.java
@@ -26,13 +26,18 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
import java.util.Objects;
+import java.util.function.Consumer;
/**
* Settings that change the behaviour of {@link HttpClient}
*/
final class ClientSettings {
+ private final Collection> decorators = new LinkedList<>();
private String baseURL;
private EntityMapper entityMapper;
@@ -60,6 +65,15 @@ final class ClientSettings {
return this.entityMapper;
}
+ /**
+ * Get all registered request decorators
+ *
+ * @return Unmodifiable collection of decorators
+ */
+ @NotNull Collection> getRequestDecorators() {
+ return Collections.unmodifiableCollection(this.decorators);
+ }
+
/**
* Set the base URL, that is prepended to
* the URL of each request
@@ -67,7 +81,7 @@ final class ClientSettings {
* @param baseURL base URL
*/
void setBaseURL(@NotNull final String baseURL) {
- this.baseURL = Objects.requireNonNull(baseURL);
+ this.baseURL = Objects.requireNonNull(baseURL, "Base URL may not be null");
}
/**
@@ -80,4 +94,14 @@ void setEntityMapper(@Nullable final EntityMapper entityMapper) {
this.entityMapper = entityMapper;
}
+ /**
+ * Add a new request decorator. This will have the opportunity
+ * to decorate every request made by this client
+ *
+ * @param decorator Decorator
+ */
+ void addDecorator(@NotNull final Consumer decorator) {
+ this.decorators.add(Objects.requireNonNull(decorator, "Decorator may not be null"));
+ }
+
}
diff --git a/src/main/java/com/intellectualsites/http/HttpClient.java b/src/main/java/com/intellectualsites/http/HttpClient.java
index 705b8fb4..53affa7 100644
--- a/src/main/java/com/intellectualsites/http/HttpClient.java
+++ b/src/main/java/com/intellectualsites/http/HttpClient.java
@@ -150,7 +150,7 @@ private Builder() {
* @return Builder instance
*/
@NotNull public Builder withBaseURL(@NotNull final String baseURL) {
- Objects.requireNonNull(baseURL);
+ Objects.requireNonNull(baseURL, "Base URL may not be null");
if (baseURL.endsWith("/")) {
this.settings.setBaseURL(baseURL.substring(0, baseURL.length() - 1));
} else {
@@ -171,6 +171,18 @@ private Builder() {
return this;
}
+ /**
+ * Add a new request decorator. This will have the opportunity
+ * to decorate every request made by this client
+ *
+ * @param decorator Decorator
+ * @return Builder instance
+ */
+ @NotNull public Builder withDecorator(@NotNull final Consumer decorator) {
+ this.settings.addDecorator(Objects.requireNonNull(decorator, "Decorator may not be null"));
+ return this;
+ }
+
/**
* Create a new {@link HttpClient} using the
* settings specified in the builder
@@ -297,6 +309,9 @@ private WrappedRequestBuilder(@NotNull final HttpMethod method, @NotNull String
* the method will return {@code null}
*/
@Nullable public HttpResponse execute() {
+ for (final Consumer decorator : settings.getRequestDecorators()) {
+ decorator.accept(this);
+ }
try {
final Throwable[] throwables = new Throwable[1];
if (this.exceptionHandler == null) {
diff --git a/src/test/java/com/intellectualsites/http/HttpClientTest.java b/src/test/java/com/intellectualsites/http/HttpClientTest.java
index 2417483..0ede973 100644
--- a/src/test/java/com/intellectualsites/http/HttpClientTest.java
+++ b/src/test/java/com/intellectualsites/http/HttpClientTest.java
@@ -49,6 +49,8 @@ public class HttpClientTest {
private static final String BASE_BODY = "Unicorns are real!";
private static final String BASE_HEADER_KEY = "X-Test-Header";
private static final String BASE_HEADER_VALUE = "yay";
+ private static final String ECHO_HEADER_KEY = "X-Test-Echo";
+ private static final String ECHO_HEADER_VALUE = "Wooo!";
private static final String ECHO_CONTENT = UUID.randomUUID().toString();
private static MockServerClient mockServer;
@@ -72,7 +74,8 @@ public class HttpClientTest {
public static final class EchoCallBack implements ExpectationResponseCallback {
@Override public org.mockserver.model.HttpResponse handle(HttpRequest httpRequest) {
- return org.mockserver.model.HttpResponse.response(httpRequest.getBodyAsString());
+ return org.mockserver.model.HttpResponse.response(httpRequest.getBodyAsString())
+ .withHeader(ECHO_HEADER_KEY, httpRequest.getFirstHeader(ECHO_HEADER_KEY));
}
}
@@ -85,8 +88,11 @@ public static final class EchoCallBack implements ExpectationResponseCallback {
@BeforeEach void setupClient() {
final EntityMapper mapper = EntityMapper.newInstance()
.registerDeserializer(JsonObject.class, GsonMapper.deserializer(JsonObject.class, GSON));
- this.client =
- HttpClient.newBuilder().withBaseURL(BASE_PATH).withEntityMapper(mapper).build();
+ this.client = HttpClient.newBuilder()
+ .withBaseURL(BASE_PATH)
+ .withEntityMapper(mapper)
+ .withDecorator(request -> request.withHeader(ECHO_HEADER_KEY, ECHO_HEADER_VALUE))
+ .build();
}
@Test void testSimpleGet() {
@@ -105,6 +111,7 @@ public static final class EchoCallBack implements ExpectationResponseCallback {
}).execute();
assertNotNull(echoResponse);
assertEquals(ECHO_CONTENT, echoResponse.getResponseEntity(String.class));
+ assertEquals(ECHO_HEADER_VALUE, echoResponse.getHeaders().getHeader(ECHO_HEADER_KEY));
}
@Test void testThrow() {