i'm currently have a look a springboot undertow and it's not really clear (for me) how to dispatch an incoming http request to a worker thread for blocking operation handling.
Looking at the class UndertowEmbeddedServletContainer.class, it look like there is no way to have this behaviour since the only HttpHandler is a ServletHandler, that allow @Controller configurations
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); } } private HttpHandler getContextHandler(HttpHandler servletHandler) { if (StringUtils.isEmpty(this.contextPath)) { return servletHandler; } return Handlers.path().addPrefixPath(this.contextPath, servletHandler); }
By default, in undertow all requests are handled by IO-Thread for non blocking operations. Does this mean that every @Controller executions will be processed by a non blocking thread ? or is there a solution to chose from IO-THREAD or WORKER-THREAD ?
I try to write a workaround, but this code is pretty uggly, and maybe someone has a better solution:
BlockingHandler.class
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface BlockingHandler { String contextPath() default "/"; }
UndertowInitializer.class
public class UndertowInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { configurableApplicationContext.addBeanFactoryPostProcessor(new UndertowHandlerPostProcessor()); } }
UndertowHandlerPostProcessor.class
public class UndertowHandlerPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AnnotationTypeFilter(BlockingHandler.class)); for (BeanDefinition beanDefinition : scanner.findCandidateComponents("org.me.lah")){ try{ Class clazz = Class.forName(beanDefinition.getBeanClassName()); beanDefinitionRegistry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition); } catch (ClassNotFoundException e) { throw new BeanCreationException(format("Unable to create bean %s", beanDefinition.getBeanClassName()), e); } } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { //no need to post process defined bean } }
override UndertowEmbeddedServletContainerFactory.class
public class UndertowEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware, ApplicationContextAware { private ApplicationContext applicationContext; @Override public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) { DeploymentManager manager = createDeploymentManager(initializers); int port = getPort(); if (port == 0) { port = SocketUtils.findAvailableTcpPort(40000); } Undertow.Builder builder = createBuilder(port); Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(BlockingHandler.class); return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(), port, port >= 0, handlers); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } ...
override UndertowEmbeddedServletContainer.class
public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager, String contextPath, int port, boolean autoStart, Map<String, Object> handlers) { this.builder = builder; this.manager = manager; this.contextPath = contextPath; this.port = port; this.autoStart = autoStart; this.handlers = handlers; } private Undertow createUndertowServer() { try { HttpHandler servletHandler = this.manager.start(); String path = this.contextPath.isEmpty() ? "/" : this.contextPath; PathHandler pathHandler = Handlers.path().addPrefixPath(path, servletHandler); for(Entry<String, Object> entry : handlers.entrySet()){ Annotation annotation = entry.getValue().getClass().getDeclaredAnnotation(BlockingHandler.class); System.out.println(((BlockingHandler) annotation).contextPath()); pathHandler.addPrefixPath(((BlockingHandler) annotation).contextPath(), (HttpHandler) entry.getValue()); } this.builder.setHandler(pathHandler); return this.builder.build(); } catch (ServletException ex) { throw new EmbeddedServletContainerException( "Unable to start embdedded Undertow", ex); } }
set initializer to the application context
public static void main(String[] args) { new SpringApplicationBuilder(Application.class).initializers(new UndertowInitializer()).run(args); }
finaly create a HttpHandler that dispatch to worker thread
@BlockingHandler(contextPath = "/blocking/test") public class DatabaseHandler implements HttpHandler { @Autowired private EchoService echoService; @Override public void handleRequest(HttpServerExchange httpServerExchange) throws Exception { if(httpServerExchange.isInIoThread()){ httpServerExchange.dispatch(); } echoService.getMessage("my message"); }
}
As you can see, my "solution" is really heavy, and i would really appreciate any help to simplify it a lot.
Thank you