Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Reactive Soy #26

Closed
wants to merge 33 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8376118
Feature: Soy Views Renderer
sgammon Aug 22, 2019
fd35a07
Support for SoySauce-based rendering
sgammon Aug 22, 2019
dbaff2b
Upgrade PRNG to faster implementation
sgammon Aug 25, 2019
9914dca
Cleanup after PRNG switchover
sgammon Aug 25, 2019
86797e8
Remove Soy patch, upgrade -> 2019-09-03
sgammon Sep 4, 2019
49656f7
Cleanup docstrings, copyright headers
sgammon Sep 4, 2019
ce87dcf
Fix version tags for views-soy (since: 1.2.1)
sgammon Sep 4, 2019
a5392b8
Remove DSI util - rely on normal Random
sgammon Sep 4, 2019
7b8b8eb
Remove Soy patch in CI
sgammon Sep 4, 2019
b0bea4d
Remove Soy runtime dependencies
sgammon Sep 4, 2019
7e656ff
Add coverage to Travis via Codecov
sgammon Sep 4, 2019
33944d7
Invoke jacocoFullReport in CI
sgammon Sep 4, 2019
8094791
Remove custom repositories in views-soy
sgammon Sep 4, 2019
dec5d05
Add documentation for Soy with Micronaut
sgammon Sep 4, 2019
b6d70ba
Support injection of CSS/XID renaming maps
sgammon Aug 24, 2019
a0ba048
Finish up docs, general cleanup for Soy
sgammon Sep 5, 2019
90cc831
Add docs for CSP nonce generation
sgammon Sep 5, 2019
f471e3b
Add configurable `Random` engine, and flag to `forceSecureRandom`
sgammon Sep 5, 2019
336ec76
Fixup copyright in SoyNamingMapProvider
sgammon Sep 5, 2019
f5b55eb
Feature: Asynchronous Views
sgammon Aug 29, 2019
3a1ca78
Fix response type with cast
sgammon Sep 4, 2019
73577d2
Fix copyright headers in added files for async render
sgammon Sep 5, 2019
841ff99
Merge branch 'feature/soy'
sgammon Sep 5, 2019
6f94cc1
Merge branch 'feature/csp-nonce'
sgammon Sep 5, 2019
5a14275
Feature: Soy CSP Integration
sgammon Aug 22, 2019
c54490f
Merge branch 'feature/async'
sgammon Sep 5, 2019
e7bdafa
Convert to ByteBuf from Writable for chunked payloads
sgammon Sep 10, 2019
0f4f7bf
Feature: Reactive Soy
sgammon Nov 13, 2019
3e9cf5f
Merge branch 'master' into soy/async-chunked
sgammon Nov 13, 2019
c600bf8
Reject responses for Soy render that don't have status code 200
sgammon Nov 13, 2019
c3444f7
Merge branch 'soy/async-chunked' of github.com:bloombox/micronaut-vie…
sgammon Nov 13, 2019
8a82029
Bigger chunk sizes, swap chunk if write exceeds size
sgammon Nov 13, 2019
99470af
Remove buffer max
sgammon Nov 13, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Finish up docs, general cleanup for Soy
sgammon committed Sep 5, 2019
commit a0ba048d41a836581b6a40d1c4acf8c4b73ef616
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ developers=Graeme Rocher
# Needed for documentation generation. Needs to be Grails 3.2.x not 3.3.x
grailsVersion=3.2.9
testsviewsThymeleaf=views-thymeleaf/src/test
testsviewsSoy=views-soy/src/test
testsviewsHandlebars=views-handlebars/src/test
testsviewsVelocity=views-velocity/src/test
testsviewsFreemarkers=views-freemarker/src/test
4 changes: 2 additions & 2 deletions src/main/docs/guide/toc.yml
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ views:
handlebars: Handlebars.java
velocity: Apache Velocity
freemarker: Apache Freemarker
soy: Soy/Closure Support
csp: CSP Header Support
views-security: Security Integration
graal: GraalVM Support
soy: Soy/Closure Support
graal: GraalVM Support
217 changes: 173 additions & 44 deletions src/main/docs/guide/views/soy.adoc
Original file line number Diff line number Diff line change
@@ -12,11 +12,13 @@ dependency:soy[groupId="com.google.template",scope="compile",version="2019-09-03
The example shown in the <<views, Views>> section, could be rendered with the following Soy template:

[source,soy]
.src/main/resources/views/home.soy
.src/main/resources/home.soy
----
include::{testsviewsSoy}/resources/views/home.soy[]
include::{testsviewsSoy}/resources/home.soy[]
----

=== 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.

@@ -26,7 +28,6 @@ Both server-side Soy rendering layers are supported in Micronaut:
`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.

@@ -39,8 +40,7 @@ micronaut:
enabled: true
engine: sauce # one of `sauce` or `tofu`

After writing up your `SoyFileSetProvider`, and configuring the view layer, you can then bind and render templates like
so:
After wiring up your `SoyFileSetProvider`, and configuring Micronaut, you can render templates like so:

[source,java]
@Validated
@@ -54,56 +54,34 @@ public class HelloWorld {
}
}

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


==== Building Soy templates
=== Configuration

Soy has tooling support from https://github.com/Medium/soynode[Node] (https://www.npmjs.com/package/gulp-soynode[Gulp],
https://www.npmjs.com/package/grunt-closure-soy[Grunt]),
http://mvnrepository.com/artifact/com.google.template/soy[Maven],
https://plugins.gradle.org/plugin/com.liferay.soy[Gradle], and
https://github.com/bazelbuild/rules_closure/#closure_js_template_library[Bazel]. You can also invoke each
Soy compiler directly via the runner classes for each one:
The entire set of supported Soy configuration properties are documented below:

* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyHeaderCompiler.java[SoyHeaderCompiler]:
Compile templates into light "headers," that can be used downstream as dependencies for other templates.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyMsgExtractor.java[SoyMsgExtractor]:
Enables easy i18n by extracting `{msg desc=""}{/msg}` declarations for processing or translation.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyParseInfoGenerator.java[SoyParseInfoGenerator]:
Generates template parser metadata information as Java sources.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyPluginValidator.java[SoyPluginValidator]:
Validates `SoySourceFunction` definitions found in a set of JARs.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyToIncrementalDomSrcCompiler.java[SoyToIncrementalDomSrcCompiler]:
Generate client-side templates that render with http://google.github.io/incremental-dom[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.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyToJbcSrcCompiler.java[SoyToJbcSrcCompiler]:
Compiles Soy templates directly to Java bytecode, packaged up in a JAR. These templates can be used together with
https://search.maven.org/search?q=g:com.google.template%20AND%20a:soy&core=gav[Soy Java] for highly performant
server-side rendering.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyToJsSrcCompiler.java[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
https://github.com/google/closure-library/tree/master/closure/goog/soy[`goog.soy`].
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyToPySrcCompiler.java[SoyToPySrcCompiler]:
Compiles templates into Python sources for use server-side.
|=======
|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`
|=======


==== Template Security
=== Features: Template Security

Soy/Closure Templates has seen significant work from Google where security is concerned, on 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 features are outlined in more details over in the
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
https://github.com/google/closure-templates/blob/master/documentation/reference/security.md[Closure Templates docs].

===== Front-end Security
==== Front-end Security

* _Trusted URIs:_ Soy is smart enough to know that `scriptUrl` might be influenced by user input, and, therefore,
introduces an https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)[XSS vulnerability] when used as a script
`src`:
`src`. To inject resources, use `uri`:
[source,soy]
/* I won't compile because I introduce a vulnerability */
{template .foo}
@@ -132,7 +110,7 @@ https://github.com/google/closure-templates/blob/master/documentation/reference/
<b>{$content}</b>
{/template}

