【问题标题】:Java EE Interceptor abort responseJava EE 拦截器中止响应
【发布时间】:2018-01-01 12:51:24
【问题描述】:

我尝试制作多租户 REST API。

Java EE 7
应用服务器:WildFly-Swarm 2017.11.0
JAX-RS:wildfly-swarm-weld

目的是从查询参数中获取租户(德语 Mandant)名称,并设置租户名称以触发代理 EntityManager。
这是 TOMAS DVORAK 的基本概念:https://www.tomas-dvorak.cz/posts/jpa-multitenancy/
我与拦截器斗争,如标题中所述,我需要使用 HTTP 代码和 JSON 错误消息中止拦截的 REST 请求的响应。

我不能使用过滤器,因为 EE 正在使用另一个带有过滤器的线程,我无法通过 ThreadLocal 传递租户名称。

我很难得到响应对象。

这是我到目前为止的代码:

import javax.inject.Inject;
import javax.interceptor.*;
import javax.servlet.http.*;
import org.multitenancy.test.beans.*;

/**
 * Wrap every call with tenant identification, detected from list of parameters
 * of called method.
 */
@Interceptor
public class TenantInterceptor
{

  @Inject
  private TenantRegistry tenantRegistry;

  @Inject
  HttpServletRequest servletRequest;

  @AroundInvoke
  public Object wrapWithTenant(final InvocationContext ctx) throws Exception
  {
    System.out.println("wrapWithTenant() Called");
    printParameter();
    if (servletRequest.getParameterMap().containsKey("mandant"))
    {
      String mandantNameReq = servletRequest.getParameterMap().get("mandant")[0];
      if (tenantRegistry.verifyMandantByName(mandantNameReq))
      {
        System.out.println(mandantNameReq + " is verified");
        final String oldValue = TenantHolder.getCurrentTenant();
        System.out.println("old value " + oldValue);
        try
        {
          TenantHolder.setTenant(mandantNameReq);
          System.out.println("Mandant gesetzt: " + mandantNameReq);
          return ctx.proceed();
        }
        finally
        {
          if (oldValue != null)
          {
            TenantHolder.setTenant(oldValue);
          }
          else
          {
            TenantHolder.cleanupTenant();
          }
        }
      }
      else
      {
        //TODO: Response einbauen
//        containerRequestContext.abortWith(
//                Response.status(Response.Status.BAD_REQUEST)
//                        .entity(new ApiError("Mandant not found"))
//                        .type(MediaType.APPLICATION_JSON)
//                        .build());
      }
    }
    else
    {
//      containerRequestContext.abortWith(
//              Response.status(Response.Status.BAD_REQUEST)
//                      .entity(new ApiError("Parameter doesnt contain mandant"))
//                      .type(MediaType.APPLICATION_JSON)
//                      .build());
    }
    return null;
  }

  private void printParameter()
  {
    if (servletRequest.getParameterMap().isEmpty())
    {
      System.out.println("No Properties given");
    }
    else
    {
      for (String key : servletRequest.getParameterMap().keySet())
      {
        for (String val : servletRequest.getParameterValues(key))
        {
          System.out.println(key + "\t" + val);
        }
      }
    }
  }
}

提前致谢:)

【问题讨论】:

  • 你的问题有点混乱,不清楚你在问什么。只是想知道,您是否尝试使用 JAX-RS ContainerRequestFilter 而不是 CDI/Interceptor 规范拦截器?您将有权中止请求。
  • 我不明白你为什么不能使用过滤器?为什么要在另一个线程中运行?
  • 您好,我尝试了过滤器,但我的 ThreadLocal 存储中的租户名称为空。我可以将我的私人 Bitbucket 项目复制到 GitHub 吗?

标签: jakarta-ee cdi interceptor


【解决方案1】:

我会避免在线程本地存储东西,因为容器实现是否使用相同的线程来进行请求调度。它可以在一个容器中工作,但不能在另一个容器中工作。

我会做以下事情:

1) 为您的租户创建一个可注入的 bean 定义:

@javax.enterprise.inject.Produces
Tentant tenant(HttpServletRequest request) throws TenantNotFoundException {
  // logic
}

2) 在 JAX-RS 中处理异常

@javax.ws.rs.ext.Provider
public class TenantNotFoundExceptionMapper implements javax.ws.rs.ext.ExceptionMapper<TenantExceptionNotFound> {
  public Response toResponse(TenantNotFoundException exception) {
    // your 400 response here
  }
}

3) 将租户注入到您的业务逻辑中

@Path("/foo")
public class Foo {
  @javax.inject.Inject
  private Tenant tenant
}

【讨论】:

    【解决方案2】:

    我不能使用过滤器,因为 EE 正在使用另一个带有过滤器的线程,我无法通过 ThreadLocal 传递租户名称。

    为什么会这样?您如何访问该应用程序?如果您通过 REST 访问它,这是应用程序的入口点,并且应该可以从此访问 ThreadLocal。重要的是您将 @Priority 设置为较低的值,以便过滤器是第一个执行的过滤器。

    我们在应用程序中使用类似的方法将租户标识符保存在 ThreadLocal

    @Provider
    @Priority(1)
    public class TenantFilter implements ContainerRequestFilter
    {
        private static final Logger LOG = LoggerFactory.getLogger(TenantFilter.class);
        private static final String TENANT_IDENTIFIER = "mandant";
    
        @Inject
        private TenantRegistry tenantRegistry;        
    
        @Override
        public void filter(ContainerRequestContext requestContext) throws IOException
        {
            List<String> identifierHeader = requestContext.getHeaders().get(TENANT_IDENTIFIER);
            if ((identifierHeader == null || identifierHeader.isEmpty()))
            {
                LOG.error("No header " + TENANT_IDENTIFIER + " found. Access denied. [path: " + path + "]");
                accessForbidden(requestContext);
                return;
            }
    
            String tenantIdentifier = identifierHeader.get(0);
            if (tenantRegistry.verifyMandantByName(tenantIdentifier)) {
                LOG.trace("Filtering request for " + TENANT_IDENTIFIER + " header, using: " + tenantIdentifier);
                TenantIdentifierResolver.tenantIdentifier.set(tenantIdentifier);
            }
        }
    
        private void accessForbidden(ContainerRequestContext requestContext)
        {
            Response accessForbidden = Response.status(Status.FORBIDDEN).build();
            requestContext.abortWith(accessForbidden);
        }
    }
    

    TenantIdentifierResolver 是一个保存租户标识符的本地线程,Hibernate 使用它来为数据库访问设置正确的架构访问。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-22
      • 2018-07-25
      • 2019-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多