Skip to content

Commit

Permalink
395606 Move Servlet/ServletFilters from StART to Scout
Browse files Browse the repository at this point in the history
  • Loading branch information
work-lto committed Jan 30, 2025
1 parent e42ba5c commit 6a4648f
Show file tree
Hide file tree
Showing 11 changed files with 457 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (c) BSI Business Systems Integration AG. All rights reserved.
* http://www.bsiag.com/
*/
package org.eclipse.scout.rt.app.filter;

import java.io.IOException;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;

import org.eclipse.jetty.io.QuietException;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.scout.rt.platform.context.CorrelationId;
import org.eclipse.scout.rt.platform.context.CorrelationIdContextValueProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

/**
* Filter that catches Exceptions thrown by consecutive filters or servlet and that logs them along with the correlation
* id if available. Logging the correlation id is the main difference to the logging otherwise performed by Jetty.
*/
public class ExceptionFilter implements Filter {

private static final Logger LOG = LoggerFactory.getLogger(ExceptionFilter.class);

@Override
public void init(FilterConfig filterConfig) {
// nothing to initialize
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
}
catch (Exception e) {
if (isCausedByQuietException(e)) {
throw e;
}

HttpServletRequest req = (HttpServletRequest) request;
MDC.put(CorrelationIdContextValueProvider.KEY, req.getHeader(CorrelationId.HTTP_HEADER_NAME));
try {
LOG.warn(req.getRequestURI(), e);
}
finally {
MDC.remove(CorrelationIdContextValueProvider.KEY);
}
throw new JettyQuietExceptionWrapper(e);
}
}

/**
* @return {@code true} if the given Throwable is a {@link QuietException} or wraps one. Otherwise {@code false}.
*/
protected boolean isCausedByQuietException(Throwable t) {
Throwable cur = t;
while (cur != null) {
// QuietException is a marker to use less verbosely logging
if (cur instanceof QuietException) {
return true;
}
cur = cur.getCause();
}
return false;
}

@Override
public void destroy() {
}

