如何使用 Netty 下载文件

使用场景:客户端向Netty请求一个文件,Netty服务端下载指定位置文件到客户端。

本实例使用的是Http协议,当然,可以通过简单的修改即可换成TCP协议。

需要注意本实例的关键点是,为了更高效的传输大数据,实例中用到了ChunkedWriteHandler编码器,它提供了以zero-memory-copy方式写文件。

第一步:先写一个HttpFileServer

[java]  view plain copy
  1. package NettyDemo.file.server;  
  2.   
  3. import io.netty.bootstrap.ServerBootstrap;  
  4. import io.netty.channel.Channel;  
  5. import io.netty.channel.ChannelInitializer;  
  6. import io.netty.channel.ChannelPipeline;  
  7. import io.netty.channel.EventLoopGroup;  
  8. import io.netty.channel.nio.NioEventLoopGroup;  
  9. import io.netty.channel.socket.SocketChannel;  
  10. import io.netty.channel.socket.nio.NioServerSocketChannel;  
  11. import io.netty.handler.codec.http.HttpObjectAggregator;  
  12. import io.netty.handler.codec.http.HttpServerCodec;  
  13. import io.netty.handler.logging.LogLevel;  
  14. import io.netty.handler.logging.LoggingHandler;  
  15. import io.netty.handler.stream.ChunkedWriteHandler;  
  16.   
  17. /******************************************************************************* 
  18.  * Reserved. BidPlanStructForm.java Created on 2014-8-19 Author:  
  19.  * href=mailto:[email protected]>wanghouda 
  20.  * @Title: HttpFileServer.java 
  21.  * @Package NettyDemo.file.server Description: Version: 1.0 
  22.  ******************************************************************************/  
  23. public class HttpFileServer {  
  24.     static final int PORT = 8080;  
  25.     public static void main(String[] args) throws Exception {  
  26.         EventLoopGroup bossGroup = new NioEventLoopGroup(1);  
  27.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  28.         try {  
  29.             ServerBootstrap b = new ServerBootstrap();  
  30.             b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO))  
  31.                     .childHandler(new ChannelInitializer() {// 有连接到达时会创建一个channel  
  32.                                 @Override  
  33.                                 protected void initChannel(SocketChannel ch) throws Exception {  
  34.                                     ChannelPipeline pipeline = ch.pipeline();  
  35.                                     pipeline.addLast(new HttpServerCodec());  
  36.                                     pipeline.addLast(new HttpObjectAggregator(65536));  
  37.                                     pipeline.addLast(new ChunkedWriteHandler());  
  38.                                     pipeline.addLast(new FileServerHandler());  
  39.                                 }  
  40.                             });  
  41.   
  42.             Channel ch = b.bind(PORT).sync().channel();  
  43.             System.err.println("打开浏览器,输入: " + ("http") + "://127.0.0.1:" + PORT + '/');  
  44.             ch.closeFuture().sync();  
  45.         } finally {  
  46.             bossGroup.shutdownGracefully();  
  47.             workerGroup.shutdownGracefully();  
  48.         }  
  49.     }  
  50. }  


[java]  view plain copy
  1.   
第二步:再写一个FileServerHandler

