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; /** * @FileName OrderServer.java * @Description: * * @Date 2016年3月5日 * @author Administroter * @version 1.0 * */ public class HttpFileServer { private static final String DEFAULT_URL = "/src/main/java/com/lxf/netty/"; public void bind(int port,final String url) throws Exception { // 配置服务端的NIO线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //添加请求消息解码器 ch.pipeline().addLast("http-decoder", new HttpRequestDecoder()); //将消息转为单一的FullHttpRequest或者FullHttpResponse ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536)); //添加响应客户端编码器 ch.pipeline().addLast("http-encoder", new HttpResponseEncoder()); //支持异步发送大的码流,但不会占用过多的内存,防止发生java内存溢出 ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler()); ch.pipeline().addLast("fileServerHandler", new HttpFileServerHandler(url)); } }); ChannelFuture f = b.bind("127.0.0.1",port).sync(); System.out.println("Http文件服务器已启动,网址是 : " + http://127.0.0.1:+port+url); // 等待服务端监听端口关闭 f.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { String url = DEFAULT_URL; int port = 8080; if (args != null && args.length > 0) { try { port = Integer.valueOf(args[0]); } catch (NumberFormatException e) { // 采用默认值 } } new HttpFileServer().bind(port,url); } }
import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive; import static io.netty.handler.codec.http.HttpHeaders.setContentLength; import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; import static io.netty.handler.codec.http.HttpMethod.GET; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR; import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED; import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelProgressiveFuture; import io.netty.channel.ChannelProgressiveFutureListener; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.stream.ChunkedFile; import java.io.File; import java.io.FileNotFoundException; import java.io.RandomAccessFile; import com.lxf.netty.common.HttpUrlKit; /** * @FileName HttpFileServerHandler.java * @Description: * * @Date 2016年3月8日 * @author Administroter * @version 1.0 * */ public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { private String url; public HttpFileServerHandler(String url) { this.url = url; } @Override protected void messageReceived(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { //如果出现编码错误,跳转404路径错误页面 if (!request.getDecoderResult().isSuccess()) { HttpUrlKit.sendError(ctx, BAD_REQUEST); return; } //如果不是浏览器或者表单发送get请求,跳转405错误 if (request.getMethod() != GET) { HttpUrlKit.sendError(ctx, METHOD_NOT_ALLOWED); } final String uri = request.getUri(); //对具体的包装具体的url路径 final String path = HttpUrlKit.sanitizeUri(uri, url); if (path == null) { HttpUrlKit.sendError(ctx, FORBIDDEN); return; } //获取文件对象 File file = new File(path); //文件属于隐藏文件或者不存在 if (file.isHidden() || !file.exists()) { HttpUrlKit.sendError(ctx, NOT_FOUND); return; } //查看路径名表示的是否是一个目录,如果是,则发送目录的链接给客户端 if (file.isDirectory()) { if (uri.endsWith("/")) { HttpUrlKit.sendListing(ctx, file); } else { HttpUrlKit.sendRedirect(ctx, uri + "/"); } return; } //查看路径名表示的文件是否是一个标准文件 if (!file.isFile()) { HttpUrlKit.sendError(ctx, FORBIDDEN); return; } //构建随机访问文件的读取和写入 RandomAccessFile raf = null; try { raf = new RandomAccessFile(file, "r"); } catch (FileNotFoundException e) { HttpUrlKit.sendError(ctx, NOT_FOUND); return; } //获取文件的长度,构造http答应消息 long fileLength = raf.length(); HttpResponse hresp = new DefaultHttpResponse(HTTP_1_1, OK); setContentLength(request, fileLength); //设置响应文件的mime类型,即文件的扩展名,而客户端浏览器获取这个类型以后,根绝mime来决定采用什么应用程序来处理数据 HttpUrlKit.setContentTypeHeader(hresp, file); if (isKeepAlive(request)) { hresp.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE); } //发送消息 ctx.write(hresp); ChannelFuture sendFileFuture; //将文件写入到发送缓冲区 sendFileFuture = ctx.write(new ChunkedFile(raf, 0, fileLength, 8192), ctx.newProgressivePromise()); //增加ChannelFuture的监听 sendFileFuture.addListener(new ChannelProgressiveFutureListener() { public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) { if (total < 0) { System.err.println("Transfer progress: " + progress); } else { System.err.println("Transfer progress: " + progress + " / " + total); } } //发送完成信息后触发 public void operationComplete(ChannelProgressiveFuture future) throws Exception { System.out.println("Transfer complete."); } }); //发送编码结束的空消息体,标识消息体发送完成,同时小勇flush方法将发送消息缓冲区的消息刷新到SocketChannel ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); //如果非keepalive则标识发送完成,关闭连接 if (!isKeepAlive(request)) { lastContentFuture.addListener(ChannelFutureListener.CLOSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); if (ctx.channel().isActive()) { HttpUrlKit.sendError(ctx, INTERNAL_SERVER_ERROR); } } }
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaders.Names.LOCATION; import static io.netty.handler.codec.http.HttpResponseStatus.FOUND; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.CharsetUtil; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.regex.Pattern; import javax.activation.MimetypesFileTypeMap; /** * @FileName HttpFileServer.java * @Description: * * @Date 2016年3月7日 * @author Administroter * @version 1.0 * */ @SuppressWarnings("restriction") public class HttpUrlKit { private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*"); private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*"); /** * @Title: sanitizeUri * @Description:uri路径合法性校验 * @param uri * @param url * @return * @author Administroter * @date 2016年3月11日 */ public static String sanitizeUri(String uri, String url) { try { //对URL进行解码 uri = URLDecoder.decode(uri, "UTF-8"); } catch (UnsupportedEncodingException e) { try { uri = URLDecoder.decode(uri, "ISO-8859-1"); } catch (UnsupportedEncodingException e1) { throw new Error(); } } /** * 对uri进行合法性的判断 */ if (!uri.startsWith(url)) { return null; } if (!uri.startsWith("/")) { return null; } //将硬编码的文件路径分隔符替换为本地操作系统文件路径分隔符,比如C:\Users\hfgff\Desktop就是将其中的“\”替换成“/” uri = uri.replace('/', File.separatorChar); //对uri进行第二次合法性验证验证请求的路径当中是否含有"\."或者".\"或者以"."开头或者结尾,再匹配正则规则。 if (uri.contains(File.separator + '.') || uri.contains('.' + File.separator) || uri.startsWith(".") || uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) { return null; } //构造项目目录+uri构造绝对路径返回(System.getProperty("user.dir")表用户当前的工作目录) return System.getProperty("user.dir") + File.separator + uri; } /** * @Title: sendListing * @Description:构建请求路径的文件目录 * @param ctx * @param dir * @author Administroter * @date 2016年3月11日 */ public static void sendListing(ChannelHandlerContext ctx, File dir) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK); //这里显示在浏览器,采用html的格式,设置消息头的类型 response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); //构建消息响应信息体 StringBuilder buf = new StringBuilder(); String dirPath = dir.getPath(); buf.append("<!DOCTYPE html>\r\n"); buf.append("<html><head><title>"); buf.append(dirPath); buf.append(" 目录:"); buf.append("</title></head><body>\r\n"); buf.append("<h3>"); buf.append("Netty学习Dome示例代码目录:"); buf.append("</h3>\r\n"); buf.append("<ul>"); buf.append("<li><a href=\"../\">返回上一级</a></li>\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("<li>链接:<a href=\""); buf.append(name); buf.append("\">"); buf.append(name); buf.append("</a></li>\r\n"); } buf.append("</ul></body></html>\r\n"); //分配对应消息的缓冲对象 ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8); //将缓冲区的消息存放到http答应消息中 response.content().writeBytes(buffer); //释放缓冲区 buffer.release(); //将响应消息发送到缓冲区并刷新到SocketChannel中 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } /** * @Title: sendRedirect * @Description: * @param ctx * @param newUri * @author Administroter * @date 2016年3月11日 */ public static void sendRedirect(ChannelHandlerContext ctx, String newUri) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND); response.headers().set(LOCATION, newUri); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } public static void sendError(ChannelHandlerContext ctx, 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"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } public static void setContentTypeHeader(HttpResponse response, File file) { MimetypesFileTypeMap mft = new MimetypesFileTypeMap(); //获取指定文件的扩展类型,并设置成消息消息头的类型,用于响应客户端后,浏览器根据这个,来决定采用 //浏览器嵌入的哪个应用程序模块处理响应的数据 response.headers().set(CONTENT_TYPE, mft.getContentType(file.getPath())); } }