netty解码

netty通过内置处理器HttpRequestDecoder和HttpObjectAggregator对Http请求报文进行解码之后,Netty会将Http请求封装成一个FullHttpRequest实例,然后发送给下一站。

Netty内置的与Http请求报文相对应的类大致有如下几个:

(1)FullHttpRequest:包含整个Http请求的信息,包含对HttpRequest首部和HttpContent请求体的结合。

(2)HttpRequest:请求首部,主要包含对Http请求行和请求头的组合。

(3)HttpContent:对Http请求体进行封装,本质上就是一个ByteBuf缓冲区实例。如果ByteBuf的长度是固定的,则请求体过大,可能包含多个HttpContent。解码的时候,最后一个解码返回对象为LastHttpContent(空的HttpContent),表示对请求体的解码已经结束。

(4)HttpMethod:主要是对Http报文请求头的封装及相关操作。

(5)HttpVersion:对Http版本的封装。

(6)HttpHeaders:包含对http报文请求头的封装及相关操作。



Netty的HttpRequest首部类中有一个String uri成员,主要是对请求uri的封装,该成员包含了Http请求的Path路径与跟随在其后的请求参数。

有关请求参数的解析,不同的Web服务器所使用的解析策略有所不同。在tomcat中,如果客户端提交的是application/x-www-form-urlencoded类型的表单post请求,则java请求参数实例除了包含跟随在uri后面的键-值对之外,请求参数还包含Http请求体body中的键-值对。在netty中,java中请求参数实例仅仅包含跟在uri后面的键-值对。

接下来介绍本文的重点:Netty的Http报文拆包方案。

一般来说,服务端收到的Http字节流可能被分成多个ByteBuf包。Netty服务端如何处理Http报文的分包问题呢?大致有以下几种策略:

(1)定长分包策略:接收端按照固定长度进行分割,发送端按照固定长度发送数据包。

(2)长度域分包策略:比如使用LengthFieldBasedFrameDecoder长度域解码器在接收端分包,而在发送端先发送4个字节表示信息的长度,紧接着发送消息的内容。

(3)分割符分割:比如说使用LineBasedFrameDecoder解码器通过换行符进行分包,或者使用DelimiterBasedFrameDecoder通过特定的分隔符进行分包。

netty结合使用上面第(2)种和第(3)种的策略完成http报文的拆包:对于请求头,应用了分隔符分包的策略,以特定分隔符("\r\n")进行拆包;对于Http请求体,应用长度字段中的分包策略,按照请求头中的内容长度进行内容拆包。

Netty的Http响应编码流程

Netty的Http响应的处理流程只需在流水线装配HttpResponseEncoder编码器即可。该编码器是一个出站处理器,有以下特点:

(1)该编码器输入的是FullHttpResponse响应实例,输出的是ByteBuf字节缓冲器。后面的处理器会将ByteBuf数据写入Channel,最终被发送到Http客户端。

(2)该编码器按照Http对入站FullHttpResponse实例的请求行,请求头,请求体进行序列化,通过请求头去判断是否含有Content-Length头或者Trunked头,然后将请求体按照对应长度规则对内容进行序列化。



如果只是发送简单的Http响应,就可以通过DefaultFullHttpResponse默认响应实现类完成。通过该默认响应类既可以设置响应的内容,又可以进行响应头的设置。

