需求:

  基于netty框架完成一个文件服务器,通过浏览器可以浏览相关的文件及下载文件。

目录结构

  在跟src同级别目录下,放一个sources 文件夹,并向文件夹内放入一些文件,用于测试下载。

  

 

 

 

代码:

  代码分为两个,一个是 HttpFileServer 用于做通道连接。另外一个是 HttpFileServerHandler 用于具体的业务处理。  

 1 import io.netty.bootstrap.ServerBootstrap;
 2 import io.netty.channel.ChannelFuture;
 3 import io.netty.channel.ChannelInitializer;
 4 import io.netty.channel.EventLoopGroup;
 5 import io.netty.channel.nio.NioEventLoopGroup;
 6 import io.netty.channel.socket.SocketChannel;
 7 import io.netty.channel.socket.nio.NioServerSocketChannel;
 8 import io.netty.handler.codec.http.HttpObjectAggregator;
 9 import io.netty.handler.codec.http.HttpRequestDecoder;
10 import io.netty.handler.codec.http.HttpResponseEncoder;
11 import io.netty.handler.stream.ChunkedWriteHandler;
12 
13 public class HttpFileServer {
14 
15     private static final String DEFAULT_URL="/sources/";
16 
17     public void run (final int port,final String url) throws Exception {
18         EventLoopGroup bossGroup = new NioEventLoopGroup();
19         EventLoopGroup workerGroup = new NioEventLoopGroup();
20 
21         try {
22             ServerBootstrap b =new ServerBootstrap();
23             b.group(bossGroup,workerGroup)
24                     .channel(NioServerSocketChannel.class)
25                     .childHandler(new ChannelInitializer<SocketChannel>() {
26                         @Override
27                         protected void initChannel(SocketChannel ch) throws Exception {
28                             ch.pipeline().addLast("http-decoder",new HttpRequestDecoder());// 解码器
29                             ch.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65536));
30                             ch.pipeline().addLast("http-encoder",new HttpResponseEncoder());// 编码器
31                             ch.pipeline().addLast("http-chunked",new ChunkedWriteHandler());// 压缩解压
32                             ch.pipeline().addLast("fileServerHandler",new HttpFileServerHandler(url));// 实际的文件下载处理handler
33                         }
34                     });
35             ChannelFuture future = b.bind(port).sync();
36             System.out.println("运行在端口号 port = " + port);
37             future.channel().closeFuture().sync();
38         }finally {
39             bossGroup.shutdownGracefully();
40             workerGroup.shutdownGracefully();
41         }
42 
43     }
44 
45     public static void main(String[] args) throws  Exception{
46         int port = 8765;
47         String url = DEFAULT_URL;
48         new HttpFileServer().run(port,url);
49     }
50 
51 }
HttpFileServer
  1 import java.io.File;
  2 import java.io.FileNotFoundException;
  3 import java.io.RandomAccessFile;
  4 import java.io.UnsupportedEncodingException;
  5 import java.net.URLDecoder;
  6 import java.util.concurrent.ExecutionException;
  7 import java.util.concurrent.TimeUnit;
  8 import java.util.concurrent.TimeoutException;
  9 import java.util.regex.Pattern;
 10 
 11 import javax.activation.MimetypesFileTypeMap;
 12 import javax.swing.text.html.MinimalHTMLWriter;
 13 
 14 import io.netty.buffer.ByteBuf;
 15 import io.netty.buffer.Unpooled;
 16 import io.netty.channel.Channel;
 17 import io.netty.channel.ChannelFuture;
 18 import io.netty.channel.ChannelFutureListener;
 19 import io.netty.channel.ChannelHandlerContext;
 20 import io.netty.channel.ChannelProgressiveFuture;
 21 import io.netty.channel.ChannelProgressiveFutureListener;
 22 import io.netty.channel.SimpleChannelInboundHandler;
 23 import io.netty.handler.codec.http.DefaultFullHttpResponse;
 24 import io.netty.handler.codec.http.DefaultHttpResponse;
 25 import io.netty.handler.codec.http.FullHttpRequest;
 26 import io.netty.handler.codec.http.FullHttpResponse;
 27 import io.netty.handler.codec.http.HttpHeaderNames;
 28 import io.netty.handler.codec.http.HttpHeaderUtil;
 29 import io.netty.handler.codec.http.HttpHeaderValues;
 30 import io.netty.handler.codec.http.HttpHeaders;
 31 import io.netty.handler.codec.http.HttpMessage;
 32 import io.netty.handler.codec.http.HttpMethod;
 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.codec.http2.Http2Headers;
 38 import io.netty.handler.stream.ChunkedFile;
 39 import io.netty.util.CharsetUtil;
 40 import io.netty.util.concurrent.Future;
 41 import io.netty.util.concurrent.GenericFutureListener;
 42 
 43 public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest>{
 44 
 45     private final String url;
 46 
 47     public HttpFileServerHandler(String url) {
 48         this.url = url;
 49     }
 50 
 51     @Override
 52     protected void messageReceived(ChannelHandlerContext ctx,
 53                                    FullHttpRequest request) throws Exception {
 54         // 对请求的解码结果进行判断
 55         if(!request.decoderResult().isSuccess())
 56         {
 57             // 400
 58             sendError(ctx, HttpResponseStatus.BAD_REQUEST);
 59             return;
 60         }
 61         // 对请求方式进行判断,如果不是get方式(如post方式)则返回异常
 62         if(request.method() != HttpMethod.GET)
 63         {
 64             // 405
 65             sendError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);
 66             return;
 67         }
 68 
 69         final String uri = request.uri();
 70         final String path = sanitizeUri(uri);
 71         if(path == null)
 72         {
 73             // 403
 74             sendError(ctx, HttpResponseStatus.FORBIDDEN);
 75             return;
 76         }
 77 
 78         File file = new File(path);
 79         if(file.isHidden() || !file.exists())
 80         {
 81             // 404
 82             sendError(ctx, HttpResponseStatus.NOT_FOUND);
 83             return;
 84         }
 85         if(file.isDirectory())
 86         {
 87             if(uri.endsWith("/"))
 88             {
 89                 sendListing(ctx, file);
 90             }else{
 91                 sendRedirect(ctx, uri + "/");
 92             }
 93             return;
 94         }
 95         if(!file.isFile())
 96         {
 97             // 403
 98             sendError(ctx, HttpResponseStatus.FORBIDDEN);
 99             return;
100         }
101 
102         RandomAccessFile randomAccessFile = null;
103         try{
104             randomAccessFile = new RandomAccessFile(file, "r");
105         }catch(FileNotFoundException fnfd){
106             sendError(ctx, HttpResponseStatus.NOT_FOUND);
107             return;
108         }
109 
110         long fileLength = randomAccessFile.length();
111         HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
112         HttpHeaderUtil.setContentLength(response, fileLength);
113 //        setContentLength(response, fileLength);
114         setContentTypeHeader(response, file);
115 
116 
117 
118         if(HttpHeaderUtil.isKeepAlive(request)){
119             response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
120         }
121 
122         ctx.write(response);
123         ChannelFuture sendFileFuture = null;
124         sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
125         sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
126 
127             @Override
128             public void operationComplete(ChannelProgressiveFuture future)
129                     throws Exception {
130                 System.out.println("Transfer complete.");
131 
132             }
133 
134             @Override
135             public void operationProgressed(ChannelProgressiveFuture future,
136                                             long progress, long total) throws Exception {
137                 if(total < 0)
138                     System.err.println("Transfer progress: " + progress);
139                 else
140                     System.err.println("Transfer progress: " + progress + "/" + total);
141             }
142         });
143 
144         ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
145         if(!HttpHeaderUtil.isKeepAlive(request))
146             lastContentFuture.addListener(ChannelFutureListener.CLOSE);
147 
148     }
149 
150     @Override
151     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
152             throws Exception {
153         cause.printStackTrace();
154         if(ctx.channel().isActive())
155             sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
156     }
157 
158     private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
159     private String sanitizeUri(String uri){
160         try{
161             uri = URLDecoder.decode(uri, "UTF-8");
162         }catch(UnsupportedEncodingException e){
163             try{
164                 uri = URLDecoder.decode(uri, "ISO-8859-1");
165             }catch(UnsupportedEncodingException e1){
166                 throw new Error();
167             }
168         }
169 
170         if(!uri.startsWith(url))
171             return null;
172         if(!uri.startsWith("/"))
173             return null;
174 
175         uri = uri.replace('/', File.separatorChar);
176         if(uri.contains(File.separator + '.') || uri.contains('.' + File.separator) || uri.startsWith(".") || uri.endsWith(".")
177                 || INSECURE_URI.matcher(uri).matches()){
178             return null;
179         }
180         return System.getProperty("user.dir") + File.separator + uri;
181     }
182 
183     private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");
184 
185     private static void sendListing(ChannelHandlerContext ctx, File dir){
186         FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
187 //        response.headers().set("CONNECT_TYPE", "text/html;charset=UTF-8");
188         response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
189 
190         String dirPath = dir.getPath();
191         StringBuilder buf = new StringBuilder();
192 
193         buf.append("<!DOCTYPE html>\r\n");
194         buf.append("<html><head><title>");
195         buf.append(dirPath);
196         buf.append("目录:");
197         buf.append("</title></head><body>\r\n");
198 
199         buf.append("<h3>");
200         buf.append(dirPath).append(" 目录:");
201         buf.append("</h3>\r\n");
202         buf.append("<ul>");
203         buf.append("<li>链接:<a href=\" ../\")..</a></li>\r\n");
204         for (File f : dir.listFiles()) {
205             if(f.isHidden() || !f.canRead()) {
206                 continue;
207             }
208             String name = f.getName();
209             if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
210                 continue;
211             }
212 
213             buf.append("<li>链接:<a href=\"");
214             buf.append(name);
215             buf.append("\">");
216             buf.append(name);
217             buf.append("</a></li>\r\n");
218         }
219 
220         buf.append("</ul></body></html>\r\n");
221 
222         ByteBuf buffer = Unpooled.copiedBuffer(buf,CharsetUtil.UTF_8);
223         response.content().writeBytes(buffer);
224         buffer.release();
225         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
226     }
227 
228 
229     private static void sendRedirect(ChannelHandlerContext ctx, String newUri){
230         FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND);
231 //        response.headers().set("LOCATIN", newUri);
232         response.headers().set(HttpHeaderNames.LOCATION, newUri);
233         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
234     }
235     private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status){
236         FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
237                 Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8));
238         response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
239         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
240     }
241     private static void setContentTypeHeader(HttpResponse response, File file){
242         MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();
243         response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimetypesFileTypeMap.getContentType(file.getPath()));
244     }
245 }
HttpFileServerHandler

页面效果:

 

 

posted on 2020-11-14 11:21  精品唯居  阅读(772)  评论(0编辑  收藏  举报