自己实现一个简易版的Tomcat

自己实现一个简易版的Tomcat

Scroll Down

简易版Tomcat 实现过程

在JavaSE中,我们的每一个程序都是依赖main函数来执行的,而到了JavaEE后,发现main方法不见了,而是转为使用Servlet来进行响应来自浏览器的请求,而Servlet又是装在Tomcat中的,Tomcat底层到底做了些啥呢?

1、Tomcat所涉及的部分技术

  • HTTP协议格式
  • JavaSE的Socket编程

2、动手实现

2.1 模拟浏览器获取服务器资源

14.png

  • 建立一个和服务端对应的Socket对象,并指明连接服务端指定的域名和端口号
  • 通过Socket获取到指向服务端的输出流对象
  • 通过Socket获取到一个输入流
  • 将HTTP协议的请求部分发送到服务器
  • 接收来自服务端的数据并输出
  • 释放资源
public class TestClient {
    public static void main(String[] args) throws IOException {
        //1。建立一个Sockety对象
        Socket socket = null;
        InputStream is = null;
        OutputStream ops = null;
        try {
            socket = new Socket("www.itcast.cn",443);
            //2.获取到输出流对象
            ops = socket.getOutputStream();
            //3.获取到输入流对象
            is = socket.getInputStream();
            //4.将HTTP协议的请求部分发送到客户端
            ops.write("GET /subject/about/index.html HTTP/1.1\\n".getBytes());
            ops.write("HOST:www.itcast.cn\\n".getBytes());
            ops.write("\\n".getBytes());
            //5.读取来自客户端的数据打印到控制台
            int len = 0;
            while ((len = is.read()) != -1){
                System.out.print((char)len);
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (null != is){
                is.close();
            }
            if (null != ops)
                ops.close();
            if (socket != null)
                socket.close();
        }
    }
}

结果如下

image.png

2.2 模拟服务端向客户端响应数据

image.png

  • 创建ServerSocket对象,监听本机的8080端口
  • 获取socket对象
  • 通过socket对象获取输出流对象
  • 将HTTP协议的响应部分发送到客户端
  • 释放资源
public class TestServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = null;
        Socket socket = null;
        OutputStream ops = null;
        try {
	    //创建ServerSocket对象,监听8080端口
            serverSocket = new ServerSocket(8080);
	    while(true){
            	//获取客户端对应的socket
       	   	socket = serverSocket.accept();
            	//获取输入输出流对象
            	ops = socket.getOutputStream();
	    	//通过获取到的输出流对象将HTTP协议的响应部分发送客户端
	    	ops.write("HTTP/1.1 200 OK\n".getBytes());
            	ops.write("Server:apache-Coyote/1.1\n".getBytes());
            	ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
            	ops.write("\n".getBytes());
	    	//写入响应体
	    	StringBuffer buf = new StringBuffer();
            	buf.append("<html>");
            	buf.append("<head><title>我是标题</title></head>");
           	buf.append("<body>");
           	buf.append("<h1>i am a head1</h1>");
            	buf.append("<h1>i am a head1</h1>");
           	buf.append("</body>");
           	buf.append("</html>");	
	   	ops.write(buf.toString().getBytes());
	   }
        } catch (Exception e){
	    e.prinStackTrace();
	} finally{
		if(null!=ops){
		   ops.close();
		   ops=null;
		}
		if(null!=socket){
		   socket.close();
		   socket=null;
		}
	}
    }
}

2.3 Tomcat1.0

  • 在WebContent下发布静态资源demo.html,demo02.html
  • 启动Tomcat服务器
  • 当客户端对服务端发起不同的请求,localhost:8080/demo.html
  • 服务端可以将对应的html页面响应到客服端

附上Project Structure image.png

image.png

demo01.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
demo01.html
</body>
</html>

demo02.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
demo02.html
</body>
</html>

服务端

public class TestServer {
    //System.getProperty("user.dir") 为获取用户当前的工作目录
    //定义一个变量,用于存放服务端WebContent目录的绝对路径
    private static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";
    //存放本次请求的静态页面名称
    private static String url = "";

