Spring Boot Undertow add both blocking handler and NIO handler in the same application

荒凉一梦 提交于 2020-01-04 06:04:04

问题


In my previous question (thank @Andy Wilkinson) I figured out that all incoming requests to an undertowEmbeddedServletContainer are handled by a worker thread (blocking operation).

According to Andy, I try to add a UndertowBuilderCustomizer in order to override the ServletInitializerHandler to handle incoming requests with a non-blocking handler.

@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory(){
    UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory = new UndertowEmbeddedServletContainerFactory();
    undertowEmbeddedServletContainerFactory.addBuilderCustomizers(new UndertowBuilderCustomizer() {

        @Override
        public void customize(Undertow.Builder builder) {
            builder.setHandler(new HttpHandler() {
                @Override
                public void handleRequest(HttpServerExchange exchange) throws Exception {
                    exchange.getResponseSender().send("test");
                }
            });
        }
    });
    return undertowEmbeddedServletContainerFactory;
}

In this customizer I set the builder rootHandler for a NIO handler. But it is overriden by UndertowEmbeddedServletContainer at startup phase with a ServletInitializerHandler:

private Undertow createUndertowServer() {
    try {
        HttpHandler servletHandler = this.manager.start();
        this.builder.setHandler(getContextHandler(servletHandler));
        return this.builder.build();
    }
    catch (ServletException ex) {
        throw new EmbeddedServletContainerException(
                "Unable to start embdedded Undertow", ex);
    }
}

As the title of this question says: I'm trying to have both blocking and non-blocking handlers, where blocking handlers are managed through @Controller annotation, and where NIO handlers are managed by Spring.

I found a solution, but as a beginner, I don't know if it's a good one.

HandlerPath annotation

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE})
public @interface HandlerPath {

    public String path() default "";

}

create bean implementing HttpHandler

@Component
@HandlerPath(path = "/hello-nio")
public class HelloHandler implements HttpHandler{

    @Autowired
   HelloService helloService;

    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        exchange.getResponseSender().send(helloService.sayHello("Josselin"));
    }  
}

Create a simple Controller

@Controller
public class HelloController {

    @RequestMapping("/hello")
    @ResponseBody
    public String sayHello(){
        return "hello";
    }
}

Create a class implementing ServletExtension

public class NonBlockingHandlerExtension implements ServletExtension{

    @Override
    public void handleDeployment(DeploymentInfo deploymentInfo, final ServletContext servletContext) {
        deploymentInfo.addInitialHandlerChainWrapper(new HandlerWrapper() {
            @Override
            public HttpHandler wrap(final HttpHandler handler) {

                WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
                Map<String, Object> handlers = ctx.getBeansWithAnnotation(HandlerPath.class);

                PathHandler rootHandler = new PathHandler();
                rootHandler.addPrefixPath("/", handler);
                for(Map.Entry<String, Object> handlerEntry : handlers.entrySet()){
                    if(handlerEntry.getValue() instanceof HttpHandler){
                        HttpHandler httpHandler = (HttpHandler) handlerEntry.getValue();
                        String path = httpHandler.getClass().getAnnotation(HandlerPath.class).path();
                        rootHandler.addPrefixPath(path, httpHandler);
                    }
                }
                return rootHandler;
            }
        });
    }
}

In this method, the default ServletInitializer handler is bound to "/" context, and managed by spring, so all blocking requests could be handled by @Controller(s). Then I try to discover all beans that are annotated with @HandlerPath, then add a new prefixPath to the rootHandler based on @HandlerPath.path property.

Finally

Create a directory META-INF.services

Create a file io.undertow.servlet.ServletExtension and add line:

org.me.undertow.NonBlockingHandlerExtension

Result

All is working like a charm, NIO handlers are called when binding URL are hit, so do blocking handlers.

Could anyone please let me know if this solution could be improved in any way? Moreover, as NIO handler URLs are not managed by Spring, I guess I have to use globaleMethodSecurity and set @PreAuthorize to secure the NIO handler?


回答1:


I had similar problem lately and I discovered that UndertowEmbeddedServletContainerFactory provides addDeploymentInfoCustomizers() which can be used to put custom HttpHandler at the beginning of handlers chain.

Example

@Bean
public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory(RootHandler rootHandler) {
    UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory();

    factory.addDeploymentInfoCustomizers(deploymentInfo ->
            deploymentInfo.addInitialHandlerChainWrapper(rootHandler::setNext));

    return factory;
}

Sample RootHandler

@Component
public class RootHandler implements HttpHandler {

    private HttpHandler next;

    @Autowired
    public RootHandler(...) {
    }

    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        if (exchange.getRelativePath().startsWith("/service")) {
            handleServiceRequest(exchange);
        } else {
            next.handleRequest(exchange);
        }
    }

    private void handleServiceRequest(HttpServerExchange exchange) {

        // ...

        exchange.getResponseSender().send("OK");
    }

    public HttpHandler setNext(HttpHandler next) {
        this.next = next;
        return this;
    }
}


来源:https://stackoverflow.com/questions/27563790/spring-boot-undertow-add-both-blocking-handler-and-nio-handler-in-the-same-appli

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!