Netty理论与实践(二) 创建http客户端 服务端

这篇具有很好参考价值的文章主要介绍了Netty理论与实践(二) 创建http客户端 服务端。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


开发实战

1. 使用echo服务器模拟http

通过上一篇文章中的echo服务器程序来模拟一次HTTP请求。

接收消息的代码如下:

public class ServerStringHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("服务端接收到消息:" + msg);
        ctx.writeAndFlush(msg);
    }
}

我们通过postman直接访问echo服务器:
Netty理论与实践(二) 创建http客户端 服务端,Netty网络编程实战训练,http,网络协议,网络,java

请求成功,echo服务器接收到了本次HTTP请求,控制台打印内容如下:

服务端接收到消息:GET / HTTP/1.1
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Postman-Token: b340a7ba-bf85-48a7-97af-0bae5e94750e
Host: localhost:8001
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

上面的原理很容易理解,postman通过tcp建立与服务器localhost:8001的连接,然后自己组装了HTTP request消息,然后发送给echo服务器,echo服务器拿到完整的内容后将其打印在控制台,随后返回一条文本数据。

也正是echo服务器返回了一条文本数据,并未组装HTTP response消息,导致postman并未识别出服务器返回的内容。


这里简单提一下HTTP协议:

超文本传输协议(HyperText Transfer Protocol,HTTP)协议属于七(四)层协议中的应用层协议。HTTP协议其实是客户端和服务端之间请求和应答的标准,它规定了每次请求或返回的标准格式。基于HTTP对消息传输的顺序性和稳定性要求的前提下,HTTP协议一般使用TCP协议进行网络传输,路由寻址依旧是IP协议。

HTTP协议的消息格式(HTTP Messages):

  • Start line CRLF:request|response的起始栏
  • n * (header CRLF):消息头,以key: value 形式组装,末尾跟上回车换行,最终构成的消息头
  • CRLF:空行用于区分消息头和消息体。
  • Body:消息体

Netty理论与实践(二) 创建http客户端 服务端,Netty网络编程实战训练,http,网络协议,网络,java


了解完HTTP协议之后,我们通过如下格式构建HTTP Response消息:

  1. Start line格式:HTTP-Version SP Status-Code SP Reason-Phrase CRLF
  2. Response Header格式:KEY: VALUE CRLF
  3. CRLF
  4. Response Body格式:data
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    System.out.println("服务端接收到消息:" + msg);
    String message = "HTTP/1.1 200 OK\n" +
            "Content-Length: 35\n" +
            "Date: " + new Date() + "\n" +
            "Connection: keep-alive\n" +
            "Content-Type: text/plain\n" +
            "\n" +
            "Reply, This is reply from server-.^";
    System.out.println(message);
    ctx.writeAndFlush(message);
}

重启后再次请求,postman成功识别出了我们拼接的结果:
Netty理论与实践(二) 创建http客户端 服务端,Netty网络编程实战训练,http,网络协议,网络,java

Netty理论与实践(二) 创建http客户端 服务端,Netty网络编程实战训练,http,网络协议,网络,java

通过上述的模拟实验,相信你已经大致理解了HTTP运作的流程。所以,我们要实现HTTP客户端,只需要自行拼凑出HTTP request内容;要实现HTTP服务端,只需要接收和解析request,并根据结果返回response即可。

听起来很简单,但是如果我们要自己来实现HTTP通信,处理各种请求头、cookie、消息体以及压缩算法等等,那么这份工作量过于巨大,所幸netty提供了完整的HTTP协议请求和接收的封装处理。通过使用netty-codec-http包中的内容,我们就可以轻松的进行HTTP解析和处理工作。

<!-- netty-all包含以下两个依赖 -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
</dependency>

<!-- 处理HTTP的请求、返回的消息发送和接收 -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-codec-http</artifactId>
</dependency>
<!-- 处理HTTP/2框架下的消息发送和接收 -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-codec-http2</artifactId>
</dependency>



2. netty http核心类

为了更好的理解netty处理HTTP收发的机制,我们有必要先了解netty-codec-http包中的HTTP核心类。

