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地址

netty http 服务器的更多相关文章

  1. Netty游戏服务器之一

    所谓磨刀不误砍柴工,所以在搭建netty游戏服务器之前,我们先要把要准备的东西做好. 首先进入netty的官网下载最新版本的netty的jar包,http://netty.io/downloads.h ...

  2. [Netty] - Netty入门(最简单的Netty客户端/服务器程序)

    Java中的NIO是一种解决阻塞式IO问题的基本技术,但是NIO的编写对java程序员是有比较高的要求的.那么Netty就是一种简化操作的一个成熟的网络IO编程框架.这里简单介绍一个程序,代码是< ...

  3. Netty游戏服务器之六服务端登录消息处理

    客户端unity3d已经把消息发送到netty服务器上了,那么ServerHandler类的public void channelRead(ChannelHandlerContext ctx, Obj ...

  4. Netty游戏服务器之四protobuf编解码和黏包处理

    我们还没讲客户端怎么向服务器发送消息,服务器怎么接受消息. 在讲这个之前我们先要了解一点就是tcp底层存在粘包和拆包的机制,所以我们在进行消息传递的时候要考虑这个问题. 看了netty权威这里处理的办 ...

  5. Netty游戏服务器之三搭建Unity客户端

    既然已经写完了相关的服务器处理类,那么我们就来搭建客户端测试一下. 打开我们的unity3d,然后新建一个c#脚本,取名为MainClient. public class MainClient : M ...

  6. Netty创建服务器与客户端

    Netty 创建Server服务端 Netty创建全部都是实现自AbstractBootstrap.客户端的是Bootstrap,服务端的则是ServerBootstrap. 创建一个 HelloSe ...

  7. [转]Netty入门(最简单的Netty客户端/服务器程序)

    Java中的NIO是一种解决阻塞式IO问题的基本技术,但是NIO的编写对java程序员是有比较高的要求的.那么Netty就是一种简化操作的一个成熟的网络IO编程框架.这里简单介绍一个程序,代码是< ...

  8. Netty游戏服务器之五Unity3d登陆消息

    今天我们来讲客户端Unity和服务器收发消息的具体过程. 首先,我们要在unity上搭建登陆界面的UI,这里呢,我用的是NGUI插件. 相信做过unity3d前端的都对这个非常的熟悉,最近官方的UGU ...

  9. Netty游戏服务器二

    上节我们写个server主类,那么发现什么事情都干不了,是的,我们还没有做任何的业务处理. 接着我们开始写处理客户端连接,发送接收数据的类ServerHandler. public class Ser ...

随机推荐

  1. 关于C++里set_intersection(取集合交集)、set_union(取集合并集)、set_difference(取集合差集)等函数的使用总结

    文章转载自https://blog.csdn.net/zangker/article/details/22984803 set里面有set_intersection(取集合交集).set_union( ...

  2. 笨办法学Python记录--习题1-11

    20140412(习题1-10),和打印较劲: 1. 读这本书时没有按照要求安装Python2,我选择的是最新版3.4.0(官方release),然后悲剧发现完全不兼容,现在摘录2,3区别: 这个星期 ...

  3. [JZOJ 5860] 荒诞

    思路: 头皮发麻的操作... 理解一下题意会发现:排名为\(i\)的前缀正好是第\(i\)个前缀. 所以问题就变成了求\(1->len\)的平方和,注意取模即可. #include <bi ...

  4. API文档管理工具

    系统庞大之后,前后端分离开发,前端调用后端提供的接口,请求协议一般是 HTTP,数据格式一般是 JSON.后台只负责数据的提供和计算,而完全不处理展现逻辑和样式:前端则负责拿到数据,组织数据并展现的工 ...

  5. SVN Cannot merge into a working copy that has local modifications

    我尝试了 主支,分支都提交,但是依然无法合并. 最终,我在服务器上将分支删除,然后主支在拷贝过去. 一,打开服务器资源 二,删除分支 三,拷贝主支到分支 四,刷新分支,就能看到了. 然后在分支项目中, ...

  6. 当对象转换成JSON的时候处理时间格式

    /// <summary> /// 格式化日期 /// </summary> /// <param name="foramt">格式化规则< ...

  7. ubuntu切换到root用户

    我们都知道使用su root命令,去切换到root权限,此时会提示输入密码,可是怎么也输不对,提示"Authentication failure", 解决办法如下 su root ...

  8. Got permission denied while trying to connect to the Docker daemon

    答案:https://stackoverflow.com/questions/48568172/docker-sock-permission-denied

  9. mysql连接卡死,很多线程sleep状态,导致CPU中mysqld占用率极高

    关闭所有 .................................. .连接: ##把全部的MySQL连接kill掉for i in $(mysql -uroot -p123456 -Bse ...

  10. Makefile 从入门到放弃

    //如有侵权 请联系我进行删除 email:YZFHKM@163.com { 什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作, ...