java 并非所有内容类型都支持 Spring REST Controller 内容/类型

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/48651351/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 10:08:09  来源:igfitidea点击:

Spring REST Controller content/type not supported for all content types

javaspringspring-mvcpostmanmedia-type

提问by Frakcool

I'm trying to create a Web Service that consumes an HTML which is later used to create a PDF with iText.

我正在尝试创建一个使用 HTML 的 Web 服务,该 HTML 稍后用于使用 iText 创建 PDF。

I'm using Postman in order to send my requests to server but everytime I get the following error, no matter which content type I select:

我正在使用 Postman 将我的请求发送到服务器,但每次出现以下错误时,无论我选择哪种内容类型:

{
    "status": "CONFLICT",
    "code": 40902,
    "message": "Content type 'text/plain' not supported",
    "developerMessage": "null",
    "moreInfoUrl": "class org.springframework.web.HttpMediaTypeNotSupportedException",
    "throwable": null
}

The message changes depending on the content type selected:

该消息根据所选的内容类型而变化:

"message": "Content type 'text/xml' not supported"
"message": "Content type 'text/html' not supported"

This is my endpoint:

这是我的终点:

@RequestMapping(path = "/reports/pdf", method = RequestMethod.POST, consumes = MediaType.TEXT_HTML_VALUE)
public ResponseEntity<byte[]> generatePdfReport(@RequestBody String html) throws Exception {
    byte[] pdfBytes = reportsService.generatePdf(html);
    ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(pdfBytes, HttpStatus.OK);
    return response;
}

I've changed consumesattribute to:

我已将consumes属性更改为:

  • MediaType.TEXT_PLAIN_VALUE
  • MediaType.TEXT_XML_VALUE
  • MediaType.TEXT_PLAIN_VALUE
  • MediaType.TEXT_XML_VALUE

to match what I'm sending on Postman.

以匹配我在 Postman 上发送的内容。

As well as per @dnault comment, removing it completely from the RequestMappingannotation with the same results.

以及根据@dnault 评论,将其从RequestMapping注释中完全删除,结果相同。

I've looked into similar questions:

我研究过类似的问题:

However none of the above and some others that aren't really close to my problem that I've checked already provide an answer that solves this issue.

但是,以上和其他一些与我检查过的问题并不真正接近的问题都没有提供解决此问题的答案。

The sample HTML I'm trying to send to server is:

我尝试发送到服务器的示例 HTML 是:

<table style="border: 1px solid black; font-size: 12px; margin-top: 1px; width: 900px;" id="table_direction">
    <tr>
        <td width="33%" colspan="2">
            <strong>Data1</strong>
        </td>
        <td width="33%" colspan="2">
            <strongData2</strong>
        </td>
        <td width="16%" colspan="1">Foo</td>
        <td width="16%" colspan="1">Bar</td>
    </tr>
    <tr>
        <td colspan="">ID</td>
        <td colspan="">123456</td>
        <td colspan="">Property 1</td>
        <td colspan="">Foo</td>
        <td colspan="">Property 2</td>
        <td colspan="">Bar</td>
    </tr>
</table>

Our configuration is made by Java configuration classes which are as follows:

我们的配置是由 Java 配置类进行的,如下所示:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.company.project")
@PropertySource("classpath:application.properties")

@EnableTransactionManagement // proxyTargetClass = true
@EnableJpaRepositories(basePackages = { "com.company.project.dao", "com.company.project.repository" })

public class HelloWorldConfiguration extends WebMvcConfigurerAdapter {

    @Inject
    Environment env;

    @Value("${jdbc.user}")
    private String userDB;

    @Value("${jdbc.password}")
    private String passDB;

    @Value("${jdbc.url}")
    private String urlDB;

    @Value("${jdbc.driverClassName}")
    private String driverClassName;

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public CommonsMultipartResolver multipartResolver() {
        return new CommonsMultipartResolver();
    }

    @Bean(name = "dataSource")
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName(driverClassName);
        driverManagerDataSource.setUrl(urlDB);
        driverManagerDataSource.setUsername(userDB);
        driverManagerDataSource.setPassword(passDB);
        return driverManagerDataSource;
    }

    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(new String[] { "com.company.project.model" });
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
            JpaVendorAdapter jpaVendorAdapter) {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource);
        entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
        entityManagerFactoryBean.setPackagesToScan("com.company.project.model");
        entityManagerFactoryBean.setJpaProperties(hibernateProperties());
        return entityManagerFactoryBean;
    }

    private Properties hibernateProperties() {
        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
        jpaProperties.put("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
        jpaProperties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
        return jpaProperties;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

    @Bean(name = "transactionManager2")
    @Autowired
    @Named("transactionManager2")
    public HibernateTransactionManager transactionManager2(SessionFactory s) {
        HibernateTransactionManager txManager = new HibernateTransactionManager();
        txManager.setSessionFactory(s);
        return txManager;
    }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        return vendorAdapter;
    }

    public MappingHymanson2HttpMessageConverter HymansonMessageConverter(){
        MappingHymanson2HttpMessageConverter messageConverter = new MappingHymanson2HttpMessageConverter();

        ObjectMapper mapper = new ObjectMapper();
        Hibernate5Module module = new Hibernate5Module();
        module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);

        mapper.registerModule(module);

        messageConverter.setObjectMapper(mapper);
        return messageConverter;

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(HymansonMessageConverter());
        super.configureMessageConverters(converters);
    }
}


