Skip to content

Commit

Permalink
Add per-path request/response JSON body filters via logbook Strategy (#…
Browse files Browse the repository at this point in the history
…68)

* Add per-path request/response JSON body filters via logbook Strategy

* Adjust readme

* Rename parameter
  • Loading branch information
skjolber authored Jan 20, 2025
1 parent 824f224 commit d60652e
Show file tree
Hide file tree
Showing 28 changed files with 828 additions and 6 deletions.
51 changes: 51 additions & 0 deletions request-response/logbook-filter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# logbook-filter
Various request/response/body filters for Logbook:

* max body size filter
* max string field value body filter

with

* per-path request and/or response body filter selection based on path

## JSON filtering

```
@Bean
public JsonOnlyMatchPathStrategy strategy() {
return new JsonOnlyMatchPathStrategy();
}
```

with request filter

```
@Bean
public RequestFilter myRequestFilter() {
JacksonJsonFieldBodyFilter customerRequestFilter = new JacksonJsonFieldBodyFilter(Arrays.asList("name"), "XXX");
return RequestMatcherRequestFilter.newPathPrefixBuilder().withPathPrefixFilter("/api/customer", customerRequestFilter).build();
}
```

and response filter

```
@Bean
public ResponseFilter myResponseFilter() {
JacksonJsonFieldBodyFilter customerResponseFilter = new JacksonJsonFieldBodyFilter(Arrays.asList("rating"), "XXX");
return RequestResponseMatcherResponseFilter.newPathPrefixBuilder().withPathPrefixFilter("/api/customer", customerResponseFilter).build();
}
```

### Advanced filtering
Construct your own `PathFilterMatcher` to handle more complex filtering, i.e.

* path
* max size
* origin

using

* SizePathFilterMatcher
* SizeOriginPathFilterMatcher

23 changes: 23 additions & 0 deletions request-response/logbook-filter/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

dependencies {
api("org.slf4j:slf4j-api:${slf4jVersion}")
api("org.zalando:logbook-api:${logbookVersion}")
api("org.zalando:logbook-api:${logbookVersion}")

api("com.fasterxml.jackson.core:jackson-core:${jacksonVersion}")
api("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}")
api "commons-io:commons-io:${commonsIoVersion}"

// JUnit Jupiter API and TestEngine implementation
testImplementation("org.junit.jupiter:junit-jupiter:${junitJupiterVersion}")

testImplementation("org.mockito:mockito-core:${mockitoVersion}")

testImplementation("com.google.truth:truth:${googleTruthVersion}")
testImplementation("com.google.truth.extensions:truth-java8-extension:${googleTruthVersion}")

}




Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import org.apache.commons.io.output.StringBuilderWriter;
import org.zalando.logbook.BodyFilter;
import org.zalando.logbook.ContentType;

import java.io.IOException;
import java.io.StringWriter;
import java.util.function.LongSupplier;
import java.util.function.Predicate;

