Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.2k views
in Technique[技术] by (71.8m points)

spring mvc - SpringMVC: Inconsistent mapping behavior depending on url extension

I have a RESTful spring based endpoint to get assets stored in a db to a javascript editor. The relevant parts boil down to:

@RestController
@RequestMapping(ThemeEndpoint.ENDPOINT_NAME)
public class ThemeEndpoint {

public static final String ENDPOINT_NAME = "/themes"; 

    @RequestMapping(value="/{id}/css/{assetName:.*}", method=RequestMethod.GET)
    public Asset getCssItem(
        @PathVariable("id") ThemeId id, 
        @PathVariable("assetName") String name) {
    CssThemeAsset themeAsset = themeService.getCssAsset(
                id, ThemeAssetId.fromString(name));
    Asset asset = new Asset();
    asset.name = themeAsset.getName();
    asset.text = themeAsset.getContent();
    return asset;
}

This works as expected for urls like

http://localhost:8080/app-url/rest/themes/ac18a080-a2f1-11e3-84f4-600308a0bd14/css/main.less

but fails as soon as i change the extension to .css.

After some debugging i'm quite sure the request is not even mapped if i use an url like

http://localhost:8080/app-url/rest/themes/ac18a080-a2f1-11e3-84f4-600308a0bd14/css/main.css

With a high log level i can see that the mapping is catched by spring:

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 
    - Mapped "{[/themes/{id}/css/{assetName:.*}],methods=[GET],params=[],headers=[],
                consumes=[],produces=[application/json],custom=[]}" 
      onto public xxx.endpoint.ThemeEndpoint$Asset
          xxx.endpoint.ThemeEndpoint.getCssItem(
              net.lacho.svc.themes.api.ThemeId,java.lang.String)   

and with a non .css extension the controller is called:

Found 1 matching mapping(s) for [/themes/ac18a080-a2f1-11e3-84f4-600308a0bd14/css/main.less]
  : [{[/themes/{id}/css/{assetName:.*}],methods=[GET],params=[],headers=[],
        consumes=[],produces=[application/json],custom=[]}]

but as soon as i use .css as extension - bang:

Looking up handler method for path /themes/ac18a080-a2f1-11e3-84f4-600308a0bd14/css/test.css
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - 
    Resolving exception from handler [null]:  
org.springframework.web.HttpMediaTypeNotAcceptableException: 
    Could not find acceptable representation

web.xmland MVC-Config as requested:

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0"
    metadata-complete="false">

<welcome-file-list>
    <welcome-file>index</welcome-file>
</welcome-file-list>

</web-app>

WebApplicationInitializer:

package net.lacho.opcenter.bootstrap;


public class WebApplicationBootstrapper implements WebApplicationInitializer {


    @Override
    public void onStartup(ServletContext container) throws ServletException {

        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.setConfigLocation(ApplicationConfig.class.getName());

        container.addListener(new ContextLoaderListener(rootContext));

        registerRestDispatcher(container);
        registerDefaultDispatcher(container);

        container.addFilter("CharacterEncodingFilter", UTF8EncodingFilter.class).addMappingForUrlPatterns(null,  true,  "/*");
        container.addFilter("headSupportFilter", HeadSupportFilter.class).addMappingForUrlPatterns(null,  true,  "/*");

        DelegatingFilterProxy shallowFrontendContextFilterProxy = new DelegatingFilterProxy("shallowFrontendContextProviderLocalFilter");
        shallowFrontendContextFilterProxy.setTargetFilterLifecycle(true);
        FilterRegistration.Dynamic shallowFrontendFilter = container.addFilter("ShallowFrontendContextFilter", shallowFrontendContextFilterProxy);
        shallowFrontendFilter.setInitParameter("ignoreNullClient", "true");
        shallowFrontendFilter.addMappingForUrlPatterns(null,  true,  "/*");

        container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
            .addMappingForUrlPatterns(null,  true,  "/*");

        container.addFilter("FrontendContextFilter", new DelegatingFilterProxy("frontendContextProviderLocalFilter"))
            .addMappingForUrlPatterns(null,  true,  "/*");

        container.addFilter("hiddenHttpMethodFilter", new HiddenHttpMethodFilter()).addMappingForUrlPatterns(null,  true,  "/rest/*");;
    }

    public void registerRestDispatcher(ServletContext container) {
        AnnotationConfigWebApplicationContext restDispatcherContext = new AnnotationConfigWebApplicationContext();
        restDispatcherContext.register(RestCommonsMvcConfig.class);

        ServletRegistration.Dynamic restDispatcher = container.addServlet("rest-dispatcher", new DispatcherServlet(restDispatcherContext));
        restDispatcher.setLoadOnStartup(1);
        restDispatcher.addMapping("/rest/*");
    }

    public void registerDefaultDispatcher(ServletContext container) {
        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(MvcConfig.class);

        ServletRegistration.Dynamic dispatcher = container.addServlet("backend-dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/*", "/index");
    }

}

MVC-config:

package net.lacho.opcenter.bootstrap;



@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"net.lacho.opcenter.ui"} )
public class MvcConfig extends WebMvcConfigurerAdapter {

    ... many lines removed, containing interceptors and velocity-config ...

@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry
            .addResourceHandler("/_r/_s/**")
            .addResourceLocations("classpath:/static-resources/")
            .setCachePeriod(365 * 86400);
        registry
            .addResourceHandler("/_r/_d/**")
            .addResourceLocations("classpath:/static-uncached-resources/");
    }

    @Override
    public void configureDefaultServletHandling(
            DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

Any idea anyone?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It's not a bug, it's a feature...

As @axtavt and @rhinds have supposed, something is messing around which the content type. The browsers sends a correct Accept: application/json but spring ignores this and uses the extension of the url (aarrgh). From the docs:

16.16.4 Configuring Content Negotiation

You can configure how Spring MVC determines the requested media types from the client for
request mapping as well as for content negotiation purposes. The available options are to 
check the file extension in the request URI, the "Accept" header, a request parameter, as 
well as to fall back on a default content type. By default, file extension in the request 
URI is checked first and the "Accept" header is checked next.

The solution is quite simple as you can disable this "feature":

@Configuration
@EnableWebMvc
public class RestCommonsMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(false);
    }

}

See also Spring does not ignore file extension for xml-config.

Related


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...