spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.46k stars 38.09k forks source link

Improve documentation regarding encoding in FreeMarker support #33071

Closed Etienne522 closed 3 months ago

Etienne522 commented 4 months ago

Hello! I found an issue with Freemarker support.

The following configuration will use the platform encoding instead of UTF-8 like I expected :

package test.springFreemarker;

import java.nio.charset.StandardCharsets;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;

@Configuration
public class FreeMarkerConfig {

    @Bean
    public FreeMarkerViewResolver freemarkerViewResolver() {
        FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
        resolver.setSuffix(".ftl");
        return resolver;
    }

    @Bean
    public FreeMarkerConfigurer freemarkerConfig() {
        FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
        freeMarkerConfigurer.setDefaultEncoding(StandardCharsets.UTF_8.name());
        freeMarkerConfigurer.setTemplateLoaderPath("classpath:/templates");
        return freeMarkerConfigurer;
    }
}

The resulting HTML page had the line :

<meta http-equiv="content-type" content="text/html; charset=windows-1252">

even though my template had no meta tag.

I had to add

        resolver.setContentType("text/html;charset=UTF-8");

to make it work.

It seems FreeMarkerConfigurer default encoding is ignored because FreeMarkerViewResolver overrides it, even if it was left empty.

The Javadoc of FreeMarkerConfigurer#setDefaultEncoding mentions it will be ignored if it is explicitly specified by FreeMarkerViewResolver, but if it was not explicitly specified, it just uses the default.

Either the behavior needs to be changed, or the Javadoc updated to state that FreeMarkerViewResolver will always override it.

I am using spring-boot-starter-freemarker with Spring Boot 3.2.5.

Thanks again for the great framework, I hope this issue report helps.

sbrannen commented 4 months ago

Hi @Etienne522,

Congratulations on opening your first GitHub issue ever! 👍

The following configuration will use the platform encoding instead of UTF-8 like I expected

Based on my analysis, the default encoding you have configured is used to read the template file.

The resulting HTML page had the line :

<meta http-equiv="content-type" content="text/html; charset=windows-1252">

even though my template had no meta tag.

I am not sure how that <meta> tag ends up in the rendered HTML. Perhaps your application has a component (or template include) that automatically adds that based on some heuristics.

I had to add

resolver.setContentType("text/html;charset=UTF-8");

to make it work.

That's currently to be expected. If you don't specify the content type via the FreeMarkerViewResolver, "text/html;charset=ISO-8859-1" will be used, which is the default set in AbstractView.

Though, since I do not have access to your application, I cannot explain the contents of the <meta> tag you've mentioned.

It seems FreeMarkerConfigurer default encoding is ignored because FreeMarkerViewResolver overrides it, even if it was left empty.

The Javadoc of FreeMarkerConfigurer#setDefaultEncoding mentions it will be ignored if it is explicitly specified by FreeMarkerViewResolver, but if it was not explicitly specified, it just uses the default.

Either the behavior needs to be changed, or the Javadoc updated to state that FreeMarkerViewResolver will always override it.

If you explicitly configure the default encoding via FreeMarkerConfigurer, that encoding will be used to read the template file. If you explicitly configure the encoding via FreeMarkerViewResolver, that encoding will in fact override the default encoding in the FreeMarker Configuration (configured via the FreeMarkerConfigurer).

What can be confusing is the difference between the configured (or default) encoding and the Content-Type header of the HTTP response (and consequently the character encoding used to render the body of the response).

Spring currently does not update the charset of the Content-Type header of the HTTP response to reflect the actual type of encoding used to read the template file and render the body of the response. I will create a new GitHub issue to address that in Spring Framework 6.2 (based on support for checking whether an explicit default encoding has been set, which is available since FreeMarker 2.3.26).

Another potential issue is that Spring does not set the output_encoding in the FreeMarker processing Environment (supported since FreeMarker 2.3.1), and I will create a separate GitHub issue to address that in Spring Framework 6.2 as well.

As for the Javadoc in FreeMarkerConfigurer and FreeMarkerView, I agree that the discussion of encoding is a bit confusing and perhaps misleading. Thus, I will repurpose this issue to improve that documentation in Spring Framework 6.1.x.

sbrannen commented 3 months ago

Current work on this issue can be viewed in the following feature branch.

sbrannen commented 3 months ago

Another potential issue is that Spring does not set the output_encoding in the FreeMarker processing Environment (supported since FreeMarker 2.3.1), and I will create a separate GitHub issue to address that in Spring Framework 6.2 as well.

sbrannen commented 3 months ago

Hi @Etienne522 (and anyone else interested),

I overhauled our FreeMarker documentation regarding encoding in d133ab60ee81582826af7e68dbf8a060e37d267e.

I had to add

        resolver.setContentType("text/html;charset=UTF-8");

Please note that as of Spring Framework 6.2, that workaround will no longer be required. For details see:

And thanks to #33102, freeMarkerConfigurer.setDefaultEncoding(StandardCharsets.UTF_8.name()) from your example can now be replaced with freeMarkerConfigurer.setDefaultCharset(StandardCharsets.UTF_8).

Feel free to check out the updated documentation or try out any of the new features and let us know if you run into any issues.

Thanks,

Sam