===== Back-end Security
==== Back-end Security

* _CSP Nonce Support_: Soy has support for _Content Security Policy_ (https://www.w3.org/TR/CSP3/[Level 3]),
specifically, embedding server-generated `nonce` attributes in `<script>` tags. This is accomplished by providing an
@@ -147,3 +125,154 @@ https://github.com/google/closure-templates/blob/master/documentation/reference/
{@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 (https://github.com/google/closure-stylesheets[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:
[source,yaml]
micronaut:
views:
soy:
enabled: true
renaming: true

2. When building your styles with GSS, or a similar tool, generate a rewrite map:
[source,bash]
> 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):
[source,soy]
{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:
[source,css]
.my-cool-class {
color: blue;
}
.my-cool-class-active {
background: yellow;
}

5. Compile your templates (see: _Building Soy_).
[source,bash]
> java -jar SoyToJbcSrcCompiler.jar \
--output templates.jar \
--srcs [...templates...];

The last step is to provide the renaming map to Micronaut:
```java
@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:
```html
<div class="a-b-c a-b-c-d">... content, and stuff...</div>
```

With your CSS rewritten and minified to match:
```css
.a-b-c{color:blue;}.a-b-c-d{background:yellow;}
```

=== Building Soy

Soy has tooling support from https://github.com/Medium/soynode[Node] (https://www.npmjs.com/package/gulp-soynode[Gulp],
https://www.npmjs.com/package/grunt-closure-soy[Grunt]),
http://mvnrepository.com/artifact/com.google.template/soy[Maven],
https://plugins.gradle.org/plugin/com.liferay.soy[Gradle], and
https://github.com/bazelbuild/rules_closure/#closure_js_template_library[Bazel]. You can also invoke each
Soy compiler directly via the runner classes for each one:

* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyHeaderCompiler.java[SoyHeaderCompiler]:
Compile templates into light "headers," that can be used downstream as dependencies for other templates.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyMsgExtractor.java[SoyMsgExtractor]:
Enables easy i18n by extracting `{msg desc=""}{/msg}` declarations for processing or translation.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyParseInfoGenerator.java[SoyParseInfoGenerator]:
Generates template parser metadata information as Java sources.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyPluginValidator.java[SoyPluginValidator]:
Validates `SoySourceFunction` definitions found in a set of JARs.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyToIncrementalDomSrcCompiler.java[SoyToIncrementalDomSrcCompiler]:
Generate client-side templates that render with http://google.github.io/incremental-dom[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.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyToJbcSrcCompiler.java[SoyToJbcSrcCompiler]:
Compiles Soy templates directly to Java bytecode, packaged up in a JAR. These templates can be used together with
https://search.maven.org/search?q=g:com.google.template%20AND%20a:soy&core=gav[Soy Java] for highly performant
server-side rendering.
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyToJsSrcCompiler.java[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
https://github.com/google/closure-library/tree/master/closure/goog/soy[`goog.soy`].
* https://github.com/google/closure-templates/blob/master/java/src/com/google/template/soy/SoyToPySrcCompiler.java[SoyToPySrcCompiler]:
Compiles templates into Python sources for use server-side.
Original file line number Diff line number Diff line change
@@ -8,24 +8,29 @@


/**
* Specifies an interface that provides renaming maps for CSS and
* XID calls in Soy templates.
* Specifies an interface that provides renaming maps for CSS and XID calls in Soy templates.
*
* @author Sam Gammon
* @since 1.3.0
*/
public interface SoyNamingMapProvider {
/**
* Provide an optional Soy renaming map for CSS class calls.
* Provide an optional Soy renaming map for CSS class calls. Default implementation just returns `null`, which opts-
* out of CSS class renaming.
*
* @return CSS renaming map.
*/
@Nullable SoyCssRenamingMap cssRenamingMap();
default @Nullable SoyCssRenamingMap cssRenamingMap() {
return null;
}

/**
* Provide an optional Soy ID renaming map for CSS IDs.
* Provide an optional Soy ID renaming map for CSS IDs. Default implementation just returns `null`, which opts-out of
* XID renaming.
*
* @return XID renaming map.
*/
@Nullable SoyIdRenamingMap idRenamingMap();
default @Nullable SoyIdRenamingMap idRenamingMap() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -44,7 +44,8 @@
@Requires(property = SoyViewsRendererConfigurationProperties.PREFIX + ".engine", notEquals = "sauce")
@Requires(property = SoyViewsRendererConfigurationProperties.PREFIX + ".enabled", notEquals = "false")
@Singleton
@SuppressWarnings({"WeakerAccess", "deprecation"})
@Deprecated
@SuppressWarnings({"WeakerAccess"})
public class SoyTofuViewsRenderer implements ViewsRenderer {

protected final ViewsConfiguration viewsConfiguration;