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. windows和linux键值表

    windows系统下对应键值 {8,KEY_BACKSPACE}, {9,KEY_TAB}, {13,KEY_ENTER}, {16,KEY_LEFTSHIFT}, {17,KEY_LEFTCTRL} ...

  2. 【pytorch】目标检测:一文搞懂如何利用kaggle训练yolov5模型

    笔者的运行环境:python3.8+pytorch2.0.1+pycharm+kaggle.yolov5对python和pytorch版本是有要求的,python>=3.8,pytorch> ...

  3. 我是如何使用Spring Retry减少1000 行代码

    本文翻译自国外论坛 medium,原文地址:https://levelup.gitconnected.com/how-i-deleted-more-than-1000-lines-of-code-us ...

  4. Python之os模块常用命令

    OS模块介绍 os模块是Python标准库中的一个用于访问操作系统相关功能的模块,os模块提供了一种可移植的使用 操作系统功能的方法.使用os模块中提供的接口,可以实现跨平台访问.该模块包含了大量的操 ...

  5. Kafka入门学习

    什么是 Kafka Kafka 是由 Linkedin 公司开发的,它是一个分布式的,支持多分区.多副本,基于 Zookeeper 的分布式消息流平台,它同时也是一款开源的基于发布订阅模式的消息引擎系 ...

  6. Tongweb远程调试

    最近,在对项目进行国产化时,要求springboot项目容器换成tongweb.在部署中,有个问题一直无法在本地重现,搜了下网上资料,基本没法实现,所以我整理了下.注意,项目包代码必须与本地代码保持一 ...

  7. 「codeforces - 1519E」Off by One

    link. 点 \(A\) 与 \((0,0)\),\(B\) 共线的充要条件是 \(\frac{y_A}{x_A}=\frac{y_B}{x_B}\),即 \(k_{OA}=k_{OB}\).又考虑 ...

  8. DotNetGuide新增C#/.NET/.NET Core充电站(让你学习不迷路)

    DotNetGuide简介 记录.收集和总结C#/.NET/.NET Core基础知识.学习路线.开发实战.学习视频.文章.书籍.项目框架.社区组织.开发必备工具.常见面试题.面试须知.简历模板.以及 ...

  9. 其它——CGI,FastCGI,WSGI,uWSGI,uwsgi一文搞懂

    文章目录 CGI, FastCGI, WSGI, uWSGI, uwsgi一文搞懂 一 CGI 二 FastCGI 三 WSGI 四 uWSGI 五 uwsgi CGI, FastCGI, WSGI, ...

  10. np.random.uniform()

    np.random.uniform(start,end,second) start:开始数 end:结束数 second:次数,也就是选择几次. 代码结果如下所示: import numpy as n ...