    public static void main(String[] args) throws IOException {
        //定义流和Socket对象
        ServerSocket serverSocket = null;
        Socket socket = null;
        OutputStream ops = null;
        InputStream is = null;
        try {
            serverSocket = new ServerSocket(8050);
            while (true){
                //获取客户端对应的socket
                socket = serverSocket.accept();
                //获取输入输出流对象
                ops = socket.getOutputStream();
                is = socket.getInputStream();
                //获取HTTP协议的请求部分,截取要获取的资源名称,并将这个名称赋给URL
                parse(is);
                //发送静态资源
                sendStaticResource(ops);
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            if (null != socket){
                socket.close();
                socket= null;
            }
            if (null != ops){
                ops.close();
                ops = null;
            }
            if (null != is){
                is.close();
                is = null;
            }
        }
    }

    /**
     * 通过获取Socket的输入流来获取HTTP协议请求数据
     * @param is
     * @throws IOException
     */
    public static void parse(InputStream is) throws IOException {
        //存放HTTP协议请求部分数据
        StringBuffer sb = new StringBuffer();
        byte[] buffer = new byte[2048];
        int len = -1;

        len = is.read(buffer);
        for (int j=0;j<len;j++){
            sb.append((char)buffer[j]);
        }
        //打印HTTP协议请求数据
        System.out.println(sb);
        //传入HTTP协议请求数据,截取客户端要请求的资源路径,赋值给URL
        parseURL(sb.toString());
    }

    /**
     * 通过传入的HTTP协议请求数据,截取客服端请求的资源路径,赋值给URL
     * @param content
     */
    public static void parseURL(String content) {
        int index1,index2;
        //获取第一个空格和第二个空格的位置,请求的资源路径是在第一个空格和第二个空格之间的
        index1 = content.indexOf(" ");
        if (index1 != -1){
            index2 = content.indexOf(" ",index1+1);
            if (index2>index1){
                url = content.substring(index1+2,index2);
            }
        }
        System.out.println(url);
    }

    /**
     * 通过Socket的输出流向客户端响应资源
     * @param ops
     * @throws IOException
     */
    public static void sendStaticResource(OutputStream ops) throws IOException {
        //存放本次请求的静态资源的内容
        byte[] bytes = new byte[2048];
        //用户获取静态资源的内容
        FileInputStream fis = null;
        try {
            //代表本次请求的静态资源文件
            File file = new File(WEB_ROOT,url);
            //文件存在
            if (file.exists()){
                //向客户端输出响应行于响应头
                ops.write("HTTP/1.1 200 OK\n".getBytes());
                ops.write("Server:apache-Coyote/1.1\n".getBytes());
                ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
                ops.write("\n".getBytes());
                //获取字节流输入对象
                fis = new FileInputStream(file);
                //读取静态资源到数组
                int ch = -1;
                while ((ch = fis.read(bytes)) != -1){
                    ops.write(bytes,0,ch);
                }
            }
            //文件不存在
            else {
                ops.write("HTTP/1.1 404 not found\n".getBytes());
                ops.write("Server:apache-Coyote/1.1\n".getBytes());
                ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
                ops.write("\n".getBytes());
                ops.write("file not found".getBytes());
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (null != fis){
                fis.close();
                fis = null;
            }
        }
    }
}

注意:端口号自己设定。

2.4 Tomcat 2.0

当客户端发送请求到服务器的时候,可以运行服务端的一段java代码,并且可以向客户端响应数据

  • 服务器启动读取配置数据到map中(即常使用的web.xml的简化,做映射用)
  • 浏览器向服务器发起请求,localhost:8050/MyTomcat/aa
  • 获取HTTP请求部分,解析本次请求路径aa,从map获取相应的路径
  • 通过反射将aa路径所对应的AServlet对象加载到内存
  • 向客户端发送HTTP协议响应头/响应行
  • 调用AServlet对象的init,service,destroy方法

定义Servlet接口

public interface Servlet {
    public void init();
    public void service(InputStream is, OutputStream ops) throws IOException;
    public void destroy();
}

配置文件 conf.properties

aa=AServlet
bb=BServlet

服务端

public class TestServer {
    //System.getProperty("user.dir") 为获取用户当前的工作目录
    //定义一个变量,用于存放服务端WebContent目录的绝对路径
    private  static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";
    //存放本次请求的静态页面名称
    private  static String url = "";

    //用于存储配置文件中的信息
    private static Map<String,String> map = new HashMap<>();

    static {
        //服务器启动前将配置信息加载到MAP中
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream(WEB_ROOT + "\\conf.properties"));
            Set set = properties.keySet();
            Iterator iterator = set.iterator();
            while (iterator.hasNext()){
                String key = (String)iterator.next();
                String value = properties.getProperty(key);
                map.put(key,value);
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = null;
        Socket socket = null;
        OutputStream ops = null;
        InputStream is = null;
        try {
            serverSocket = new ServerSocket(8050);
            while (true){
                //获取客户端对应的socket
                socket = serverSocket.accept();
                //获取输入输出流对象
                ops = socket.getOutputStream();
                is = socket.getInputStream();
                //获取HTTP协议的请求部分,截取要获取的资源名称,并将这个名称赋给URL
                parse(is);
                //判断本次请求是请求静态demo,还是Servlet
                if (null != url){
                    if (url.indexOf(".") != -1){
                        //发送静态资源
                        sendStaticResource(ops);
                    } else {
                        //发送动态资源
                        sendDynamicResource(is,ops);
                    }
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (null != socket){
                socket.close();
                socket= null;
            }
            if (null != ops){
                ops.close();
                ops = null;
            }
            if (null != is){
                is.close();
                is = null;
            }
        }
    }

    public static void sendDynamicResource(InputStream is, OutputStream ops) throws Exception {
        //输出响应行和响应头
        ops.write("HTTP/1.1 200 OK\n".getBytes());
        ops.write("Server:apache-Coyote/1.1\n".getBytes());
        ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
        ops.write("\n".getBytes());

        if (map.containsKey(url)){
            String value = map.get(url);
            Class clazz = Class.forName(value);
            Servlet servlet = (Servlet)clazz.newInstance();
            servlet.init();
            servlet.service(is,ops);
            servlet.destroy();
        }
    }

    public static void sendStaticResource(OutputStream ops) throws IOException {
        //存放本次请求的静态资源的内容
        byte[] bytes = new byte[2048];
        //用户获取静态资源的内容
        FileInputStream fis = null;
        try {
            //代表本次请求的静态资源文件
            File file = new File(WEB_ROOT,url);
            //文件存在
            if (file.exists()){
                //向客户端输出响应行于响应头
                ops.write("HTTP/1.1 200 OK\n".getBytes());
                ops.write("Server:apache-Coyote/1.1\n".getBytes());
                ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
                ops.write("\n".getBytes());
                //获取字节流输入对象
                fis = new FileInputStream(file);
                //读取静态资源到数组
                int ch = -1;
                while ((ch = fis.read(bytes)) != -1){
                    ops.write(bytes,0,ch);
                }
            }
            //文件不存在
            else {
                ops.write("HTTP/1.1 404 not found\n".getBytes());
                ops.write("Server:apache-Coyote/1.1\n".getBytes());
                ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
                ops.write("\n".getBytes());
                ops.write("file not found".getBytes());
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (null != fis){
                fis.close();
                fis = null;
            }
        }
    }

    public static void parse(InputStream is) throws IOException {
        //存放HTTP协议请求部分数据
        StringBuffer sb = new StringBuffer();
        //存放HTTP协议请求部分数据
        byte[] buffer = new byte[2048];
        int len = -1;

        len = is.read(buffer);
        for (int j=0;j<len;j++){
            sb.append((char)buffer[j]);
        }
        //打印HTTP协议请求数据
        System.out.println(sb);
        //截取客户端要请求的资源路径,赋值给URL
        parseURL(sb.toString());
    }

    public static void parseURL(String content) {
        int index1,index2;
        //获取第一个空格和第二个空格的位置
        index1 = content.indexOf(" ");
        if (index1 != -1){
            index2 = content.indexOf(" ",index1+1);
            if (index2>index1){
                url = content.substring(index1+2,index2);
            }
        }
        System.out.println(url);
    }
}