Netty 实现简易 Http 服务器

创建maven项目,导入netty

        
            io.netty
            netty-all
            4.1.6.Final
        

项目结构为

Netty 实现简易 Http 服务器_第1张图片

首先编写tomcat启动类 Tomcat.java 代码如下,注释清晰

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;


public class Tomcat {

    public void start(int port) throws Exception {

        // Boss线程
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // Worker线程
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // Netty服务
            ServerBootstrap server = new ServerBootstrap();
            // 链路式编程
            server.group(bossGroup, workerGroup)
                    // 主线程处理类
                    .channel(NioServerSocketChannel.class)
                    // 子线程处理类 , Handler
                    .childHandler(new ChannelInitializer() {
                        // 客户端初始化处理
                        protected void initChannel(SocketChannel client) throws Exception {
                            // 无锁化串行编程
                            // HttpResponseEncoder 编码器
                            client.pipeline().addLast(new HttpResponseEncoder());
                            // HttpRequestDecoder 解码器
                            client.pipeline().addLast(new HttpRequestDecoder());
                            // 业务逻辑处理
                            client.pipeline().addLast(new TomcatHandler());
                        }

                    })
                    // 针对主线程的配置 分配线程最大数量 128
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 针对子线程的配置 保持长连接
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 启动服务器 sync 同步阻塞
            ChannelFuture f = server.bind(port).sync();
            System.out.println("Tomcat Startd Port: " + port);
            f.channel().closeFuture().sync();
        } finally {
            // 关闭线程池
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        try {
            new Tomcat().start(8080);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

接下来 我们来实现我们自己的业务处理类

继承 ChannelInboundHandlerAdapter 重写 channelRead 方法 指定我们自己实现的Servlet

import com.pro.catalina.http.Request;
import com.pro.catalina.http.Response;
import com.pro.catalina.servlets.MyServlet;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpRequest;

public class TomcatHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof HttpRequest){
            HttpRequest r = (HttpRequest) msg;

            // 转交给我们自己的request实现
            Request request = new Request(ctx,r);
            // 转交给我们自己的response实现
            Response response = new Response(ctx,r);
            // 实际业务处理
            MyServlet.class.newInstance().doGet(request,response);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

    }
}

然后 我们来编写 Request Response 实现

Request 很简单 不细说

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;

import java.util.List;
import java.util.Map;

public class Request {

    private ChannelHandlerContext ctx;

    private HttpRequest r;

    public Request(ChannelHandlerContext ctx, HttpRequest r) {
        this.ctx = ctx;
        this.r = r;
    }

    public String getUri() {
        return r.uri();
    }

    public String getMethod() {
        return r.method().name();
    }

    public Map> getParameters() {
        QueryStringDecoder decoder = new QueryStringDecoder(r.uri());
        return decoder.parameters();
    }

    public String getParameter(String name) {
        Map> params = getParameters();
        List param = params.get(name);
        if (null == param) {
            return null;
        } else {
            return param.get(0);
        }
    }

}

Response 需要设置http协议及请求头信息 , 代码如下,注释详细

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

import static io.netty.handler.codec.http.HttpHeaderNames.*;

public class Response {

    private ChannelHandlerContext ctx;

    private HttpRequest r;

    public Response(ChannelHandlerContext ctx, HttpRequest r) {
        this.ctx = ctx;
        this.r = r;
    }

    public void write(String out) throws Exception {
        try {
            if (out == null || out.length() == 0) {
                return;
            }
            // 设置 http协议及请求头信息
            FullHttpResponse response = new DefaultFullHttpResponse(
                    // 设置http版本为1.1
                    HttpVersion.HTTP_1_1,
                    // 设置响应状态码
                    HttpResponseStatus.OK,
                    // 将输出值写出 编码为UTF-8
                    Unpooled.wrappedBuffer(out.getBytes("UTF-8")));
            // 设置连接类型 为 JSON
            response.headers().set(CONTENT_TYPE, "text/json");
            // 设置请求头长度
            response.headers().set(CONTENT_LANGUAGE, response.content().readableBytes());
            // 设置超时时间为5000ms
            response.headers().set(EXPIRES, 5000);
            // 当前是否支持长连接
//            if (HttpUtil.isKeepAlive(r)) {
//                // 设置连接内容为长连接
//                response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
//            }
            ctx.write(response);
        } finally {
            ctx.flush();
            ctx.close();
        }
    }

接下来进行Servlet编写

首先编写一个抽象的Servlet类

public abstract class Servlet {

    public abstract void doGet(Request request,Response response);

    public abstract void doPost(Request request,Response response);

}

然后实现我们自己的MyServlet类,进行业务处理

这里我们从 request 中获取 参数 name 再通过 response 写出

import com.pro.catalina.http.Request;
import com.pro.catalina.http.Response;
import com.pro.catalina.http.Servlet;

public class MyServlet extends Servlet {

    public void doGet(Request request, Response response) {
        try {
            // 获取 name 参数 并返回
            response.write(request.getParameter("name"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void doPost(Request request, Response response) {
        doGet(request,response);
    }

}

接下来 启动Tomcat进行测试

访问本地localhost端口为8080在URL上增加参数name=tomcat

Netty 实现简易 Http 服务器_第2张图片

name的值tomcat 被成功返回 测试成功

项目地址: netty-tomcat-demo

 

你可能感兴趣的:(Netty)