Netty实现Http服务器案例

功能:

Netty服务器在6668端口监听,浏览器发出请求"http://localhost:6668"

服务器可以恢复消息给浏览器:“hello,我是服务器”,并对特定请求资源进行过滤

目的:

Netty可以做服务器端开发,并且理解handle实例和客户端及其请求的关系.

服务端代码(Server):

package com.liubujun.netty.http;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;

import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @Author: liubujun
 * @Date: 2023/2/11 15:06
 */


public class TestServer {

    public static void main(String[] args) throws Exception{

        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //创建服务器端的启动对象,配置参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup,workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用nioSocketChannel作为服务器的通道实现
                    .childHandler(new TestServerInitalizer());

            //绑定一个端口并且同步,生成了一个ChannelFuture对象
            //启动服务器(并绑定端口)
            ChannelFuture cf = serverBootstrap.bind(8080).sync();
            //对关联通道进行监听
            cf.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

ServerInitalizer

public class TestServerInitalizer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        //向管道加入处理器

        //得到管道
        ChannelPipeline pipeline = ch.pipeline();

        //加入一个netty 提供的HttpServerCodec
        //1.HttpServerCodec是netty提供的处理http的编-解码器
        pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
        //2.增加一个自定义的handler
        pipeline.addLast("MyTestHttpServerHandler",new TestHttpServerHandler());


    }
}

HttpServerHandler:

package com.liubujun.netty.http;

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

/**
 * @Author: liubujun
 * @Date: 2023/2/11 15:07
 */

/**
 * 说明:
 * 1. SimpleChannelInboundHandler 是 ChannelInboundHandlerAdapter
 * 2 HttpObject客户端和服务端相互通讯的数据被封装成HttpObject
 */
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {


    //channelRead0 读取客户端数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

        //判断msg是不是httpRequest请求
        if (msg != null){
            System.out.println("msg 类型="+msg.getClass());
            System.out.println("客户端地址"+ctx.channel().remoteAddress());

            //回复信息给浏览器
            ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器", CharsetUtil.UTF_8);

            //构造一个http响应,即httpresponse
            DefaultFullHttpResponse response =
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);

            response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());

            //将构建好的response返回
            ctx.writeAndFlush(response);


        }
    }
}

启动服务端,浏览器输入:http://localhost:8080/,得到服务端响应数据

但是服务端控制台却输出了如下请求:

我们只请求了一次,但是浏览器却发出了2次请求,并且对这两个请求都做了响应。

那么如何对这个网站图标进行过滤,使服务器对其不做响应呢?

可以在我们的服务端Handle加上如下代码:

            HttpRequest httpRequest = (HttpRequest)msg;
            URI uri = new URI(httpRequest.uri());
            if ("/favicon.ico".equals(uri.getPath())){
                System.out.println("请求了favicon.ico,不做响应");
                return;
            }

完整版HttpServerHandler代码如下:

package com.liubujun.netty.http;

import com.sun.jndi.toolkit.url.Uri;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

import java.net.URI;

/**
 * @Author: liubujun
 * @Date: 2023/2/11 15:07
 */

/**
 * 说明:
 * 1. SimpleChannelInboundHandler 是 ChannelInboundHandlerAdapter
 * 2 HttpObject客户端和服务端相互通讯的数据被封装成HttpObject
 */
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {


    //channelRead0 读取客户端数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

        //判断msg是不是httpRequest请求
        if (msg != null){


            System.out.println("msg 类型="+msg.getClass());
            System.out.println("客户端地址"+ctx.channel().remoteAddress());

            HttpRequest httpRequest = (HttpRequest)msg;
            URI uri = new URI(httpRequest.uri());
            if ("/favicon.ico".equals(uri.getPath())){
                System.out.println("请求了favicon.ico,不做响应");
                return;
            }

            //回复信息给浏览器
            ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器", CharsetUtil.UTF_16);

            //构造一个http响应,即httpresponse
            DefaultFullHttpResponse response =
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);

            response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());

            //将构建好的response返回
            ctx.writeAndFlush(response);


        }
    }
}

再次重新请求发现:已经成功拦截。

注意:

http协议不是一个长连接

每个请求对应的handle和pineline是独立的,不会共享。

对此,可以请求2次并且打印出其hash值进行比对

第一次请求:

第二次请求:

发现连续2次请求所打印的hash值都不一样。

posted @ 2023-02-12 11:40  小猪不会叫  阅读(91)  评论(0)    收藏  举报  来源