一. HTTP 简介

  HTTP(超文本传输协议) 协议是建立在 TCP 传输协议之上的应用层协议,它的发展是万维网协会和 Internet 工作小组和 IETF 合作的结果. HTTP 是一个属于应用层的面向对象的协议,由于其便捷,快速的方式,适用于分布式超媒体信息系统.

  HTTP 协议的主要特点如下:

  1. 支持 Client/Server 模式.
  2. 简单---- 客户端向服务器请求服务时,只需指定服务的 URL, 携带必要的请求参数或者消息体;
  3. 灵活---- HTTP 允许传输任意类型的数据对象,传输内容类型有 HTTP 消息头中得 Content-Type 指定.
  4. 无状态---- HTTP 协议是无状态协议,无状态是指协议对于会话状态没有记忆功能,缺少状态意味着如果后续处理需要之前的信息,则它必须重传.这样导致每次连接传送的数据量加大另外一方面,在服务器不需要之前的信息时,它的应答就较快,负载较轻.

  netty 的 Http 协议栈是基于 Netty 的 Nio 通信框架开发的.因此, Netty 的 Http 协议也是异步非阻塞的.

代码如下:

HttpFileServer

  1. package netty.protocol.http;
  2.  
  3. import io.netty.bootstrap.ServerBootstrap;
  4. import io.netty.channel.ChannelFuture;
  5. import io.netty.channel.ChannelInitializer;
  6. import io.netty.channel.EventLoopGroup;
  7. import io.netty.channel.nio.NioEventLoopGroup;
  8. import io.netty.channel.socket.SocketChannel;
  9. import io.netty.channel.socket.nio.NioServerSocketChannel;
  10. import io.netty.handler.codec.http.HttpObjectAggregator;
  11. import io.netty.handler.codec.http.HttpRequestDecoder;
  12. import io.netty.handler.codec.http.HttpResponseEncoder;
  13. import io.netty.handler.stream.ChunkedWriteHandler;
  14.  
  15. /**
  16. * TODO
  17. *
  18. * @description
  19. * @author ez
  20. * @time 2015年6月3日 上午10:47:37
  21. */
  22. public class HttpFileServer {
  23. private static final String DEFAULT_URL = "/src/main/java/netty/protocol/http/";
  24.  
  25. public void run(final int port, final String url) throws Exception {
  26. EventLoopGroup bossGroup = new NioEventLoopGroup();
  27. EventLoopGroup workerGroup = new NioEventLoopGroup();
  28. try {
  29. ServerBootstrap b = new ServerBootstrap();
  30. b.group(bossGroup, workerGroup)
  31. .channel(NioServerSocketChannel.class)
  32. .childHandler(new ChannelInitializer<SocketChannel>() {
  33. @Override
  34. protected void initChannel(SocketChannel ch)
  35. throws Exception {
  36. ch.pipeline().addLast("http-decoder",
  37. new HttpRequestDecoder()); // http 请求消息解码器,
  38. /*
  39. * httpObject 解码器,
  40. * 它的作用是将多个消息转换为单一的FullHttpRequest或FullHttpResponse
  41. * 对象,原因是HTTP 解码器在每个HTTP消息中会生成多个消息对象 (
  42. * HttpRequest/HttpResponse
  43. * ,HttpContent,LastHttpContent)
  44. */
  45. ch.pipeline().addLast("http-aggregator",
  46. new HttpObjectAggregator(65536));
  47. /*
  48. * HTTP 响应消息编码器
  49. */
  50. ch.pipeline().addLast("http-encoder",
  51. new HttpResponseEncoder());
  52. /*
  53. * ChunkedWriteHandler
  54. * 的主要作用是支持异步发送大的码流(例如大文件传输),但不占用过多的内存,防止JAVA内存溢出
  55. */
  56. ch.pipeline().addLast("http-chunked",
  57. new ChunkedWriteHandler());
  58. /*
  59. * 业务处理类
  60. */
  61. ch.pipeline().addLast("fileServerHandler",
  62. new HttpFileServerHandler(url));
  63. }
  64. });
  65. ChannelFuture future = b.bind("localhost", port).sync();
  66. System.out.println("HTTP文件目录服务器启动,网址是 : " + "http://localhost:"
  67. + port + url);
  68. future.channel().closeFuture().sync();
  69. } catch (Exception e) {
  70. e.printStackTrace();
  71. } finally {
  72. bossGroup.shutdownGracefully();
  73. workerGroup.shutdownGracefully();
  74. }
  75. }
  76.  
  77. public static void main(String[] args) throws Exception {
  78. int port = 8080;
  79. if (args.length > 0) {
  80. try {
  81. port = Integer.parseInt(args[0]);
  82. } catch (NumberFormatException e) {
  83. e.printStackTrace();
  84. }
  85. }
  86. String url = DEFAULT_URL;
  87. if (args.length > 1)
  88. url = args[1];
  89. new HttpFileServer().run(port, url);
  90. }
  91. }