[java]  view plain copy
  1. package NettyDemo.file.server;  
  2.   
  3. import static io.netty.handler.codec.http.HttpHeaders.Names.CACHE_CONTROL;  
  4. import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;  
  5. import static io.netty.handler.codec.http.HttpHeaders.Names.DATE;  
  6. import static io.netty.handler.codec.http.HttpHeaders.Names.EXPIRES;  
  7. import static io.netty.handler.codec.http.HttpHeaders.Names.IF_MODIFIED_SINCE;  
  8. import static io.netty.handler.codec.http.HttpHeaders.Names.LAST_MODIFIED;  
  9. import static io.netty.handler.codec.http.HttpHeaders.Names.LOCATION;  
  10. import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;  
  11. import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;  
  12. import static io.netty.handler.codec.http.HttpResponseStatus.FOUND;  
  13. import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;  
  14. import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;  
  15. import static io.netty.handler.codec.http.HttpResponseStatus.NOT_MODIFIED;  
  16. import static io.netty.handler.codec.http.HttpResponseStatus.OK;  
  17. import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;  
  18. import io.netty.buffer.ByteBuf;  
  19. import io.netty.buffer.Unpooled;  
  20. import io.netty.channel.ChannelFuture;  
  21. import io.netty.channel.ChannelFutureListener;  
  22. import io.netty.channel.ChannelHandlerContext;  
  23. import io.netty.channel.ChannelProgressiveFuture;  
  24. import io.netty.channel.ChannelProgressiveFutureListener;  
  25. import io.netty.channel.DefaultFileRegion;  
  26. import io.netty.channel.SimpleChannelInboundHandler;  
  27. import io.netty.handler.codec.http.DefaultFullHttpResponse;  
  28. import io.netty.handler.codec.http.DefaultHttpResponse;  
  29. import io.netty.handler.codec.http.FullHttpRequest;  
  30. import io.netty.handler.codec.http.FullHttpResponse;  
  31. import io.netty.handler.codec.http.HttpChunkedInput;  
  32. import io.netty.handler.codec.http.HttpHeaders;  
  33. import io.netty.handler.codec.http.HttpResponse;  
  34. import io.netty.handler.codec.http.HttpResponseStatus;  
  35. import io.netty.handler.codec.http.HttpVersion;  
  36. import io.netty.handler.codec.http.LastHttpContent;  
  37. import io.netty.handler.ssl.SslHandler;  
  38. import io.netty.handler.stream.ChunkedFile;  
  39. import io.netty.util.CharsetUtil;  
  40. import io.netty.util.internal.SystemPropertyUtil;  
  41.   
  42. import java.io.File;  
  43. import java.io.FileNotFoundException;  
  44. import java.io.RandomAccessFile;  
  45. import java.io.UnsupportedEncodingException;  
  46. import java.net.URLDecoder;  
  47. import java.text.SimpleDateFormat;  
  48. import java.util.Calendar;  
  49. import java.util.Date;  
  50. import java.util.GregorianCalendar;  
  51. import java.util.Locale;  
  52. import java.util.TimeZone;  
  53. import java.util.regex.Pattern;  
  54.   
  55. import javax.activation.MimetypesFileTypeMap;  
  56.   
  57. /******************************************************************************* 
  58.  * Created on 2014-8-19 Author: 
  59.  * wanghouda 
  60.  *  
  61.  * @Title: HttpFileServerHandler.java 
  62.  * @Package NettyDemo.file.server Description: Version: 1.0 
  63.  ******************************************************************************/  
  64. public class FileServerHandler extends SimpleChannelInboundHandler {  
  65.     public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";  
  66.     public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";  
  67.     public static final int HTTP_CACHE_SECONDS = 60;  
  68.   
  69.     @Override  
  70.     protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {  
  71.         // 监测解码情况  
  72.         if (!request.getDecoderResult().isSuccess()) {  
  73.             sendError(ctx, BAD_REQUEST);  
  74.             return;  
  75.         }  
  76.         final String uri = request.getUri();  
  77.         final String path = sanitizeUri(uri);  
  78.         if (path == null) {  
  79.             sendError(ctx, FORBIDDEN);  
  80.             return;  
  81.         }  
  82.         //读取要下载的文件  
  83.         File file = new File(path);  
  84.         if (file.isHidden() || !file.exists()) {  
  85.             sendError(ctx, NOT_FOUND);  
  86.             return;  
  87.         }  
  88.         if (file.isDirectory()) {  
  89.             if (uri.endsWith("/")) {  
  90.                 sendListing(ctx, file);  
  91.             } else {  
  92.                 sendRedirect(ctx, uri + '/');  
  93.             }  
  94.             return;  
  95.         }  
  96.         if (!file.isFile()) {  
  97.             sendError(ctx, FORBIDDEN);  
  98.             return;  
  99.         }  
  100.         // Cache Validation  
  101.         String ifModifiedSince = request.headers().get(IF_MODIFIED_SINCE);  
  102.         if (ifModifiedSince != null && !ifModifiedSince.isEmpty()) {  
  103.             SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);  
  104.             Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);  
  105.             // Only compare up to the second because the datetime format we send  
  106.             // to the client  
  107.             // does not have milliseconds  
  108.             long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;  
  109.             long fileLastModifiedSeconds = file.lastModified() / 1000;  
  110.             if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {  
  111.                 sendNotModified(ctx);  
  112.                 return;  
  113.             }  
  114.         }  
  115.         RandomAccessFile raf;  
  116.         try {  
  117.             raf = new RandomAccessFile(file, "r");  
  118.         } catch (FileNotFoundException ignore) {  
  119.             sendError(ctx, NOT_FOUND);  
  120.             return;  
  121.         }  
  122.         long fileLength = raf.length();  
  123.         HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);  
  124.         HttpHeaders.setContentLength(response, fileLength);  
  125.         setContentTypeHeader(response, file);  
  126.         setDateAndCacheHeaders(response, file);  
  127.         if (HttpHeaders.isKeepAlive(request)) {  
  128.             response.headers().set("CONNECTION", HttpHeaders.Values.KEEP_ALIVE);  
  129.         }  
  130.   
  131.         // Write the initial line and the header.  
  132.         ctx.write(response);  
  133.   
  134.         // Write the content.  
  135.         ChannelFuture sendFileFuture;  
  136.         if (ctx.pipeline().get(SslHandler.class) == null) {  
  137.             sendFileFuture = ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());  
  138.         } else {  
  139.             sendFileFuture = ctx.write(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)), ctx.newProgressivePromise());  
  140.         }  
  141.         sendFileFuture.addListener(new ChannelProgressiveFutureListener() {  
  142.             @Override  
  143.             public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {  
  144.                 if (total < 0) { // total unknown  
  145.                     System.err.println(future.channel() + " Transfer progress: " + progress);  
  146.                 } else {  
  147.                     System.err.println(future.channel() + " Transfer progress: " + progress + " / " + total);  
  148.                 }  
  149.             }  
  150.   
  151.             @Override  
  152.             public void operationComplete(ChannelProgressiveFuture future) {  
  153.                 System.err.println(future.channel() + " Transfer complete.");  
  154.             }  
  155.         });  
  156.   
  157.         // Write the end marker  
  158.         ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);  
  159.   
  160.         // Decide whether to close the connection or not.  
  161.         if (!HttpHeaders.isKeepAlive(request)) {  
  162.             // Close the connection when the whole content is written out.  
  163.             lastContentFuture.addListener(ChannelFutureListener.CLOSE);  
  164.         }  
  165.     }  
  166.   
  167.     @Override  
  168.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {  
  169.         cause.printStackTrace();  
  170.         if (ctx.channel().isActive()) {  
  171.             sendError(ctx, INTERNAL_SERVER_ERROR);  
  172.         }  
  173.     }  
  174.   
  175.     private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");  
  176.   
  177.     private static String sanitizeUri(String uri) {  
  178.         // Decode the path.  
  179.         try {  
  180.             uri = URLDecoder.decode(uri, "UTF-8");  
  181.         } catch (UnsupportedEncodingException e) {  
  182.             throw new Error(e);  
  183.         }  
  184.   
  185.         if (!uri.startsWith("/")) {  
  186.             return null;  
  187.         }  
  188.   
  189.         // Convert file separators.  
  190.         uri = uri.replace('/', File.separatorChar);  
  191.   
  192.         // Simplistic dumb security check.  
  193.         // You will have to do something serious in the production environment.  
  194.         if (uri.contains(File.separator + '.') || uri.contains('.' + File.separator) || uri.startsWith(".") || uri.endsWith(".")  
  195.                 || INSECURE_URI.matcher(uri).matches()) {  
  196.             return null;  
  197.         }  
  198.   
  199.         // Convert to absolute path.  
  200.         return SystemPropertyUtil.get("user.dir") + File.separator + uri;  
  201.     }  
  202.   
  203.     private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");  
  204.   
  205.     private static void sendListing(ChannelHandlerContext ctx, File dir) {  
  206.         FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);  
  207.         response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");  
  208.   
  209.         StringBuilder buf = new StringBuilder();  
  210.         String dirPath = dir.getPath();  
  211.   
  212.         buf.append("\r\n");  
  213.         buf.append(""</span><span style="margin:0px; padding:0px; border:none; background-color:inherit">);  </span></span></li> <li style="border-style:none none none solid; border-left-width:3px; border-left-color:rgb(108,226,108); list-style:decimal-leading-zero outside; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important; background-color:rgb(248,248,248)"> <span style="margin:0px; padding:0px; border:none; color:black; background-color:inherit">        buf.append(<span class="string" style="margin:0px; padding:0px; border:none; color:blue; background-color:inherit">"Listing of: "</span><span style="margin:0px; padding:0px; border:none; background-color:inherit">);  </span></span></li> <li class="alt" style="border-style:none none none solid; border-left-width:3px; border-left-color:rgb(108,226,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> <span style="margin:0px; padding:0px; border:none; color:black; background-color:inherit">        buf.append(dirPath);  </span></li> <li style="border-style:none none none solid; border-left-width:3px; border-left-color:rgb(108,226,108); list-style:decimal-leading-zero outside; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important; background-color:rgb(248,248,248)"> <span style="margin:0px; padding:0px; border:none; color:black; background-color:inherit">        buf.append(<span class="string" style="margin:0px; padding:0px; border:none; color:blue; background-color:inherit">"\r\n");  
  214.   
  215.         buf.append("

    Listing of: ");  

  216.         buf.append(dirPath);  
  217.         buf.append("\r\n");  
  218.   
  219.         buf.append("
      ");  
    •         buf.append("
    • ..
    • \r\n");  
    •   
    •         for (File f : dir.listFiles()) {  
    •             if (f.isHidden() || !f.canRead()) {  
    •                 continue;  
    •             }  
    •   
    •             String name = f.getName();  
    •             if (!ALLOWED_FILE_NAME.matcher(name).matches()) {  
    •                 continue;  
    •             }  
    •   
    •             buf.append("
    • );  
    •             buf.append(name);  
    •             buf.append("\">");  
    •             buf.append(name);  
    •             buf.append("
    • \r\n");  
    •         }  
    •   
    •         buf.append("
    \r\n"
    );  
  220.         ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);  
  221.         response.content().writeBytes(buffer);  
  222.         buffer.release();  
  223.   
  224.         // Close the connection as soon as the error message is sent.  
  225.         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  
  226.     }  
  227.   
  228.     private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {  
  229.         FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);  
  230.         response.headers().set(LOCATION, newUri);  
  231.   
  232.         // Close the connection as soon as the error message is sent.  
  233.         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  
  234.     }  
  235.   
  236.     private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {  
  237.         FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));  
  238.         response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");  
  239.   
  240.         // Close the connection as soon as the error message is sent.  
  241.         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  
  242.     }  
  243.   
  244.     /** 
  245.      * When file timestamp is the same as what the browser is sending up, send a 
  246.      * "304 Not Modified" 
  247.      *  
  248.      * @param ctx 
  249.      *            Context 
  250.      */  
  251.     private static void sendNotModified(ChannelHandlerContext ctx) {  
  252.         FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED);  
  253.         setDateHeader(response);  
  254.   
  255.         // Close the connection as soon as the error message is sent.  
  256.         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  
  257.     }  
  258.   
  259.     /** 
  260.      * Sets the Date header for the HTTP response 
  261.      *  
  262.      * @param response 
  263.      *            HTTP response 
  264.      */  
  265.     private static void setDateHeader(FullHttpResponse response) {  
  266.         SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);  
  267.         dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));  
  268.   
  269.         Calendar time = new GregorianCalendar();  
  270.         response.headers().set(DATE, dateFormatter.format(time.getTime()));  
  271.     }  
  272.   
  273.     /** 
  274.      * Sets the Date and Cache headers for the HTTP Response 
  275.      *  
  276.      * @param response 
  277.      *            HTTP response 
  278.      * @param fileToCache 
  279.      *            file to extract content type 
  280.      */  
  281.     private static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) {  
  282.         SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);  
  283.         dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));  
  284.   
  285.         // Date header  
  286.         Calendar time = new GregorianCalendar();  
  287.         response.headers().set(DATE, dateFormatter.format(time.getTime()));  
  288.   
  289.         // Add cache headers  
  290.         time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);  
  291.         response.headers().set(EXPIRES, dateFormatter.format(time.getTime()));  
  292.         response.headers().set(CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);  
  293.         response.headers().set(LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));  
  294.     }  
  295.   
  296.     /** 
  297.      * Sets the content type header for the HTTP Response 
  298.      *  
  299.      * @param response 
  300.      *            HTTP response 
  301.      * @param file 
  302.      *            file to extract content type 
  303.      */  
  304.     private static void setContentTypeHeader(HttpResponse response, File file) {  
  305.         MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();  
  306.         response.headers().set(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));  
  307.     }  
  308.   
  309. }  
第三步:启动Netty服务,在浏览器中输入
     http://127.0.0.1:8080/

     如图所示:即可在浏览器中看到工程目录下所有文件,点击即可下载

    如何使用 Netty 下载文件_第1张图片




ps:通过对本例进行简单修改可实现各种方式的文件下载


你可能感兴趣的:(netty学习)