/**
* {@link QuietException}s are not logged by Jetty unless the log level of {@link HttpChannel} is DEBUG.
*/
public static class JettyQuietExceptionWrapper extends RuntimeException implements QuietException {
private static final long serialVersionUID = 1L;

public JettyQuietExceptionWrapper(Throwable cause) {
super(cause);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) BSI Business Systems Integration AG. All rights reserved.
* http://www.bsiag.com/
*/
package org.eclipse.scout.rt.rest.log;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
* Specifies that REST calls to the annotated target should not be logged to the application's <i>access log</i>.
*/
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface NoLog {

/**
* @return <code>true</code> to disable logging for this target. This is the default value. It can be set to
* <code>false</code> to re-enable the logging.
*/
boolean value() default true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) BSI Business Systems Integration AG. All rights reserved.
* http://www.bsiag.com/
*/
package org.eclipse.scout.rt.rest.log;

import java.util.Collections;
import java.util.Set;

import jakarta.ws.rs.container.DynamicFeature;
import jakarta.ws.rs.container.ResourceInfo;
import jakarta.ws.rs.core.FeatureContext;

import org.eclipse.scout.rt.rest.RestApplication.IRestApplicationClassesContributor;

/**
* Installs the {@link NoLogFilter} for all REST methods that are annotated with &#64;{@link NoLog}.
*/
public class NoLogFeature implements DynamicFeature {

@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
NoLog noLogAnnotationMethod = resourceInfo.getResourceMethod().getAnnotation(NoLog.class);
NoLog noLogAnnotationClass = resourceInfo.getResourceClass().getAnnotation(NoLog.class);

// Compute if logging is disabled
boolean noLog = false;
if (noLogAnnotationMethod != null) {
noLog = noLogAnnotationMethod.value();
}
else if (noLogAnnotationClass != null) {
noLog = noLogAnnotationClass.value();
}

// If not loggable, install a filter that sets the "NoLog" attribute
if (noLog) {
context.register(NoLogFilter.class);
}
}

/**
* Installs the {@link NoLogFilter} into the REST application.
*/
public static class NoLogFilterFeatureContributor implements IRestApplicationClassesContributor {

@Override
public Set<Class<?>> contribute() {
return Collections.singleton(NoLogFeature.class);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) BSI Business Systems Integration AG. All rights reserved.
* http://www.bsiag.com/
*/
//FIXME
package org.eclipse.scout.rt.rest.log;

import java.io.IOException;

import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;

/**
* Dynamically installed request filter that sets the {@link #REQUEST_ATTRIBUTE} flag.
*/
public class NoLogFilter implements ContainerRequestFilter {

/**
* Name of the attribute that is set to the current request by {@link NoLogFilter}. The attribute value is irrelevant.
*/
public static final String REQUEST_ATTRIBUTE = NoLogFilter.class.getName();

@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestContext.setProperty(REQUEST_ATTRIBUTE, "X");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) BSI Business Systems Integration AG. All rights reserved.
* http://www.bsiag.com/
*/
package org.eclipse.scout.rt.rest.resource;

import java.io.InputStream;

import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.Response;

import org.eclipse.scout.rt.platform.ApplicationScoped;
import org.eclipse.scout.rt.platform.util.IOUtility;
import org.eclipse.scout.rt.platform.util.ObjectUtility;

/**
* Helper class used by REST resources.
*/
@ApplicationScoped
public class ResourceHelper {

public static final String IMAGE_TYPES = "image/jpeg, image/gif, image/png";
public static final String ZIP_TYPE = "application/zip";

/**
* Creates a response with streaming binary data and no-cache headers. Use this method to return large data / files.
*/
// FIXME sme check for occurrences of this header and replace by new utility (source from DownloadHttpResponseInterceptor.calcContentDispositionHeaderValue(String)).
public Response binaryResponse(InputStream in, String filename, String mimeType) {
return Response.ok(in, mimeType)
.header("Content-Disposition", "attachment; filename*=utf-8''" + IOUtility.urlEncode(filename))
.cacheControl(createNoCacheControl())
.build();
}

/**
* Creates a response with binary data and no-cache headers.
*/
public Response binaryResponse(byte[] data, String mimeType) {
return binaryResponse(data, mimeType, null);
}

/**
* Creates a response with binary data and cache-control headers.
*/
public Response binaryResponse(byte[] data, String mimeType, CacheControl cacheControl) {
cacheControl = ObjectUtility.nvlOpt(cacheControl, this::createNoCacheControl);
return Response.ok(data, mimeType)
.cacheControl(cacheControl)
.build();
}

public CacheControl createNoCacheControl() {
CacheControl cc = new CacheControl();
cc.setNoCache(true);
cc.setMustRevalidate(true);
cc.setNoStore(true);
return cc;
}
}
5 changes: 5 additions & 0 deletions org.eclipse.scout.rt.server.commons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@


<dependencies>
<!-- TODO Other way? -->
<dependency>
<groupId>org.eclipse.scout.rt</groupId>
<artifactId>org.eclipse.scout.rt.rest</artifactId>
</dependency>
<!-- Build Dependencies -->
<dependency>
<groupId>org.eclipse.scout.rt</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.scout.rt.platform.Platform;
import org.eclipse.scout.rt.platform.util.LazyValue;
import org.eclipse.scout.rt.platform.util.StringUtility;
import org.eclipse.scout.rt.rest.log.NoLogFilter;
import org.eclipse.scout.rt.server.commons.healthcheck.IHealthChecker.IHealthCheckCategory;
import org.eclipse.scout.rt.server.commons.servlet.AbstractHttpServlet;
import org.eclipse.scout.rt.server.commons.servlet.HttpServletControl;
Expand Down Expand Up @@ -52,6 +53,8 @@ public class HealthCheckServlet extends AbstractHttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute(NoLogFilter.REQUEST_ATTRIBUTE, "X"); // prevent logging of calls to health servlet in LogFilter

disableCaching(req, resp);
BEANS.get(HttpServletControl.class).doDefaults(this, req, resp);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) BSI Business Systems Integration AG. All rights reserved.
* http://www.bsiag.com/
*/
package org.eclipse.scout.rt.server.commons.servlet;

import org.eclipse.scout.rt.platform.util.ObjectUtility;
import org.eclipse.scout.rt.rest.RestApplication;
import org.eclipse.scout.rt.rest.RestApplicationScope;
import org.eclipse.scout.rt.rest.RestApplicationScopes;

/**
* @see RestApplicationScopes#API
*/
public class ApiRestApplication extends RestApplication {

@Override
protected boolean filterClass(Class<?> clazz) {
RestApplicationScope annotation = clazz.getAnnotation(RestApplicationScope.class);
return annotation == null || ObjectUtility.isOneOf(RestApplicationScopes.API, annotation.value());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) BSI Business Systems Integration AG. All rights reserved.
* http://www.bsiag.com/
*/
package org.eclipse.scout.rt.server.commons.servlet;

import org.eclipse.scout.rt.platform.util.ObjectUtility;
import org.eclipse.scout.rt.rest.IRestResource;
import org.eclipse.scout.rt.rest.RestApplication;
import org.eclipse.scout.rt.rest.RestApplicationScope;
import org.eclipse.scout.rt.rest.RestApplicationScopes;
import org.eclipse.scout.rt.rest.container.AntiCsrfContainerFilter;

/**
* @see RestApplicationScopes#EXT
*/
public class ExtRestApplication extends RestApplication {

@Override
protected boolean filterClass(Class<?> clazz) {
return !AntiCsrfContainerFilter.class.isAssignableFrom(clazz)
&& (!IRestResource.class.isAssignableFrom(clazz) || isExtScope(clazz));
}

protected boolean isExtScope(Class<?> clazz) {
RestApplicationScope annotation = clazz.getAnnotation(RestApplicationScope.class);
return annotation != null && ObjectUtility.isOneOf(RestApplicationScopes.EXT, annotation.value());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) BSI Business Systems Integration AG. All rights reserved.
* http://www.bsiag.com/
*/
package org.eclipse.scout.rt.server.commons.servlet.filter;

import java.io.IOException;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpSession;

public class EnforceNoHttpSessionFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) {
// nothing to initialize
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(wrapRequest(request), response);
}

@Override
public void destroy() {
// nothing to destroy
}

protected HttpServletRequest wrapRequest(ServletRequest request) {
return new SessionlessHttpServletRequestWrapper((HttpServletRequest) request);
}

public static class SessionlessHttpServletRequestWrapper extends HttpServletRequestWrapper {

public SessionlessHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}

@Override
public HttpSession getSession() {
throw new UnsupportedOperationException("HTTP session not allowed");
}

@Override
public HttpSession getSession(boolean create) {
if (!create) {
return null;
}
throw new UnsupportedOperationException("HTTP session not allowed");
}
}
}
Loading

0 comments on commit 6a4648f

Please # to comment.