《Netty权威指南 第2版》学习笔记(6)--- HTTP协议开发应用

HTTP协议介绍

HTTP是一个属于应用层的面向对象的协议,其主要特点如下。

  • 支持Client、Server模式
  • 简单—客户向服务器请求服务时,只需要指定服务URL,携带必要的请求参数或者消息体
  • 灵活—HTTP允许传输任意类型的数据对象,传输的内容类型由HTTP消息头中的Content-Type去标记
  • 无状态—HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能力,缺少状态意味着如果后续处理需要之前的信息,则它必须重传,这样可能导致每次连接传送的数据量增大,另一方面,在服务器不需要先前信息时它的应答就较快,负载较轻

HTTP请求消息

HTTP请求由三部分组成,分别为:HTTP请求行、HTTP请求头、HTTP请求正文。

请求行

以一个方法符开头,以空格分开,后面跟着请求的URI和协议的版本,格式为:
Method Request-URI HTTP-Version CRLF

其中Method表示请求方法(GET/POST/DELETE等),Request-URI是一个统一资源标识符,HTTP-Version表示请求的HTTP协议版本,CRLF表示回车和换号

请求头

包含表示请求的各种条件和属性的各类首部信息

请求正文

这部分内容是可选的,请求正文通常都是应该被发送的数据

完整示例:

在这里插入图片描述

HTTP响应消息

HTTP响应也是由三个部分组成,分别是状态行、消息报头、响应正文。

状态行

包含表明响应结果的状态码、原因短语和HTTP版本

消息报头

包含表示请求的各种条件和属性的各类首部

响应正文

即服务端发送给客户端的数据,可以是文本,xml,json等。

完整示例:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

HTTP请求过程

1、首先进行DNS域名解析(本地浏览器缓存、操作系统缓存或者DNS服务器)

  • 首先会搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存)
  • 如果浏览器自身的缓存里面没有找到,那么浏览器会搜索系统自身的DNS缓存
  • 如果还没找到,那么尝试从hosts文件里面去找
  • 在前面三个过程都没获取到的情况下,就去域名服务器查找

2、三次握手建立TCP连接

在HTTP工作开始之前,客户端首先要通过网络与服务器建立连接,HTTP连接是通过TCP来完成的,HTTP是比TCP更高层次的应用层协议,根据规则,只有底层协议建立之后,才能进行高层协议的连接,因此,首先要建立TCP连接,一般TCP连接的端口号是80

3、客户端发起HTTP请求

4、服务器响应HTTP请求

#5、客户端解析html代码,并请求html代码中的资源

浏览器拿到html文件后,就开始解析其中的html代码,遇到js/css/image等静态资源时,就向服务器端去请求下载

6、客户端渲染展示内容

7、关闭TCP连接

一般情况下,一旦服务器向客户单返回了请求数据,它就要关闭TCP连接,但是如果客户端或者服务器在其头信息中加入了这行代码 Conneciton:keep-alive,TCP连接在发送后将仍然保持打开状态,于是,客户端可以继续通过相同的连接发送请求,也就是说前面的3到6步,可以反复进行。保持连接节省了为每个请求建立新连接所需要的时间,还节约了网络带宽。

Netty提供的HTTP编解码器

HttpRequestEncoder

将 HttpRequest、HttpContent 和 LastHttpContent 消息编码为字节

HttpResponseEncoder

将 HttpResponse、HttpContent 和 LastHttpContent 消息编码为字节

HttpRequestDecoder

将字节解码为 HttpRequest、HttpContent 和 LastHttpContent 消息

HttpResponseDecoder

将字节解码为 HttpResponse、HttpContent 和 LastHttpContent 消息

在这里插入图片描述

在这里插入图片描述
如图所示:一个HTTP 请求/响应可能由多个数据部分组成,FullHttpRequest 和FullHttpResponse 消息是特殊的子类型,分别代表了完整的请求和响应。所有类型的 HTTP 消息(FullHttpRequest、 LastHttpContent 等等)都实现了 HttpObject 接口。

构建基于Netty的HTTP服务端


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;

