基于netty手写Tomcat

netty 简介

Netty一个基于NIO的客户、服务器端的编程框架

1.环境准备

maven依赖

  <dependency>

            <groupId>io.netty</groupId>

            <artifactId>netty-all</artifactId>

            <version>4.1.42.Final</version>

  </dependency>

12345

RequestMethodEnum 请求方式

public enum RequestMethodEnum {

  GET("GET"),

  POST("POST");

  public String code;

  RequestMethodEnum(String code) {

    this.code=code;

  }

}

12345678

ParentServlet 父类servlet

public abstract class ParentServlet {

  public void service(ParentRequest request, ParentResponse response) throws Exception {

    //service 方法决定调用doGet、doPost;

    if (RequestMethodEnum.GET.code.equalsIgnoreCase(request.getMethod())) {

      doGet(request, response);

    } else {

      doPost(request, response);

    }

  }

  protected abstract void doPost(ParentRequest request, ParentResponse response) throws Exception;

  protected abstract void doGet(ParentRequest request, ParentResponse response) throws Exception;

}

12345678910111213141516

FirstServlet

public class FirstServlet extends ParentServlet {

  @Override

  protected void doPost(ParentRequest request, ParentResponse response) throws Exception {

    response.write("This is the first");

  }

  @Override

  protected void doGet(ParentRequest request, ParentResponse response) throws Exception {

    this.doPost(request,response);

  }

}

1234567891011

SecondServlet

public class SecondServlet extends ParentServlet {

  @Override

  protected void doPost(ParentRequest request, ParentResponse response) throws Exception {

    response.write("this is the second");

  }

  @Override

  protected void doGet(ParentRequest request, ParentResponse response) throws Exception {

    this.doPost(request,response);

  }

}

1234567891011

ParentRequest

public class ParentRequest {

  private String method;

  private String url;

  public String getUrl() {

    return url;

  }

  public String getMethod() {

    return method;

  }

}

1234567891011121314

ParentResponse

public class ParentResponse {

  private OutputStream out;

  public ParentResponse (OutputStream out) {

    this.out = out;

  }

  public void write(String s) throws Exception{

    //输出也要遵循HTTP

    //状态码为200

    StringBuilder sb = new StringBuilder();

    sb.append("HTTP/1.1 200 OK \n")

      .append("Content-Type: text/html;\n")

      .append("\r\n")

      .append(s);

    out.write(sb.toString().getBytes());

  }

}

1234567891011121314151617

web.properties

servlet.first.url=/first

servlet.first.className=com.aiden.servlet.FirstServlet

servlet.second.url=/second

servlet.second.className=com.aiden.servlet.SecondServlet

1234

2.基于传统I/O手写Tomcat

修改ParentRequest

public class ParentRequest {

  private String method;

  private String url;

  public ParentRequest(InputStream in) {

    try {

      String content = "";

      byte[] buff = new byte[1024];

      int len = 0;

      if ((len = in.read(buff)) > 0) {

        content = new String(buff,0,len);

      }

      String line = content.split("\\n")[0];

      String [] arr = line.split("\\s");

      this.method = arr[0];

      System.out.println(method);

      this.url = arr[1].split("\\?")[0];

    } catch (IOException e) {

      e.printStackTrace();

    }

  }

  public String getUrl() {

    return url;

  }

  public String getMethod() {

    return method;

  }

}

12345678910111213141516171819202122232425262728293031

编写tomcatStart类

public class TomcatStart {

  private int port = 8080;

  private ServerSocket server;

  private Map<String, ParentServlet> servletMapping = new HashMap<String, ParentServlet>();

  private Properties webProperties = new Properties();

  private void init() {

    try {

      String WEB_INF = this.getClass().getResource("/").getPath();

      FileInputStream fis = new FileInputStream(WEB_INF + "web.properties");

      webProperties.load(fis);

      for (Object k : webProperties.keySet()) {

        String key = k.toString();

        if (key.endsWith(".url")) {

          String servletName = key.replaceAll("\\.url$", "");

          String url = webProperties.getProperty(key);

          String className = webProperties.getProperty(servletName + ".className");

          //单实例  多线程

          ParentServlet obj = (ParentServlet) Class.forName(className).newInstance();

          servletMapping.put(url, obj);

        }

      }

    } catch (Exception e) {

      e.printStackTrace();

    }

  }

  public void start() {

    //1.加载配置类,初始化servletMapping

    init();

    try {

      //2.绑定端口启动

      server = new ServerSocket(this.port);

      System.out.println("Tomcat 已启动,监听端口是:" + this.port);

      //3.等待用户请求,用一个死循环

      while (true) {

        Socket client = server.accept();

        //4.http 请求

        process(client);

      }

    } catch (IOException e) {

      e.printStackTrace();

    }

  }

