9.1 高性能Web应用架构
9.1.1 十万级并发的Web应用架构
9.1.2 千万级高并发的Web应用架构
9.2 详解HTTP应用层协议
9.2.1 HTTP简介
HTTP的主要特点可概括如下:
- 支持客户端/服务器模式。
- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST,且每种方法规定了客户与服务器联系的类型不同。HTTP简单,使得HTTP服务器的程序规模较小,因此通信速度很快。
- 灵活:HTTP允许传输任意类型的数据对象,数据的类型由Content-Type加以标记。
- 无连接:每次连接只处理一个请求,服务器处理完客户的请求并收到客户的应答后即断开连接。
无状态:协议对于事务处理没有记忆能力。如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另外,在服务器不需要先前信息时它的应答较快。
9.2.2 HTTP的请求URL
9.2.3 HTTP的请求报文
9.2.4 HTTP的响应报文
9.2.5 HTTP中GET和POST的区别
二者请求数据的放置位置不同
- 二者所能传输数据的大小不同
-
9.3 HTTP的演进
9.3.1 HTTP的1.0版本
9.3.2 HTTP的1.1版本
9.3.3 HTTP的2.0版本
9.4 基于Netty实现简单的Web服务器
9.4.1 基于Netty的HTTP服务器演示实例
9.4.2 基于Netty的HTTP请求的处理流程
客户端(如Postman工具、浏览器、Java程序等)向服务端发送HTTP请求。
- 服务端对HTTP请求进行解析。
- 服务端向客户端发送HTTP响应报文。
- 客户端解析HTTP响应的应用层协议内容。
Netty的HTTP请求的处理流程大致如图
以本节的HttpEchoServer演示实例的服务端处理器为例,大致的流水线装配代码如下:
ChannelPipeline pipeline = ch.pipeline();
//请求的解码器
pipeline.addLast(new HttpRequestDecoder());
//请求聚合器
pipeline.addLast(new HttpObjectAggregator(65535));
//响应的编码器
pipeline.addLast(new HttpResponseEncoder());
//自定义的业务Handler
pipeline.addLast(new HttpEchoHandler());
Netty内置的与HTTP请求报文相对应的类
FullHttpRequest
HttpRequest
HttpContent
HttpMethod
HttpVersion
HttpHeaders
对于请求参数的解析,不同的WEB服务器有不同的解析的策略:
例如:Tomcat
不仅会解析URI后面的键值对,还会解析HTTP请求体Body中的键值对。而在Netty中,Java中请求参数实例仅仅包含跟在URI后面的键值对
Netty将Http报文的分包策略为以下的2和3:
- 定长分包策略
- 长度域分包策略
- 分隔符分割
9.4.3 Netty内置的HTTP报文解码流程
通过内置处理器HttpRequestDecoder和HttpObjectAggregator对HTTP请求报文进行解码之后,Netty会将HTTP请求封装成一个FullHttpRequest实例
Netty的HTTP报文拆包方案。
- 定长分包策略:接收端按照固定长度进行数据包分割,发送端按照固定长度发送数据包。
- 长度域分包策略:比如使用LengthFieldBasedFrameDecoder长度域解码器在接收端分包,而在发送端先发送4个字节表示消息的长度,紧接着发送消息的内容。
分隔符分割:比如使用LineBasedFrameDecoder解码器通过换行符进行分包,或者使用DelimiterBasedFrameDecoder通过特定的分隔符进行分包。
9.4.4 基于Netty的HTTP响应编码流程
Netty的HTTP响应的处理流程只需在流水线装配HttpResponseEncoder编码器即可。
如果只是发送简单的HTTP响应,就可以通过DefaultFullHttpResponse默认响应实现类完成。通过该默认响应类既可以设置响应的内容,又可以进行响应头的设置。在本书的随书源码中编写了一个HttpProtocolHelper帮助类,通过该响应类进行HTTP响应的设置和发送,相关的部分代码如下: ```java package com.crazymakercircle.netty.util; … public class HttpProtocolHelper { … /**- 发送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(HttpHeaderNames.CONTENT_TYPE,
/**"application/json; charset=UTF-8");
- 发送FullHttpResponse响应内容 */ sendAndCleanupConnection(ctx, response); }
/**
发送FullHttpResponse响应 */ 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(
HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
} else if (isHTTP_1_0(ctx)) {
//如果是1.0版本的长连接,就设置connection:keep-alive头部
response.headers().set(
HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
} //发送内容 ChannelFuture flushPromise = ctx.writeAndFlush(response);
if (!keepAlive) {
//如果不是长连接,那么发送完成之后关闭连接
flushPromise.addListener(ChannelFutureListener.CLOSE);
} } …
}
<a name="LTpwu"></a>
## 9.4.5 HttpEchoHandler回显业务处理器的实战案例
![image.png](https://cdn.nlark.com/yuque/0/2022/png/27178439/1650163124145-1d6a4a30-0743-4220-8940-dca0d67d5c53.png#clientId=ua9be26fd-a868-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=305&id=u670aa0c4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=381&originWidth=803&originalType=binary&ratio=1&rotation=0&showTitle=false&size=68652&status=done&style=none&taskId=u5e361e06-f8bd-4799-9407-bbbf67ca7af&title=&width=642.4)
```java
package com.crazymakercircle.netty.http.echo;
…
@Slf4j
public class HttpEchoHandler extends
SimpleChannelInboundHandler<FullHttpRequest>
{
@Override
public void channelRead0(ChannelHandlerContext ctx,
FullHttpRequest request) throws Exception
{
if (!request.decoderResult().isSuccess())
{
HttpProtocolHelper.sendError(ctx, BAD_REQUEST);
return;
}
/**
* 调用辅助类的方法,缓存HTTP协议的版本号
*/
HttpProtocolHelper.cacheHttpProtocol(ctx, request);
Map<String, Object> echo = new HashMap<String, Object>();
//1.获取URI
String uri = request.uri();
echo.put("request uri", uri);
//2.获取请求方法
HttpMethod method = request.method();
echo.put("request method", method.toString());
//3.获取请求头
Map<String, Object> echoHeaders = new HashMap<String, Object>();
HttpHeaders headers = request.headers();
//迭代请求头
Iterator<Map.Entry<String, String>> hit =
headers.entries().iterator();
while (hit.hasNext())
{
Map.Entry<String, String> header = hit.next();
echoHeaders.put(header.getKey(), header.getValue());
}
echo.put("request header", echoHeaders);
/**
* 获取URI请求参数
*/
Map<String, Object> uriDatas = paramsFromUri(request);
echo.put("paramsFromUri", uriDatas);
//处理POST请求
if (POST.equals(request.method()))
{
/**
* 获取请求体数据到 map
*/
Map<String, Object> postData = dataFromPost(request);
echo.put("dataFromPost", postData);
}
/**
* 回显内容转换成json字符串
*/
String sendContent = JsonUtil.pojoToJson(echo);
/**
* 发送回显内容到客户端
*/