public class HttpServer {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
    
    
                        protected void initChannel(SocketChannel ch) throws Exception {
    
    
                            ChannelPipeline pipeline = ch.pipeline();
                            //对Response 响应报文进行编码
                            pipeline.addLast("encoder",new HttpResponseEncoder());
                            //对Request 请求报文进行解码
                            pipeline.addLast("decoder",new HttpRequestDecoder());
                            /*
                            聚合http为一个完整的报文,
                            它可以将多个消息部分合并为FullHttpRequest或者FullHttpResponse消息,
                            1*1024*1024规定了聚合内容的最大长度为1M,如果超过这个长度将会返回:413 Request Entity Too Large
                             */
                            pipeline.addLast("aggregator",new HttpObjectAggregator(1*1024*1024));
                            /*
                            当使用HTTP时,建议开启压缩功能以尽可能多的减少传输数据的大小,特别是对文本数据来说,虽然压缩会带来一些CPU上的开销。
                             */
                            pipeline.addLast("compressor",new HttpContentCompressor());
                            //业务handler
                            pipeline.addLast(new HttpServerHandler());
                        }
                    });
            ChannelFuture future = b.bind(8088).sync();
            future.channel().closeFuture().sync();
        } finally {
    
    
            group.shutdownGracefully();
        }
    }
}


import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;


public class HttpServerHandler extends ChannelInboundHandlerAdapter {
    
    

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        System.out.println("客户端连接信息:" + ctx.channel().remoteAddress());
    }

    private void response(ChannelHandlerContext ctx, String context, HttpResponseStatus status) {
    
    
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        FullHttpRequest httpRequest = (FullHttpRequest) msg;
        try {
    
    
            String uri = httpRequest.uri();
            String content = httpRequest.content().toString(CharsetUtil.UTF_8);
            HttpMethod method = httpRequest.method();
            System.out.println("httpRequest uri:" + uri);
            System.out.println("httpRequest method: " + method);
            System.out.println("httpRequest content: " + content);
            if (!"/test".equals(uri)) {
    
    
                response(ctx, "非法请求!" + uri, HttpResponseStatus.BAD_REQUEST);
                return;
            }
            response(ctx, "hello client", HttpResponseStatus.OK);
            return;
        } finally {
    
    
            httpRequest.release();
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        cause.printStackTrace();
    }
}

请求结果

在这里插入图片描述

除了浏览器之外,当然也可以自己构建客户端

HTTP客户端


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;

public class HttpClient {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
    
    
                        protected void initChannel(SocketChannel ch) throws Exception {
    
    
                            ChannelPipeline pipeline = ch.pipeline();
                            //和服务端正好相反,对Response 响应报文进行解码
                            pipeline.addLast(new HttpResponseDecoder());
                            //对Request 请求报文进行编码
                            pipeline.addLast(new HttpRequestEncoder());
                            //聚合http
                            pipeline.addLast(new HttpObjectAggregator(1*1024*1024));
                            //解压缩
                            pipeline.addLast(new HttpContentDecompressor());
                            //业务handler
                            pipeline.addLast(new HttpClientHandler());
                        }
                    });
            ChannelFuture future = b.connect("127.0.0.1", 8088).sync();
            future.channel().closeFuture().sync();
        } finally {
    
    
            group.shutdownGracefully();
        }
    }
}


import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

import java.net.URI;

public class HttpClientHandler extends ChannelInboundHandlerAdapter {
    
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        URI uri = new URI("/test");
        //构建http请求
        FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
                HttpMethod.GET,
                uri.toString(),
                Unpooled.copiedBuffer("你好,服务端!", CharsetUtil.UTF_8));
        httpRequest.headers().set(HttpHeaderNames.HOST, "127.0.0.1");
        httpRequest.headers().set(HttpHeaderNames.CONNECTION,
                HttpHeaderValues.KEEP_ALIVE);
        httpRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH,
                httpRequest.content().readableBytes());
        // 发送http请求
        ctx.writeAndFlush(httpRequest);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        FullHttpResponse httpResponse = (FullHttpResponse) msg;
        System.out.println("httpResponse status: " + httpResponse.status());
        System.out.println("httpResponse header: " + httpResponse.headers());
        System.out.println("httpResponse content: " + httpResponse.content().toString(CharsetUtil.UTF_8));
        httpResponse.release();
    }
}

请求结果

客户端
在这里插入图片描述
服务端
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CSDN_WYL2016/article/details/114386091