手把手带你写一个 MiniTomcat——代码篇

手把手带你写一个 MiniTomcat

四个小版本

分别在指定包下如v1,v2,v3,v4每个代表一个版本

  • v1 简单的返回指定字符串
  • v2 返回静态页面
  • v3 单线程处理servelt请求(多个请求会阻塞)
  • v4 多线程处理
  • v5 部署外部项目(多个项目,多线程处理) 部署外部项目到webapps下面,在server.xml里面配置webapps路径以及port端口

说明

每个版本中Bootstrap类中main方法启动默认监听8080端口,如有需要可以提供配置到指定xml文件里;


V1

v1 简单的返回指定字符串

package com.udeam.v1;

import com.udeam.util.HttpUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 启动类入库
 * 用于启动tomcat
 * <p>
 * 版本1:返回指定字符串
 */
public class Bootstrap {

    /**
     * 监听端口号
     * 用于启动socket监听的端口号
     */
    private int port = 8080;

    /**
     * 启动方法
     */
    public void start() throws IOException {
        //返回固定字符串到客户端
        ServerSocket socket = new ServerSocket(port);
        System.out.println("--------- start port : " + port);

        while (true) {
            Socket accept = socket.accept();
            //获取输入流
            //InputStream inputStream = accept.getInputStream();
            //输出流
            OutputStream outputStream = accept.getOutputStream();
            String result = HttpUtil.resp_200("hello world ...");
            System.out.println(" ------ 响应返回内容 : " + result);
            outputStream.write(result.getBytes());
            outputStream.flush();
            outputStream.close();
            socket.close();
        }

    }

