完整代码参考:码云仓库

import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;

import java.io.File;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URL;

/** * @author 夏天 * @date 2020年11月07日 15:14 * 处理 Http 请求 */
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    //1
    private final String wsUri;
    private static final File INDEX;

    static {
   
        URL location = HttpRequestHandler.class.getProtectionDomain().getCodeSource().getLocation();
        try {
   
            String path = location.toURI() + "WebsocketDanMu.html";
            try {
   
                //进行路径编码
                path = java.net.URLDecoder.decode(path, "utf-8");
            } catch (UnsupportedEncodingException e) {
   
                e.printStackTrace();
            }
            path = !path.contains("file:") ? path : path.substring(5);
            INDEX = new File(path);
        } catch (URISyntaxException e) {
   
            throw new IllegalStateException("Unable to locate WebsocketChatClient.html", e);
        }
    }

    public HttpRequestHandler(String wsUri) {
   
        this.wsUri = wsUri;
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
   
        // 如果请求了Websocket,协议升级,将他传递给下一个 ChannelHandler
        // 之所以需要调用 retain() 方法,是因为调用 channelRead() 之后,资源会被 release() 方法释放掉,需要调用 retain() 保留资源
        if (wsUri.equalsIgnoreCase(request.getUri())) {
   
            ctx.fireChannelRead(request.retain());                  //2
        } else {
   
            if (HttpHeaders.is100ContinueExpected(request)) {
   
                send100Continue(ctx);                               //3
            }

            RandomAccessFile file = new RandomAccessFile(INDEX, "r");//4

            HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);
            response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");

            boolean keepAlive = HttpHeaders.isKeepAlive(request);

            if (keepAlive) {
                                           //5
                response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length());
                response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
            }
            ctx.write(response);                    //6

            if (ctx.pipeline().get(SslHandler.class) == null) {
        //7
                ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
            } else {
   
                ctx.write(new ChunkedNioFile(file.getChannel()));
            }
            ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);           //8
            if (!keepAlive) {
   
                future.addListener(ChannelFutureListener.CLOSE);        //9
            }

            file.close();
        }
    }

    private static void send100Continue(ChannelHandlerContext ctx) {
   
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
        ctx.writeAndFlush(response);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
   
        Channel incoming = ctx.channel();
        System.out.println("Client:" + incoming.remoteAddress() + "异常");
        // 当出现异常就关闭连接
        cause.printStackTrace();
        ctx.close();
    }
}