Skip to content

Support async views rendering #857

@PepeBotella25

Description

@PepeBotella25

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));
				});
	}

I created this repo where the problem and the workaround can be tested

Metadata

Metadata

Type

No type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions