Netty+SpringBoot写一个基于Http协议的文件服务器

本文参考《Netty权威指南》

NettyApplication
package com.xh.netty;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class NettyApplication {

   public static void main(String[] args) {

      SpringApplication.run(NettyApplication.class, args);

      String[] argList =args;
      System.out.println("+++++++++++++Simple Netty HttpFileServer+++++++++++++++");
      System.out.println("+            VERSION 1.0.1                            +");
      System.out.println("+            AUTHER:XH                                +");
      System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++");
      if (args.length==0){
         System.out.println("Usage: java -var thisPackageName.jar [-options][args...]");
         System.out.println("Use -h for more infomation");
         System.out.println("default port is 8080 , webRoot is /root ");
      }else {
         for (int i=0;i<argList.length;i++){
            if (argList[i].equalsIgnoreCase("-h")){
               System.out.println("-p your Listern port");
               System.out.println("-f your webRoot path");
               System.out.println("Example:java -jar netty-0.0.1-SNAPSHOT.jar -p 80 -f /root");
               return;
            }else {
               if (argList[i].equalsIgnoreCase("-p")){
                  try{
                     HttpFileServer.PORT=Integer.valueOf(argList[i+1]);
                  }catch (NumberFormatException e){
                     System.out.println("wrong number for you port");
                     System.out.println("Use -h for more infomation");
                     e.printStackTrace();
                     return;
                  }
               }
               if (argList[i].equalsIgnoreCase("-f")){
                  try{
                     HttpFileServer.WEBROOT=argList[i+1];
                  }catch (Exception e){
                     System.out.println("wrong path for you webRoot");
                     System.out.println("Use -h for more infomation");
                     e.printStackTrace();
                     return;
                  }
               }
            }
         }
      }

      try {
         HttpFileServer.main();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

   }
}

 

HttpFileServer
package com.xh.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;

/**
 * Created by root on 8/14/17.
 */
public class HttpFileServer {

    public static  String WEBROOT = "/root";
    public static  int PORT = 8080;

    public void run(final int port ,  final String url) throws InterruptedException {
        EventLoopGroup bossGroup=new NioEventLoopGroup();
        EventLoopGroup workerGroup=new NioEventLoopGroup();
        try{
            ServerBootstrap bootstrap=new ServerBootstrap();
            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast("http-decoder",new HttpRequestDecoder());
                            socketChannel.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65536));
                            socketChannel.pipeline().addLast("http-encoder",new HttpResponseEncoder());
                            socketChannel.pipeline().addLast("http-chunked",new ChunkedWriteHandler());
                            socketChannel.pipeline().addLast("fileServerHandler",new HttpFileServerHandler(url));

                        }
                    });
            ChannelFuture future = bootstrap.bind("127.0.0.1",port).sync();
            System.out.println("服务器已启动>>网址:"+"127.0.0.1:"+port+url);
            future.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

    public static void main() throws InterruptedException {

        new HttpFileServer().run(PORT,WEBROOT);
    }
}

 

HttpFileServerHandler
package com.xh.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;

import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.net.URLDecoder;
import java.util.regex.Pattern;

import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpHeaderUtil.isKeepAlive;
import static io.netty.handler.codec.http.HttpHeaderUtil.setContentLength;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * Created by root on 8/14/17.
 */