public class HttpProtocolHelper
{
public static final int HTTP_CACHE_SECONDS = 60; public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";
private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*"); public static final AttributeKey<HttpVersion> PROTOCOL_VERSION_KEY =
AttributeKey.valueOf("PROTOCOL_VERSION");
public static final AttributeKey<Boolean> KEEP_ALIVE_KEY =
AttributeKey.valueOf("KEEP_ALIVE_KEY"); /**
* 通过channel 缓存 Http 的协议版本,以及是否为长连接
*
* @param ctx 上下文
* @param request 报文
*/
public static void cacheHttpProtocol(ChannelHandlerContext ctx, final FullHttpRequest request)
{
//每一个连接设置一次即可,不需要重复设置
if (ctx.channel().attr(KEEP_ALIVE_KEY).get() == null)
{
ctx.channel().attr(PROTOCOL_VERSION_KEY).set(request.protocolVersion());
final boolean keepAlive = HttpUtil.isKeepAlive(request);
ctx.channel().attr(KEEP_ALIVE_KEY).set(keepAlive);
}
} public static void setKeepAlive(ChannelHandlerContext ctx, boolean val)
{
ctx.channel().attr(KEEP_ALIVE_KEY).set(val);
} public static String sanitizeUri(String uri, String dir)
{
// Decode the path.
try
{
uri = URLDecoder.decode(uri, "UTF-8");
} catch (UnsupportedEncodingException e)
{
throw new Error(e);
} if (uri.isEmpty() || uri.charAt(0) != '/')
{
return null;
} // Convert file separators.
uri = uri.replace('/', File.separatorChar); // Simplistic dumb security check.
// You will have to do something serious in the production environment.
if (uri.contains(File.separator + '.') ||
uri.contains('.' + File.separator) ||
uri.charAt(0) == '.' || uri.charAt(uri.length() - 1) == '.' ||
INSECURE_URI.matcher(uri).matches())
{
return null;
} // Convert to absolute path.
return dir + File.separator + uri;
} private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[^-\\._]?[^<>&\\\"]*"); public static void sendListing(ChannelHandlerContext ctx, final FullHttpRequest request,
File dir, String dirPath)
{
StringBuilder buf = new StringBuilder()
.append("<!DOCTYPE html>\r\n")
.append("<html><head><meta charset='utf-8' /><title>")
.append("Listing of: ")
.append(dirPath)
.append("</title></head><body>\r\n") .append("<h3>Listing of: ")
.append(dirPath)
.append("</h3>\r\n") .append("<ul>")
.append("<li><a href=\"../\">..</a></li>\r\n"); File[] files = dir.listFiles();
if (files != null)
{
for (File f : files)
{
if (f.isHidden() || !f.canRead())
{
continue;
} String name = f.getName();
if (!ALLOWED_FILE_NAME.matcher(name).matches())
{
continue;
} buf.append("<li><a href=\"")
.append(name)
.append("\">")
.append(name)
.append("</a></li>\r\n");
}
} buf.append("</ul></body></html>\r\n"); ByteBuf buffer = ctx.alloc().buffer(buf.length());
buffer.writeCharSequence(buf.toString(), CharsetUtil.UTF_8); FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, buffer);
response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); sendAndCleanupConnection(ctx, response);
} public static void sendRedirect(ChannelHandlerContext ctx, final FullHttpRequest request, String newUri)
{
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND, Unpooled.EMPTY_BUFFER);
response.headers().set(LOCATION, newUri); sendAndCleanupConnection(ctx, response);
} public static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status)
{
HttpVersion version = getHttpVersion(ctx);
FullHttpResponse response = new DefaultFullHttpResponse(
version, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); sendAndCleanupConnection(ctx, response);
} /**
* 发送普通文本响应
*
* @param ctx 上下文
* @param content 响应内容
*/
public static void sendContent(ChannelHandlerContext ctx, String content)
{
HttpVersion version = getHttpVersion(ctx);
FullHttpResponse response = new DefaultFullHttpResponse(
version, OK, Unpooled.copiedBuffer(content, CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); sendAndCleanupConnection(ctx, response);
} /**
* 发送html页面响应
*
* @param ctx 上下文
* @param content 响应内容
*/
public static void sendWebPage(ChannelHandlerContext ctx, String content)
{
HttpVersion version = getHttpVersion(ctx);
FullHttpResponse response = new DefaultFullHttpResponse(
version, OK, Unpooled.copiedBuffer(content, CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); sendAndCleanupConnection(ctx, response);
} /**
* 发送Json格式的响应
*
* @param ctx 上下文
* @param content 响应内容
*/
public static void sendJsonContent(ChannelHandlerContext ctx, String content)
{
HttpVersion version = getHttpVersion(ctx);
/**
* 构造一个默认的FullHttpResponse实例
*/
FullHttpResponse response = new DefaultFullHttpResponse(
version, OK, Unpooled.copiedBuffer(content, CharsetUtil.UTF_8));
/**
* 设置响应头
*/
response.headers().set(CONTENT_TYPE, "application/json; charset=UTF-8");
/**
* 发送响应内容
*/
sendAndCleanupConnection(ctx, response);
} /**
* 发送响应
*/
public static void sendAndCleanupConnection(ChannelHandlerContext ctx, FullHttpResponse response)
{
final boolean keepAlive = ctx.channel().attr(KEEP_ALIVE_KEY).get();
HttpUtil.setContentLength(response, response.content().readableBytes());
if (!keepAlive)
{
// 如果不是长连接,设置 connection:close 头部
response.headers().set(CONNECTION, CLOSE);
} else if (isHTTP_1_0(ctx))
{
// 如果是1.0版本的长连接,设置 connection:keep-alive 头部
response.headers().set(CONNECTION, KEEP_ALIVE);
} //发送内容
ChannelFuture writePromise = ctx.channel().writeAndFlush(response); if (!keepAlive)
{
// 如果不是长连接,发送完成之后,关闭连接
writePromise.addListener(ChannelFutureListener.CLOSE);
}
} private static HttpVersion getHttpVersion(ChannelHandlerContext ctx)
{
HttpVersion version;
if (isHTTP_1_0(ctx))
{
version = HTTP_1_0;
} else
{
version = HTTP_1_1;
}
return version;
} /**
* When file timestamp is the same as what the browser is sending up, send a "304 Not Modified"
*
* @param ctx Context
*/
public static void sendNotModified(ChannelHandlerContext ctx)
{
HttpVersion version = getHttpVersion(ctx);
FullHttpResponse response = new DefaultFullHttpResponse(version, NOT_MODIFIED, Unpooled.EMPTY_BUFFER);
setDateHeader(response); sendAndCleanupConnection(ctx, response);
} public static boolean isHTTP_1_0(ChannelHandlerContext ctx)
{ HttpVersion protocol_version =
ctx.channel().attr(PROTOCOL_VERSION_KEY).get();
if (null == protocol_version)
{
return false;
}
if (protocol_version.equals(HTTP_1_0))
{
return true;
}
return false;
} /**
* Sets the Date header for the HTTP response
*
* @param response HTTP response
*/
public static void setDateHeader(FullHttpResponse response)
{
SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); Calendar time = new GregorianCalendar();
response.headers().set(DATE, dateFormatter.format(time.getTime()));
} /**
* Sets the Date and Cache headers for the HTTP Response
*
* @param response HTTP response
* @param fileToCache file to extract content type
*/
public static void setDateAndCacheHeaders(HttpResponse response, File fileToCache)
{
SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); // Date header
Calendar time = new GregorianCalendar();
response.headers().set(DATE, dateFormatter.format(time.getTime())); //设置缓存过期时间
time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
response.headers().set(EXPIRES, dateFormatter.format(time.getTime()));
response.headers().set(CACHE_CONTROL, "private, max-platform=" + HTTP_CACHE_SECONDS); //最近修改时间
String lastModified = dateFormatter.format(new Date(fileToCache.lastModified()));
response.headers().set(LAST_MODIFIED, lastModified);
} /**
* Sets the content type header for the HTTP Response
*
* @param response HTTP response
* @param file file to extract content type
*/
public static void setContentTypeHeader(HttpResponse response, File file)
{
MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
response.headers().set(CONTENT_TYPE,
mimeTypesMap.getContentType(file.getPath()));
} public static void setKeepAlive(ChannelHandlerContext ctx, HttpResponse response)
{
final boolean keepAlive = ctx.channel().attr(KEEP_ALIVE_KEY).get(); if (!keepAlive)
{
response.headers().set(CONNECTION, CLOSE); } else if (isHTTP_1_0(ctx))
{
response.headers().set(CONNECTION, KEEP_ALIVE);
} } public static boolean isKeepAlive(ChannelHandlerContext ctx)
{
boolean keepAlive = ctx.channel().attr(KEEP_ALIVE_KEY).get();
return keepAlive;
} /**
* 发送目录或者错误信息,如果是文件,则返回
*
* @param ctx 上下文
* @param request 请求
* @return 文件对象
*/
public static File sendErrorOrDirectory(ChannelHandlerContext ctx, FullHttpRequest request)
{
/**
* 路径不对
*/
final String uri = request.uri();
final String path = HttpProtocolHelper.sanitizeUri(uri, SystemConfig.getFileServerDir());
if (path == null)
{
HttpProtocolHelper.sendError(ctx, FORBIDDEN);
return null;
}
File file = new File(path); /**
* 文件不存在
*/
if (!file.exists())
{
HttpProtocolHelper.sendError(ctx, NOT_FOUND);
return null;
} /**
* 发送文件目录
*/
if (file.isDirectory())
{
if (uri.endsWith("/"))
{
HttpProtocolHelper.sendListing(ctx, request, file, uri);
} else
{
HttpProtocolHelper.sendRedirect(ctx, request, uri + '/');
}
return null;
}
/**
* 文件不可用访问
*/
if (!file.isFile())
{
HttpProtocolHelper.sendError(ctx, FORBIDDEN);
return null;
} return file;
} /**
* 根据文件,获取只读的随机访问文件实例
*
* @param ctx 上下文
* @param file 文件
* @return 随机访问文件实例
*/
public static RandomAccessFile openFile(ChannelHandlerContext ctx, File file)
{
RandomAccessFile raf = null;
try
{
raf = new RandomAccessFile(file, "r");
} catch (FileNotFoundException ignore)
{
HttpProtocolHelper.sendError(ctx, NOT_FOUND);
return null;
}
return raf;
}
}