HTTP消息相关类

  • HttpObject:HTTP对象,是HTTP消息的顶层接口。
  • HttpMessage:HTTP消息的接口定义,提供HttpRequest和HttpResponse的共用属性,如协议版本HttpVersion和请求头HttpHeaders,默认实现类DefaultHttpMessage
  • HttpContent:HTTP消息体,用于存储body内容,默认实现类DefaultHttpContent。在进行大文件传输或消息头参数有Transfer-Encoding:chunked时使用,消息体将会进行分块传输编码(Chunked transfer encoding)技术,如果有需要可以对消息体会划分多个HttpContent块(0-N个块),最后总是以LastHttpContent作为分块传输的结束标识,它的块大小为0,实现类参考DefaultLastHttpContent
  • HttpRequest:HTTP请求,提供访问和设置请求URI、method和cookie的编码解码等信息,默认实现类DefaultHttpRequest
  • HttpResponse:HTTP响应,提供设置返回状态码、版本协议等内容,默认实现类DefaultHttpResponse
  • FullHttpMessage:HttpMessage和HttpContent的组合,在抽象定义上,它就代表了整个HTTP消息。
  • FullHttpRequest:FullHttpMessage和HttpRequest的组合,代表一个完整的HTTP请求,参考DefaultFullHttpRequest
  • FullHttpResponse:FullHttpMessage和HttpResponse的组合,代表一个完整的HTTP响应,参考DefaultFullHttpResponse

FullHttpRequest和FullHttpResponse消息的封装情况如下所示:
Netty理论与实践(二) 创建http客户端 服务端,Netty网络编程实战训练,http,网络协议,网络,java


netty处理器相关类

  • HttpObjectDecoder:入站处理器,将字节流解析为HttpMessage、HttpContent(如果有的话)。HttpRequestDecoderHttpResponseDecoder是其子类。作用是将字节流解析为HttpRequest / HttpResponse、HttpContent。
  • HttpObjectEncoder:出站处理器,将HttpMessage和HttpContent(如果有的话)转为字节流。HttpRequestEncoderHttpResponseEncoder是其子类。作用是将HttpRequest / HttpResponse和HttpContent转为字节流。
  • HttpClientCodec:客户端HTTP消息处理器,是HttpRequestEncoder与HttpResponseDecoder的组合。
  • HttpServerCodec:服务器HTTP消息处理器,是HttpRequestDecoder与HttpResponseEncoder的组合。



3. 服务端

有了上面的理论和实践,要实现一个可用的HTTP已经是非常简单的操作了。这里我们只需根据request请求来生成response即可。

我们新建一个处理器ServerHttpMessageHandler,它的作用是接收request、创建response设置状态码和消息体:“Hello World”。

代码如下:

public class HttpServerRunner {
    private int port;
    public HttpServerRunner(Integer port) {
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        HttpServerRunner runner = new HttpServerRunner(8002);
        runner.start();
    }

    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(
                                    new LoggingHandler(LogLevel.DEBUG),
                                    new HttpServerCodec(),
                                    new ServerHttpMessageHandler()
                            );
                        }
                    });
            // 绑定监听服务端口,并开始接收进来的连接
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

ServerHttpMessageHandler中我们是用HttpRequest来接收HttpMessage,如果要接收消息体HttpContent的内容,需要再建一个if分支语句。这是因为netty在读取消息的时候,它并不会把消息直接转为FullHttpRequest,而是将其划为两个部分:HttpMessage和HttpContent,所以channelRead0将会读取两次以上(HttpMessage读取一次、HttpContent读取0次或多次(分块时)、LastHttpContent读取一次)。

public class ServerHttpMessageHandler extends SimpleChannelInboundHandler<HttpObject> {
    private static final byte[] CONTENT = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpRequest) {
            HttpRequest request = (HttpRequest) msg;
            boolean keepAlive = HttpUtil.isKeepAlive(request);
            // 返回http信息
            FullHttpResponse response = new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.OK, Unpooled.wrappedBuffer(CONTENT));
            // 设置请求头
            response.headers()
                    .set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
                    .setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
            // 是否长连接
            if (keepAlive) {
                if (!request.protocolVersion().isKeepAliveDefault()) {
                    response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
                }
            } else {
                // 本次传输完毕后断开连接
                response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
            }
            ChannelFuture f = ctx.writeAndFlush(response);
            if (!keepAlive) {
                f.addListener(ChannelFutureListener.CLOSE);
            }
        }
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