public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest>{

    private final String url;
    String WEBROOT = HttpFileServer.WEBROOT;
    public HttpFileServerHandler(String url) {
        this.url = url;
    }

    protected void messageReceived(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
        if (!fullHttpRequest.decoderResult().isSuccess()){
            sendError(channelHandlerContext, HttpResponseStatus.BAD_REQUEST);
            return;
        }

        if (fullHttpRequest.method()!= HttpMethod.GET){
            sendError(channelHandlerContext,HttpResponseStatus.METHOD_NOT_ALLOWED);
            return;
        }

        String uri=fullHttpRequest.uri();
        if (uri==null||uri.trim().equalsIgnoreCase("")){
            uri="/";
        }
        if (uri.trim().equalsIgnoreCase("/")){
            uri= WEBROOT;
        }
        if(!uri.startsWith(WEBROOT)){
            uri= WEBROOT +uri;
        }
        final String path=sanitizeUri(uri);
        if (path==null){
            sendError(channelHandlerContext,HttpResponseStatus.FORBIDDEN);
            return;
        }

        File file=new File(path);
        if (file.isHidden()||!file.exists()){
            sendError(channelHandlerContext,HttpResponseStatus.NOT_FOUND);
            return;
        }

        if (file.isDirectory()){
            if (uri.endsWith("/")){
                senfListing(channelHandlerContext,file);

            }else {
                sendRedirect(channelHandlerContext,uri+"/");
            }

            return;
        }

        if (!file.isFile()){
            sendError(channelHandlerContext,HttpResponseStatus.FORBIDDEN);
            return;
        }

        RandomAccessFile randomAccessFile=null;
        try{
            randomAccessFile=new RandomAccessFile(file,"r");
        }catch (FileNotFoundException e){
            e.printStackTrace();
            sendError(channelHandlerContext,HttpResponseStatus.NOT_FOUND);
            return;
        }

        Long fileLength=randomAccessFile.length();
        HttpResponse httpResponse=new DefaultHttpResponse(HTTP_1_1,OK);
        setContentLength(httpResponse,fileLength);
        setContentTypeHeader(httpResponse,file);

        if (isKeepAlive(fullHttpRequest)){
            httpResponse.headers().set(CONNECTION,KEEP_ALIVE);
        }

        channelHandlerContext.writeAndFlush(httpResponse);
        ChannelFuture sendFileFuture = channelHandlerContext.write(
                new ChunkedFile(randomAccessFile,0,fileLength,8192),channelHandlerContext.newProgressivePromise());

        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
            public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
                if (total<0){
                    System.err.println("progress:"+progress);
                }else {
                    System.err.println("progress:"+progress+"/"+total);
                }
            }

            public void operationComplete(ChannelProgressiveFuture future) {
                System.err.println("complete");
            }
        });


        ChannelFuture lastChannelFuture=channelHandlerContext.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        if (!isKeepAlive(fullHttpRequest)){
            lastChannelFuture.addListener(ChannelFutureListener.CLOSE );

        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        if (ctx.channel().isActive()){
            sendError(ctx,INTERNAL_SERVER_ERROR);
        }
    }



    private static final Pattern INSECURE_URI=Pattern.compile(".*[<>&\"].*");

    public String sanitizeUri(String uri){
        try{
            uri= URLDecoder.decode(uri,"UTF-8");
        }catch (Exception e){
            try{
                uri= URLDecoder.decode(uri,"ISO-8859-1");
            }catch (Exception ew){
                ew.printStackTrace();
            }
        }

        if (!uri.startsWith(url)){
            return null;

        }

        if (!uri.startsWith("/")){
            return null;

        }

        uri=uri.replace('/',File.separatorChar);
        if (uri.contains(File.separator+'.')||uri.startsWith(".")||uri.endsWith(".")||INSECURE_URI.matcher(uri).matches()){
            return null;
        }

        return uri;//System.getProperty("user.dir")+uri

    }

    private static final Pattern ALLOWED_FILE_NAME=Pattern.compile("[a-zA-Z0-9\\.]*");
    private void senfListing(ChannelHandlerContext channelHandlerContext, File dir) {
        FullHttpResponse response=new DefaultFullHttpResponse(HTTP_1_1,OK);
        response.headers().set(CONTENT_TYPE,"text/html;charset=UTF-8");
        StringBuilder builder =new StringBuilder();
        String dirPath=dir.getPath();
        builder.append("<!DOCTYPE html> \r\n");
        builder.append("<html><head><title>");
        builder.append(dirPath);
        builder.append("目录:");
        builder.append("</title></head><body>\r\n");
        builder.append("<h3>");
        builder.append(dirPath).append("目录:");
        builder.append("</h3>\r\n");
        builder.append("<ul>");
        builder.append("<li>链接:<a href=\"../\">..</a></li>\r\n");
        for (File f:dir.listFiles()){
            if (f.isHidden()||!f.canRead()){
                continue;
            }
            String fname=f.getName();
            if (!ALLOWED_FILE_NAME.matcher(fname).matches()){
                continue;
            }
            builder.append("<li>链接:<a href=\" ");
            builder.append(fname);
            builder.append("\" >");
            builder.append(fname);
            builder.append("</a></li>\r\n");
        }
        builder.append("</ul></body></html>\r\n");

        ByteBuf byteBuf= Unpooled.copiedBuffer(builder, CharsetUtil.UTF_8);
        response.content().writeBytes(byteBuf);
        byteBuf.release();
        channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

    }


    private void sendRedirect(ChannelHandlerContext channelHandlerContext, String newUri) {
        FullHttpResponse response=new DefaultFullHttpResponse(HTTP_1_1,FOUND);
        response.headers().set(LOCATION,newUri);
        channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private void sendError(ChannelHandlerContext channelHandlerContext, HttpResponseStatus status) {
        FullHttpResponse response=new DefaultFullHttpResponse(
                HTTP_1_1,status,Unpooled.copiedBuffer("Failure: "+status.toString()+"\r\n",
                CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE,"text/plain; charset=UTF-8");
        channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

    }


    private void setContentTypeHeader(HttpResponse httpResponse, File file) {

        MimetypesFileTypeMap mimetypesFileTypeMap=new MimetypesFileTypeMap();
        httpResponse.headers().set(CONTENT_TYPE,mimetypesFileTypeMap.getContentType(file.getPath()));
    }



}

 

打包发布:

cd 到项目target同级目录

mvn clean package

然后 cd target/

 java -jar netty-0.0.1-SNAPSHOT.jar -h运行

posted @ 2017-10-08 20:27  懒企鹅  阅读(4413)  评论(2编辑  收藏  举报