Netty5 + HTTPS 练习
本次以《Netty权威指南》第十章里面的例子为基础修改而来
HttpsFileServerHandler.java
1 package com.jieli.nettytest.httpsfile; 2 3 import java.io.File; 4 import java.io.RandomAccessFile; 5 import java.net.URLDecoder; 6 import java.util.regex.Pattern; 7 8 import javax.activation.MimetypesFileTypeMap; 9 10 import io.netty.buffer.ByteBuf; 11 import io.netty.buffer.Unpooled; 12 import io.netty.channel.ChannelFuture; 13 import io.netty.channel.ChannelFutureListener; 14 import io.netty.channel.ChannelHandlerContext; 15 import io.netty.channel.ChannelProgressiveFuture; 16 import io.netty.channel.ChannelProgressiveFutureListener; 17 import io.netty.channel.SimpleChannelInboundHandler; 18 import io.netty.handler.codec.http.DefaultFullHttpResponse; 19 import io.netty.handler.codec.http.DefaultHttpResponse; 20 import io.netty.handler.codec.http.FullHttpRequest; 21 import io.netty.handler.codec.http.FullHttpResponse; 22 import io.netty.handler.codec.http.HttpHeaderNames; 23 import io.netty.handler.codec.http.HttpHeaderUtil; 24 import io.netty.handler.codec.http.HttpHeaderValues; 25 import io.netty.handler.codec.http.HttpMethod; 26 import io.netty.handler.codec.http.HttpResponse; 27 import io.netty.handler.codec.http.HttpResponseStatus; 28 import io.netty.handler.codec.http.HttpVersion; 29 import io.netty.handler.codec.http.LastHttpContent; 30 import io.netty.handler.stream.ChunkedFile; 31 import io.netty.util.CharsetUtil; 32 33 public class HttpsFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest>{ 34 35 private final String url; 36 37 public HttpsFileServerHandler(String url){ 38 this.url = url; 39 } 40 41 @Override 42 protected void messageReceived(ChannelHandlerContext ctx, 43 FullHttpRequest request) throws Exception { 44 if(!request.decoderResult().isSuccess()){ 45 sendError(ctx, HttpResponseStatus.BAD_REQUEST); 46 return ; 47 } 48 if(request.method() != HttpMethod.GET){ 49 sendError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED); 50 return ; 51 } 52 53 final String uri = request.uri(); 54 final String path = sanitizeUri(uri); 55 if(path == null){ 56 sendError(ctx, HttpResponseStatus.FORBIDDEN); 57 return ; 58 } 59 File file = new File(path); 60 if(file.isHidden() || !file.exists()){ 61 sendError(ctx, HttpResponseStatus.NOT_FOUND); 62 return ; 63 } 64 if(file.isDirectory()){ 65 if(uri.endsWith("/")){ 66 sendListing(ctx, file); 67 }else{ 68 sendRedirect(ctx, uri+'/'); 69 } 70 return ; 71 } 72 if(!file.isFile()){ 73 sendError(ctx, HttpResponseStatus.FORBIDDEN); 74 } 75 RandomAccessFile accessFile = null; 76 try { 77 accessFile = new RandomAccessFile(file, "r"); 78 } catch (Exception e) { 79 e.printStackTrace(); 80 sendError(ctx, HttpResponseStatus.NOT_FOUND); 81 return ; 82 } 83 long len = accessFile.length(); 84 HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); 85 HttpHeaderUtil.setContentLength(response, len); 86 setContentTypeHeader(response, file); 87 if(HttpHeaderUtil.isKeepAlive(request)){ 88 response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); 89 } 90 ctx.write(response); 91 ChannelFuture future; 92 future = ctx.write(new ChunkedFile(accessFile, 0, len, 8192), ctx.newProgressivePromise()); 93 future.addListener(new ChannelProgressiveFutureListener() { 94 95 @Override 96 public void operationComplete(ChannelProgressiveFuture arg0) 97 throws Exception { 98 System.out.println("Transfer complete."); 99 } 100 101 @Override 102 public void operationProgressed(ChannelProgressiveFuture future, long progress, 103 long total) throws Exception { 104 if(total < 0){ 105 System.err.println("Transfer progress:" + progress); 106 }else{ 107 System.err.println("Transfer progress:" + progress +"/" +total); 108 } 109 } 110 }); 111 ChannelFuture lastfuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); 112 if(!HttpHeaderUtil.isKeepAlive(request)){ 113 lastfuture.addListener(ChannelFutureListener.CLOSE); 114 } 115 } 116 117 @Override 118 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 119 throws Exception { 120 cause.printStackTrace(); 121 if(ctx.channel().isActive()){ 122 sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR); 123 } 124 } 125 126 private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*"); 127 private String sanitizeUri(String uri){ 128 try { 129 uri = URLDecoder.decode(uri, "UTF-8"); 130 } catch (Exception e) { 131 try { 132 uri = URLDecoder.decode(uri, "ISO-8859-1"); 133 } catch (Exception e2) { 134 throw new Error(); 135 } 136 } 137 138 if(!uri.startsWith(url)){ 139 return null; 140 } 141 142 if(!uri.startsWith("/")){ 143 return null; 144 } 145 146 uri = uri.replace('/', File.separatorChar); 147 if(uri.contains('.'+File.separator) || uri.startsWith(".") 148 || uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()){ 149 return null; 150 } 151 return System.getProperty("user.dir") + File.separator + uri; 152 } 153 154 private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*"); 155 156 private static void sendListing(ChannelHandlerContext ctx, File dir){ 157 FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); 158 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8"); 159 StringBuilder buf = new StringBuilder(); 160 String dirPath = dir.getPath(); 161 buf.append("<!DOCTYPE html\r\n"); 162 buf.append("<html><head><title>"); 163 buf.append(dirPath); 164 buf.append(" 目录: "); 165 buf.append("</title></head><body>\r\n"); 166 buf.append("<h3>"); 167 buf.append(dirPath).append(" 目录 :"); 168 buf.append("</h3>"); 169 buf.append("<ul>"); 170 buf.append("<li>链接: <a href=\"../\">..</a></li>\r\n"); 171 for(File f :dir.listFiles()){ 172 if(f.isHidden() || !f.canRead()){ 173 continue; 174 } 175 String name = f.getName(); 176 if(!ALLOWED_FILE_NAME.matcher(name).matches()){ 177 continue; 178 } 179 buf.append("<li>链接:<a href=\""); 180 buf.append(name); 181 buf.append("\">"); 182 buf.append(name); 183 buf.append("</a></li>\r\n"); 184 } 185 buf.append("</ul></body></html>\r\n"); 186 ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8); 187 response.content().writeBytes(buffer); 188 buffer.release(); 189 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); 190 } 191 192 private static void sendRedirect(ChannelHandlerContext ctx, String newuri){ 193 FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND); 194 response.headers().set(HttpHeaderNames.LOCATION, newuri); 195 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); 196 } 197 198 private static void sendError(ChannelHandlerContext ctx, 199 HttpResponseStatus status){ 200 FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, 201 Unpooled.copiedBuffer("Failure: " + status.toString()+"\r\n", CharsetUtil.UTF_8)); 202 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); 203 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); //异步发送 发送完成后就关闭连接 204 } 205 206 private static void setContentTypeHeader(HttpResponse response, File file){ 207 MimetypesFileTypeMap typeMap = new MimetypesFileTypeMap(); 208 response.headers().set(HttpHeaderNames.CONTENT_TYPE, typeMap.getContentType(file.getPath())); 209 } 210 211 }
HttpsFileServer.java
1 package com.jieli.nettytest.httpsfile; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.EventLoopGroup; 7 import io.netty.channel.nio.NioEventLoopGroup; 8 import io.netty.channel.socket.SocketChannel; 9 import io.netty.channel.socket.nio.NioServerSocketChannel; 10 import io.netty.handler.codec.http.HttpObjectAggregator; 11 import io.netty.handler.codec.http.HttpRequestDecoder; 12 import io.netty.handler.codec.http.HttpResponseEncoder; 13 import io.netty.handler.ssl.SslContext; 14 import io.netty.handler.ssl.util.SelfSignedCertificate; 15 import io.netty.handler.stream.ChunkedWriteHandler; 16 17 public class HttpsFileServer { 18 private static final String DEFAULT_URL = "/src/com/"; 19 20 21 public void run(final int port, final String url) throws Exception{ 22 23 final SslContext sslCtx; 24 SelfSignedCertificate ssc = new SelfSignedCertificate(); 25 //具体场景要通过文件 26 sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey()); 27 28 EventLoopGroup bossGroup = new NioEventLoopGroup(); 29 EventLoopGroup workerGroup = new NioEventLoopGroup(); 30 try { 31 ServerBootstrap b = new ServerBootstrap(); 32 b.group(bossGroup, workerGroup) 33 .channel(NioServerSocketChannel.class) 34 .childHandler(new ChannelInitializer<SocketChannel>() { 35 @Override 36 protected void initChannel(SocketChannel ch) throws Exception { 37 ch.pipeline().addLast(sslCtx.newHandler(ch.alloc())); 38 ch.pipeline().addLast("http_decoder", new HttpRequestDecoder()) 39 .addLast("http-aggregator", new HttpObjectAggregator(65536)) 40 .addLast("http-encoder", new HttpResponseEncoder()) 41 .addLast("http-chunked", new ChunkedWriteHandler()) 42 .addLast("fileserverhandler", new HttpsFileServerHandler(url)); 43 } 44 }); 45 46 ChannelFuture f = b.bind(port).sync(); 47 48 System.out.println("HTTP File Server Start.. http://localhost:"+port+url); 49 f.channel().closeFuture().sync(); 50 } catch (Exception e) { 51 e.printStackTrace(); 52 } finally { 53 bossGroup.shutdownGracefully(); 54 workerGroup.shutdownGracefully(); 55 } 56 } 57 58 public static void main(String[] args) throws Exception { 59 new HttpsFileServer().run(7777, DEFAULT_URL); 60 } 61 }
运行结果
作者:无脑仔的小明 出处:http://www.cnblogs.com/wunaozai/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。有需要沟通的,可以站内私信,文章留言,或者关注“无脑仔的小明”公众号私信我。一定尽力回答。 |