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
MultipartResolver
around, 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
MultipartResolver
does and feed the newHttpServletRequest
to Jersey)? This can, of course, be done by hand, but I'd rather rely on the existing libraries.