I'm trying to plug my set of RESTful endpoints (which rely on Jersey 2 JAX-RS implementation) into a 3rd party Spring MVC web application running on top of Tomcat 9.
I'm not allowed to modify web.xml, nor is my plug-in available when the container starts, so annotation-based configuration is not an option, either. This means I can't deploy Jersey's ServletContainer directly.
What I am allowed is plugging into Spring MVC, namely
- creating a custom partial Spring application context and
- implementing a custom
Controller, so I have written this naïve implementation which is actually close to the standardServletWrappingController:
package com.example import org.glassfish.jersey.server.ResourceConfig import org.glassfish.jersey.servlet.ServletContainer import org.springframework.web.servlet.ModelAndView import org.springframework.web.servlet.mvc.AbstractController import java.io.IOException import java.util.Collections import java.util.Enumeration import javax.servlet.ServletConfig import javax.servlet.ServletContext import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse class JerseyServletInitializer(servletContext: ServletContext, componentClasses: List<Class<*>>, components: List<Any>): AbstractController() { private val servletContainer: ServletContainer init { val resourceConfig = ResourceConfig() .apply { // Register org.glassfish.jersey.media.multipart.MultiPartFeature componentClasses.forEach { clazz -> register(clazz) } // Register REST endpoints components.forEach { component -> register(component) } } // Provide a dummy ServletConfig val servletConfig = object : ServletConfig { override fun getInitParameter(name: String): String? = null override fun getInitParameterNames(): Enumeration<String> = emptyList<String>().let { Collections.enumeration(it) } override fun getServletName(): String = ServletContainer::class.java.name override fun getServletContext(): ServletContext = servletContext } // Create and initialize Jersey's ServletContainer servletContainer = ServletContainer(resourceConfig).apply { init(servletConfig) } setSupportedMethods("GET", "POST", "PUT", "OPTIONS") } @Throws(IOException::class) override fun handleRequestInternal(request: HttpServletRequest, response: HttpServletResponse): ModelAndView? { // Delegate all HTTP requests to Jersey servletContainer.service(request, response) return null } } The above works fine with the only exception of file upload scenario (i. e. HTTP POST with Content-Type: multipart/form-data).
Spring detects such requests and converts a regular HttpServletRequest into a MultipartHttpServletRequest, reading out and exhausting its input stream (mark()/reset() are not supported). The web application has a custom MultipartResolver which I can't affect in any way:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/> This means Jersey receives a multipart HTTP POST with an empty request body, throws a MIMEParsingException and responds with HTTP 400 Bad Request.
As far as I understand, multipart resolution can only be disabled globally, and not on a per-controller basis (1, 2, 3).
Questions:
- What is the best way to work the
MultipartResolveraround, so that Jersey receives an non-modified request? - Alternatively, can you recommend an API to re-construct the request body (i. e. do the opposite of what
MultipartResolverdoes and feed the newHttpServletRequestto Jersey)? This can, of course, be done by hand, but I'd rather rely on the existing libraries.