Micronaut Views

Provides integration between Micronaut and server-side views technologies

Version: 1.3.0.BUILD-SNAPSHOT

1 Introduction

This project integrates Micronaut and Server Side View Rendering.

2 Server Side View Rendering

Although Micronaut is primarily designed around message encoding / decoding there are occasions where it is convenient to render a view on the server side.

The views module provides support for view rendering on the server side and does so by rendering views on the I/O thread pool in order to avoid blocking the Netty event loop.

To use the view rendering features described in this section, add the following dependency on your classpath.

compile 'io.micronaut:micronaut-views'
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-views</artifactId>
</dependency>

For file-based view schemes, views and templates can be placed in the src/main/resources/views directory of your project. If you use this feature and wish to use a different folder, set the property micronaut.views.folder.

Your controller’s method can render the response with a template with the the View annotation.

The following is an example of a controller which renders a template by passing a model as a java.util.Map via the returned response object.

src/main/java/myapp/ViewsController.java
@Controller("/views")
class ViewsController {

    @View("home")
    @Get("/")
    public HttpResponse index() {
        return HttpResponse.ok(CollectionUtils.mapOf("loggedIn", true, "username", "sdelamo"))
    }

}
1 Use @View annotation to indicate the view name which should be used to render a view for the route.

In addition, you can return any POJO object and the properties of the POJO will be exposed to the view for rendering:

src/main/java/myapp/ViewsController.java
@Controller("/views")
class ViewsController {

    @View("home")
    @Get("/pogo")
    public HttpResponse<Person> pogo() {
        return HttpResponse.ok(new Person("sdelamo", true))
    }

}
1 Use @View annotation to indicate the view name which should be used to render the POJO responded by the controller.

Use the @Introspected annotation on your POJO object to avoid reflection at runtime (It will also fix a problem with OpenJ9 jvm.)

You can also return a ModelAndView and skip specifying the View annotation.

src/main/java/myapp/ViewsController.java
@Controller("/views")
class ViewsController {

