Skip to content

Commit

Permalink
Added support for OkHttp
Browse files Browse the repository at this point in the history
Fixes #204
  • Loading branch information
Willi Schönborn committed Feb 14, 2018
1 parent 7e516de commit d933b05
Show file tree
Hide file tree
Showing 8 changed files with 524 additions and 1 deletion.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Logbook is ready to use out of the box for most common setups. Even for uncommon

- **Logging**: of HTTP requests and responses, including the body; partial logging (no body) for unauthorized requests
- **Customization**: of logging format, logging destination, and conditions that request to log
- **Support**: for Servlet containers, Apache’s HTTP client, and (via its elegant API) other frameworks
- **Support**: for Servlet containers, Apache’s HTTP client, Square's OkHttp, and (via its elegant API) other frameworks
- Optional obfuscation of sensitive data
- [Spring Boot](http://projects.spring.io/spring-boot/) Auto Configuration
- [Scalyr](docs/scalyr.md) compatible
Expand All @@ -32,6 +32,7 @@ Logbook is ready to use out of the box for most common setups. Even for uncommon
- Any build tool using Maven Central, or direct download
- Servlet Container (optional)
- Apache HTTP Client (optional)
- OkHttp (optional)
- Spring Boot (optional)

## Installation
Expand All @@ -54,6 +55,11 @@ Selectively add the following dependencies to your project:
<artifactId>logbook-httpclient</artifactId>
<version>${logbook.version}</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-okhttp</artifactId>
<version>${logbook.version}</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
Expand Down Expand Up @@ -390,6 +396,16 @@ CloseableHttpAsyncClient client = HttpAsyncClientBuilder.create()
client.execute(producer, new LogbookHttpAsyncResponseConsumer<>(consumer), callback)
```

### OkHttp

The `logbook-okhttp` module contains an `Interceptor` to use with the `OkHttpClient`:

```java
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LogbookInterceptor(logbook))
.build();
```

### Spring Boot Starter

Logbook comes with a convenient auto configuration for Spring Boot users. It sets up all of the following parts automatically with sensible defaults:
Expand Down
54 changes: 54 additions & 0 deletions logbook-okhttp/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<prerequisites>
<maven>3.0.4</maven>
</prerequisites>

<parent>
<groupId>org.zalando</groupId>
<artifactId>logbook-parent</artifactId>
<version>1.6.0-SNAPSHOT</version>
</parent>

<artifactId>logbook-okhttp</artifactId>

<name>Logbook: OkHttp</name>
<description>HTTP Client interceptor for request and response logging</description>

<scm>
<url>https://github.com/zalando/logbook</url>
<connection>scm:git:git@github.com:zalando//logbook.git</connection>
<developerConnection>scm:git:git@github.com:zalando//logbook.git</developerConnection>
</scm>

<dependencies>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-api</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.9.1</version>
</dependency>

<!-- testing -->
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
</dependency>
<dependency>
<groupId>com.github.rest-driver</groupId>
<artifactId>rest-client-driver</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package org.zalando.logbook.okhttp;

import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okio.Buffer;
import okio.Okio;
import org.zalando.logbook.HttpRequest;
import org.zalando.logbook.Origin;
import org.zalando.logbook.RawHttpRequest;

import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static java.nio.charset.StandardCharsets.UTF_8;
import static okhttp3.RequestBody.create;

final class LocalRequest implements RawHttpRequest, HttpRequest {

private Request request;
private byte[] body;

public LocalRequest(final Request request) {
this.request = request;
}

@Override
public String getRemote() {
return "localhost";
}

@Override
public String getMethod() {
return request.method();
}

@Override
public String getScheme() {
return request.url().scheme();
}

@Override
public String getHost() {
return request.url().host();
}

@Override
public Optional<Integer> getPort() {
// TODO empty for 80/443?
return Optional.of(request.url().port());
}

@Override
public String getPath() {
return request.url().encodedPath();
}

@Override
public String getQuery() {
return Optional.ofNullable(request.url().query()).orElse("");
}

@Override
public String getProtocolVersion() {
// TODO find the real thing
return "HTTP/1.1";
}

@Override
public Origin getOrigin() {
return Origin.LOCAL;
}

@Override
public Map<String, List<String>> getHeaders() {
return request.headers().toMultimap();
}

@Override
public String getContentType() {
return contentType().map(MediaType::toString).orElse("");
}

@Override
public Charset getCharset() {
return contentType().map(MediaType::charset).orElse(UTF_8);
}

private Optional<MediaType> contentType() {
return Optional.ofNullable(request.body())
.map(RequestBody::contentType);
}

@Override
public HttpRequest withBody() throws IOException {
@Nullable final RequestBody body = request.body();

if (body == null) {
this.body = new byte[0];
} else {
final byte[] bytes = bytes(body);

this.request = request.newBuilder()
.method(request.method(), create(body.contentType(), bytes))
.build();

this.body = bytes;
}

return this;
}

private static byte[] bytes(final RequestBody body) throws IOException {
final Buffer buffer = new Buffer();
body.writeTo(buffer);
return buffer.readByteArray();
}

Request toRequest() {
return request;
}

@Override
public byte[] getBody() {
return body;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.zalando.logbook.okhttp;

import okhttp3.Interceptor;
import okhttp3.Response;
import org.zalando.logbook.Correlator;
import org.zalando.logbook.Logbook;

import java.io.IOException;
import java.util.Optional;

public final class LogbookInterceptor implements Interceptor {

private final Logbook logbook;

public LogbookInterceptor(final Logbook logbook) {
this.logbook = logbook;
}

@Override
public Response intercept(final Chain chain) throws IOException {
final LocalRequest request = new LocalRequest(chain.request());
final Optional<Correlator> correlator = logbook.write(request);

final RemoteResponse response = new RemoteResponse(chain.proceed(request.toRequest()));

if (correlator.isPresent()) {
correlator.get().write(response);
}

return response.toResponse();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.zalando.logbook.okhttp;

import okhttp3.MediaType;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.zalando.logbook.HttpResponse;
import org.zalando.logbook.Origin;
import org.zalando.logbook.RawHttpResponse;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static okhttp3.ResponseBody.create;

final class RemoteResponse implements RawHttpResponse, HttpResponse {

private Response response;
private byte[] body;

public RemoteResponse(Response response) {
this.response = response;
}

@Override
public int getStatus() {
return response.code();
}

@Override
public String getProtocolVersion() {
// TODO find the real thing
return "HTTP/1.1";
}

@Override
public Origin getOrigin() {
return Origin.REMOTE;
}

@Override
public Map<String, List<String>> getHeaders() {
return response.headers().toMultimap();
}

@Override
public String getContentType() {
return contentType().map(MediaType::toString).orElse("");
}

@Override
public Charset getCharset() {
return contentType().map(MediaType::charset).orElse(UTF_8);
}

private Optional<MediaType> contentType() {
return Optional.ofNullable(response.body())
.map(ResponseBody::contentType);
}

@Override
public HttpResponse withBody() throws IOException {
final ResponseBody body = requireNonNull(response.body(), "Body is never null for normal responses");

if (body.contentLength() == 0L) {
this.body = new byte[0];
} else {
final byte[] bytes = body.bytes();

this.response = response.newBuilder()
.body(create(body.contentType(), bytes))
.build();

this.body = bytes;
}

return this;
}

public Response toResponse() {
return response;
}

@Override
public byte[] getBody() {
return body;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.zalando.logbook.okhttp;

import okhttp3.Request;
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

public final class LocalRequestTest {

private LocalRequest unit(final Request request) {
return new LocalRequest(request);
}

@Test
void shouldResolveLocalhost() {
final LocalRequest unit = unit(get("http://localhost/"));

assertThat(unit.getRemote(), is("localhost"));
}

private Request get(final String uri) {
return new Request.Builder()
.url(uri)
.get()
.build();
}

}
Loading

0 comments on commit d933b05

Please sign in to comment.