区分多个connectors/ports in embedded Tomcat in Spring Boot 之间的请求

Distinguish requests between multiple connectors/ports in embedded Tomcat in Spring Boot

我有一个 Spring 基于 Boot rest 服务的应用程序配置在多个端口上,需要区分每个请求通过的端口。为应用程序设置多个端口的想法是由于不同的 public 和私有 sub-networks(具有不同的安全访问级别)可以访问应用程序公开的服务的不同部分。

从概念上讲,这个想法是向嵌入式 tomcat 添加额外的连接器,然后通过向每个指定 "channel" 添加自定义 header 来捕获所有更改它们的传入请求通过.

我面临的问题是我不知道如何在连接器级别捕获这些传入请求(在它到达任何过滤器或 servlet 之前)。

因此,对于 multi-port 解决方案,我有:

@Configuration
public class EmbeddedTomcatConfiguration {

    @Value("${server.additional-ports}")
    private String additionalPorts;

    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        Connector[] additionalConnectors = additionalConnector();
        if(additionalConnectors != null && additionalConnectors.length > 0) {
            tomcat.addAdditionalTomcatConnectors(additionalConnectors);
        }
        return tomcat;
    }

    private Connector[] additionalConnector() {
        if(StringUtils.isNotBlank(additionalPorts)) {
            return Arrays.stream(additionalPorts.split(","))
                .map(String::trim)
                .map(p -> {
                    Connector connector = new Connector(Http11NioProtocol.class.getCanonicalName());
                    connector.setScheme("http");
                    connector.setPort(Integer.valueOf(p));
                    return connector;
                })
                .toArray(Connector[]::new);
        }
        return null;
    }
}

理论上,我可以为每个连接器注册一个自定义 LifecycleListener,但据我所知,这无济于事。我也听说过一些关于阀门的事情,但我不确定如何为每个连接器实现它们。

或者我走的路完全错了。

我非常感谢任何帮助。

您似乎下定决心要尝试使用 Valve,但是,经过更多研究后,我建议您使用 ServletFilter 而不是 Valve 来完成这项工作。

我相信您可以在 Valve 中完成这项工作,但 Valve 必须部署到 tomcat/lib 目录中,而不是打包到您的应用程序中。我建议您考虑尝试将您的应用程序放在可部署工件中,而不必记住在创建新部署时将一个额外的 jar 文件部署到您的 tomcat 实例。

根据这个答案,Difference between getLocalPort() and getServerPort() in servlets您应该能够通过在 HttpServletRequest 上调用 getLocalPort() 来访问 tomcat 端口。

然后根据你的想法在题中加一个specific context name into the request header.

public class PortContextFilter implements Filter {

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

        int localPort = request.getLocalPort();

        // if else or case statements

        ServletRequest newRequest = PortContextHttpServletRequestWrapper(request, YourPortContext)

        filterChain.doFilter(newRequest, response);
    }

    public void destroy() {
        // Nothing to do
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        // Nothing to do.
    }

}

完成后,理论上,你应该可以使用@RequestMapping注解根据header中的名称进行路由。

@PreAuthorize("hasPermission(#your_object, 'YOUR_OBJECT_WRITE')")
@RequestMapping("/yourobject/{identifier}", headers="context=<context_name>", method = RequestMethod.POST)
public String postYourObject(@PathVariable(value = "identifier") YourObject yourObject) {
    // Do something here...
    ...
}

也可以使用 RequestCondition 基于端口路由请求,我认为这更接近库存 spring。见