Netty实现简单HTTP服务器
netty
package com.dxz.nettydemo.http; import java.io.UnsupportedEncodingException; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder; class HttpServerHandler extends ChannelInboundHandlerAdapter { @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException { if (msg instanceof HttpRequest) { // 请求,解码器将请求转换成HttpRequest对象
HttpRequest request = (HttpRequest) msg; // 获取请求参数
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri());
String name = "World";
if (queryStringDecoder.parameters().get("name") != null) {
name = queryStringDecoder.parameters().get("name").get(0);
} // 响应HTML
String responseHtml = "<html><body>Hello, " + name + "</body></html>";
byte[] responseBytes = responseHtml.getBytes("UTF-8");
int contentLength = responseBytes.length; // 构造FullHttpResponse对象,FullHttpResponse包含message body
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.wrappedBuffer(responseBytes));
response.headers().set("Content-Type", "text/html; charset=utf-8");
response.headers().set("Content-Length", Integer.toString(contentLength)); ctx.writeAndFlush(response);
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
主类:
package com.dxz.nettydemo.http; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.HttpServerCodec; public class HttpServer { public static void main(String[] args) throws InterruptedException {
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
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpServerHandler());
}
});
ChannelFuture f = b.bind(8080).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
测试:
用浏览器访问:http://localhost:8080/
netty构建的文件服务器
1、首先加入的是HTTP请求消息解码器
ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
2、第2添加HttpObjectAggregator解密器,其作用是将多个消息转换为单一的FullHttpRequest或者FullHttpResponse,原因是HTTP解码器在每个HTTP消息中会生成多个消息对象:有1、HttpRequest/HttpResponse;2、HttpContent;3、LastHttpContent;
ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
3、第3增加HTTP响应编码器,对HTTP响应信息进行编码
ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
4、第4Chunked handler的主要作用是支持异步发送大的码流(例如大的文件传输),但不占用过多的内存,防止发生JAVA内存溢出错误
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
5、第5HttpFileServerHandler用于文件服务器的业务逻辑处理
package com.dxz.nettydemo.http; 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 { private static final String DEFAULT_URL = "/src/main/java/com/dxz/nettydemo/http"; public void run(final int port, final String url) throws Exception {
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 {
//首先加入的是HTTP请求消息解码器
ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
//第2添加HttpObjectAggregator解密器,其作用是将多个消息转换为单一的FullHttpRequest或者FullHttpResponse,
//原因是HTTP解码器在每个HTTP消息中会生成多个消息对象:有1、HttpRequest/HttpResponse;2、HttpContent;3、LastHttpContent;
ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
//第3增加HTTP响应编码器,对HTTP响应信息进行编码
ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
//第4Chunked handler的主要作用是支持异步发送大的码流(例如大的文件传输),但不占用过多的内存,防止发生JAVA内存溢出错误
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
//第5HttpFileServerHandler用于文件服务器的业务逻辑处理
ch.pipeline().addLast("fileServerHandler", new HttpFileServerHandler(url));
}
});
ChannelFuture future = b.bind("localhost", port).sync();
System.out.println("HTTP文件目录服务器启动,网址是 : " + "http://localhost:" + port + url);
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
int port = 8080;
if (args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
String url = DEFAULT_URL;
if (args.length > 1)
url = args[1];
new HttpFileServer().run(port, url);
}
}
文件读取下载
package com.dxz.nettydemo.http; 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.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.LOCATION;
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.FOUND;
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.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil; 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 javax.activation.MimetypesFileTypeMap; public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private final String url; public HttpFileServerHandler(String url) {
this.url = url;
} @Override
public void messageReceived(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
//http请求消息解码结果的判断,如果解码失败,直接返回构造HTTP400 错误返回
if (!request.getDecoderResult().isSuccess()) {
sendError(ctx, BAD_REQUEST);
return;
}
if (request.getMethod() != GET) {
sendError(ctx, METHOD_NOT_ALLOWED);
return;
}
final String uri = request.getUri();
final String path = sanitizeUri(uri);
if (path == null) {
sendError(ctx, FORBIDDEN);
return;
}
File file = new File(path);
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;
} //待下载文件的长度
long fileLength = randomAccessFile.length();
HttpResponse response = new DefaultHttpResponse(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 = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
if (total < 0) { // total unknown
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.");
}
});
//使用chunked编码,最后需要发送一个编码结束 的空消息体,将LastHttpContent的EMPTY_LAST_CONTENT发送到缓冲区中,标识所有的消息体已经发送完成,同时
//用flush方法将之前在发送缓冲区的消息刷新到SocketChannel中发送给对方
ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
//如果不是keep-alive,服务端在发送完成之后,服务端主动关闭连接
if (!isKeepAlive(request)) {
lastContentFuture.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(".*[<>&\"].*"); /**
* 请求路径的处理
* @param uri
* @return
*/
private String sanitizeUri(String 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);
if (uri.contains(File.separator + '.') || uri.contains('.' + File.separator) || uri.startsWith(".")
|| uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) {
return null;
}
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) {
//创建成功的HTTP响应消息
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
//设置消息头类型
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(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应答消息中
response.content().writeBytes(buffer);
//释放缓冲区
buffer.release();
//将响应消息刷新到SocketChannel中
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} private 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);
} private 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);
} private static void setContentTypeHeader(HttpResponse response, File file) {
MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
response.headers().set(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
}
}
演示:
Netty实现简单HTTP服务器的更多相关文章
- Netty实现简单UDP服务器
本文参考<Netty权威指南> 文件列表: ├── ChineseProverbClientHandler.java ├── ChineseProverbClient.java ├── C ...
- Netty实现简单WebSocket服务器
本文参考<Netty权威指南>├── WebSocketServerHandler.java├── WebSocketServer.java└── wsclient.html packag ...
- Netty实现高性能RPC服务器优化篇之消息序列化
在本人写的前一篇文章中,谈及有关如何利用Netty开发实现,高性能RPC服务器的一些设计思路.设计原理,以及具体的实现方案(具体参见:谈谈如何使用Netty开发实现高性能的RPC服务器).在文章的最后 ...
- Netty实现高性能IOT服务器(Groza)之手撕MQTT协议篇上
前言 诞生及优势 MQTT由Andy Stanford-Clark(IBM)和Arlen Nipper(Eurotech,现为Cirrus Link)于1999年开发,用于监测穿越沙漠的石油管道.目标 ...
- Netty学习——基于netty实现简单的客户端聊天小程序
Netty学习——基于netty实现简单的客户端聊天小程序 效果图,聊天程序展示 (TCP编程实现) 后端代码: package com.dawa.netty.chatexample; import ...
- 高强度学习训练第十三天总结:使用Netty实现一个http服务器
Netty入门 Netty的重要性不言而喻.那么今天就来学习一下Netty. 整个项目基于Gradle搭建. Build如下所示: plugins { id 'java' } group 'cn.ba ...
- netty 实现简单的rpc调用
yls 2020/5/23 netty 实现简单rpc准备 使用netty传输java bean对象,可以使用protobuf,也可以通过json转化 客户端要将调用的接口名称,方法名称,参数列表的类 ...
- C#中使用Socket实现简单Web服务器
上一篇博客中介绍了怎样使用socket访问web服务器.关键有两个: 熟悉Socket编程: 熟悉HTTP协议. 上一篇主要是通过socket来模拟浏览器向(任何)Web服务器发送(HTTP)请求,重 ...
- 用nodejs搭建一个简单的服务器
使用nodejs搭建一个简单的服务器 nodejs优点:性能高(读写文件) 数据操作能力强 官网:www.nodejs.org 验证是否安装成功:cmd命令行中输入node -v 如果显示版本号表示安 ...
随机推荐
- NumPy矩阵库
NumPy - 矩阵库 NumPy 包包含一个 Matrix库numpy.matlib.此模块的函数返回矩阵而不是返回ndarray对象. matlib.empty() matlib.empty()函 ...
- Jexus部署Asp.Net Core项目
在之前的我的博客项目中,我将.net Core发布到Cent OS 上,使用的Nginx代理以及Supervisor进程守护,看过我的博客的童鞋,也会发现,这种方式比较麻烦,光命令行就看的头大,总共部 ...
- Android调用系统相机拍照保存照片很小解决方案
保存图片小的一般操作步骤: 1. 调用系统相机 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityFo ...
- 51nod 1161 组合数,规律
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1161 显然,题目可以转化为矩阵求解,但复杂度显然时空都不允许,我们如果自 ...
- Xcode export/upload error: Your session has expired. Please log in 解决方法
问题: 突然打包账号不好使了 重登 重启 清缓存 一套都打完了 还是不好使 解决方法: 删除掉其他账号 重新登录 参考网址 http://stackoverflow.com/ques ...
- mongodb停止遇到shutdownServer failed: unauthorized: this command must run from localhost when running db without auth解决方法
停止mongodb use admin db.shutdownServer(); mongos> db.shutdownServer(); assert failed : unexpected ...
- Cassandra 数据模型设计,根据你的查询来制定设计——反范式设计本质:空间换时间
转自:http://www.infoq.com/cn/articles/best-practice-of-cassandra-data-model-design 不要把Cassandra model想 ...
- Cassandra key说明——Cassandra 整体数据可以理解成一个巨大的嵌套的Map Map<RowKey, SortedMap<ColumnKey, ColumnValue>>
Cassandra之中一共包含下面5种Key: Primary Key Partition Key Composite Key Compound Key Clustering Key 首先,Prima ...
- 27-THREE.JS 平面
<!DOCTYPE html> <html> <head> <title></title> <script src="htt ...
- 【SQL查询】模糊查询_like
[格式]:SELECT 字段 FROM 表 WHERE 某字段 Like 条件 [说明]: 1. %表示任意0个或多个字符,可匹配任意类型和长度的字符. 2. _ 表示任意单个字符.匹配单个任意字符. ...