HttpFileServer

package com.zhaowb.netty.ch10_1;

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; public class HttpFileServer { // 在网上看到/src/com/ 但是在实际操作中,这个一直提示404,调试的时候发现,路径错误,换成了"/netty/src/main/java/com/"; 我的是idea 在 eclipse 中 /src/main/java/com/就可以。
private static final String DEFAULT_URL = "/netty/src/main/java/com/"; public void run(final 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());
ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("http-encoder", new HttpResponseEncoder()); // Chunked Handler 主要作用是支持异步发送大的码流(例如大的文件传输),但不占用过多的内存,
// 防止发生java内存溢出错误。
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler()); ch.pipeline().addLast("fileServerHandler", new HttpFileServerHandler(url));
}
}); // 绑定端口,同步等待成功。
ChannelFuture f = b.bind("192.168.1.156", port).sync();
System.out.println("HTTP 文件目录服务器启动,网址是 :" + "http://192.168.1.156:" + port + url);
// 等待服务端监听端口关闭。
f.channel().closeFuture().sync();
} catch (Exception e) {
// 退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
int port = 8080; if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) { e.printStackTrace();
}
}
String url = DEFAULT_URL;
if (args.length > 1) {
url = args[1];
}
new HttpFileServer().run(port, url);
}
}

HttpFileServerHandler

package com.zhaowb.netty.ch10_1;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
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.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.regex.Pattern; import static io.netty.handler.codec.http.HttpHeaders.Names.*;
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.HttpResponseStatus.*; public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { private final String url; public HttpFileServerHandler(String url) {
this.url = url;
} @Override
protected void messageReceived(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { // 对 HTTP 请求消息的解码结果进行判断,如果解码失败,直接构造 HTTP 400错误返回。
if (!request.getDecoderResult().isSuccess()) {
sendError(ctx, BAD_REQUEST);
return;
}
// 对请求行中的方法进行判断,如果不是从浏览器或者表单设置为 GET 发起的请求,则构造 HTTP 405 错误返回。
if (request.getMethod() != HttpMethod.GET) {
sendError(ctx, METHOD_NOT_ALLOWED);
return;
}
final String uri = request.getUri();
final String path = sanitizeUri(uri); // 如果构造的 URI 不合法,则返回 HTTP 403 错误,
if (path == null) {
sendError(ctx, FORBIDDEN);
return;
}
File file = new File(path); // 使用新组装的URI 路径构造FILE对象。 // 如果文件不存在或者是系统隐藏文件,则构造 HTTP 404 返回异常。
if (file.isHidden() || !file.exists()) {
sendError(ctx, NOT_FOUND);
return;
}
// 如果文件是目录,则发送目录的连接给客户端浏览器。
if (file.isDirectory()) {
if (uri.endsWith("/")) {
sendListing(ctx, file);
} else {
sendRedirect(ctx, uri + '/');
}
return;
}
if (!file.isFile()) {
sendError(ctx, FORBIDDEN);
return;
} RandomAccessFile randomAccessFile = null;
try {
randomAccessFile = new RandomAccessFile(file, "r");
} catch (FileNotFoundException fnfe) {
sendError(ctx, NOT_FOUND);
return;
} // 获取文件的长度,构造成功的HTTP 应答消息,然后在消息头中设置 ContentLength 和 ContentType 判断是否是Keep-Alive
// 如果是,则在应答消息头中设置 CONNECTION 为 KEEP_ALIVE
long fileLength = randomAccessFile.length();
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, OK);
setContentLength(response, fileLength);
setContentTypeHeader(response, file);
if (isKeepAlive(request)) {
response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
} ctx.write(response);
ChannelFuture sendFileFuture;
// 通过 netty 的 ChunkedFile直接将文件写入到发送缓冲区中。最后为 sendFileFuture 增加 GenericFutureListener
// 如果发送完成,打印 “Transfer complete. ”
sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
@Override
public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) throws Exception {
if (total < 0) {
System.err.println("Transfer progress: " + progress);
} else {
System.err.println("Transfer progress: " + progress + "/" + total);
}
} @Override
public void operationComplete(ChannelProgressiveFuture future) throws Exception {
System.out.println("Transfer complete. ");
}
}); ChannelFuture lastConnectFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
if (!isKeepAlive(request)) {
lastConnectFuture.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(".*[<>&\"].*"); private String sanitizeUri(String uri) { // 使用java.net.URLDecoder 对URL 进行解码,使用UTF-8 字符集解码
// 成功之后,对URI进行合法性判断,如果URI与允许访问的URI一直或者是其子目录(文件),则校验通过,否则返回空。
try {
uri = URLDecoder.decode(uri, "UTF-8");
} catch (UnsupportedEncodingException e) {
try {
uri = URLDecoder.decode(uri, "ISO-8859-1");
} catch (UnsupportedEncodingException e1) {
throw new Error();
}
}
if (!uri.startsWith(url)) {
return null;
}
if (!uri.startsWith("/")) {
return null;
}
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 构造绝对路径返回。
return System.getProperty("user.dir") + File.separator + uri;
} private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*"); private static void sendListing(ChannelHandlerContext ctx, File dir) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK); // 创建成功的HTTP 响应消息,
// 随后设置消息头的类型为 text/html;charset=UTF-8
response.headers().set(CONTENT_TYPE, "text/html;charset=UTF-8");
StringBuilder buf = new StringBuilder(); // 用于构造响应消息体,由于需要将响应结果显示在浏览器上,采用HTML 格式。
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(dirPath).append(" 目录:");
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 应答消息中,然后释放缓冲区,最后调用 writeAndFlush
// 将响应消息发送到缓冲区并刷新 SocketChannel
response.content().writeBytes(buffer);
buffer.release();
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} private static void sendRedirect(ChannelHandlerContext ctx, String newUri) { FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, FOUND);
response.headers().set(LOCATION, newUri);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.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);
} private static void setContentTypeHeader(HttpResponse response, File file) { MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
response.headers().set(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
}
}

码云地址

GitHub地址

最新文章

  1. ios常见的页面传值方式
  2. asp.net下出现其中的组件“访问被拒绝”的解决方法
  3. Javascript中判断变量是 array还是object(是数组还是对象)
  4. Java基础,输入输出
  5. linux eclipse epic perl padwalker
  6. 日志级别的选择:Debug、Info、Warn、Error还是Fatal
  7. 架构设计:负载均衡层设计方案(4)——LVS原理
  8. 批处理 Mysql Findstr
  9. UITableView初始
  10. SAX解析
  11. 【干货篇】步步为营,带你轻松掌握jQuery!
  12. Java第4次实训作业
  13. js 二维数组 for 循环重新赋值
  14. 解决无法连接到 reCAPTCHA 服务
  15. Java复习 之多线程
  16. 学JS的心路历程 -非同步执行
  17. DBS-Tally book(记账本)
  18. Linux下常用命令wget的使用技巧
  19. MessageRPC
  20. python3 操作sqlSever

热门文章

  1. iBatis开发的一个应用
  2. bash字符串前导美元符号的作用
  3. 7 Scatter-loading Features
  4. java could not open `C|D|E|F:\jre\lib\amd64\jvm.cfg' 解决方案与原因
  5. 偏函数-functools.partial
  6. Future Parttern 先给你这张提货单
  7. Django ORM 之F、Q查询与事务
  8. 502Bad Gateway
  9. 安装rubygems
  10. sysobjects syscolumns