问题
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