    @Get("/modelAndView")
    ModelAndView modelAndView() {
        return new ModelAndView("home",
                new Person(loggedIn: true, username: 'sdelamo'))
    }

You can also provide implementations of ViewsModelDecorator to decorate the model with extra properties.

The following sections show different template engines integrations.

To create your own implementation create a class which implements ViewsRenderer and annotate it with @Produces to the media types the view rendering supports producing.

2.1 Thymeleaf

Micronaut includes ThymeleafViewsRenderer which uses the Thymeleaf Java template engine.

In addition to the Views dependency, add the following dependency on your classpath.

runtime 'org.thymeleaf:thymeleaf:3.0.11.RELEASE'
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.11.RELEASE</version>
    <scope>runtime</scope>
</dependency>

Thymeleaf integration instantiates a ClassLoaderTemplateResolver.

The properties used can be customized by overriding the values of:

🔗
Table 1. Configuration Properties for ThymeleafViewsRendererConfigurationProperties
Property Type Description

micronaut.views.thymeleaf.enabled

boolean

Sets whether thymeleaf rendering is enabled. Default value (true).

micronaut.views.thymeleaf.character-encoding

java.lang.String

Sets the character encoding to use. Default value ("UTF-8").

micronaut.views.thymeleaf.template-mode

org.thymeleaf.templatemode.TemplateMode

Sets the template mode.

micronaut.views.thymeleaf.suffix

java.lang.String

Sets the suffix to use.

micronaut.views.thymeleaf.force-suffix

boolean

Sets whether to force the suffix. Default value (false).

micronaut.views.thymeleaf.force-template-mode

boolean

Sets whether to force template mode. Default value (false).

micronaut.views.thymeleaf.cacheable

boolean

Sets whether templates are cacheable.

micronaut.views.thymeleaf.cache-ttlms

java.lang.Long

Sets the cache TTL in millis.

micronaut.views.thymeleaf.check-existence

boolean

Sets whether templates should be checked for existence.

micronaut.views.thymeleaf.cache-ttl

java.time.Duration

Sets the cache TTL as a duration.

The example shown in the Views section, could be rendered with the following Thymeleaf template:

src/main/resources/views/home.html
<!DOCTYPE html>
<html lang="en" th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home</title>
</head>
<body>
<section>
    <h1 th:if="${loggedIn}">username: <span th:text="${username}"></span></h1>
    <h1 th:unless="${loggedIn}">You are not logged in</h1>
</section>
</body>
</html>

and layout:

src/main/resources/views/layoutFile.html
<!DOCTYPE html>
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:replace="${title}">Layout Title</title>
</head>
<body>
<h1>Layout H1</h1>
<div th:replace="${content}">
    <p>Layout content</p>
</div>
<footer>
    Layout footer
</footer>
</body>
</html>

2.2 Handlebars.java

Micronaut includes HandlebarsViewsRenderer which uses the Handlebars.java project.

In addition to the Views dependency, add the following dependency on your classpath. For example, in build.gradle

runtime 'com.github.jknack:handlebars:4.1.0'
<dependency>
    <groupId>com.github.jknack</groupId>
    <artifactId>handlebars</artifactId>
    <version>4.1.0</version>
    <scope>runtime</scope>
</dependency>

The example shown in the Views section, could be rendered with the following Handlebars template:

src/main/resources/views/home.hbs
<!DOCTYPE html>
<html>
<head>
    <title>Home</title>
</head>
<body>
    {{#if loggedIn}}
    <h1>username: <span>{{username}}</span></h1>
    {{else}}
    <h1>You are not logged in</h1>
    {{/if}}
</body>
</html>

2.3 Apache Velocity

Micronaut includes VelocityViewsRenderer which uses the Apache Velocity Java-based template engine.

In addition to the Views dependency, add the following dependency on your classpath.

runtime 'org.apache.velocity:velocity-engine-core:2.0'
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.0</version>
    <scope>runtime</scope>
</dependency>

The example shown in the Views section, could be rendered with the following Velocity template:

src/main/resources/views/home.vm
<!DOCTYPE html>
<html>
<head>
    <title>Home</title>
</head>
<body>
    #if( $loggedIn )
    <h1>username: <span>$username</span></h1>
    #else
    <h1>You are not logged in</h1>
    #end
</body>
</html>

2.4 Apache Freemarker

Micronaut includes FreemarkerViewsRenderer which uses the Apache Freemarker Java-based template engine.

In addition to the Views dependency, add the following dependency on your classpath.

runtime 'org.freemarker:freemarker:2.3.28'
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.28</version>
    <scope>runtime</scope>
</dependency>

The example shown in the Views section, could be rendered with the following Freemarker template:

src/main/resources/views/home.ftl
<#--

    Copyright 2017-2019 original authors

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

-->
<!DOCTYPE html>
<html>
<head>
    <title>Home</title>
</head>
<body>
    <#if loggedIn??>
    <h1>username: <span>${username}</span></h1>
    <#else>
    <h1>You are not logged in</h1>
    </#if>
</body>
</html>

Freemarker integration instantiates a freemarker Configuration.

All configurable properties are extracted from Configuration and Configurable, and properties names are reused in the Micronaut configuration.

If a value is not declared and is null, the default configuration from Freemarker is used. The expected format of each value is the same from Freemarker, and no conversion or validation is done by Micronaut. You can find in Freemarker documentation how to configure each one.

2.5 Soy/Closure Support

Micronaut includes SoySauceViewsRenderer which uses Soy, also known as Closure Templates, a template compiler from Google. Soy is usable standalone, or together with the rest of the Closure Tools ecosystem, and targets both server-side and client-side, with the ability to compile templates into Java, Python, or JavaScript.

In addition to the Views dependency, add the following dependency.

compile 'com.google.template:soy:2019-09-03'
<dependency>
    <groupId>com.google.template</groupId>
    <artifactId>soy</artifactId>
    <version>2019-09-03</version>
</dependency>

The example shown in the Views section, could be rendered with the following Soy template:

src/main/resources/home.soy
// Copyright 2017-2019 original authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

{namespace sample}


// Sample template.
{template .home}
  {@param loggedIn: bool}  // Whether the user is logged in.
  {@param? username: string}  // The user's username, if they are logged in.

  <!DOCTYPE html>
  <html lang="en">
  <head>
    <title>Home</title>
    <script type="text/javascript">alert("Hello, CSP!");</script>
  </head>
  <body>
  {if $loggedIn}
      <h1>username: <span>{$username}</span></h1>
  {else}
      <h1>You are not logged in</h1>
  {/if}
  </body>
  </html>
{/template}

Usage

Soy integration is invoked by providing a bean that complies with SoyFileSetProvider. The object that complies with this interface is responsible for loading the Soy templates, either in compiled or source form.

Both server-side Soy rendering layers are supported in Micronaut:

  • SoySauceViewsRenderer: This renderer uses templates pre-compiled to Java bytecode, generally AOT, with SoyToJbcSrcCompiler. If compiled templates can’t be located by the SoyFileSetProvider, templates are pre-compiled into bytecode at server startup. This can be impactful on startup-time, so, if that’s an important metric for your app, pre-compile your templates using the AOT bytecode compiler.

  • SoyTofuViewsRenderer (Deprecated): This renderer is supported for compatibility and toy uses, but it’s deprecated in Soy Java, so it’s deprecated here. SoyTofu renders templates from sources.

Which renderer is active depends on the Micronaut configuration:

micronaut:
  views:
    soy:
      enabled: true
      engine: sauce  # one of `sauce` or `tofu`

After wiring up your SoyFileSetProvider, and configuring Micronaut, you can render templates like so:

@Validated
@Controller("/")
public class HelloWorld {
  @Get(value = "/soy", produces = MediaType.TEXT_HTML)
  @View("some.soy.template.path")
  public HttpResponse soyDemo() {
    // return template context
    return HttpResponse.ok(ImmutableMap.of("username", "jdoe", "loggedIn", true));
  }
}

The return value is converted to Soy template context parameters, and passed to the @View-annotation-bound template.

Configuration

The entire set of supported Soy configuration properties are documented below:

Configuration property

Type

Default value

micronaut.views.soy.enabled

Boolean

false

micronaut.views.soy.engine

string (one of sauce or tofu)

sauce (tofu is deprecated)

micronaut.views.soy.renaming

Boolean

false

Features: Template Security

Soy/Closure Templates has seen significant work from Google where security is concerned, both in the browser and on the backend. Soy is extremely strict and strongly typed, with validation being performed by Soy and then subsequently either by javac or Closure Compiler, and additionally in some cases at render-time. What follows is a brief guide of these security features. These are outlined in more detail over in the Closure Templates docs.

Front-end Security

  • Trusted URIs: Soy is smart enough to know that scriptUrl might be influenced by user input, and, therefore, introduces an XSS vulnerability when used as a script src. To inject resources, use uri:

    /* I won't compile because I introduce a vulnerability */
    {template .foo}
      {@param scriptUrl: string}
      <script src={$scriptUrl}></script>
    {/template}
    /* I will compile because I accept a URI type, which is trusted in this context */
    {template .foo}
      {@param scriptUrl: uri}
      <script src={$scriptUrl}></script>
    {/template}
  • Markup: Passing in HTML for content won’t work if it’s a string. Soy will comply and inject your content, but it will be escaped. To inject markup, use the strict markup types (js, css, html).

    /* I will compile, but I may escape the injected content if it contains markup. */
    {template .foo}
      {@param content: string}
      <b>{$content}</b>
    {/template}
    /* I allow markup because the appropriate types are used */
    {template .foo}
      {@param content: html}
      <b>{$content}</b>
    {/template}

Back-end Security

  • CSP Nonce Support: Soy has support for Content Security Policy (Level 3), specifically, embedding server-generated nonce attributes in <script> tags. This is accomplished by providing an "injected value" with the key "csp_nonce". The nonce, which should change on each page load, is available in the template like so:

    /* Soy will inject the nonce for you, but if you need it anyway, this is how you access it. */
    {template .foo}
      {@param injectedScript: uri}
      {@inject csp_nonce: string}
      <script src={$injectedScript} nonce={$csp_nonce} type="text/javascript"></script>
    {/template}

Features: Renaming

Soy has powerful built-in renaming features, that both obfuscate and optimize your code as it is rendered. Renaming is opt-in and requires a few things:

  • A compatible CSS compiler (GSS / Closure Stylesheets is a good one)

  • Special calls in your templates that map CSS classes and IDs

  • JSON renaming map provided at compile time for JS templates, and runtime for Java rendering

Renaming is similar to other frameworks' correponding uglify features, but it’s significantly more powerful, allowing you to rewrite CSS classes and IDs as you would JavaScript symbols. Here’s how you can use renaming server-side with Micronaut:

  1. Configure Micronaut to enable renaming:

    micronaut:
      views:
        soy:
          enabled: true
          renaming: true
  2. When building your styles with GSS, or a similar tool, generate a rewrite map:

    > java -jar closure-stylesheets.jar \
        --output-renaming-map-format JSON \
        --output-renaming-map src/main/resources/renaming-map.json \
        --rename CLOSURE \
        [...inputs...] > src/main/resources/styles/app-styles.min.css
  3. In your template sources, annotate CSS classes with {css('name')} calls. Note that the value passed into each call must be a string literal (variables cannot be used):

    {template .foo}
      <div class="{css('my-cool-class')} {css('my-cool-class-active')}">
        ... content, and stuff...
      </div>
    {/template}
  4. In your CSS, use the names you mentioned in your template:

    .my-cool-class {
      color: blue;
    }
    .my-cool-class-active {
      background: yellow;
    }
  5. Compile your templates (see: Building Soy).

    > java -jar SoyToJbcSrcCompiler.jar \
        --output templates.jar \
        --srcs [...templates...];

The last step is to provide the renaming map to Micronaut:

@Singleton
public class RewriteMapProvider implements SoyNamingMapProvider {
  /** Filename for the JSON class renaming map. */
  private static final String RENAMING_MAP_NAME = "renaming-map.json";

  /** Naming map to use for classes. */
  private static SoyCssRenamingMap CSS_RENAMING_MAP = null;

  /**
   * Load JSON embedded in a JAR resource into a naming map for Soy rendering.
   *
   * @param mapPath URL to the JAR resource we should load.
   */
  @Nullable
  private static Map<String, String> loadMapFromJSON(URL mapPath) {
    try {
      return new ObjectMapper().readValue(mapPath, new TypeReference<Map<String, String>>() { });
    } catch (Throwable thr) {
      //handle `JsonMappingException` and `IOException`, if, for instance, you're using Jackson
      throw new RuntimeException(thr);
    }
  }

  static {
    final URL mapPath =
        SoyRenderConfigProvider.class.getClassLoader().getResource(RENAMING_MAP_NAME);
    if (mapPath != null) {
      // load the renaming map
      final Map<String, String> cssRenamingMap = loadMapFromJSON(mapPath);
      if (renamingMapRaw != null) {
        CSS_RENAMING_MAP = new SoyCssRenamingMap() {
          @Nullable @Override
          public String get(@NotNull String className) {
            // (or whatever logic you need to rewrite the class)
            return cssRenamingMap.get(className);
          }
        };
      }
    }
  }

  /**
   * Provide a CSS renaming map to Soy/Micronaut.
   *
   * @return Inflated Soy CSS renaming map.
   */
  @Nullable @Override public SoyCssRenamingMap cssRenamingMap() {
    return CSS_RENAMING_MAP;
  }
}

Then, your output will be renamed. Referencing the Soy template sample above, output would look something like this:

<div class="a-b-c a-b-c-d">... content, and stuff...</div>

With your CSS rewritten and minified to match:

.a-b-c{color:blue;}.a-b-c-d{background:yellow;}

Building Soy

Soy has tooling support from Node (Gulp, Grunt), Maven, Gradle, and Bazel. You can also invoke each Soy compiler directly via the runner classes for each one:

  • SoyHeaderCompiler: Compile templates into light "headers," that can be used downstream as dependencies for other templates.

  • SoyMsgExtractor: Enables easy i18n by extracting {msg desc=""}{/msg} declarations for processing or translation.

  • SoyParseInfoGenerator: Generates template parser metadata information as Java sources.

  • SoyPluginValidator: Validates SoySourceFunction definitions found in a set of JARs.

  • SoyToIncrementalDomSrcCompiler: Generate client-side templates that render with IncrementalDOM (AKA idom). This compiler uses direct calls into the DOM to incrementally render content, as opposed to the traditional client-side approach, which renders strings into element.innerHTML. This support is currently experimental and involves a number of external dependencies.

  • SoyToJbcSrcCompiler: Compiles Soy templates directly to Java bytecode, packaged up in a JAR. These templates can be used together with Soy Java for highly performant server-side rendering.

  • SoyToJsSrcCompiler: Traditional JS client-side compiler, which assembles strings of rendered template content. Like idom, these compiled templates work well with Closure Compiler, and Closure Library via goog.soy.

  • SoyToPySrcCompiler: Compiles templates into Python sources for use server-side.

2.6 Content Security Policy

Micronaut supports CSP (Content Security Policy, Level 3) out of the box. By default, CSP is disabled. To enable CSP, modify your configuration. For example:

application.yml
micronaut:
  views:
    csp:
      enabled: true

See the following table for all configuration options:

🔗
Table 1. Configuration Properties for CspConfiguration
Property Type Description

micronaut.views.csp.enabled

boolean

Sets whether CSP is enabled. Default false.

micronaut.views.csp.policy-directives

java.lang.String

Sets the policy directives.

micronaut.views.csp.report-only

boolean

If true, the Content-Security-Policy-Report-Only header will be sent instead of Content-Security-Policy. Default false.

micronaut.views.csp.generate-nonce

boolean

If true, the CSP header will contain a generated nonce that is made available to view renderers. The nonce should change for each request/response cycle and can be used by views to authorize inlined script blocks.

micronaut.views.csp.filter-path

java.lang.String

Sets the path the CSP filter should apply to. Default value "/**".

micronaut.views.csp.force-secure-random

boolean

Sets whether SecureRandom is forced for use in generated nonce values. Defaults to false. Enabling this requires careful consideration, because SecureRandom will block infinitely without enough entropy.

micronaut.views.csp.random-engine

java.util.Random

Sets the Random data engine used to generate nonce values. Ignored if forceSecureRandom is set to true.

Nonce Support

At the developer’s option, Micronaut can generate a nonce with each render cycle. This nonce value can be used in script-src and style-src directives in a CSP response header (note that nonce values generally do not have any effect when Content Security Policy is set via a <meta http-equiv> tag).

To opt-in to this behavior, configure Micronaut with generateNonce set to true. Additionally, provide a spot for the nonce value in your CSP directives, with the token {#nonceValue}. It must be preceded by nonce- and wrapped in single quotes, as per the CSP3 spec:

application.yml
micronaut:
  views:
    csp:
      enabled: true
      generateNonce: true
      policyDirectives: "default-src https: self:; script-src 'nonce-{#nonceValue}';"

That’s it! After applying the above configuration, HTTP responses might include a header that look like this:

Content-Security-Policy: default-src https: self:; script-src 'nonce-4ze2IRazk4Yu/j5K6SEzjA';

Inline scripts which aren’t otherwise whitelisted will be declined for execution, unless CSP is operating in report-only mode. Inline scripts can be whitelisted with the syntax:

<script type="text/javascript" nonce="4ze2IRazk4Yu/j5K6SEzjA">
  // some javascript code here
</script>

2.7 Security Integration

The views project has integration with the Micronaut security project. SecurityViewModelProcessor is enabled by default and injects the current username in the view. The following properties allow you to customize the injection:

🔗
Table 1. Configuration Properties for SecurityViewModelProcessorConfigurationProperties
Property Type Description

micronaut.security.views-model-decorator.enabled

boolean

Enable {@link SecurityViewModelProcessor}. Default value (true).

micronaut.security.views-model-decorator.security-key

java.lang.String

Model key name. Default value ("security").

micronaut.security.views-model-decorator.principal-name-key

java.lang.String

Nested security map key for the user’s name property. Default value ("name").

micronaut.security.views-model-decorator.attributes-key

java.lang.String

Nested security map key for the user’s attributes property. Default value ("attributes").

In a controller, you can return a model without specifying in the model the authenticated user:

src/main/java/myapp/BooksController.java
@Controller("/")
public class BooksController {

    @Secured(SecurityRule.IS_AUTHENTICATED)
    @View("securitydecorator")
    @Get
    public Map<String, Object> index() {
        Map<String, Object> model = new HashMap<>();
        model.put("books", Arrays.asList("Developing Microservices"));
        return model;
    }
}

and still access the authenticated user in the view (for example a velocity template):

src/main/resources/views/books.vm
<!DOCTYPE html>
<html>
<head>
    <title>User Books</title>
</head>
<body>
    #if( $security )
      <h1>User: ${security['name']} email: ${security['attributes']['email']}</h1>
    #end

    #foreach($book in $books)
      $book
    #end

    #if( $securitycustom )
    <h1>Custom: ${securitycustom['name']}</span></h1>
    #end

</body>
</html>

You can access information about the current user with the security map.

2.8 GraalVM Support

Micronaut supports GraalVM and starting in 1.2.0 special configuration files have been added to make easier the native image creation.

The first change needed is to add the dependency of the view technology used instead of the generic micronaut-views. For example, when using Thymeleaf, instead of defining this dependency:

compile 'io.micronaut:micronaut-views'
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-views</artifactId>
</dependency>

Use this:

compile 'io.micronaut:micronaut-views-thymeleaf'
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-views-thymeleaf</artifactId>
</dependency>

Then it is necessary to tell Graal to include all the static resources in the native image. To do that edit the file native-image.properties included in the project when created with the graal-native-image feature and add the necessary directories:

src/main/resources/META-INF/native-image/example.micronaut/mn-graal-application/native-image.properties
Args = -H:IncludeResources=logback.xml|application.yml|views/.* \ (1)
       -H:Name=mn-graal-application \
       -H:Class=example.micronaut.Application
1 Include all static resources in views directory in the native-image.
If you want to serve static resources like html, css and javascript files with Micronaut it is also necessary to include the public directory in the resources.
Learn more about Micronaut’s GraalVM support.