micronaut-views
micronaut-views copied to clipboard
Support async views rendering
Feature description
Currently the Views rendering (ReactViewsRenderer::render
) cannot be async, if happens that it's, it fails with io.netty.util.IllegalReferenceCountException: refCnt: 0
.
In my case I'm using micronaut-views-react
and my Jasascript waits for a Java Mono
.
Despite the docs says that's not supported, I was able to workaround that by implementing my own ViewFilter
where I create a Publisher
from the Writable
returned by ReactViewsRenderer::render
, basically the idea is to wait until Writer::write
gets called.
So I wonder whether something like this can be implemented to support async "rendering".
I copied the ViewsFilter
(removing some logic I didn't need in my case), and "delay" the response until the Writer::write
gets called.
It's only waiting for the first call to write
, but leaving subscriber.onComplete();
only in flush
and close
should support multiple calls. As micronaut-views-react
does not call neither flush
nor close
I had to call subscriber.onComplete();
in write
too.
public final Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
ServerFilterChain chain) {
return Flux.from(chain.proceed(request))
.switchMap(response -> {
Object body = response.body();
String view = viewsResolver.resolveView(request, response).orElse(null);
if (view == null || !view.equals("App")) { // Only use this for my App View
return Flux.just(response);
}
MediaType type = UTF8_HTML;
Optional<ViewsRenderer> optionalViewsRenderer = viewsRendererLocator.resolveViewsRenderer(view, type.getName(), body);
if(optionalViewsRenderer.isEmpty()) {
return Flux.just(response);
}
ModelAndView<?> modelAndView = new ModelAndView<>(view, ((ModelAndView<?>) body).getModel().orElse(null));
viewsModelDecorator.decorate(request, modelAndView);
Writable writable = optionalViewsRenderer.get().render(view, modelAndView.getModel().orElse(null), request);
return Flux.from((Publisher<String>) subscriber -> {
subscriber.onSubscribe(new Subscription() {
@Override
public void request(long n)
{
try
{
writable.writeTo(new Writer() {
@Override
public void write(char[] buffer, int off, int len) throws IOException
{
subscriber.onNext(String.valueOf(buffer));
subscriber.onComplete();
}
@Override
public void flush() throws IOException
{
subscriber.onComplete();
}
@Override
public void close() throws IOException
{
subscriber.onComplete();
}
});
}
catch (Exception e)
{
subscriber.onError(e);
}
}
@Override
public void cancel()
{
subscriber.onComplete();
}
});
}).map(b -> response.body(b).contentType(type));
});
}