content: package com.lgp.netty.netty_ch6;
  
  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.net.URISyntaxException;
  import java.net.URL;
  
  /**
   * @AUTHOR lgp
   * @DESCRIPTION //1 扩展 SimpleChannelInboundHandler 用于处理 FullHttpRequest信息
   **/
  public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
      private final String wsUri;
      private static final File INDEX;
  
      static {
          URL location = HttpRequestHandler.class.getProtectionDomain().getCodeSource().getLocation();
          try {
              String path = location.toURI() + "index.html";
              path = !path.contains("file:") ? path : path.substring(5);
              INDEX = new File(path);
          } catch (URISyntaxException e) {
              throw new IllegalStateException("Unable to locate index.html", e);
          }
      }
  
      public HttpRequestHandler(String wsUri) {
          this.wsUri = wsUri;
      }
  
      @Override
      public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
          if (wsUri.equalsIgnoreCase(request.getUri())) {
              //2 如果请求是一次升级了的 WebSocket 请求，则递增引用计数器（retain）并且将它传递给在 ChannelPipeline 中的下个 ChannelInboundHandler
              ctx.fireChannelRead(request.retain());
          } else {
              if (HttpHeaders.is100ContinueExpected(request)) {
                  //3 .处理符合 HTTP 1.1的 "100 Continue" 请求
                  send100Continue(ctx);
              }
              //4  读取 index.html
              RandomAccessFile file = new RandomAccessFile(INDEX, "r");
  
              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);
              //5 判断 keepalive 是否在请求头里面
              if (keepAlive) {
                  response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length());
                  response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
              }
              //6 写 HttpResponse 到客户端
              ctx.write(response);
              //7.写 index.html 到客户端，根据 ChannelPipeline 中是否有 SslHandler 来决定使用 DefaultFileRegion 还是 ChunkedNioFile
              if (ctx.pipeline().get(SslHandler.class) == null) {
                  ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
              } else {
                  ctx.write(new ChunkedNioFile(file.getChannel()));
              }
              //8 写并刷新 LastHttpContent 到客户端，标记响应完成
              ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
              if (!keepAlive) {
                  //9 如果 请求头中不包含 keepalive，当写完成时，关闭 Channel
                  future.addListener(ChannelFutureListener.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 {
          cause.printStackTrace();
          ctx.close();
      }
  }
  