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. 《SQL与数据库基础》06. 函数

    目录 函数 字符串函数 数值函数 日期函数 流程函数 本文以 MySQL 为例 函数 函数是指一段可以直接被另一段程序调用的程序或代码. 要查看函数操作的结果,可以使用 SELECT 函数(参数); ...

  2. jmeter 二次开发详解

    背景: JMeter 是一个功能强大的性能测试工具,但它可能无法满足特定项目或组织的特定需求.通过进行二次开发,可以定制 JMeter,使其适应具体项目的需求.例如,可能需要添加自定义的 测试元件.报 ...

  3. Excel中的数值四舍五入方法详解

    在日常工作和数据处理中,我们经常需要对数值进行四舍五入操作.Excel作为一款强大的电子表格软件,提供了多种方法来实现数值的四舍五入.本文将介绍Excel中常用的四舍五入函数及其基本使用方法. ROU ...

  4. 树状数组复习 leetcode 307

    Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive ...

  5. 新零售SaaS架构:面向中小连锁的SaaS系统整体规划

    零售企业的发展路径 零售企业的发展路径一般可分为以下几个阶段: 单店经营阶段:企业在一个地区或城市开设单个门店.这时,企业需要把精力放在了解当地市场和顾客需求上,这是积累经验和品牌知名度的重要环节.为 ...

  6. 10.4 认识Capstone反汇编引擎

    Capstone 是一款开源的反汇编框架,目前该引擎支持的CPU架构包括x86.x64.ARM.MIPS.POWERPC.SPARC等,Capstone 的特点是快速.轻量级.易于使用,它可以良好地处 ...

  7. BS系统的登录鉴权流程演变

    1 基础知识 用户登录是使用指定用户名和密码登录到系统,以对用户的私密数据进行访问和操作.在一个有登录鉴权的BS系统中,通常用户访问数据时,后端拦截请求,对用户进行鉴权,以验证用户身份和权限.用户名. ...

  8. RedisStack部署/持久化/安全/与C#项目集成

    前言 Redis可好用了,速度快,支持的数据类型又多,最主要的是现在可以用来向量搜索了. 本文记录一下官方提供的 redis-stack 部署和配置过程. 关于 redis-stack redis-s ...

  9. 使用yum管理RPM软件包

    yum概念 对比rpm命令,rpm命令需要手动寻找安装该软件包所需要的一系列依赖关系.当软件包需要卸载时,容易由于卸载掉了某个依赖关系而导致其他的软件包不能用. yum(Yellow dog upda ...

  10. android 中service的简单事例

    源码 public class ServiceDemoActivity extends Activity { private static final String TAG = "Servi ...