Skip to content

Commit

Permalink
Add README
Browse files Browse the repository at this point in the history
  • Loading branch information
Citymonstret committed Jul 15, 2020
1 parent dade242 commit 81854f9
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 5 deletions.
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
<repository>
<id>intellectualsites-snapshots</id>
<url>https://mvn.intellectualsites.com/content/repositories/snapshots</url>
</repository>
```

```xml
<dependency>
<groupId>com.intellectualsites.http</groupId>
<artifactId>HTTP4J</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
```

### 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 `<builder>.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)
26 changes: 25 additions & 1 deletion src/main/java/com/intellectualsites/http/ClientSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Consumer<HttpClient.WrappedRequestBuilder>> decorators = new LinkedList<>();
private String baseURL;
private EntityMapper entityMapper;

Expand Down Expand Up @@ -60,14 +65,23 @@ final class ClientSettings {
return this.entityMapper;
}

/**
* Get all registered request decorators
*
* @return Unmodifiable collection of decorators
*/
@NotNull Collection<Consumer<HttpClient.WrappedRequestBuilder>> getRequestDecorators() {
return Collections.unmodifiableCollection(this.decorators);
}

/**
* Set the base URL, that is prepended to
* the URL of each request
*
* @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");
}

/**
Expand All @@ -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<HttpClient.WrappedRequestBuilder> decorator) {
this.decorators.add(Objects.requireNonNull(decorator, "Decorator may not be null"));
}

}
17 changes: 16 additions & 1 deletion src/main/java/com/intellectualsites/http/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<WrappedRequestBuilder> 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
Expand Down Expand Up @@ -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<WrappedRequestBuilder> decorator : settings.getRequestDecorators()) {
decorator.accept(this);
}
try {
final Throwable[] throwables = new Throwable[1];
if (this.exceptionHandler == null) {
Expand Down
13 changes: 10 additions & 3 deletions src/test/java/com/intellectualsites/http/HttpClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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));
}

}
Expand All @@ -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() {
Expand All @@ -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() {
Expand Down

0 comments on commit 81854f9

Please sign in to comment.