/**
* Thread-safe filter for JSON fields.
Expand All @@ -34,7 +32,7 @@ public static JsonMaxBodySizeFilter newInstance(int maxBodySize) {

public JsonMaxBodySizeFilter(int maxBodySize) {
this.maxBodySize = maxBodySize;
this.factory = new ObjectMapper().getFactory();
this.factory = new MappingJsonFactory();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.zalando.logbook.BodyFilter;
import org.zalando.logbook.ContentType;
Expand All @@ -29,7 +30,7 @@ public static JsonMaxValueLengthBodyFilter newInstance(int maxFieldLength) {

public JsonMaxValueLengthBodyFilter(int maxFieldLength) {
this.maxFieldLength = maxFieldLength;
this.factory = new ObjectMapper().getFactory();
this.factory = new MappingJsonFactory();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package no.entur.logging.cloud.logbook.filter.path;

import java.nio.charset.StandardCharsets;

import org.zalando.logbook.ForwardingHttpRequest;
import org.zalando.logbook.HttpRequest;

public class BodyReplacementHttpRequest implements ForwardingHttpRequest {

private final HttpRequest request;
private final String replacement;

public BodyReplacementHttpRequest(HttpRequest request, String replacement) {
super();
this.request = request;
this.replacement = replacement;
}

@Override
public HttpRequest delegate() {
return request;
}

@Override
public HttpRequest withBody() {
return withoutBody();
}

@Override
public HttpRequest withoutBody() {
return new BodyReplacementHttpRequest(request.withoutBody(), replacement);
}

@Override
public byte[] getBody() {
return replacement.getBytes(StandardCharsets.UTF_8);
}

@Override
public String getBodyAsString() {
return replacement;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package no.entur.logging.cloud.logbook.filter.path;
import org.zalando.logbook.ForwardingHttpResponse;
import org.zalando.logbook.HttpResponse;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.nio.charset.StandardCharsets;

public class BodyReplacementHttpResponse implements ForwardingHttpResponse, HttpResponse {

private final HttpResponse response;
private final String replacement;

public BodyReplacementHttpResponse(HttpResponse response, String replacement) {
super();
this.response = response;
this.replacement = replacement;
}

@Override
public HttpResponse delegate() {
return response;
}

@Override
public HttpResponse withBody() {
return withoutBody();
}

@Override
public HttpResponse withoutBody() {
return new BodyReplacementHttpResponse(response.withoutBody(), replacement);
}

@Override
public byte[] getBody() {
return replacement.getBytes(UTF_8);
}

@Override
public String getBodyAsString() {
return replacement;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package no.entur.logging.cloud.logbook.filter.path;

import no.entur.logging.cloud.logbook.filter.path.matcher.MatcherPathFilterCollection;
import no.entur.logging.cloud.logbook.filter.path.matcher.PathFilterMatcher;
import no.entur.logging.cloud.logbook.filter.path.matcher.SimplePathFilterMatcher;
import org.zalando.logbook.BodyFilter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PathPrefixRequestMatcherRequestFilter {

private Map<String, BodyFilter> pathPrefixFilters = new HashMap<>();

public PathPrefixRequestMatcherRequestFilter withPathPrefixFilter(String path, BodyFilter bodyFilter) {
pathPrefixFilters.put(path, bodyFilter);
return this;
}

public RequestMatcherRequestFilter build() {
List<PathFilterMatcher> matchers = new ArrayList<>(pathPrefixFilters.size());

for (Map.Entry<String, BodyFilter> entry : pathPrefixFilters.entrySet()) {

SimplePathFilterMatcher matcher = new SimplePathFilterMatcher( (path) -> path != null && path.startsWith(entry.getKey()), entry.getValue());

matchers.add(matcher);
}
MatcherPathFilterCollection collection = new MatcherPathFilterCollection(matchers);
return new RequestMatcherRequestFilter(collection);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package no.entur.logging.cloud.logbook.filter.path;

import no.entur.logging.cloud.logbook.filter.path.matcher.MatcherPathFilterCollection;
import no.entur.logging.cloud.logbook.filter.path.matcher.PathFilterMatcher;
import no.entur.logging.cloud.logbook.filter.path.matcher.SimplePathFilterMatcher;
import org.zalando.logbook.BodyFilter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PathPrefixRequestResponseMatcherResponseFilterBuilder {

private Map<String, BodyFilter> pathPrefixFilters = new HashMap<>();

public PathPrefixRequestResponseMatcherResponseFilterBuilder withPathPrefixFilter(String path, BodyFilter bodyFilter) {
pathPrefixFilters.put(path, bodyFilter);
return this;
}

public RequestResponseMatcherResponseFilter build() {
List<PathFilterMatcher> matchers = new ArrayList<>(pathPrefixFilters.size());

for (Map.Entry<String, BodyFilter> entry : pathPrefixFilters.entrySet()) {

SimplePathFilterMatcher matcher = new SimplePathFilterMatcher( (path) -> path != null && path.startsWith(entry.getKey()), entry.getValue());

matchers.add(matcher);
}
MatcherPathFilterCollection collection = new MatcherPathFilterCollection(matchers);
return new RequestResponseMatcherResponseFilter(collection);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package no.entur.logging.cloud.logbook.filter.path;

import org.zalando.logbook.BodyFilter;
import org.zalando.logbook.HttpRequest;

/**
*
* Interface for returning the proper {@link BodyFilter} for a given request path. <br><br>
*
*
*/

public interface RequestFilterCollection {

BodyFilter getBodyFilter(HttpRequest request, int size);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package no.entur.logging.cloud.logbook.filter.path;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zalando.logbook.BodyFilter;
import org.zalando.logbook.HttpRequest;
import org.zalando.logbook.RequestFilter;

public class RequestMatcherRequestFilter implements RequestFilter {

private final static Logger log = LoggerFactory.getLogger(RequestMatcherRequestFilter.class);

public static PathPrefixRequestMatcherRequestFilter newPathPrefixBuilder() {
return new PathPrefixRequestMatcherRequestFilter();
}

private final RequestFilterCollection filter;

public RequestMatcherRequestFilter(RequestFilterCollection filter) {
this.filter = filter;
}

@Override
public HttpRequest filter(HttpRequest request) {
try {
String body = request.getBodyAsString();

if(body == null || body.length() == 0) {
return request;
}

BodyFilter f = filter.getBodyFilter(request, body.length());

if(f != null) {
String filtered = f.filter(request.getContentType(), body);
if(filtered != null) {
return new BodyReplacementHttpRequest(request, filtered);
}
}
} catch (IOException e) {
log.warn("Problem filtering request body", e);
}

return request;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package no.entur.logging.cloud.logbook.filter.path;

import org.zalando.logbook.BodyFilter;
import org.zalando.logbook.HttpRequest;
import org.zalando.logbook.HttpResponse;

/**
*
* Interface for returning the proper {@link BodyFilter} for a given response path. <br><br>
*
*/

public interface RequestResponseFilterCollection {

BodyFilter getBodyFilter(HttpRequest request, HttpResponse httpResponse, int size);

}
Loading

0 comments on commit d60652e

Please sign in to comment.