通过postman测试一下:
Netty理论与实践(二) 创建http客户端 服务端,Netty网络编程实战训练,http,网络协议,网络,java

HTTP服务器流程验证成功!

不过这个服务器只要是个HTTP请求我们就会返回响应,因为我们并未对method、uri、header和body等做处理。

接下来就是构建客户端HTTP请求了。



4. 客户端

客户端这里有两个处理器:

  • ClientMessageToHttpHandler:将客户端发送的字符串封装为HTTP请求,并发送给服务端。
  • ClientHttpReadHandler:接收和解析服务器的响应数据。
public class HttpClientRunner {
    private String host;
    private Integer port;
    public HttpClientRunner(String host, Integer port) {
        this.host = host;
        this.port = port;
    }
    public static void main(String[] args) throws Exception {
        HttpClientRunner client = new HttpClientRunner("127.0.0.1", 8002);
        client.start();
    }
    public void start() throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG), new HttpClientCodec(), new ClientMessageToHttpHandler(), new ClientHttpReadHandler());
                }
            });
            // 创建一个连接
            ChannelFuture f = b.connect(host, port).sync();
            // 创建连接后手动发送一个请求
            f.channel().writeAndFlush("Hello!");
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

ClientMessageToHttpHandler:

public class ClientMessageToHttpHandler extends MessageToMessageEncoder<String> {

    private static final byte[] CONTENT = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, List<Object> out) throws Exception {
        FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/get", Unpooled.wrappedBuffer(CONTENT));
        // 消息发送完毕后关闭连接
        httpRequest.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
        ctx.writeAndFlush(httpRequest);
    }
}

ClientHttpReadHandler,因为TCP的消息顺序性,我们可以保证每次读取HttpContent前,HttpResponse是已经接收完毕的。

public class ClientHttpReadHandler extends SimpleChannelInboundHandler<HttpObject> {

    private HttpResponse request;
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ChannelFuture sync = ctx.close().sync();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpResponse) {
            request = (HttpResponse) msg;
        }
        if (msg instanceof HttpContent) {
            HttpContent content = (HttpContent) msg;
            String length = request.headers().get(HttpHeaderNames.CONTENT_LENGTH);
            ByteBuf byteBuf = content.content();
            CharSequence charSequence = byteBuf.getCharSequence(0, Integer.parseInt(length), StandardCharsets.UTF_8);
            System.out.println(charSequence);
        }
    }
}





总结和源码

本文简单介绍了HTTP协议相关知识,然后在netty代码中实现HTTP消息的接收发送。服务端客户端的功能较为简单,很多服务器功能并未实现,如地址、参数、请求方法的解析,请求头、cookie等验证,消息体接收、分块消息处理,DNS解析,HTTPS消息的处理,文件流上传以及接收,HTTP消息压缩解压处理,跨域问题等等。所以本篇文章只是netty-HTTP的入门学习文章。后续有时间或者要求会再深入学习一下netty中关于HTTP的更多知识。

源码地址:netty-demo





参考

HTTP Messages
Transfer-Encoding
分块传输编码文章来源地址https://www.toymoban.com/news/detail-604266.html

