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