-
Notifications
You must be signed in to change notification settings - Fork 34
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
Conversation
This changeset introduces a new Micronaut package to render views via *[Soy](https://github.com/google/closure-templates)* (also known as *Closure Templates*). For now, the renderer simply uses `SoyTofu` to render views from Soy template sources. There are more efficient ways to render Soy templates in Java, and I'm planning to add those, too, subject to feedback from the Micronaut team. I've based this proposal on the structure of `views-freemarker`, to make sure I've adopted Micronaut's coding style as closely as I can. There is a corresponding set of unit tests as well, that pass, at least locally. _NOTE:_ This change requires a patched version of Soy Java, which applies a PR upstream to support querying a `SoyTofu` instance for templates (see: google/closure-templates#185). A patched JAR for Soy can be installed with the script `patch-soy.sh` added at the root of the repo. This is run in Travis, for instance, but can be eliminated when the upstream PR is merged and a release is cut. Changes made: - [x] Support for `SoyTofu` - i.e. rendering from template sources - [x] `SoyTofuRenderer` - where the rendering happens - [x] `SoyTofuViewsRendererConfiguration` - where a `SoyFileSet` is loaded and configured - [x] `SoyFileSetProvider` - uses DI to resolve a `SoyFileSet`, which is used to back the `SoyTofu.Renderer` - [x] Unit tests for `SoyTofuViewsRenderer` - [ ] Coming shortly: `SoySauce` - i.e. rendering via templates pre-compiled to Java sources
- Add bean for generating or using `SoySauce`-based renderer - Move `AppendableToWritable` to its own file, enhance it with support for `AdvisingAppendable` - Add `SoySauceRenderer` - Add ability for `SoyFileSetProvider` to provide pre-compiled templates via an existing `SoySauce` instance
Adopts upstream release after request at google/closure-templates#188
Force-SecureRandom defaults to `false`, to avoid infinitely blocking if there isn't enough entropy available. This can be mitigated with an overridden Java system property in Linux like so: -Djava.security.egd=file:/dev/./urandom
This changeset proposes a structure for an alternate `View` annotation, `AsyncView`. If `AsyncView` is used with a controller, it will try to load an `AsyncViewsRenderer` rather than a regular one, which is allowed to return a `Publisher` rather than producing an HTTP response synchronously from template context. Changes so far: - [x] Add `AsyncView` annotation - [x] Add `AsyncViewsRenderer` interface - [x] Refactor common items to `BaseViewsRenderer` - [x] Make `ViewsRenderer` comply with `BaseViewsRenderer`
This changeset builds upon PRs micronaut-projects#13 (*Soy Views Renderer*) and micronaut-projects#14 (*CSP Nonces*) to enable Soy with integrated CSP support when used with Micronaut. The CSP nonce may be provided via the Soy injected data, as documented [here](https://csp.withgoogle.com/docs/faq.html#passing-nonces). When the nonce value is provided, this way, inline scripts will automatically gain a `nonce="{nonceValue"}` attribute. All one must do in Micronaut to enable this is: ``` micronaut.views.soy.enabled = true micronaut.views.soy.engine = "sauce" micronaut.views.csp.enabled = true micronaut.views.csp.generateNonce = true micronaut.views.csp.policyDirectives = "default-src self:; script-src 'nonce-{#nonceValue}';" ``` And the framework will take care of the rest. Changes so far: - [x] Resolve CSP nonce when rendering templates via Soy - [x] Provide CSP nonce to Soy render context - [x] Add unit tests to make sure nonce lines up, is injected on both `<script>` tags and headers
This changeset amends the Soy view renderer, to support a new interface called `ReactiveViewRenderer`. The new interface generates a `Flowable` instead of a `Writable`, so that reactive chunked responses can be generated when the renderer is waiting on `Future` values to resolve. - [x] Refactor async views to use Flowable.generate - [x] Rename AsyncViewsRenderer -> ReactiveViewRenderer - [x] Render until told to DETACH or LIMIT(ED), then wait for future - [ ] Bug: Does not work for views that hit DETACH (see below)
@graemerocher This works great, save for one rather large issue. When Soy gives the But, it doesn't terminate it. It just hangs somehow, forever, never emitting a response to the unit test client I have setup in I've debugged extensively and added plenty of debug/trace logging. The trace log appears to be the correct flow. I'll take another crack at debugging this but it would help immensely if you could take a look - perhaps what I'm doing wrong is simple. Here is the trace log, which can be obtained by running 22:49:47.936 [nioEventLoopGroup-1-4] DEBUG i.m.context.condition.Condition - Bean [Definition: io.micronaut.management.endpoint.caches.CachesEndpoint] will not be loaded due to failing conditions:
22:49:47.936 [nioEventLoopGroup-1-4] DEBUG i.m.context.condition.Condition - * Custom condition [class io.micronaut.management.endpoint.EndpointEnabledCondition] failed evaluation
22:49:47.938 [nioEventLoopGroup-1-4] DEBUG i.m.context.condition.Condition - Bean [Definition: io.micronaut.management.endpoint.stop.ServerStopEndpoint] will not be loaded due to failing conditions:
22:49:47.938 [nioEventLoopGroup-1-4] DEBUG i.m.context.condition.Condition - * Custom condition [class io.micronaut.management.endpoint.EndpointEnabledCondition] failed evaluation
22:49:47.975 [pool-1-thread-1] INFO io.micronaut.docs.SoyController - Rendering async Soy page.
22:49:47.978 [pool-1-thread-1] INFO io.micronaut.docs.SoyController - Kicking off async task
22:49:48.011 [pool-1-thread-1] INFO io.micronaut.docs.SoyController - Task is immediately: NOT DONE.
22:49:48.028 [pool-1-thread-1] DEBUG i.m.views.soy.SoySauceViewsRenderer - Preparing render for template path 'sample.home'
22:49:48.063 [pool-1-thread-1] DEBUG i.m.views.soy.SoySauceViewsRenderer - CSP nonce injection is active.
22:49:48.063 [pool-1-thread-1] TRACE i.m.views.soy.SoySauceViewsRenderer - Injecting nonce value 'ldHUV1x8Bah93o7B3SNrJA'.
22:49:48.064 [pool-1-thread-1] DEBUG i.m.views.soy.SoySauceViewsRenderer - Style renaming is disabled.
22:49:48.078 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Allocating chunk of sizing = i: 1024, m: 2048
22:49:48.080 [pool-1-thread-1] TRACE i.m.views.soy.SoySauceViewsRenderer - Initial render for view 'sample.home'
22:49:48.085 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 30 (c: 30, b: 0)
22:49:48.085 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:48.085 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 56 (c: 86, b: 0)
22:49:48.085 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:48.087 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 8 (c: 94, b: 0)
22:49:48.087 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:48.088 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 22 (c: 116, b: 0)
22:49:48.088 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:48.088 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 1 (c: 117, b: 0)
22:49:48.088 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:48.088 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 44 (c: 161, b: 0)
22:49:48.088 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:48.088 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 20 (c: 181, b: 0)
22:49:48.089 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:48.090 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 7 (c: 188, b: 0)
22:49:48.090 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 12 (c: 200, b: 0)
22:49:48.090 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:48.091 [pool-1-thread-1] DEBUG i.m.views.soy.SoySauceViewsRenderer - Render emitting chunk
22:49:48.093 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Allocating chunk of sizing = i: 1024, m: 2048
22:49:48.094 [pool-1-thread-1] DEBUG io.micronaut.views.soy.SoyRender - Exporting capped chunk: ReadOnlyByteBuf(ridx: 0, widx: 200, cap: 200/200, unwrapped: UnpooledSlicedByteBuf(ridx: 0, widx: 200, cap: 200/200, unwrapped: CompositeByteBuf(ridx: 200, widx: 200, cap: 200, components=1)))
22:49:48.110 [pool-1-thread-1] DEBUG io.micronaut.views.soy.SoyRender - SoyRender received DETACH signal.
22:49:48.110 [pool-1-thread-1] TRACE i.m.views.soy.SoySauceViewsRenderer - Continue render for view 'sample.home'
22:49:48.110 [pool-1-thread-1] DEBUG io.micronaut.views.soy.SoyRender - SoyRender received DETACH signal.
22:49:48.111 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Still waiting on future value
22:49:48.111 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Future value is not yet ready. Blocking for return.
22:49:53.006 [pool-3-thread-1] INFO io.micronaut.docs.SoyController - Emitting async content
22:49:53.008 [pool-3-thread-1] INFO io.micronaut.docs.SoyController - Told executor to kick rocks
22:49:53.009 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Future value block finished.
22:49:53.009 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Future value has arrived: SoyRender is READY.
22:49:53.009 [pool-1-thread-1] DEBUG i.m.views.soy.SoySauceViewsRenderer - Render is READY to proceed. Continuing.
22:49:53.009 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 10 (c: 10, b: 200)
22:49:53.009 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:53.009 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 7 (c: 17, b: 200)
22:49:53.010 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 14 (c: 31, b: 200)
22:49:53.010 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:53.010 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Appended char seq of size = 14 (c: 45, b: 200)
22:49:53.010 [pool-1-thread-1] TRACE io.micronaut.views.soy.SoyRender - Soft limit not yet reached
22:49:53.011 [pool-1-thread-1] DEBUG i.m.views.soy.SoySauceViewsRenderer - Render emitting chunk
22:49:53.011 [pool-1-thread-1] TRACE i.m.views.soy.SoyResponseBuffer - Allocating chunk of sizing = i: 1024, m: 2048
22:49:53.012 [pool-1-thread-1] DEBUG io.micronaut.views.soy.SoyRender - Exporting capped chunk: ReadOnlyByteBuf(ridx: 0, widx: 45, cap: 45/45, unwrapped: UnpooledSlicedByteBuf(ridx: 0, widx: 45, cap: 45/45, unwrapped: CompositeByteBuf(ridx: 245, widx: 245, cap: 245, components=2)))
22:49:53.012 [pool-1-thread-1] DEBUG io.micronaut.views.soy.SoyRender - SoyRender flow is DONE.
22:49:53.012 [pool-1-thread-1] DEBUG i.m.views.soy.SoySauceViewsRenderer - Finished Soy render routine. Calling `onComplete`.
22:49:53.012 [pool-1-thread-1] DEBUG io.micronaut.views.soy.SoyRender - Closing render buffer (state: CLOSED)
22:49:53.012 [pool-1-thread-1] DEBUG i.m.views.soy.SoyResponseBuffer - Closing render buffer (state: CLOSED)
22:49:53.013 [pool-1-thread-1] DEBUG i.m.views.soy.SoyResponseBuffer - Chunk references = 0
22:49:53.013 [pool-1-thread-1] DEBUG i.m.views.soy.SoyResponseBuffer - Buffer references = 0
Expected no exception to be thrown, but got 'io.micronaut.http.client.exceptions.ReadTimeoutException'
Expected no exception to be thrown, but got 'io.micronaut.http.client.exceptions.ReadTimeoutException'
at spock.lang.Specification.noExceptionThrown(Specification.java:118)
at io.micronaut.docs.SoySauceReactiveRendererSpec.invoking /soy/asyncContent should produce a chunked response(SoySauceReactiveRendererSpec.groovy:69)
Caused by: io.micronaut.http.client.exceptions.ReadTimeoutException: Read Timeout
at io.micronaut.http.client.exceptions.ReadTimeoutException.<clinit>(ReadTimeoutException.java:26)
at io.micronaut.http.client.DefaultHttpClient$10.exceptionCaught(DefaultHttpClient.java:1917)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:285)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:264)
at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:256) the log shows:
AFAIK nothing else is broken. all other tests pass, including the existing suite for Soy. it's just when there is a future value in the Soy render context that isn't immediately ready. |
613e267
to
9a5aae1
Compare
9a5aae1
to
99470af
Compare
I will take a look shortly. Thanks. |
@graemerocher Two ideas popped into my head in case they are relevant. To yield a chunk, I am issuing a read-only slice of the backing render buffer ( The read-only slice is retained. After more real-world testing, I get a reference count for the underlying buffer (at close time) that approaches 10/12. Could it be that:
If you point me to the relevant code, I'd be happy to understand more and try and pry it open, perhaps with some trace logging. Thank you for taking a look EDIT: one final note. once we get this working, i'm happy to clean up this PR, add some more testing for the chunking/buffer sizing, and squash/rebase on |
What may be missing is the writing of the |
@graemerocher Perfect! Thank you. I'll try debugging starting from If I can generate a more useful log, I'll post it. |
@graemerocher I took a look at the code you pointed out, but I don't quite understand how |
@sgammon I will be looking at this this afternoon. Sorry for the delay |
Yeah so the issue is that the decision to switch to streaming the data is statically decided based on the return type of the controller method, not based on the body of the response emitted. We need to add the streaming logic to core before we can proceed ahead with this otherwise dynamically switching to streaming is not possible. |
@graemerocher no problem on the delay, thank you for taking the time to look :) i see, well that does explain it. for now, this isn't a blocker for us, because we resolve all futures before rendering anyway. but i am happy to pick it back up again or contribute to get it merged eventually. thank you again for your help |
@graemerocher, happy new year - is there any update on the upstream support for dynamically deciding on streaming vs. buffered responses? |
closing this because it is superseded by #34 (Reactive Soy v2) |
This changeset amends the Soy view renderer, to support a new interface called
ReactiveViewRenderer
. The new interface generates aFlowable
instead of aWritable
, so that reactive chunked responses can be generated when the renderer is waiting onFuture
values to resolve.