9.1 高性能Web应用架构

9.1.1 十万级并发的Web应用架构

image.png

9.1.2 千万级高并发的Web应用架构

image.png

9.2 详解HTTP应用层协议

9.2.1 HTTP简介

HTTP的主要特点可概括如下:

  1. 支持客户端/服务器模式。
  2. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST,且每种方法规定了客户与服务器联系的类型不同。HTTP简单,使得HTTP服务器的程序规模较小,因此通信速度很快。
  3. 灵活:HTTP允许传输任意类型的数据对象,数据的类型由Content-Type加以标记。
  4. 无连接:每次连接只处理一个请求,服务器处理完客户的请求并收到客户的应答后即断开连接。
  5. 无状态:协议对于事务处理没有记忆能力。如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另外,在服务器不需要先前信息时它的应答较快。

    9.2.2 HTTP的请求URL

    9.2.3 HTTP的请求报文

    image.png

    9.2.4 HTTP的响应报文

    image.png

    9.2.5 HTTP中GET和POST的区别

  6. 二者请求数据的放置位置不同

  7. 二者所能传输数据的大小不同
  8. 二者传输数据的安全性不同

    9.3 HTTP的演进

    image.png

    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服务器演示实例

    image.png

    9.4.2 基于Netty的HTTP请求的处理流程

  9. 客户端(如Postman工具、浏览器、Java程序等)向服务端发送HTTP请求。

  10. 服务端对HTTP请求进行解析。
  11. 服务端向客户端发送HTTP响应报文。
  12. 客户端解析HTTP响应的应用层协议内容。

Netty的HTTP请求的处理流程大致如图
image.png
以本节的HttpEchoServer演示实例的服务端处理器为例,大致的流水线装配代码如下:

  1. ChannelPipeline pipeline = ch.pipeline();
  2. //请求的解码器
  3. pipeline.addLast(new HttpRequestDecoder());
  4. //请求聚合器
  5. pipeline.addLast(new HttpObjectAggregator(65535));
  6. //响应的编码器
  7. pipeline.addLast(new HttpResponseEncoder());
  8. //自定义的业务Handler
  9. pipeline.addLast(new HttpEchoHandler());

Netty内置的与HTTP请求报文相对应的类

  1. FullHttpRequest
  2. HttpRequest
  3. HttpContent
  4. HttpMethod
  5. HttpVersion
  6. HttpHeaders

image.png
对于请求参数的解析,不同的WEB服务器有不同的解析的策略:
例如:Tomcat不仅会解析URI后面的键值对,还会解析HTTP请求体Body中的键值对。而在Netty中,Java中请求参数实例仅仅包含跟在URI后面的键值对

Netty将Http报文的分包策略为以下的2和3:

  1. 定长分包策略
  2. 长度域分包策略
  3. 分隔符分割

9.4.3 Netty内置的HTTP报文解码流程

通过内置处理器HttpRequestDecoder和HttpObjectAggregator对HTTP请求报文进行解码之后,Netty会将HTTP请求封装成一个FullHttpRequest实例
image.png
Netty的HTTP报文拆包方案。

  1. 定长分包策略:接收端按照固定长度进行数据包分割,发送端按照固定长度发送数据包。
  2. 长度域分包策略:比如使用LengthFieldBasedFrameDecoder长度域解码器在接收端分包,而在发送端先发送4个字节表示消息的长度,紧接着发送消息的内容。
  3. 分隔符分割:比如使用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(

      1. 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,
        1. "application/json; charset=UTF-8");
        /**
      • 发送FullHttpResponse响应内容 */ sendAndCleanupConnection(ctx, response); }

      /**

    • 发送FullHttpResponse响应 */ public static void sendAndCleanupConnection(

      1. ChannelHandlerContext ctx, FullHttpResponse response)

      { final boolean keepAlive =

      1. ctx.channel().attr(KEEP_ALIVE_KEY).get();

      HttpUtil.setContentLength(

      1. response, response.content().readableBytes());

      if (!keepAlive) {

      1. //如果不是长连接,就设置connection:close头部
      2. response.headers().set(
      3. HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);

      } else if (isHTTP_1_0(ctx)) {

      1. //如果是1.0版本的长连接,就设置connection:keep-alive头部
      2. response.headers().set(
      3. HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);

      } //发送内容 ChannelFuture flushPromise = ctx.writeAndFlush(response);

      if (!keepAlive) {

      1. //如果不是长连接,那么发送完成之后关闭连接
      2. flushPromise.addListener(ChannelFutureListener.CLOSE);

      } } …

}

  1. <a name="LTpwu"></a>
  2. ## 9.4.5 HttpEchoHandler回显业务处理器的实战案例
  3. ![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)
  4. ```java
  5. package com.crazymakercircle.netty.http.echo;
  6. @Slf4j
  7. public class HttpEchoHandler extends
  8. SimpleChannelInboundHandler<FullHttpRequest>
  9. {
  10. @Override
  11. public void channelRead0(ChannelHandlerContext ctx,
  12. FullHttpRequest request) throws Exception
  13. {
  14. if (!request.decoderResult().isSuccess())
  15. {
  16. HttpProtocolHelper.sendError(ctx, BAD_REQUEST);
  17. return;
  18. }
  19. /**
  20. * 调用辅助类的方法,缓存HTTP协议的版本号
  21. */
  22. HttpProtocolHelper.cacheHttpProtocol(ctx, request);
  23. Map<String, Object> echo = new HashMap<String, Object>();
  24. //1.获取URI
  25. String uri = request.uri();
  26. echo.put("request uri", uri);
  27. //2.获取请求方法
  28. HttpMethod method = request.method();
  29. echo.put("request method", method.toString());
  30. //3.获取请求头
  31. Map<String, Object> echoHeaders = new HashMap<String, Object>();
  32. HttpHeaders headers = request.headers();
  33. //迭代请求头
  34. Iterator<Map.Entry<String, String>> hit =
  35. headers.entries().iterator();
  36. while (hit.hasNext())
  37. {
  38. Map.Entry<String, String> header = hit.next();
  39. echoHeaders.put(header.getKey(), header.getValue());
  40. }
  41. echo.put("request header", echoHeaders);
  42. /**
  43. * 获取URI请求参数
  44. */
  45. Map<String, Object> uriDatas = paramsFromUri(request);
  46. echo.put("paramsFromUri", uriDatas);
  47. //处理POST请求
  48. if (POST.equals(request.method()))
  49. {
  50. /**
  51. * 获取请求体数据到 map
  52. */
  53. Map<String, Object> postData = dataFromPost(request);
  54. echo.put("dataFromPost", postData);
  55. }
  56. /**
  57. * 回显内容转换成json字符串
  58. */
  59. String sendContent = JsonUtil.pojoToJson(echo);
  60. /**
  61. * 发送回显内容到客户端
  62. */

9.4.6 使用Postman发送多种类型的请求体