Netty HTTP 服务端入门开发
一. HTTP 简介
HTTP(超文本传输协议) 协议是建立在 TCP 传输协议之上的应用层协议,它的发展是万维网协会和 Internet 工作小组和 IETF 合作的结果. HTTP 是一个属于应用层的面向对象的协议,由于其便捷,快速的方式,适用于分布式超媒体信息系统.
HTTP 协议的主要特点如下:
- 支持 Client/Server 模式.
- 简单---- 客户端向服务器请求服务时,只需指定服务的 URL, 携带必要的请求参数或者消息体;
- 灵活---- HTTP 允许传输任意类型的数据对象,传输内容类型有 HTTP 消息头中得 Content-Type 指定.
- 无状态---- HTTP 协议是无状态协议,无状态是指协议对于会话状态没有记忆功能,缺少状态意味着如果后续处理需要之前的信息,则它必须重传.这样导致每次连接传送的数据量加大另外一方面,在服务器不需要之前的信息时,它的应答就较快,负载较轻.
netty 的 Http 协议栈是基于 Netty 的 Nio 通信框架开发的.因此, Netty 的 Http 协议也是异步非阻塞的.
代码如下:
HttpFileServer
- package netty.protocol.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;
- /**
- * TODO
- *
- * @description
- * @author ez
- * @time 2015年6月3日 上午10:47:37
- */
- public class HttpFileServer {
- private static final String DEFAULT_URL = "/src/main/java/netty/protocol/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 {
- ch.pipeline().addLast("http-decoder",
- new HttpRequestDecoder()); // http 请求消息解码器,
- /*
- * httpObject 解码器,
- * 它的作用是将多个消息转换为单一的FullHttpRequest或FullHttpResponse
- * 对象,原因是HTTP 解码器在每个HTTP消息中会生成多个消息对象 (
- * HttpRequest/HttpResponse
- * ,HttpContent,LastHttpContent)
- */
- ch.pipeline().addLast("http-aggregator",
- new HttpObjectAggregator(65536));
- /*
- * HTTP 响应消息编码器
- */
- ch.pipeline().addLast("http-encoder",
- new HttpResponseEncoder());
- /*
- * ChunkedWriteHandler
- * 的主要作用是支持异步发送大的码流(例如大文件传输),但不占用过多的内存,防止JAVA内存溢出
- */
- ch.pipeline().addLast("http-chunked",
- new ChunkedWriteHandler());
- /*
- * 业务处理类
- */
- 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();
- } catch (Exception e) {
- e.printStackTrace();
- } 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);
- }
- }
ServerHandler
- package netty.protocol.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请求消息的解码结构进行判断,如果解码失败,直接构造HTTP404 错误返回,
- */
- if (!request.getDecoderResult().isSuccess()) {
- sendError(ctx, BAD_REQUEST);
- return;
- }
- /*
- * 如果不是get请求, 则构造 HTTP405 返回
- */
- if (request.getMethod() != GET) {
- sendError(ctx, METHOD_NOT_ALLOWED);
- return;
- }
- final String uri = request.getUri();
- /*
- * 对URL进行解码, 使用 UTF-8字符集,解码之后对URI进行合法性判断,
- */
- 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;
- 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(".*[<>&\"].*");
- 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) {
- 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);
- response.content().writeBytes(buffer);
- buffer.release();
- 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 权威指南>
Netty HTTP 服务端入门开发的更多相关文章
- Taurus.MVC 微服务框架 入门开发教程:项目集成:1、服务端:注册中心、网关(提供可运行程序下载)。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...
- Java 服务端入门和进阶指南
作者:谢龙 链接:https://www.zhihu.com/question/29581524/answer/44872235 来源:知乎 著作权归作者所有,转载请联系作者获得授权. 现在互联网上资 ...
- 原理剖析-Netty之服务端启动工作原理分析(上)
一.大致介绍 1.Netty这个词,对于熟悉并发的童鞋一点都不陌生,它是一个异步事件驱动型的网络通信框架: 2.使用Netty不需要我们关注过多NIO的API操作,简简单单的使用即可,非常方便,开发门 ...
- Netty搭建服务端的简单应用
Netty简介 Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客 ...
- Taurus.MVC 微服务框架 入门开发教程:项目部署:1、微服务应用程序常规部署实现多开,节点扩容。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...
- Taurus.MVC 微服务框架 入门开发教程:项目部署:2、让Kestrel支持绑定多个域名转发,替代Ngnix使用。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...
- Taurus.MVC 微服务框架 入门开发教程:项目部署:3、微服务应用程序版本升级:全站升级和局部模块升级。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...
- Taurus.MVC 微服务框架 入门开发教程:项目集成:2、客户端:ASP.NET Core(C#)项目集成:应用中心。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...
- Taurus.MVC 微服务框架 入门开发教程:项目集成:5、统一的日志管理。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...
随机推荐
- Systick时钟定时
主函数 /* Note:Your choice is C IDE */ #include "stdio.h" #include "led.h" void mai ...
- BZOJ3253 : 改编
设$f[x][y]$表示从x和y出发相遇的期望长度,则$f[x][x]=0$,且$f[x][y]$对称,共$C(n,2)$个未知量. 列出方程组$G$,得到$G\times F=B$. 高斯消元求出$ ...
- entity framework core 2.0 & sqlite 配置教程
我用的是vs2017,需要下载.net core 2.0 sdk. .net core 下载地址:点我下载 1.在Visual Studio之中创建一个.net core的控制台项目 2.修改cspr ...
- ironic驱动-IMPITool
概述 IMPITool驱动是通过ipmitool工具来管理部署节点的,目前主要有两个驱动: agent_ipmitool pxe_ipmitool 配置驱动 要修改ironic支持的驱动需要修改配置文 ...
- [jzoj]3777.最短路(shortest)
Link https://jzoj.net/senior/#main/show/3777 Description 小Y最近学得了最短路算法,一直想找个机会好好练习一下.话虽这么说,OJ上最短路的题目都 ...
- ES6 Set 和 Map
ES5 模拟Set 与 Map 集合 Set 常用于检查对象中是否存在某个键名 Map集合常被用于获取已存的信息 所有对象的属性名必须是字符串,那么必须确保每个键名都是字符串类型且在对象中是唯一的 数 ...
- bzoj1531: [POI2005]Bank notes(多重背包)
1531: [POI2005]Bank notes Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 521 Solved: 285[Submit][Sta ...
- js实现60s倒计时效果
适用于获取验证码等其他场景,下面代码直接粘贴句可以使用 // 60s获取验证码的js与html var timer = null; var count = 60; $('.box>button' ...
- select 多选 (EasyUI)
<script type="text/javascript" src="/EasyUI/jquery.min.js"></script> ...
- RS485 VS 20mA 电流环
RS485采用差分信号负逻辑,+2V-+6V表示“0”,- 6V-- 2V表示“1”.RS485有两线制和四线制两种接线,四线制只能实现点对点的通信方式,现很少采用,现在多采用的是两线制接线方式,这种 ...