ServerHandler

  1. package netty.protocol.http;
  2.  
  3. import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
  4. import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
  5. import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
  6. import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
  7. import static io.netty.handler.codec.http.HttpHeaders.Names.LOCATION;
  8. import static io.netty.handler.codec.http.HttpMethod.GET;
  9. import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
  10. import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
  11. import static io.netty.handler.codec.http.HttpResponseStatus.FOUND;
  12. import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
  13. import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
  14. import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
  15. import static io.netty.handler.codec.http.HttpResponseStatus.OK;
  16. import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
  17. import io.netty.buffer.ByteBuf;
  18. import io.netty.buffer.Unpooled;
  19. import io.netty.channel.ChannelFuture;
  20. import io.netty.channel.ChannelFutureListener;
  21. import io.netty.channel.ChannelHandlerContext;
  22. import io.netty.channel.ChannelProgressiveFuture;
  23. import io.netty.channel.ChannelProgressiveFutureListener;
  24. import io.netty.channel.SimpleChannelInboundHandler;
  25. import io.netty.handler.codec.http.DefaultFullHttpResponse;
  26. import io.netty.handler.codec.http.DefaultHttpResponse;
  27. import io.netty.handler.codec.http.FullHttpRequest;
  28. import io.netty.handler.codec.http.FullHttpResponse;
  29. import io.netty.handler.codec.http.HttpHeaders;
  30. import io.netty.handler.codec.http.HttpResponse;
  31. import io.netty.handler.codec.http.HttpResponseStatus;
  32. import io.netty.handler.codec.http.LastHttpContent;
  33. import io.netty.handler.stream.ChunkedFile;
  34. import io.netty.util.CharsetUtil;
  35.  
  36. import java.io.File;
  37. import java.io.FileNotFoundException;
  38. import java.io.RandomAccessFile;
  39. import java.io.UnsupportedEncodingException;
  40. import java.net.URLDecoder;
  41. import java.util.regex.Pattern;
  42.  
  43. import javax.activation.MimetypesFileTypeMap;
  44.  
  45. public class HttpFileServerHandler extends
  46. SimpleChannelInboundHandler<FullHttpRequest> {
  47. private final String url;
  48.  
  49. public HttpFileServerHandler(String url) {
  50. this.url = url;
  51. }
  52.  
  53. @Override
  54. public void messageReceived(ChannelHandlerContext ctx,
  55. FullHttpRequest request) throws Exception {
  56. /*
  57. * 首先对HTTP请求消息的解码结构进行判断,如果解码失败,直接构造HTTP404 错误返回,
  58. */
  59. if (!request.getDecoderResult().isSuccess()) {
  60. sendError(ctx, BAD_REQUEST);
  61. return;
  62. }
  63. /*
  64. * 如果不是get请求, 则构造 HTTP405 返回
  65. */
  66. if (request.getMethod() != GET) {
  67. sendError(ctx, METHOD_NOT_ALLOWED);
  68. return;
  69. }
  70. final String uri = request.getUri();
  71. /*
  72. * 对URL进行解码, 使用 UTF-8字符集,解码之后对URI进行合法性判断,
  73. */
  74. final String path = sanitizeUri(uri);
  75. if (path == null) {
  76. sendError(ctx, FORBIDDEN);
  77. return;
  78. }
  79. File file = new File(path);
  80. if (file.isHidden() || !file.exists()) {
  81. sendError(ctx, NOT_FOUND);
  82. return;
  83. }
  84. if (file.isDirectory()) {
  85. if (uri.endsWith("/")) {
  86. sendListing(ctx, file);
  87. } else {
  88. sendRedirect(ctx, uri + '/');
  89. }
  90. return;
  91. }
  92. if (!file.isFile()) {
  93. sendError(ctx, FORBIDDEN);
  94. return;
  95. }
  96. RandomAccessFile randomAccessFile = null;
  97. try {
  98. randomAccessFile = new RandomAccessFile(file, "r");// 以只读的方式打开文件
  99. } catch (FileNotFoundException fnfe) {
  100. sendError(ctx, NOT_FOUND);
  101. return;
  102. }
  103. long fileLength = randomAccessFile.length();
  104. HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
  105. setContentLength(response, fileLength);
  106. setContentTypeHeader(response, file);
  107. if (isKeepAlive(request)) {
  108. response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
  109. }
  110. ctx.write(response);
  111. ChannelFuture sendFileFuture;
  112. sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0,
  113. fileLength, 8192), ctx.newProgressivePromise());
  114. sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
  115. public void operationProgressed(ChannelProgressiveFuture future,
  116. long progress, long total) {
  117. if (total < 0) { // total unknown
  118. System.err.println("Transfer progress: " + progress);
  119. } else {
  120. System.err.println("Transfer progress: " + progress + " / "
  121. + total);
  122. }
  123. }
  124.  
  125. public void operationComplete(ChannelProgressiveFuture future)
  126. throws Exception {
  127. System.out.println("Transfer complete.");
  128. }
  129. });
  130. /*
  131. * 如果是chunked 编码, 最后需要发送一个编码结束的空消息体,将LastHttpContent的EMPTY_LAST_CONTENT
  132. * 发送到缓存区中,标识所有的消息体已经发送完成,同时调用flush方法将之前在发送缓冲区的消息刷新到SocketChannel中发送给对方
  133. */
  134. ChannelFuture lastContentFuture = ctx
  135. .writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
  136. /*
  137. * 如果是非 Keep-Alive 的,最后一包消息发送完之后,服务端要主动关闭连接.
  138. */
  139. if (!isKeepAlive(request)) {
  140. lastContentFuture.addListener(ChannelFutureListener.CLOSE);
  141. }
  142. }
  143.  
  144. @Override
  145. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
  146. throws Exception {
  147. cause.printStackTrace();
  148. if (ctx.channel().isActive()) {
  149. sendError(ctx, INTERNAL_SERVER_ERROR);
  150. }
  151. }
  152.  
  153. private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
  154.  
  155. private String sanitizeUri(String uri) {
  156. try {
  157. uri = URLDecoder.decode(uri, "UTF-8");
  158. } catch (UnsupportedEncodingException e) {
  159. try {
  160. uri = URLDecoder.decode(uri, "ISO-8859-1");
  161. } catch (UnsupportedEncodingException e1) {
  162. throw new Error();
  163. }
  164. }
  165. if (!uri.startsWith(url)) {
  166. return null;
  167. }
  168. if (!uri.startsWith("/")) {
  169. return null;
  170. }
  171. uri = uri.replace('/', File.separatorChar);
  172. if (uri.contains(File.separator + '.')
  173. || uri.contains('.' + File.separator) || uri.startsWith(".")
  174. || uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) {
  175. return null;
  176. }
  177. return System.getProperty("user.dir") + File.separator + uri;
  178. }
  179.  
  180. private static final Pattern ALLOWED_FILE_NAME = Pattern
  181. .compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");
  182.  
  183. private static void sendListing(ChannelHandlerContext ctx, File dir) {
  184. FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
  185. response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
  186. StringBuilder buf = new StringBuilder();
  187. String dirPath = dir.getPath();
  188. buf.append("<!DOCTYPE html>\r\n");
  189. buf.append("<html><head><title>");
  190. buf.append(dirPath);
  191. buf.append(" 目录:");
  192. buf.append("</title></head><body>\r\n");
  193. buf.append("<h3>");
  194. buf.append(dirPath).append(" 目录:");
  195. buf.append("</h3>\r\n");
  196. buf.append("<ul>");
  197. buf.append("<li>链接:<a href=\"../\">..</a></li>\r\n");
  198. for (File f : dir.listFiles()) {
  199. if (f.isHidden() || !f.canRead()) {
  200. continue;
  201. }
  202. String name = f.getName();
  203. if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
  204. continue;
  205. }
  206. buf.append("<li>链接:<a href=\"");
  207. buf.append(name);
  208. buf.append("\">");
  209. buf.append(name);
  210. buf.append("</a></li>\r\n");
  211. }
  212. buf.append("</ul></body></html>\r\n");
  213. ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);
  214. response.content().writeBytes(buffer);
  215. buffer.release();
  216. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
  217. }
  218.  
  219. private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
  220. FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);
  221. response.headers().set(LOCATION, newUri);
  222. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
  223. }
  224.  
  225. private static void sendError(ChannelHandlerContext ctx,
  226. HttpResponseStatus status) {
  227. FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
  228. status, Unpooled.copiedBuffer("Failure: " + status.toString()
  229. + "\r\n", CharsetUtil.UTF_8));
  230. response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
  231. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
  232. }
  233.  
  234. private static void setContentTypeHeader(HttpResponse response, File file) {
  235. MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
  236. response.headers().set(CONTENT_TYPE,
  237. mimeTypesMap.getContentType(file.getPath()));
  238. }
  239. }