@ControllerAdvice
public class GenericRepositoryRestExceptionHandler extends RepositoryRestExceptionHandler {

    @Autowired
    public GenericRepositoryRestExceptionHandler(MessageSource messageSource) {
        super(messageSource);
        // TODO Auto-generated constructor stub
    }

    @ResponseBody
    @ExceptionHandler(Exception.class)
    ResponseEntity<?> handleException(Exception e) {
        //  return response(HttpStatus.CONFLICT, 40902, e.getMessage());
        return response(HttpStatus.CONFLICT, 40902, e.getMessage(), e.getCause() + "", e.getClass() + "");
    }

    private ResponseEntity<RestError> response(HttpStatus status, int code, String msg) {
        return response(status, code, msg, "", "");
    }

    private ResponseEntity<RestError> response(HttpStatus status, int code, String msg, String devMsg, String moreInfo) {
        return new ResponseEntity<>(new RestError(status, code, msg, devMsg, moreInfo, null), status);
    }

}


public class CORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        System.out.println("Filtering on...........................................................");

        SecurityContext ctx = SecurityContextHolder.getContext();
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}

}

回答by Sotirios Delimanolis

Because of this

因为这

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(HymansonMessageConverter());
    super.configureMessageConverters(converters);
}

Spring MVC skips all the default converters it would have otherwise registered. (If you're curious, this is done in WebMvcConfigurationSupport#getMessageConverters(..).)

Spring MVC 会跳过它本来会注册的所有默认转换器。(如果你很好奇,这是在WebMvcConfigurationSupport#getMessageConverters(..).)

Your only HttpMessageConverter, MappingHymanson2HttpMessageConverter, can only read MediaType.APPLICATION_JSONcontent, ie. application/json. Therefore, every other request content type will be rejected.

您唯一的HttpMessageConverter,MappingHymanson2HttpMessageConverter只能阅读MediaType.APPLICATION_JSON内容,即。application/json. 因此,所有其他请求内容类型都将被拒绝。

You can register all the regular defaults yourself in your configureMessageConvertersoverride (or just the ones you need to read HTML forms, XML, plain text, etc.).

您可以自己在configureMessageConverters覆盖中注册所有常规默认值(或者只是您需要阅读 HTML 表单、XML、纯文本等的那些)。

Or, you could instead override extendMessageConvertersto find the default MappingHymanson2HttpMessageConverterinstance and configure it to use your custom ObjectMapper. For example,

或者,您可以改为覆盖extendMessageConverters以查找默认MappingHymanson2HttpMessageConverter实例并将其配置为使用您的自定义ObjectMapper. 例如,

public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    ObjectMapper mapper = new ObjectMapper();
    // ...initialize...

    for (HttpMessageConverter<?> converter : converters) {
        if (converter instanceof MappingHymanson2HttpMessageConverter) {
            MappingHymanson2HttpMessageConverter m = (MappingHymansonHttpMessageConverter) converter;
            m.setObjectMapper(mapper);
        }
    } 
}

And maybe drop a comment about relying on the default list of converters.

并且可能会发表关于依赖默认转换器列表的评论。

回答by Algorini

I realize that the case mentioned in the OP'S problem was slightly different than mine, although I had the same error. In my case, it showed up after I started using a different ObjectMapper.

我意识到 OP'S 问题中提到的案例与我的略有不同,尽管我有同样的错误。就我而言,它是在我开始使用不同的 ObjectMapper 后出现的。

Initially I was using the

最初我使用的是

"com.fasterxml.Hymanson.core.databind" as the ObjectMapper in my project.

“com.fasterxml.Hymanson.core.databind”作为我项目中的ObjectMapper。

I refactored some code, and in the process switched to org.codehaus.Hymanson as the source for the ObjectMapper.

我重构了一些代码,并在此过程中切换到 org.codehaus.Hymanson 作为 ObjectMapper 的源。

That's when all hell broke loose, and Spring would simply reject every request, irrespective of the Content-Type with the same error-message as the OP.

那时一切都乱了套,Spring 会简单地拒绝每个请求,而不管与 OP 具​​有相同错误消息的 Content-Type 是什么。

It took me 3 days to sort this out, but finally I simply switched back to "databind" for the ObjectMapper. Everything worked magically after that.

我花了 3 天的时间来解决这个问题,但最后我只是简单地切换回 ObjectMapper 的“数据绑定”。在那之后,一切都神奇地运作了。

I'm mentioning this here, in case it helps someone. Unline the OP I hadn't touched MessageConverters or anything that complicated.

我在这里提到这一点,以防它对某人有所帮助。取消我没有接触过 MessageConverters 或任何复杂的 OP。