到了这里,关于Netty理论与实践(二) 创建http客户端 服务端的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Netty客户端发送数据给服务器的两个通道(1)

    EventLoopGroup group = new NioEventLoopGroup();// 设置的连接group。 Bootstrap bootstrap = new Bootstrap().group(group).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) // 超时时间。 .channel(NioSocketChannel.class).handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new

    2024年04月14日
    浏览(45)
  • 使用Netty构建TCP和UDP服务器和客户端

    Netty是一个基于Java NIO实现的网络通信框架,提供了高性能、低延迟的网络通信能力。使用Netty构建TCP和UDP服务器和客户端非常简单,下面是一个简单的示例代码: 构建TCP服务器 构建TCP客户端 构建UDP服务器 构建UDP客户端   上述示例代码中,分别定义了一个TCP服务器、TCP客户

    2024年02月16日
    浏览(49)
  • SpringBoot搭建Netty+Socket+Tcp服务端和客户端

    yml配置:    完成,启动项目即可自动监听对应端口 这里为了测试,写了Main方法,可以参考服务端,配置启动类 ,实现跟随项目启动   ......想写就参考服务端...... 有测试的,,,但是忘记截图了................

    2024年02月15日
    浏览(53)
  • 【netty】java如何作为websocket客户端 对服务端发起请求

    是的 本文介绍java如何作为客户端 发起websocket请求 博主不做标题党 不会服务端客户端分不清就写个标题 乱写文章 为什么会使用java作为websocket客户端? 虽说websocket协议 本意是web与服务端之间的通讯协议,那假设有一天 我们的供应商 或者是甲方大爷 只提供了websocket接口呢?

    2024年02月05日
    浏览(50)
  • 32.Netty源码之服务端如何处理客户端新建连接

    Netty 服务端完全启动后,就可以对外工作了。接下来 Netty 服务端是如何处理客户端新建连接的呢? 主要分为四步: md Boss NioEventLoop 线程轮询客户端新连接 OP_ACCEPT 事件; ​ 构造 初始化Netty 客户端 NioSocketChannel; ​ 注册 Netty 客户端 NioSocketChannel 到 Worker 工作线程中; ​ 从

    2024年02月12日
    浏览(42)
  • Netty示例教程:结合Spring Boot构建客户端/服务器通信

    当涉及到在客户端/服务器应用程序中使用Netty进行通信时,以下是一个结合Spring Boot的示例教程,演示如何使用Netty构建客户端和服务器应用程序。 简介 本教程将指导您如何使用Netty结合Spring Boot构建客户端和服务器应用程序。通过Netty的可靠网络通信功能,您可以轻松构建高

    2024年02月15日
    浏览(58)
  • SpringBoot中使用Netty实现TCP通讯,服务器主动向客户端发送数据

    Springboot项目的web服务后台,web服务运行在9100端口。 后台使用netty实现了TCP服务,运行在8000端口。 启动截图如下: 启动类修改: 服务器查看当前所有连接的客户端  服务器获取到所有客户单的ip地址及端口号后,即可通过其给指定客户端发送数据  

    2024年02月11日
    浏览(43)
  • SpringCloud Alibaba - HTTP 客户端 OpenFeign 、自定义配置、优化、最佳实践

    目录 一、OpenFeign 是什么,有什么用呢? 二、OpenFeign 客户端的使用 2.1、远程调用 1.引入依赖 2.在order-service(发起远程调用的微服务)的启动类添加注解开启Feign的功能 3.编写 OpenFeign 客户端 4.通过 OpenFeign 客户端发起远程调用 2.2、自定义 OpenFeign 配置 1.配置文件方式 2.j

    2024年02月08日
    浏览(50)
  • Spring Cloud - HTTP 客户端 Feign 、自定义配置、优化、最佳实践

    目录 一、OpenFeign 是什么,有什么用呢? 二、OpenFeign 客户端的使用 2.1、远程调用 1.引入依赖 2.在order-service(发起远程调用的微服务)的启动类添加注解开启Feign的功能 3.编写 OpenFeign 客户端 4.通过 OpenFeign 客户端发起远程调用 2.2、自定义 OpenFeign 配置 1.配置文件方式 2.j

    2024年02月16日
    浏览(42)
  • 微服务——http客户端Feign

    目录 Restemplate方式调用存在的问题 Feign的介绍 基于Feign远程调用 Feign自定义配置 修改日志方式一(基于配置文件) 修改日志方式二(基于java代码) Feign的性能优化 连接池使用方法  Feign_最佳实践分析   方式一: 方式二  实现Feign最佳实践(方式二)  两种解决方案 就像早期的事务

    2024年02月15日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包