参考文献:java高并发核心编程Nio、Netty、Redis、ZooKeeper 作者:尼恩

Netty内置的http报文解码流程的更多相关文章

  1. python 之 前端开发( JavaScript变量、数据类型、内置对象、运算符、流程控制、函数)

    11.4 JavaScript 11.41 变量 1.声明变量的语法 // 1. 先声明后定义 var name; // 声明变量时无需指定类型,变量name可以接受任意类型 name= " ...

  2. 前端开发:4、JavaScript简介、变量与常量、数据类型及内置方法、运算符、流程控制、循环结构、内置方法

    前端开发之JavaScript 目录 前端开发之JavaScript 一.JavaScript简介 二.JS基础 三.变量与常量 四.基本数据类型 1.数值类型 2.字符类型 3.布尔类型 五.特殊数 ...

  3. Error js内置错误 js处理错误流程 Throw语句

    Exceptional Exception Handling in JavaScript       MDN资料 Anything that can go wrong, will go wrong. ...

  4. Netty内置的编解码器和ChannelHandler

    Netty 为许多通用协议提供了编解码器和处理器,几乎可以开箱即用,这减少了你在那些相当繁琐的事务上本来会花费的时间与精力. 通过SSL/TLS 保护Netty 应用程序 SSL和TLS这样的安全协议 ...

  5. Mysql内置功能《六》流程控制

    一 流程控制 delimiter // CREATE PROCEDURE proc_if () BEGIN declare i int default 0; if i = 1 THEN SELECT ...

  6. Mysql内置功能《一》流程控制

    delimiter // CREATE PROCEDURE proc_if () BEGIN declare i int default 0; if i = 1 THEN SELECT 1; ELSE ...

  7. 5、前端--js常量、变量、5种基本数据类型(number string boolean undefined object)、运算符、流程控制、三元运算符、函数、自定义对象、内置对象、BOM操作

    变量与常量 在JS中声明变量需要使用关键字 老版本 var(全部都是全局变量) 新版本 let(可以声明局部变量) # 推荐使用let(其实问题不大) 在JS中声明常量也需要使用关键字 const # ...

  8. awk(流程控制、内置变量、内置函数、数组)

    摘自:http://bbs.51cto.com/thread-883948-1-1.html awk(流程控制.内置变量.内置函数.数组) ... 参考其他的资料,给大家看看.一.awk流程控制语句 ...

  9. 第一百零八节,JavaScript,内置对象,Global对象字符串编码解码,Math对象数学公式

    JavaScript,内置对象,Global对象字符串编码解码,Math对象数学公式 学习要点: 1.Global对象 2.Math对象 ECMA-262对内置对象的定义是:"由ECMASc ...

  10. Javascript初识之流程控制、函数和内置对象

    一.JS流程控制 1. 1.if else var age = 19; if (age > 18){ console.log("成年了"); }else { console. ...

随机推荐

  1. uniapp 地图如何添加?你要的教程来喽!

    地图在 app 中使用还是很广泛的,常见的应用常见有: 1.获取自己的位置,规划路线. 2.使用标记点进行标记多个位置. 3.绘制多边形,使用围墙标记位置等等. 此篇文章就以高德地图为例,以上述三个常 ...

  2. ES13 中11个令人惊叹的 JavaScript 新特性

    前言 与许多其他编程语言一样,JavaScript 也在不断发展.每年,该语言都会通过新功能变得更加强大,使开发人员能够编写更具表现力和简洁的代码. 小编今天就为大家介绍ES13中添加的最新功能,并查 ...

  3. 【krpano】 ASP点赞插件

    简述 这是一个Asp版krpano点赞案例,运用asp+xml读写.存储数据,结合krpano代码实现的功能:现将案例上传网站供大家学习研究,希望对大家有所帮助. 功能 用户在网页可点赞后显示已点赞数 ...

  4. 【RocketMQ】【源码】延迟消息实现原理

    RocketMQ设定了延迟级别可以让消息延迟消费,延迟消息会使用SCHEDULE_TOPIC_XXXX这个主题,每个延迟等级对应一个消息队列,并且与普通消息一样,会保存每个消息队列的消费进度(dela ...

  5. python入门基础(13)--类、对象

    面向过程的编程语言,如C语言,所使用的数据和函数之间是没有任何直接联系的,它们之间是通过函数调用提供参数的形式将数据传入函数进行处理. 但可能因为错误的传递参数.错误地修改了数据而导致程序出错,甚至是 ...

  6. Solution -「NOI 2007」货币兑换

    Description Link. 一共 \(n\) 天,每天可以卖出或者买入两种股票 \(A\) 和 \(B\).这两种股票在第 \(i\) 天的价值为 \(A_i\) 和 \(B_i\). 每天可 ...

  7. Ds100p -「数据结构百题」21~30

    21.P4172 [WC2006]水管局长 SC 省 MY 市有着庞大的地下水管网络,嘟嘟是 MY 市的水管局长(就是管水管的啦),嘟嘟作为水管局长的工作就是:每天供水公司可能要将一定量的水从 \(x ...

  8. DBeaver Ultimate 22.1.0 连接数据库(MySQL+Mongo+Clickhouse)

    前言 继续书接上文 Docker Compose V2 安装常用数据库MySQL+Mongo,部署安装好之后我本来是找了一个web端的在线连接数据库的工具,但是使用过程中并不丝滑,最终还是选择了使用 ...

  9. Asp-Net-Core开发笔记:快速在已有项目中引入EFCore

    前言 很多项目一开始选型的时候没有选择EFCore,不过EFCore确实好用,也许由于种种原因后面还是需要用到,这时候引入EFCore也很方便. 本文以 StarBlog 为例,StarBlog 目前 ...

  10. 我们又组织了一次欧洲最大开源社区活动,Hugging Face 博客欢迎社区成员发帖、Hugging Chat 功能更新!

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...