Uploaded image for project: 'Spring Framework'
  1. Spring Framework
  2. SPR-15866

Jackson2JsonEncoder and Jackson2JsonDecoder should use provided mime types

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Minor
    • Resolution: Complete
    • Affects Version/s: 5.0 RC3
    • Fix Version/s: 5.0 RC4
    • Component/s: Web
    • Labels:
    • Last commented by a User:
      true

      Description

      I'm using Webclient to consume a webservice that uses a legacy mime type for JSON: text/javascript.

      This works when using the Webclient builder.

      @Autowired
      public ItunesAlbumServiceImpl(ObjectMapper mapper) {
          ExchangeStrategies strategies = ExchangeStrategies.builder().codecs(clientCodecConfigurer ->
              clientCodecConfigurer.customCodecs().decoder(
                      new Jackson2JsonDecoder(mapper,
                              new MimeType("text", "javascript", StandardCharsets.UTF_8)))
          ).build();
       
          webClient = WebClient.builder()
                  .exchangeStrategies(strategies)
                  .baseUrl("https://itunes.apple.com")
                  .build();
      }
      

      But when trying to configure this on application level it doesn't work.

      @SpringBootApplication
      public class SpringReactiveApplication {
       
          public static void main(String[] args) {
              SpringApplication.run(SpringReactiveApplication.class, args);
          }
       
          @Bean
          public CodecCustomizer jacksonLegacyJsonCustomizer(ObjectMapper mapper) {
              return (configurer) -> {
                  MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
                  CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs();
                  customCodecs.decoder(
                          new Jackson2JsonDecoder(mapper, textJavascript));
                  customCodecs.encoder(
                          new Jackson2JsonEncoder(mapper, textJavascript));
              };
          }
      }
      

      While stepping through the code I noticed that the Jackson2JsonDecoder.getDecodableMimeTypes() and Jackson2JsonEncoder.getEncodableMimeTypes() methods always return the default JSON mime types, not the one(s) provided as constructor argument.

      public class Jackson2JsonDecoder extends AbstractJackson2Decoder {
       
          public Jackson2JsonDecoder() {
              super(Jackson2ObjectMapperBuilder.json().build());
          }
       
          public Jackson2JsonDecoder(ObjectMapper mapper, MimeType... mimeTypes) {
              super(mapper, mimeTypes);
          }
       
          @Override
          public List<MimeType> getDecodableMimeTypes() {
              return JSON_MIME_TYPES;
          }
      }
      

      Causing Jackson2CodecSupport.supportsMimeType to return false for the provided mime types.

      Looks related to SPR-15474

      I created this PR with a proposed change: 1499

        Activity

        Hide
        rstoya05-aop Rossen Stoyanchev added a comment - - edited

        hi, small correction. The Jackson2CodecSupport.supportsMimeType method does refer to the list of configured mime types. It's getDecodableMimeTypes and getEncodableMimeTypes that are effectively hard-coded. As a result the BodyExtractors on the client side and the argument resolver on the server side do not perform content negotiation correctly, which is why I also don't follow the comment that it works when using the WebClient.Builder.

        Regardless of this, there is an actual issue and the fix looks right. I will however adjust the tests to verify what is actually failing.

        Show
        rstoya05-aop Rossen Stoyanchev added a comment - - edited hi, small correction. The Jackson2CodecSupport.supportsMimeType method does refer to the list of configured mime types. It's getDecodableMimeTypes and getEncodableMimeTypes that are effectively hard-coded. As a result the BodyExtractors on the client side and the argument resolver on the server side do not perform content negotiation correctly, which is why I also don't follow the comment that it works when using the WebClient.Builder. Regardless of this, there is an actual issue and the fix looks right. I will however adjust the tests to verify what is actually failing.
        Hide
        rlindooren Ricardo Lindooren added a comment - - edited

        small correction. The Jackson2CodecSupport.supportsMimeType method does refer to the list of configured mime types

        Yes, this was an incorrect observation by me. I modified my pull request comment but forgot to update it here as well.

        Show
        rlindooren Ricardo Lindooren added a comment - - edited small correction. The Jackson2CodecSupport.supportsMimeType method does refer to the list of configured mime types Yes, this was an incorrect observation by me. I modified my pull request comment but forgot to update it here as well.
        Hide
        rlindooren Ricardo Lindooren added a comment -

        I found out why configuration on application level didn't work for me.

        This configuration:

        @Bean
        public CodecCustomizer jacksonLegacyJsonCustomizer(ObjectMapper mapper) {
            return (configurer) -> {
                MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
                CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs();
                customCodecs.decoder(
                        new Jackson2JsonDecoder(mapper, textJavascript));
                customCodecs.encoder(
                        new Jackson2JsonEncoder(mapper, textJavascript));
            };
        }
        

        Is picked up by WebClientAutoConfiguration when it configures a base WebClient.Builder
        and then exposes the following bean configuration:

        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        public WebClient.Builder webClientBuilder(List<WebClientCustomizer> customizers) {
            return this.webClientBuilder.clone();
        }
        

        So that bean must be used when creating a new Webclient instance.
        This then works:

        @Autowired
        public ItunesAlbumServiceImpl(WebClient.Builder webclientBuilder) {
            webClient = webclientBuilder.baseUrl("https://itunes.apple.com").build();
        }
        

        Show
        rlindooren Ricardo Lindooren added a comment - I found out why configuration on application level didn't work for me. This configuration: @Bean public CodecCustomizer jacksonLegacyJsonCustomizer(ObjectMapper mapper) { return (configurer) -> { MimeType textJavascript = new MimeType( "text" , "javascript" , StandardCharsets.UTF_8); CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs(); customCodecs.decoder( new Jackson2JsonDecoder(mapper, textJavascript)); customCodecs.encoder( new Jackson2JsonEncoder(mapper, textJavascript)); }; } Is picked up by WebClientAutoConfiguration when it configures a base WebClient.Builder and then exposes the following bean configuration: @Bean @Scope ( "prototype" ) @ConditionalOnMissingBean public WebClient.Builder webClientBuilder(List<WebClientCustomizer> customizers) { return this .webClientBuilder.clone(); } So that bean must be used when creating a new Webclient instance. This then works: @Autowired public ItunesAlbumServiceImpl(WebClient.Builder webclientBuilder) { webClient = webclientBuilder.baseUrl( "https://itunes.apple.com" ).build(); }

          People

          • Assignee:
            rstoya05-aop Rossen Stoyanchev
            Reporter:
            rlindooren Ricardo Lindooren
            Last updater:
            St├ęphane Nicoll
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:
              Days since last comment:
              5 weeks, 4 days ago