Repository: micronaut-projects/micronaut-core
Issue: Inconsistent @Body handling in browser

Body is occasionally lost when uploaded from browser

### Steps to Reproduce

See sample

### Expected Behaviour

Body handling should work

### Actual Behaviour

Occasional:

```
i.m.h.s.netty.RoutingInBoundHandler - Encoding emitted response object [ /command - Required argument [LoginCommand command] not specified] using codec: io.micronaut.jackson.codec.JsonMediaTypeCodec@3ee39da0
```

### Example Application

https://github.com/liwujun0513/micronaut_demo



Changed files:
http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpServer.java
http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettySystemFileCustomizableResponseType.java
http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsHandler.java
http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettyStreamedFileCustomizableResponseType.java
http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsServerHandler.java

diff --git a/http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsHandler.java b/http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsHandler.java
index cffc0be9cf..6c91f26c8e 100644
--- a/http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsHandler.java
+++ b/http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsHandler.java
@@ -173,7 +173,7 @@ abstract class HttpStreamsHandler<In extends HttpMessage, Out extends HttpMessag
     @Override
     public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
 
-        if (inClass.isInstance(msg)) {
+        if (isValidInMessage(msg)) {
 
             receivedInMessage(ctx);
             final In inMsg = inClass.cast(msg);
@@ -279,7 +279,7 @@ abstract class HttpStreamsHandler<In extends HttpMessage, Out extends HttpMessag
 
     @Override
     public void write(final ChannelHandlerContext ctx, Object msg, final ChannelPromise promise) throws Exception {
-        if (outClass.isInstance(msg)) {
+        if (isValidOutMessage(msg)) {
 
             Outgoing out = new Outgoing(outClass.cast(msg), promise);
             receivedOutMessage(ctx);
@@ -404,6 +404,22 @@ abstract class HttpStreamsHandler<In extends HttpMessage, Out extends HttpMessag
         }
     }
 
+    /**
+     * @param msg The message
+     * @return True if the handler should write the message
+     */
+    protected boolean isValidOutMessage(Object msg) {
+        return outClass.isInstance(msg);
+    }
+
+    /**
+     * @param msg The message
+     * @return True if the handler should read the message
+     */
+    protected boolean isValidInMessage(Object msg) {
+        return inClass.isInstance(msg);
+    }
+
     /**
      * The outgoing class.
      */
@@ -420,4 +436,5 @@ abstract class HttpStreamsHandler<In extends HttpMessage, Out extends HttpMessag
             this.promise = promise;
         }
     }
+
 }
diff --git a/http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsServerHandler.java b/http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsServerHandler.java
index 69b1e028a0..6b464abd05 100644
--- a/http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsServerHandler.java
+++ b/http-netty/src/main/java/io/micronaut/http/netty/stream/HttpStreamsServerHandler.java
@@ -22,15 +22,7 @@ import io.micronaut.http.netty.reactive.HandlerSubscriber;
 import io.netty.channel.ChannelHandler;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelPipeline;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpContent;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpRequest;
-import io.netty.handler.codec.http.HttpResponse;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpUtil;
-import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.*;
 import io.netty.handler.codec.http.websocketx.WebSocketFrame;
 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
 import io.netty.handler.codec.http.websocketx.WebSocketVersion;
@@ -185,6 +177,11 @@ public class HttpStreamsServerHandler extends HttpStreamsHandler<HttpRequest, Ht
         }
     }
 
