You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2018/10/16 13:22:53 UTC
[isis] 02/02: ISIS-2006: adding a jax-rs request/response debug
logging filter
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch v2
in repository https://gitbox.apache.org/repos/asf/isis.git
commit 0d4c71b9841c661ca34dc4e2959fbc51835c543c
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Oct 16 12:51:36 2018 +0200
ISIS-2006: adding a jax-rs request/response debug logging filter
Task-Url: https://issues.apache.org/jira/browse/ISIS-2006
---
.../apache/isis/applib/client/RestfulClient.java | 56 ++++++---
.../isis/applib/client/RestfulClientConfig.java | 42 ++++---
.../isis/applib/client/auth/BasicAuthFilter.java | 2 +
.../applib/client/log/RestfulLoggingFilter.java | 128 +++++++++++++++++++++
.../isis/commons/internal/base/_Strings.java | 10 ++
.../internal/base/_Strings_KeyValuePair.java | 26 +++++
6 files changed, 233 insertions(+), 31 deletions(-)
diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java
index 8009e25..6496db1 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java
@@ -23,6 +23,7 @@ import static org.apache.isis.commons.internal.base._NullSafe.stream;
import java.util.EnumSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.ws.rs.client.Client;
@@ -32,6 +33,7 @@ import javax.ws.rs.core.Response;
import org.apache.isis.applib.client.auth.BasicAuthFilter;
import org.apache.isis.applib.client.auth.BasicAuthFilter.Credentials;
+import org.apache.isis.applib.client.log.RestfulLoggingFilter;
import org.apache.isis.commons.internal.base._Strings;
import org.apache.isis.commons.internal.context._Context;
@@ -41,9 +43,11 @@ import org.apache.isis.commons.internal.context._Context;
RestfulClientConfig clientConfig = new RestfulClientConfig();
clientConfig.setRestfulBase("http://localhost:8080/helloworld/restful/");
// setup basic-auth
-clientConfig.setUseBasicAuth(true);
+clientConfig.setUseBasicAuth(true); // default = false
clientConfig.setRestfulAuthUser("sven");
clientConfig.setRestfulAuthPassword("pass");
+// setup request/response debug logging
+clientConfig.setUseRequestDebugLogging(true); // default = false
RestfulClient client = RestfulClient.ofConfig(clientConfig);
* </pre></blockquote>
@@ -125,7 +129,9 @@ if(digest.isSuccess()) {
*/
public class RestfulClient {
- public static String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json;profile=urn:org.apache.isis/v1";
+ private static final Logger LOG = Logger.getLogger(RestfulClient.class.getName());
+
+ public static String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json;profile=\"urn:org.apache.isis/v1\"";
private RestfulClientConfig clientConfig;
private Client client;
@@ -140,22 +146,11 @@ public class RestfulClient {
this.clientConfig = clientConfig;
client = ClientBuilder.newClient();
- if(clientConfig.isUseBasicAuth()){
- final Credentials credentials = Credentials.of(
- clientConfig.getRestfulAuthUser(),
- clientConfig.getRestfulAuthPassword());
- client.register(BasicAuthFilter.of(credentials));
- }
-
- try {
- Class<?> MOXyJsonProvider = _Context.loadClass("org.eclipse.persistence.jaxb.rs.MOXyJsonProvider");
- client.register(MOXyJsonProvider);
- } catch (Exception e) {
- // this is just provided for convenience
- }
-
+ registerDefaultJsonProvider();
+ registerBasicAuthFilter();
+ registerRequestDebugLoggingFilter();
}
-
+
public RestfulClientConfig getConfig() {
return clientConfig;
}
@@ -210,6 +205,33 @@ public class RestfulClient {
return completableFuture;
}
+ // -- FILTER
+
+ private void registerDefaultJsonProvider() {
+ try {
+ Class<?> MOXyJsonProvider = _Context.loadClass("org.eclipse.persistence.jaxb.rs.MOXyJsonProvider");
+ client.register(MOXyJsonProvider);
+ } catch (Exception e) {
+ LOG.warning("This implementaion of RestfulClient does require the class 'MOXyJsonProvider' on the class-path."
+ + " Are you missing a maven dependency?");
+ }
+ }
+
+ private void registerBasicAuthFilter() {
+ if(clientConfig.isUseBasicAuth()){
+ final Credentials credentials = Credentials.of(
+ clientConfig.getRestfulAuthUser(),
+ clientConfig.getRestfulAuthPassword());
+ client.register(BasicAuthFilter.of(credentials));
+ }
+ }
+
+ private void registerRequestDebugLoggingFilter() {
+ if(clientConfig.isUseRequestDebugLogging()){
+ client.register(new RestfulLoggingFilter());
+ }
+ }
+
// -- HELPER
private String relativePathToUri(String path) {
diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java
index c0578b1..cdd9b84 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java
@@ -32,23 +32,23 @@ import javax.xml.bind.annotation.XmlRootElement;
public class RestfulClientConfig {
// --
-
+
@XmlElement(name="restfulBase")
private String restfulBase;
-
+
public String getRestfulBase() {
return restfulBase;
}
-
+
public void setRestfulBase(String restfulBase) {
this.restfulBase = restfulBase;
}
-
+
// --
-
+
@XmlElement(name="useBasicAuth")
private boolean useBasicAuth;
-
+
public boolean isUseBasicAuth() {
return useBasicAuth;
}
@@ -56,32 +56,46 @@ public class RestfulClientConfig {
public void setUseBasicAuth(boolean useBasicAuth) {
this.useBasicAuth = useBasicAuth;
}
-
+
// --
@XmlElement(name="restfulAuthUser")
private String restfulAuthUser;
-
+
public String getRestfulAuthUser() {
return restfulAuthUser;
}
-
+
public void setRestfulAuthUser(String restfulAuthUser) {
this.restfulAuthUser = restfulAuthUser;
}
-
+
// --
@XmlElement(name="restfulAuthPassword")
private String restfulAuthPassword;
-
+
public String getRestfulAuthPassword() {
return restfulAuthPassword;
}
-
+
public void setRestfulAuthPassword(String restfulAuthPassword) {
this.restfulAuthPassword = restfulAuthPassword;
}
-
-
+
+ // --
+
+ @XmlElement(name="useRequestDebugLogging")
+ private boolean useRequestDebugLogging;
+
+ public boolean isUseRequestDebugLogging() {
+ return useRequestDebugLogging;
+ }
+
+ public void setUseRequestDebugLogging(boolean useRequestDebugLogging) {
+ this.useRequestDebugLogging = useRequestDebugLogging;
+ }
+
+ // --
+
}
diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java b/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java
index 3f4f39e..201e35b 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java
@@ -23,6 +23,7 @@ import static org.apache.isis.commons.internal.base._With.requires;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import javax.annotation.Priority;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.xml.bind.DatatypeConverter;
@@ -33,6 +34,7 @@ import org.apache.isis.commons.internal.base._Strings;
*
* @since 2.0.0-M2
*/
+@Priority(100)
public class BasicAuthFilter implements ClientRequestFilter {
/**
diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/log/RestfulLoggingFilter.java b/core/applib/src/main/java/org/apache/isis/applib/client/log/RestfulLoggingFilter.java
new file mode 100644
index 0000000..8ea45f1
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/client/log/RestfulLoggingFilter.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.applib.client.log;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.annotation.Priority;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+
+import org.apache.isis.commons.internal.base._Strings;
+
+/**
+ *
+ * @since 2.0.0-M2
+ */
+@Priority(999)
+public class RestfulLoggingFilter implements ClientRequestFilter, ClientResponseFilter{
+ private static final Logger LOG = Logger.getLogger(RestfulLoggingFilter.class.getName());
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ final String endpoint = requestContext.getUri().toString();
+ final String method = requestContext.getMethod();
+
+ Exception acceptableMediaTypeParsingFailure;
+ try {
+ @SuppressWarnings("unused")
+ final String acceptableMediaTypes = requestContext.getAcceptableMediaTypes().toString();
+ acceptableMediaTypeParsingFailure = null;
+ } catch (Exception e) {
+ acceptableMediaTypeParsingFailure = e;
+ }
+ final String acceptHeaderParsing = acceptableMediaTypeParsingFailure != null
+ ? "Failed to parse accept header, cause: " + acceptableMediaTypeParsingFailure.getMessage()
+ : "OK";
+
+ final String headers = requestContext.getStringHeaders().entrySet().stream()
+ .map(entry->entry.toString())
+ .map(this::obscureAuthHeader)
+ .collect(Collectors.joining(",\n\t"));
+
+ final String requestBody = requestContext.getEntity().toString();
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append("\n")
+ .append("---------- JAX-RS REQUEST -------------\n")
+ .append("uri: ").append(endpoint).append("\n")
+ .append("method: ").append(method).append("\n")
+ .append("accept-header-parsing: ").append(acceptHeaderParsing).append("\n")
+ .append("headers: \n\t").append(headers).append("\n")
+ .append("request-body: ").append(requestBody).append("\n")
+ .append("----------------------------------------\n")
+ ;
+
+ LOG.log(Level.INFO, sb.toString());
+ }
+
+ @Override
+ public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
+
+ final InputStream inputStream = responseContext.getEntityStream();
+ final String responseBody;
+ if(inputStream!=null) {
+ responseBody = _Strings.read(responseContext.getEntityStream(), StandardCharsets.UTF_8);
+ responseContext.setEntityStream(new ByteArrayInputStream(responseBody.getBytes(StandardCharsets.UTF_8)));
+ } else {
+ responseBody = "null";
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append("\n")
+ .append("---------- JAX-RS RESPONSE -------------\n")
+ .append("response-body: ").append(responseBody).append("\n")
+ .append("----------------------------------------\n")
+ ;
+
+ LOG.log(Level.INFO, sb.toString());
+
+ }
+
+ // -- HELPER
+
+ private final String basicAuthMagic = "Authorization=[Basic ";
+
+ String obscureAuthHeader(String keyValueLiteral) {
+ if(_Strings.isEmpty(keyValueLiteral)) {
+ return keyValueLiteral;
+ }
+ if(keyValueLiteral.startsWith(basicAuthMagic)) {
+
+ final String obscured = _Strings.padEnd(basicAuthMagic, keyValueLiteral.length() - 1, '*') + "]";
+ return obscured;
+
+ }
+ return keyValueLiteral;
+ }
+
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
index 38fd902..8596840 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
@@ -28,6 +28,7 @@ import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Map;
+import java.util.Optional;
import java.util.Scanner;
import java.util.Spliterator;
import java.util.Spliterators;
@@ -76,6 +77,15 @@ public final class _Strings {
return _Strings_KeyValuePair.of(key, value);
}
+ /**
+ * Parses a string assumed to be of the form <kbd>key=value</kbd> into its parts.
+ *
+ * @return a non-empty Optional, if (and only if) the {@code keyValueLiteral} does contain at least one '='
+ */
+ public static Optional<KeyValuePair> parseKeyValuePair(@Nullable String keyValueLiteral) {
+ return _Strings_KeyValuePair.parse(keyValueLiteral);
+ }
+
// -- FILLING
public static String of(int length, char c) {
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_KeyValuePair.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_KeyValuePair.java
index 7696a48..3fa619a 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_KeyValuePair.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_KeyValuePair.java
@@ -21,6 +21,8 @@ package org.apache.isis.commons.internal.base;
import static org.apache.isis.commons.internal.exceptions._Exceptions.notImplemented;
+import java.util.Optional;
+
import org.apache.isis.commons.internal.base._Strings.KeyValuePair;
/**
@@ -56,5 +58,29 @@ final class _Strings_KeyValuePair implements _Strings.KeyValuePair {
public String setValue(String value) {
throw notImplemented();
}
+
+ /**
+ * Parses a string assumed to be of the form <kbd>key=value</kbd> into its parts.
+ *
+ * @return a non-empty Optional, if (and only if) the {@code keyValueLiteral} does contain at least one '='
+ */
+ public static Optional<KeyValuePair> parse(String keyValueLiteral) {
+
+ if(_Strings.isNullOrEmpty(keyValueLiteral)) {
+ return Optional.empty();
+ }
+
+ final int equalsIndex = keyValueLiteral.indexOf('=');
+ if (equalsIndex == -1) {
+ return Optional.empty();
+ }
+
+ String aKey = keyValueLiteral.substring(0, equalsIndex);
+ String aValue = equalsIndex == keyValueLiteral.length() - 1
+ ? ""
+ : keyValueLiteral.substring(equalsIndex + 1);
+
+ return Optional.of(of(aKey, aValue));
+ }
}