以上内容出自 <netty 权威指南>

Netty HTTP 服务端入门开发的更多相关文章

  1. Taurus.MVC 微服务框架 入门开发教程:项目集成:1、服务端:注册中心、网关(提供可运行程序下载)。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...

  2. Java 服务端入门和进阶指南

    作者:谢龙 链接:https://www.zhihu.com/question/29581524/answer/44872235 来源:知乎 著作权归作者所有,转载请联系作者获得授权. 现在互联网上资 ...

  3. 原理剖析-Netty之服务端启动工作原理分析(上)

    一.大致介绍 1.Netty这个词,对于熟悉并发的童鞋一点都不陌生,它是一个异步事件驱动型的网络通信框架: 2.使用Netty不需要我们关注过多NIO的API操作,简简单单的使用即可,非常方便,开发门 ...

  4. Netty搭建服务端的简单应用

    Netty简介 Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客 ...

  5. Taurus.MVC 微服务框架 入门开发教程:项目部署:1、微服务应用程序常规部署实现多开,节点扩容。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...

  6. Taurus.MVC 微服务框架 入门开发教程:项目部署:2、让Kestrel支持绑定多个域名转发,替代Ngnix使用。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...

  7. Taurus.MVC 微服务框架 入门开发教程:项目部署:3、微服务应用程序版本升级:全站升级和局部模块升级。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...

  8. Taurus.MVC 微服务框架 入门开发教程:项目集成:2、客户端:ASP.NET Core(C#)项目集成:应用中心。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...

  9. Taurus.MVC 微服务框架 入门开发教程:项目集成:5、统一的日志管理。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...

随机推荐

  1. Systick时钟定时

    主函数 /* Note:Your choice is C IDE */ #include "stdio.h" #include "led.h" void mai ...

  2. BZOJ3253 : 改编

    设$f[x][y]$表示从x和y出发相遇的期望长度,则$f[x][x]=0$,且$f[x][y]$对称,共$C(n,2)$个未知量. 列出方程组$G$,得到$G\times F=B$. 高斯消元求出$ ...

  3. entity framework core 2.0 & sqlite 配置教程

    我用的是vs2017,需要下载.net core 2.0 sdk. .net core 下载地址:点我下载 1.在Visual Studio之中创建一个.net core的控制台项目 2.修改cspr ...

  4. ironic驱动-IMPITool

    概述 IMPITool驱动是通过ipmitool工具来管理部署节点的,目前主要有两个驱动: agent_ipmitool pxe_ipmitool 配置驱动 要修改ironic支持的驱动需要修改配置文 ...

  5. [jzoj]3777.最短路(shortest)

    Link https://jzoj.net/senior/#main/show/3777 Description 小Y最近学得了最短路算法,一直想找个机会好好练习一下.话虽这么说,OJ上最短路的题目都 ...

  6. ES6 Set 和 Map

    ES5 模拟Set 与 Map 集合 Set 常用于检查对象中是否存在某个键名 Map集合常被用于获取已存的信息 所有对象的属性名必须是字符串,那么必须确保每个键名都是字符串类型且在对象中是唯一的 数 ...

  7. bzoj1531: [POI2005]Bank notes(多重背包)

    1531: [POI2005]Bank notes Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 521  Solved: 285[Submit][Sta ...

  8. js实现60s倒计时效果

    适用于获取验证码等其他场景,下面代码直接粘贴句可以使用 // 60s获取验证码的js与html var timer = null; var count = 60; $('.box>button' ...

  9. select 多选 (EasyUI)

    <script type="text/javascript" src="/EasyUI/jquery.min.js"></script> ...

  10. RS485 VS 20mA 电流环

    RS485采用差分信号负逻辑,+2V-+6V表示“0”,- 6V-- 2V表示“1”.RS485有两线制和四线制两种接线,四线制只能实现点对点的通信方式,现很少采用,现在多采用的是两线制接线方式,这种 ...