+    @Override
+    protected boolean isValidOutMessage(Object msg) {
+        return msg instanceof FullHttpResponse || msg instanceof StreamedHttpResponse || msg instanceof WebSocketHttpResponse;
+    }
+
     private boolean canHaveBody(HttpResponse message) {
         HttpResponseStatus status = message.status();
         // All 1xx (Informational), 204 (No Content), and 304 (Not Modified)
diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpServer.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpServer.java
index 31319e04c2..884125f275 100644
--- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpServer.java
+++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpServer.java
@@ -69,6 +69,7 @@ import io.netty.handler.codec.http.multipart.DiskFileUpload;
 import io.netty.handler.flow.FlowControlHandler;
 import io.netty.handler.logging.LoggingHandler;
 import io.netty.handler.ssl.SslContext;
+import io.netty.handler.stream.ChunkedWriteHandler;
 import io.netty.handler.timeout.IdleStateHandler;
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.GlobalEventExecutor;
@@ -104,6 +105,7 @@ import java.util.function.BiConsumer;
 @Singleton
 public class NettyHttpServer implements EmbeddedServer, WebSocketSessionRepository {
     public static final String HTTP_STREAMS_CODEC = "http-streams-codec";
+    public static final String HTTP_CHUNKED_HANDLER = "http-chunked-handler";
     @SuppressWarnings("WeakerAccess")
     public static final String HTTP_CODEC = "http-codec";
     @SuppressWarnings("WeakerAccess")
@@ -260,6 +262,7 @@ public class NettyHttpServer implements EmbeddedServer, WebSocketSessionReposito
                         pipeline.addLast(HTTP_KEEP_ALIVE_HANDLER, new HttpServerKeepAliveHandler());
                         pipeline.addLast(HTTP_COMPRESSOR, new SmartHttpContentCompressor());
                         pipeline.addLast(HTTP_STREAMS_CODEC, new HttpStreamsServerHandler());
+                        pipeline.addLast(HTTP_CHUNKED_HANDLER, new ChunkedWriteHandler());
                         pipeline.addLast(HttpRequestDecoder.ID, new HttpRequestDecoder(
                                 NettyHttpServer.this,
                                 environment,
diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettyStreamedFileCustomizableResponseType.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettyStreamedFileCustomizableResponseType.java
index de30bb8f82..ffadc22f02 100644
--- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettyStreamedFileCustomizableResponseType.java
+++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettyStreamedFileCustomizableResponseType.java
@@ -19,7 +19,6 @@ package io.micronaut.http.server.netty.types.files;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.MutableHttpResponse;
 import io.micronaut.http.netty.NettyMutableHttpResponse;
-import io.micronaut.http.server.netty.NettyHttpServer;
 import io.micronaut.http.server.netty.types.NettyFileCustomizableResponseType;
 import io.micronaut.http.server.types.files.StreamedFile;
 import io.netty.channel.ChannelHandlerContext;
@@ -27,11 +26,9 @@ import io.netty.handler.codec.http.DefaultHttpResponse;
 import io.netty.handler.codec.http.FullHttpResponse;
 import io.netty.handler.codec.http.HttpChunkedInput;
 import io.netty.handler.stream.ChunkedStream;
-import io.netty.handler.stream.ChunkedWriteHandler;
 
 import java.io.InputStream;
 import java.net.URL;
-import java.util.Optional;
 
 /**
  * Writes an {@link InputStream} to the Netty context.
@@ -76,11 +73,6 @@ public class NettyStreamedFileCustomizableResponseType extends StreamedFile impl
         if (response instanceof NettyMutableHttpResponse) {
             FullHttpResponse nettyResponse = ((NettyMutableHttpResponse) response).getNativeResponse();
 
-            //The streams codec prevents non full responses from being written
-            Optional
-                .ofNullable(context.pipeline().get(NettyHttpServer.HTTP_STREAMS_CODEC))
-                .ifPresent(handler -> context.pipeline().replace(handler, "chunked-handler", new ChunkedWriteHandler()));
-
             // Write the request data
             context.write(new DefaultHttpResponse(nettyResponse.protocolVersion(), nettyResponse.status(), nettyResponse.headers()), context.voidPromise());
             context.writeAndFlush(new HttpChunkedInput(new ChunkedStream(getInputStream())));
diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettySystemFileCustomizableResponseType.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettySystemFileCustomizableResponseType.java
index b120b6e0f4..7c3bd17646 100644
--- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettySystemFileCustomizableResponseType.java
+++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/types/files/NettySystemFileCustomizableResponseType.java
@@ -19,7 +19,6 @@ package io.micronaut.http.server.netty.types.files;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.MutableHttpResponse;
 import io.micronaut.http.netty.NettyMutableHttpResponse;
-import io.micronaut.http.server.netty.NettyHttpServer;
 import io.micronaut.http.server.netty.SmartHttpContentCompressor;
 import io.micronaut.http.server.netty.types.NettyFileCustomizableResponseType;
 import io.micronaut.http.server.types.CustomizableResponseTypeException;
@@ -29,12 +28,10 @@ import io.netty.channel.DefaultFileRegion;
 import io.netty.handler.codec.http.DefaultHttpResponse;
 import io.netty.handler.codec.http.FullHttpResponse;
 import io.netty.handler.codec.http.HttpChunkedInput;
-import io.netty.handler.codec.http.HttpContentCompressor;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.LastHttpContent;
 import io.netty.handler.ssl.SslHandler;
 import io.netty.handler.stream.ChunkedFile;
-import io.netty.handler.stream.ChunkedWriteHandler;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -112,11 +109,6 @@ public class NettySystemFileCustomizableResponseType extends SystemFileCustomiza
 
             FullHttpResponse nettyResponse = ((NettyMutableHttpResponse) response).getNativeResponse();
 
-            //The streams codec prevents non full responses from being written
-            Optional
-                .ofNullable(context.pipeline().get(NettyHttpServer.HTTP_STREAMS_CODEC))
-                .ifPresent(handler -> context.pipeline().replace(handler, "chunked-handler", new ChunkedWriteHandler()));
-
             // Write the request data
             HttpHeaders headers = nettyResponse.headers();
             context.write(new DefaultHttpResponse(nettyResponse.protocolVersion(), nettyResponse.status(), headers), context.voidPromise());
@@ -124,12 +116,6 @@ public class NettySystemFileCustomizableResponseType extends SystemFileCustomiza
             // Write the content.
             if (context.pipeline().get(SslHandler.class) == null && SmartHttpContentCompressor.shouldSkip(headers)) {
                 // SSL not enabled - can use zero-copy file transfer.
-                // Remove the content compressor to prevent incorrect behavior with zero-copy
-                HttpContentCompressor compressor = context.pipeline().get(HttpContentCompressor.class);
-                if (compressor != null) {
-                    context.pipeline().remove(HttpContentCompressor.class);
-                }
-
                 context.write(new DefaultFileRegion(raf.getChannel(), 0, getLength()), context.newProgressivePromise());
                 context.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
             } else {