Monday, April 27, 2015

Setting up Undertow with Spring MVC

In case you still don't know about it, Undertow is web server developed under JBoss community, and is praised a lot for its impressive performance. I usually deployed my web application in .war form under some servlet container (mostly Tomcat), but now I wanted to try out embedded servlet container, and Undertow had seemed especially good for that considering its small footprint.

I am also heavy Spring user, so I wanted to integrate this web server with Spring MVC, but only examples so far where I could find Spring-Undertow integration is within Spring Boot project, and for some reason I still use plain Spring Framework, so I set out to do it my own.

Of course, as everything in Spring is a bean, I developed my own UndertowServer bean which was pretty straightforward, and bigger question was how to define my web application deployment in it because there are couple of ways to do it. I decided to use Servlet 3.0+ ServletContainertInitializer (one other option would be to straight use Undertow's API to specify web application deployment).

Here is piece of code from our implementation of this interface:
 public class WebAppServletContainerInitializer implements ServletContainerInitializer, ApplicationContextAware {  
   private ApplicationContext applicationContext;  

   @Override  
   public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {  
     XmlWebApplicationContext rootWebAppContext = new XmlWebApplicationContext();  
     rootWebAppContext.setConfigLocation("/WEB-INF/applicationContext.xml");  
     rootWebAppContext.setParent(applicationContext);  
     ctx.addListener(new ContextLoaderListener(rootWebAppContext));
  
     FilterRegistration.Dynamic encodingFilter = ctx.addFilter("encoding-filter", CharacterEncodingFilter.class);  
     encodingFilter.setInitParameter("encoding", "UTF-8");  
     encodingFilter.setInitParameter("forceEncoding", "true");  
     encodingFilter.addMappingForServletNames(EnumSet.allOf(DispatcherType.class), false, "admin");  

     FilterRegistration.Dynamic springSecurityFilterChain = ctx.addFilter("springSecurityFilterChain", DelegatingFilterProxy.class);  
     springSecurityFilterChain.addMappingForServletNames(EnumSet.allOf(DispatcherType.class), false, "admin");  
     ServletRegistration.Dynamic dispatcher = ctx.addServlet("admin", DispatcherServlet.class);  
     dispatcher.setLoadOnStartup(1);  
     dispatcher.addMapping("/admin/*");  
 ...  
 ...  

We had to inject our main ApplicationContext (that contains UndertowServer bean) via ApplicationContextAware marker, to be able to use it as parent context for root WebApplicationContext that we put under /WEB-INF/ together with other DispatcherServlet contexts.

Later on, we use this ServletContainertInitializer implementation during startup phase of Undertow server to construct DeploymentInfo object:
 InstanceFactory<? extends ServletContainerInitializer> instanceFactory = new ImmediateInstanceFactory<>(servletContainerInitializer);  
 ServletContainerInitializerInfo sciInfo = new ServletContainerInitializerInfo(WebAppServletContainerInitializer.class, instanceFactory, new HashSet<>());  

 DeploymentInfo deploymentInfo = constructDeploymentInfo(sciInfo);  

FInal result is that we can now readily use this web server bean in our Spring XML deployment descriptors, such as:
 <bean class="vmarcinko.undertow.UndertowServer">  
   <property name="port" value="8080"/>  
   <property name="webAppName" value="myapp"/>  
   <property name="webAppRoot" value="${distribution.dir}/web-app-root"/>  
   <property name="servletContainerInitializer">  
     <bean class="vmarcinko.web.admin.WebAppServletContainerInitializer"/>  
   </property>  
 </bean>  

If you ask me why I still use Spring XML in 2015 instead of Java config - well, I think XML is still nicer DSL for describing deployment than Java, but that's just me :)

Anyway, when you boot the system, this little web server will be up and running under specified port, serving this single web application under given root directory. In the example above, my administration web console (specific DispatcherServlet serving that under '/admin/*') would be available under:

http://localhost:8080/myapp/admin

Whole code for both classes is available at this Gist.



17 comments:

  1. Hello Vjeran Marcinko,

    I'm trying your code downloaded from Gist. I created a main class to start the Spring application context with the following line:

    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("file:" + appPath + "/config/rootContext.xml");

    After creating web-app-root in the project dir and passing -Ddistribution.dir=/full/path/to/my/project and creating WEB-INF inside web-app-root and there the requested (by the application) xml files (applicationContext.xml, admin-servlet.xml and customer-servlet.xml), I'm getting the following error:

    DEBUG org.springframework.web.filter.DelegatingFilterProxy - Initializing filter 'springSecurityFilterChain'
    ERROR io.undertow.request - UT005023: Exception handling request to /myapp/admin
    org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined

    I'm not pretty sure what to do now. Could you give me a hint? Could you put a full project in Gist, instead of just the two classes? That way it will be easer to understand your example which, by the way, it's the most complete I've found on the net until now.

    Regards.

    ReplyDelete
    Replies
    1. I have bunch of my custom-related stuff in it, so I don't have time right now to pull it out of the project, but you just go on setting Spring MVC and Security the usual way...
      In you case, it seems the problem is that you haven't set up Spring Security properly, meaning, you should have something like special securityContext.xml referenced from some of your higher-level cotexts (something like applicationContext.xml) and use special Spring Security namepsace support to set up required beans.

      Delete

  2. Nice to see your blog post.. I really enjoyed by reading your blog post. Thanks a lot for sharing this with us.. Keep on sharing like this informative post.

    Salesforce Training in Chennai

    ReplyDelete
  3. This is a great article, I have been always to read something with specific tips! I will have to work on the time for scheduling my learning.
    Jobs in Kolkata
    Jobs in Mumbai

    ReplyDelete
  4. It is really very excellent,I find all articles was amazing.Awesome way to get exert tips from everyone,not only i like that post all peoples like that post.Because of all given information was wonderful and it's very helpful for me.
    SAP Training in Chennai
    SAP ABAP Training in Chennai
    SAP FICO Training in Chennai
    SAP MM Training in Chennai

    ReplyDelete
  5. Wonderful blog.. Thanks for sharing informative Post. Its very useful to me.

    dailyconsumerlife
    Guest posting sites

    ReplyDelete
  6. Very nice post here and thanks for it .I always like and such a super contents of these post.Excellent and very cool idea and great content of different kinds of the valuable information's.
    Data Science course in rajaji nagar
    Data Science with Python course in chenni
    Data Science course in electronic city
    Data Science course in USA
    Data science course in pune | Data Science Training institute in Pune

    ReplyDelete
  7. Hey Nice Blog!! Thanks For Sharing!!!Wonderful blog & good post.Its really helpful for me, waiting for a more new post. Keep Blogging!
    SEO company in coimbatore
    SEO Service in Coimbatore
    web design company in coimbatore

    ReplyDelete
  8. This post is so interactive and informative.keep update more information...
    Android Training in Tambaram
    Android Training in Chennai

    ReplyDelete