    public static void main(String[] args) {

        try {
            new Bootstrap().start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

V2

返回静态页面

    public void start() throws IOException {
        //返回固定字符串到客户端
        ServerSocket socket = new ServerSocket(port);
        System.out.println("--------- start port : " + port);

        while (true) {
            Socket accept = socket.accept();
            //获取输入流
            InputStream inputStream = accept.getInputStream();
            //封装请求和响应对象
            Request request = new Request(inputStream);
            Response response = new Response(accept.getOutputStream());
            response.outPutHtml(request.getUrl());
        }

    }

V3

    /**
     * 启动方法
     */
    public void start() throws IOException {

        ServerSocket socket = new ServerSocket(port);
        System.out.println("--------- start port : " + port);

        while (true) {
            Socket accept = socket.accept();
            //获取输入流
            InputStream inputStream = accept.getInputStream();
            //封装请求和响应对象
            Request request = new Request(inputStream);
            Response response = new Response(accept.getOutputStream());
            if (!servletMap.containsKey(request.getUrl())) {
                response.outPutStr(HttpUtil.resp_200(request.getUrl() + " is not found ... "));
            } else {
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                try {
                    //处理请求
                    httpServlet.service(request, response);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * 加载解析web.xml,初始化Servlet
     */
    private void loadServlet() {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();

        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element = selectNodes.get(i);
                // <servlet-name>server</servlet-name>
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();

                // 根据servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                // /server
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());

            }


        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }


    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.loadServlet();
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

V4

    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.loadServlet();
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

V5

package com.udeam.v5;

import com.udeam.util.HttpUtil;
import com.udeam.v2.bean.Request;
import com.udeam.v2.bean.Response;
import com.udeam.v3.inteface.HttpServlet;
import com.udeam.v5.bean.Context;
import com.udeam.v5.bean.Host;
import com.udeam.v5.bean.MapperContext;
import com.udeam.v5.bean.Wrapper;
import com.udeam.v5.config.SunClassloader;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 启动类入库
 * 用于启动tomcat
 * <p>
 * 版本五: 根据webapps下部署的项目,动态根据不同的url处理请求。
 * 使用线程改造 每个请求互相不受影响,不阻塞
 */
public class Bootstrap {

    /**
     * 映射 端口,虚拟主机以及项目,url
     */
    private static final MapperContext mapperContext = new MapperContext();

    /**
     * 存储web项目下的web.xml路径便于之后解析xml
     */
    private static final Map<String, String> DEMO_XML = new HashMap<>();

    /**
     * 存储web项目下web的对象路径
     */
    private static final Map<String, String> DEMO_CLASS = new HashMap<>();
    /**
     * 存储web项目下html的对象路径
     */
    private static final Map<String, String> DEMO_HTML = new HashMap<>();


    /**
     * 端口可以配置在xml里面
     * 监听端口号
     * 用于启动socket监听的端口号
     */
    private int port;

    public void setPort(int port) {
        this.port = port;
    }

    /**
     * 参数可以配置在xml里面
     */
    private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 1, TimeUnit.HOURS, new ArrayBlockingQueue<>(500));


    /**
     * 启动方法
     */
    public void start() throws IOException {

        //获取上下文
        List<Context> contextList = mapperContext.getHost().getContextList();


        ServerSocket socket = new ServerSocket(port);
        System.out.println("--------- start port : " + port);

        while (true) {
            Socket accept = socket.accept();
            //获取输入流
            InputStream inputStream = accept.getInputStream();
            //封装请求和响应对象
            Request request = new Request(inputStream);
            Response response = new Response(accept.getOutputStream());

            //请求url
            String url = request.getUrl();
            //获取上下文
            String context = url.substring(0).substring(0, url.substring(1).indexOf("/") + 1);

            //真正请求的url
            String realUrl = url.replace(context, "");
            boolean falg = false;
            //上下文
            Context context1 = null;
            //判断上下文
            for (Context con : contextList) {
                String name = con.getName();
                if (context.equalsIgnoreCase("/" + name)) {
                    falg = true;
                    context1 = con;
                    break;
                }
            }
            if (!falg) {
                response.outPutStr(HttpUtil.resp_404());
                return;
            }

            //获取wrapper  处理请求
            List<Wrapper> wrappersList = context1.getWrappersList();
            for (Wrapper wrapper : wrappersList) {
                //静态资源 html 请求
                if (realUrl.equals(wrapper.getUrl()) && url.endsWith(".html")) {
                    //html 暂时没写,,同servlet一样
                    //剩下的当做servlet请求处理
                } else if (realUrl.equals(wrapper.getUrl())) {
                    HttpServlet httpServlet = (HttpServlet) wrapper.getObject();
                    //1 单线程处理
                    MyThread5 myThread = new MyThread5(httpServlet, response, request);
                    threadPoolExecutor.submit(myThread);
                }
            }

        }

    }

    /**
     * 加载解析web.xml,保存url,Servlet信息
     *
     * @param context    项目上下文
     * @param webXmlPath web.xml路径
     */
    private void loadServlet(String context, String webXmlPath) throws FileNotFoundException {
        //存储url  以及 配置servlet 以及请求url
        List<Wrapper> wrappersList = null;
        //获取上下文
        List<Context> contextList = mapperContext.getHost().getContextList();
        for (Context context1 : contextList) {
            if (context.equals(context1.getName())) {
                wrappersList = context1.getWrappersList();
            }
        }

        //这里读取磁盘位置绝对路径的xml
        InputStream resourceAsStream = new FileInputStream(webXmlPath);


        try {
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element = selectNodes.get(i);
                // <servlet-name>server</servlet-name>
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();

                // 根据servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                // /server
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                //servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());

                Wrapper wrapper = new Wrapper();
                wrapper.setServletClass(servletClass);
                wrapper.setUrl(urlPattern);
                //存储servelt信心
                wrappersList.add(wrapper);

            }


        } catch (DocumentException e) {
            e.printStackTrace();
        }

    }


    public void loadServerXml() throws DocumentException {

        //1 加载解析 server.xml文件
        InputStream resourceAsStream = this.getClass().getResourceAsStream("/conf/server.xml");

        SAXReader saxReader = new SAXReader();
        Document read = saxReader.read(resourceAsStream);
        //获取跟路径
        Element rootElement = read.getRootElement();

        Document document = rootElement.getDocument();

        //2 获取端口
        Element node = (Element) document.selectSingleNode("//Connector");
        String port = node.attributeValue("port");
        this.setPort(Integer.valueOf(port));
        //3 获取host
        Element element = (Element) document.selectSingleNode("//Host");
        //虚拟主机
        String localhost = element.attributeValue("name");
        //虚拟主机
        Host host = new Host();
        host.setHostName(localhost);
        mapperContext.setHost(host);


        //部署的地址路径
        String appBase = element.attributeValue("appBase");
        //4 根据这个路径去解析里面的项目 映射端口和虚拟主机,项目,以及url->servlet
        parseAppBase(appBase);


    }


    /**
     * 解析appBase路径下部署的项目,并且将端口,虚拟主机,项目Context,url一一映射封装到MapperContext中
     * 请求的时候可以根据不同的项目以及端口,url响应不同项目的请求
     */
    public void parseAppBase(String path) {
        File file = new File(path);
        //根据路径去加载类
        //1 获取顶级文件名
        File[] files = file.listFiles();

        //设置项目Context
        List<Context> contextList = mapperContext.getHost().getContextList();


        //第一级 路径 也就是文件名 即 项目工程名context
        for (File file1 : files) {
            String name = file1.getName();
            //设置context上下文路径
            contextList.add(new Context(name));
            //递归处理 如果是WEB-INF 和 classes文件则特殊处理
            doFile(file1.getPath(), name);
        }

        System.out.println(DEMO_XML);
        System.out.println(DEMO_CLASS);


        //加载解析web.xml
        doWebXml();
        //实例化class 更新url对应的实例
        doNewInstance();

    }

    /**
     * 读取解析web.xml
     */
    private void doWebXml() {
        for (Map.Entry<String, String> stringStringEntry : DEMO_XML.entrySet()) {

            String context = stringStringEntry.getKey();
            String value = stringStringEntry.getValue();

            try {
                this.loadServlet(context, value);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

        }

    }

    /**
     * 类加载实例化
     */
    public static void doNewInstance() {

        //获取上下文集合
        List<Context> contextList1 = mapperContext.getHost().getContextList();
        //所有的上下文
        List<String> contextList = contextList1.stream().map(Context::getName).collect(Collectors.toList());

        //类加载实例化
        for (Map.Entry<String, String> stringStringEntry : DEMO_CLASS.entrySet()) {
            String webDemoName = stringStringEntry.getKey();
            String classPath = stringStringEntry.getValue();


            //加载class 然后实例化
            SunClassloader sunClazz = new SunClassloader();
            try {
                Class<?> clazz = sunClazz.findClass(classPath);
                //根据url查找项目对应的servlet
                if (contextList.contains(webDemoName)) {
                    contextList1.stream().forEach(x -> {
                        if (x.getName().equals(webDemoName)) {
                            List<Wrapper> wrappersList = x.getWrappersList();

                            //判断当前类是否在web.xml配置的servlet class里面
                            wrappersList.stream().forEach(x2 -> {
                                if (classPath.replaceAll("/", ".").contains(x2.getServletClass())) {
                                    //保存实例对象
                                    try {
                                        x2.setObject(clazz.newInstance());
                                    } catch (InstantiationException e) {
                                        e.printStackTrace();
                                    } catch (IllegalAccessException e) {
                                        e.printStackTrace();
                                    }
                                }
                            });

                        }
                    });
                }


            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 启动入口
     *
     * @param args
     * @throws DocumentException
     */
    public static void main(String[] args) throws DocumentException {
        //启动tomcat
        Bootstrap bootstrap = new Bootstrap();
        try {
            //加载配置server.xml文件
            bootstrap.loadServerXml();
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 处理web.xml 和 获取字节码
     *
     * @param path
     * @param webDemoName
     */
    static void doFile(String path, String webDemoName) {

        File pathList = new File(path);
        File[] list1 = pathList.listFiles();
        if (list1 == null) {
            return;
        }
        //循环处理每个项目下web.xml
        for (File s : list1) {
            File file1 = new File(s.getPath());
            if (file1.isDirectory()) {
                doFile(file1.getPath(), webDemoName);
            } else {
                if (s.getName().equals("web.xml")) {
                    //保存当前项目下的web.xml
                    DEMO_XML.put(webDemoName, s.getPath());
                }
                //保存字节码路径  这里目前只有一个class文件,其他业务class忽略...
                if (s.getName().endsWith(".class")) {

                    String classPath = s.getPath();
                    DEMO_CLASS.put(webDemoName, classPath);

                }

                //保存html文件
                if (s.getName().endsWith(".html")) {

                    String classPath = s.getPath();
                    DEMO_HTML.put(webDemoName, classPath);

                }
            }
        }

    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值