  private void process(Socket client) throws IOException {

    InputStream is = null;

    OutputStream os = null;

    try {

      is = client.getInputStream();

      os = client.getOutputStream();

      //5.Request(inputstream) Response (outputstream)

      ParentRequest request = new ParentRequest(is);

      ParentResponse response = new ParentResponse(os);

      //6.从协议内容中获取url 映射相应的servlet

      String url = request.getUrl();

      if (servletMapping.containsKey(url)) {

        //7.调用实例化对象的service方法

        servletMapping.get(url).service(request, response);

      } else {

        response.write("404 - Not Found");

      }

    } catch (Exception e) {

      e.printStackTrace();

    } finally {

      if (os != null) {

        os.flush();

        os.close();

      }

      if (is != null) {

        is.close();

      }

      client.close();

    }

  }

  public static void main(String[] args) {

    //启动

    new TomcatStart().start();

  }

}

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182

3.基于netty手写Tomcat

修改ParentRequest

public class ParentRequest {

  private ChannelHandlerContext ctx;

  private HttpRequest req;

  public ParentRequest(ChannelHandlerContext ctx, HttpRequest req) {

    this.ctx = ctx;

    this.req = req;

  }

  public String getUrl() {

    return req.uri();

  }

  public String getMethod() {

    return req.method().name();

  }

  public Map<String, List<String>> getParameters() {

    QueryStringDecoder decoder = new QueryStringDecoder(req.uri());

    return decoder.parameters();

  }

  public String getParameter(String name) {

    Map<String, List<String>> params = getParameters();

    List<String> param = params.get(name);

    if (null == param) {

      return null;

    } else {

      return param.get(0);

    }

  }

}

123456789101112131415161718192021222324252627282930313233

修改ParentResponse

public class ParentResponse {

  //SocketChannel的封装

  private ChannelHandlerContext ctx;

  private HttpRequest req;

  public ParentResponse(ChannelHandlerContext ctx, HttpRequest req) {

    this.ctx = ctx;

    this.req = req;

  }

  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")));

      response.headers().set("Content-Type", "text/html;");

      ctx.write(response);

    } finally {

      ctx.flush();

      ctx.close();

    }

  }

}

12345678910111213141516171819202122232425262728293031323334

修改TomcatStart

public class TomcatStart {

  private int port = 8080;

  private Map<String, ParentServlet> servletMapping = new HashMap<String, ParentServlet>();

  private Properties webProperties = new Properties();

  private void init() {

    try {

      String WEB_INF = this.getClass().getResource("/").getPath();

      FileInputStream fis = new FileInputStream(WEB_INF + "web.properties");

      webProperties.load(fis);

      for (Object k : webProperties.keySet()) {

        String key = k.toString();

        if (key.endsWith(".url")) {

          String servletName = key.replaceAll("\\.url$", "");

          String url = webProperties.getProperty(key);

          String className = webProperties.getProperty(servletName + ".className");

          //单实例  多线程

          ParentServlet obj = (ParentServlet) Class.forName(className).newInstance();

          servletMapping.put(url, obj);

        }

      }

    } catch (Exception e) {

      e.printStackTrace();

    }

  }

  public void start() {

    //1.加载配置类,初始化servletMapping

    init();

    // Netty  NIO Reactor模型 Boss Worker

    //Boss 线程

    EventLoopGroup bossGroup = new NioEventLoopGroup();

    //Work线程

    EventLoopGroup workGroup = new NioEventLoopGroup();

    ServerBootstrap server = null;

    try {

      //创建对象

      server = new ServerBootstrap();

      //配置参数

      //链式编程

      server.group(bossGroup, workGroup)

        //主线程处理类,

        .channel(NioServerSocketChannel.class)

        //子线程处理类

        .childHandler(new ChannelInitializer<SocketChannel>() {

          @Override

          protected void initChannel(SocketChannel client) throws Exception {

            //无锁化串行编程

            //netty对http的封装 对顺序有要求

            //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);

      ChannelFuture f = server.bind(port).sync();

      System.out.println("Tomcat 已启动,监听端口是:" + this.port);

      f.channel().closeFuture().sync();

    } catch (Exception e) {

      e.printStackTrace();

    } finally {

      bossGroup.shutdownGracefully();

      workGroup.shutdownGracefully();

    }

  }

  public class TomcatHandler extends ChannelInboundHandlerAdapter {

    @Override

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

      if (msg instanceof HttpRequest) {

        System.out.println("hello request");

        HttpRequest req = (HttpRequest) msg;

        ParentRequest request = new ParentRequest(ctx, req);

        ParentResponse response = new ParentResponse(ctx, req);

        String url = request.getUrl();

        if (servletMapping.containsKey(url)) {

          //7.调用实例化对象的service方法

          servletMapping.get(url).service(request, response);

        } else {

          response.write("404 - Not Found");

        }

      }

    }

  }

  public static void main(String[] args) {

    //启动

    new TomcatStart().start();

  }

}

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798

4.访问

http://localhost:8080/first

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342