4

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

  1. creating a custom partial Spring application context and
  2. implementing a custom Controller, so I have written this naïve implementation which is actually close to the standard ServletWrappingController:
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:

  1. What is the best way to work the MultipartResolver around, so that Jersey receives an non-modified request?
  2. Alternatively, can you recommend an API to re-construct the request body (i. e. do the opposite of what MultipartResolver does and feed the new HttpServletRequest to Jersey)? This can, of course, be done by hand, but I'd rather rely on the existing libraries.
Bass
  • 4,977
  • 2
  • 36
  • 82

0 Answers0