一个Tomcat的简单实现

实现迷你的Tomcat

学完JavaSE向JavaWeb过渡后,总有种不习惯的感觉。就像从C语言向Java过渡一样,感觉好多事情都不必自己实现,学起来总不踏实。所以我就有了想要简单了解一下tomcat作为容器到底是怎样工作的的想法

首先我们得了解一下http请求的结构

一会我们会参照这种结构手动构造响应消息

打开IDEA,创建一个新的项目

结构如下:

在项目下新增一个WebContent文件夹,在文件夹中创建一个.properties配置文件和其他静态资源,如Html文件
在src下创建一个包,然后创建MyTomcat类,和一个Servlet接口,所有的servlet都必须实现这个接口

代码如下(附注释):

package com.mytomcatv2;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;


public class TestServer  {

	//创建静态全局变量存储该项目的绝对路径,WebContent为静态资源(Html文件)位置getProperty()为系统方法
    public static String WEB_ROOT = System.getProperty("user.dir") + "/" +"WebContent";
    //url用来保存通过解析Http请求头得到的请求资源名,Http请求头结构为上图所示
    public static String url = "";

    //存储配置信息(配置信息为键值对,通过读取配置文件获得)
    private static Map<String,String> map = new HashMap<>();

	//静态代码块,在main方法之前运行,可保证程序首先读取配置文件
    static {
        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 = (String) properties.get(key);
                map.put(key,value);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
    	//服务端套接字绑定8080端口
        ServerSocket serverSocket = new ServerSocket(8080);
        //初始化套接字,后面会用来保存客户端套接字
        Socket socket = null;
        //输入输出流通过一会接收到的套接字获得
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
        	//设死循环可以是服务端接受多个请求
            while (true)
            {
            	//serverSocket调用accept时阻塞,直到监听到客户端请求,将其用socket保存
                socket = serverSocket.accept();
                //通过socket获得客户端的输入输出流
                inputStream = socket.getInputStream();
                outputStream = socket.getOutputStream();
                //输入流中保存着来自客户端的Http请求,用函数parseToURL(自己实现的函数都在下面)处理,将请求的资源解析出来保存至全局变量url
                parseToURL(inputStream);
                //判断是否正确得到url
                if (url!=null)
                {
                	//如果indexOf的反回值不为-1,则说明url中存在".",即为.html等静态资源,调用senStaticResource(自写函数)处理
                    if(url.indexOf(".")!=-1)
                    {
                        sendStaticResource(outputStream);
                    } else {

						//不含".",则说明请求的是动态资源servlet,调用该函数通过反射加载Servlet来处理请求
                        sendDynamicResource(inputStream,outputStream);
                    }
                }

            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (inputStream!=null)
            {
                inputStream.close();
                inputStream = null;
            }
            if (outputStream!=null)
            {
                outputStream.close();
                outputStream = null;
            }
            if (socket!=null)
            {
                socket.close();
                socket = null;
            }
        }
    }

    private static void sendDynamicResource(InputStream inputStream,OutputStream outputStream) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {

		//向响应中写入Http格式响应头,格式不了解的话对比最上面的图片
        outputStream.write("HTTP/1.1 200 OK\nContent-Type:text/html;charset=utf-8\nServer:Apache-Coyote/1.1\n\n".getBytes());
        //判断map中是否含有该url,有的话就可得到对应的Servlet路径
        if (map.containsKey(url))
        {
        	//value保存url对应的Servlet的路径名
            String value = map.get(url);
            //加载该Servlet
            Class clazz = Class.forName(value);
            //Servlet是一个接口,所有的Servlet都必须实现该接口,就是为了这一步不管url对应的哪个servlet都能成功赋值Servlet接口的实例
            Servlet servlet = (Servlet)clazz.newInstance();
           	//调用Servlet接口的init方法
            servlet.init();
            //调用servlet的服务方法,我们平时在Tomcat中写的代码就放在这里面
            servlet.Service(inputStream,outputStream);
        }
    }

    private static void sendStaticResource(OutputStream outputStream) throws IOException {

        byte[] buffer = new byte[2048];
        FileInputStream fileIn = null;
        try{
            File file = new File(WEB_ROOT,url);
            if (file.exists())
            {
            	//文件存在就向浏览器返回成功的消息
                outputStream.write("HTTP/1.1 200 OK\nContent-Type:text/html;charset=utf-8\nServer:Apache-Coyote/1.1\n\n".getBytes());
                fileIn = new FileInputStream(file);
                //将url对应的静态资源读至字节数组
                int ch = fileIn.read(buffer);
                while (ch!=-1)
                {
                    outputStream.write(buffer,0,ch);
                    ch = fileIn.read(buffer);
                }
            }else {
            	//url对应的静态资源不存在就返回响应404
                outputStream.write("HTTP/1.1 404 not found\nContent-Type:text/html;charset=utf-8\nServer:Apache-Coyote/1.1\n\n\nfile not found".getBytes());
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }finally {
            if (fileIn!=null){
                fileIn.close();
                fileIn = null;
            }
        }
    }

    private static void parseToURL (InputStream inputStream) throws IOException{

        StringBuffer content = new StringBuffer();
        byte[] buffer = new byte[2048];
        int i = -1;
        //将浏览器请求读至字节数组,请求一般不会多于2048字节,int i 保存请求大小
        i = inputStream.read(buffer);
        //循环i次将请求保存至content
        for (int j=0;j<i;j++)
            content.append((char)buffer[j]);

		//将请求变成字符串传给该函数,该函数解析请求获得url
        parseURL(content.toString());
    }

    private static void parseURL(String content) {

        //由开篇图片的格式得,url位于第一个空格和第二个空格之间,所以通过两个空格的下标得到url
        int index1,index2;
        index1 = content.indexOf(" ");
        if (index1!=-1) {
            index2 = content.indexOf(" ", index1 + 1);
            if (index1<index2)
            {
                url = content.substring(index1+2,index2);
            }
        }
    }


}

Servlet接口代码:

package com.mytomcatv2;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public interface Servlet {
	//所有的servlet都必须实现该接口,就相当于我们平常使用的HttpServlet
    public void init();
    public void Service(InputStream in, OutputStream out) throws IOException;
    public void destroy();
}

配置文件内容如下:

aa=com.mytomcatv2.AAServlet
bb=com.mytomcatv2.BBServlet

即如果在URL中请求aa(localhost:8080/aa),程序就会加载aa对应路径的类(servlet),就相当于平常的注解或者在XML文件中的配置

大概了解了Tomcat的工作方式后,心里就踏实了很多,如果文章有任何错误,欢迎大家指正

你可能感兴趣的:(java)