implementation("io.micronaut:micronaut-views")
Micronaut Views
Provides integration between Micronaut and server-side views technologies
Version:
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.
<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.
@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:
@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.
@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.
If you want to render a template directly on your code (for example, to generate the body of an email) you can inject the ViewsRenderer bean and use its method "render". |
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:
Property | Type | Description |
---|---|---|
|
boolean |
Sets whether thymeleaf rendering is enabled. Default value (true). |
|
java.lang.String |
Sets the character encoding to use. Default value ("UTF-8"). |
|
org.thymeleaf.templatemode.TemplateMode |
Sets the template mode. |
|
java.lang.String |
Sets the suffix to use. |
|
boolean |
Sets whether to force the suffix. Default value (false). |
|
boolean |
Sets whether to force template mode. Default value (false). |
|
boolean |
Sets whether templates are cacheable. |
|
java.lang.Long |
Sets the cache TTL in millis. |
|
boolean |
Sets whether templates should be checked for existence. |
|
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:
<!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:
<!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>
Use Thymeleaf templates with Micronaut i18n messages. Internationalization messages will be resolved from Micronaut’s MessageSource automatically. For more information on the MessageSource, see the core documentation.
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:
<!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:
<!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:
<#--
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 Rocker
Micronaut includes RockerEngine which uses the Rocker Java-based template engine.
Add the Views Rocker dependency to your classpath.
The example shown in the Views section, could be rendered with the following Rocker template:
<html>
<head>
<title>Home</title>
</head>
<body>
@if (loggedIn != null && loggedIn) {
<h1>username: <span>@username</span></h1>
} else {
<h1>You are not logged in</h1>
}
</body>
</html>
Compiling Templates
Rocker templates must be precompiled at build time. This can be done either by a Gradle or Maven plugin.
plugins {
id "com.fizzed.rocker" version "1.2.3"
}
sourceSets {
main {
rocker {
srcDir('src/main/resources')
}
}
}
<build>
<plugins>
<plugin>
<groupId>com.fizzed</groupId>
<artifactId>rocker-maven-plugin</artifactId>
<version>1.2.3</version>
<executions>
<execution>
<id>generate-rocker-templates</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<templateDirectory>src/main/resources</templateDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Compile Time Type Checking
Rocker templates are compiled at build time giving the ability to statically type check template arguments.
Using the standard @View
annotation will invoke the template dynamically without these checks.
Instead the compiled template should be invoked within the controller and a RockerWritable
returned.
import io.micronaut.views.rocker.RockerWritable;
import views.index;
...
@Get("/")
@Produces(TEXT_HTML)
public HttpResponse<?> staticTemplate() {
return ok(new RockerWritable(index.template("World", true)));
}
...
Configuration
The properties used can be customized by overriding the values of:
Property | Type | Description |
---|---|---|
|
boolean |
Whether Rocker views are enabled. Default value (true). |
|
java.lang.String |
Sets the default extension to use for Rocker templates. Default value ("rocker.html"). |
|
boolean |
Whether hot reloading is enabled. Default value (false). |
|
boolean |
Whether relaxed binding is enabled for dynamic templates. Default value (false). |
2.6 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.
implementation("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:
// 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, withSoyToJbcSrcCompiler
. If compiled templates can’t be located by theSoyFileSetProvider
, 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 |
|
|
|
|
|
|
|
|
|
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 scriptsrc
. To inject resources, useuri
:/* 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 astring
. 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:
-
Configure Micronaut to enable renaming:
micronaut: views: soy: enabled: true renaming: true
-
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
-
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}
-
In your CSS, use the names you mentioned in your template:
.my-cool-class { color: blue; } .my-cool-class-active { background: yellow; }
-
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 intoelement.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 viagoog.soy
. -
SoyToPySrcCompiler: Compiles templates into Python sources for use server-side.
2.7 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:
micronaut:
views:
csp:
enabled: true
See the following table for all configuration options:
Property | Type | Description |
---|---|---|
|
boolean |
Sets whether CSP is enabled. Default false. |
|
java.lang.String |
Sets the policy directives. |
|
boolean |
If true, the Content-Security-Policy-Report-Only header will be sent instead of Content-Security-Policy. Default false. |
|
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. |
|
java.lang.String |
Sets the path the CSP filter should apply to. Default value "/**". |
|
boolean |
Sets whether |
|
java.util.Random |
Sets the |
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:
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.8 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:
Property | Type | Description |
---|---|---|
|
boolean |
Enable {@link SecurityViewModelProcessor}. Default value (true). |
|
java.lang.String |
Model key name. Default value ("security"). |
|
java.lang.String |
Nested security map key for the user’s name property. Default value ("name"). |
|
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:
@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):
<!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.9 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:
implementation("io.micronaut:micronaut-views")
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-views</artifactId>
</dependency>
Use this:
implementation("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:
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. |
3 Repository
You can find the source code of this project in this repository: