自制简易Tomcat服务器

在学习javaweb的时候我感觉自己是很懵逼的,因为好像就是写servlet然后配置web.xml,就可以让服务器提供服务了,对于这整个过程都不是很了解。为了弄懂整个的过程,我就找了黑马的自制简易Tomcat的视频,现将代码记录与这篇文章中。相信你看完整个过程,会对javaweb有更深的理解

模拟浏览器给服务器发送请求

// 模拟浏览器向服务器发送HTTP请求
public class TestClient {
    private static Socket socket = null;
    private static InputStream is = null;
    private static OutputStream os = null;

    public static void main(String[] args) throws Exception {
        //1_建立一个socket连接,连接itcast域名的80端口
        socket = new Socket("www.itcast.cn", 80);
        //2_获取到输出流对象
        is = socket.getInputStream();
        //3_获取到输入流对象
        os = socket.getOutputStream();
        //4_将HTTP协议的请求部分发送到服务端
        os.write("GET index.html HTTP/1.1\n".getBytes());
        os.write("HOST:www.itcast.cn\n".getBytes());
        os.write("\n".getBytes());
        //5_读取来自服务端的数据打印到控制台
        int i = is.read();
        while(i != -1){
            System.out.print((char)i);
            i = is.read();
        }
        //6_释放资源
        if(socket!=null){
            socket.close();
            socket = null;
        }
        if(is!=null){
            is.close();
            is = null;
        }
        if(os!=null){
            os.close();
            os = null;
        }
    }

}

模拟服务器响应客户端请求

public class TestServer {

    private static ServerSocket serverSocket = null;
    private static Socket socket = null;
    private static OutputStream os = null;

    public static void main(String[] args) throws IOException {
        try {
            //1_创建ServerSocket对象,监听本机的8080端口号
            serverSocket = new ServerSocket(8080);
            while(true) {
                //2_等待来自哭护短的请求获取和客户端对应的Socket对象
                socket = serverSocket.accept();
                //3_通过获取到的Socket对象获取到输出流对象
                os = socket.getOutputStream();
                //4_通过获取得到的输出流对象将HTTP协议的响应部分发送到客户端
                os.write("HTTP/1.1 200 OK\n".getBytes());
                os.write("Content-Type:text/html;charset=utf-8\n".getBytes());
                os.write("Server:Apache-Coyote/1.1\n".getBytes());
                os.write("\n\n".getBytes());
                StringBuffer buf = new StringBuffer();
                buf.append("<html>");
                buf.append("<head><title>我是标题</title></head>");
                buf.append("<body>");
                buf.append("<h1> I am header 1</h1>");
                buf.append("</body>");
                buf.append("</html>");
                os.write(buf.toString().getBytes());
                os.flush();
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            //5_释放资源
            if(serverSocket != null){
                serverSocket.close();
                serverSocket = null;
            }
            if(socket != null){
                socket.close();
                socket = null;
            }
            if(os != null){
                os.close();
                os = null;
            }
        }


    }

}

模拟服务器响应客户端对于静态资源的请求

我们要做的实际上就是读取客户端要访问的静态资源,然后将静态资源输出到客户端。

public class TestServer {

    // 定义一个变量,存放服务器WebContent的绝对路径
    public static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent\\";
    // 定义静态变量,用于存放本次请求的静态页面名称
    private static String url = "";

    public static void main(String[] args) throws IOException {
        //System.out.println(WEB_ROOT);
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream is = null;
        OutputStream os = null;
        // 创建一个ServerSocket,监听本机8080端口,等待客户端请求
        try {
            serverSocket = new ServerSocket(8080);
            while(true){
                socket = serverSocket.accept();
                is = socket.getInputStream();
                os = socket.getOutputStream();
                // 获取协议请求部分,截取客户端要访问的资源名称,将这个资源名称赋值给url
                parse(is);
                // 发送静态资源
                sendStaaticResource(os);
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if(serverSocket != null){
                serverSocket.close();
                serverSocket = null;
            }
            if(socket != null){
                socket.close();
                socket = null;
            }
            if(is != null){
                is.close();
                is = null;
            }
            if(os != null){
                os.close();
                os = null;
            }
        }
    }
    // 获取协议请求部分,截取客户端要访问的资源名称,将这个资源名称赋值给url
    public static void parse(InputStream is) throws IOException {
        // 定义一个变量,存放HTTP协议请求部分数据
        StringBuffer content = new StringBuffer(2048);
        // 定义一个数组,存放HTTP协议请求部分数据
        byte[] buffer = new byte[2048];
        // 定义一个变量i,代表读取数据到数组中之后,数据量的带下
        int i;
        // 读取数据
        i = is.read(buffer);
        // 遍历字节数组,将数据追加到content变量中
        for(int j=0; j<i; j++){
            content.append((char)buffer[j]);
        }
        // 打印HTTP协议请求部分数据
        System.out.println(content);
        // 截取客户端请求的资源路径demo.html,赋值给url
        parseURL(content.toString());
    }

    // 截取客户端请求的资源路径demo.html,赋值给url
    private 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);
    }

    // 发送静态资源
    public static void sendStaaticResource(OutputStream os) throws IOException {
        byte[] bytes = new byte[2048];
        FileInputStream fis = null;
        try{
            File file = new File(WEB_ROOT, url);
            if(file.exists()){
                os.write("HTTP/1.1 200 OK\n".getBytes());
                os.write("Server:apache-Coyote/1.1\n".getBytes());
                os.write("Content-Type:text/html;charset=utf-8\n".getBytes());
                os.write("\n".getBytes());
                fis = new FileInputStream(file);
                // 读取静态资源的内容到数组中
                int ch = fis.read(bytes);
                while(ch!=-1){
                    os.write(bytes, 0, ch);
                    ch = fis.read(bytes);
                }
            } else {
                os.write("HTTP/1.1 404 OK\n".getBytes());
                os.write("Server:apache-Coyote/1.1\n".getBytes());
                os.write("Content-Type:text/html;charset=utf-8\n".getBytes());
                os.write("\n".getBytes());
                String errorMessage="file not found";
                os.write(errorMessage.getBytes());
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if(fis!=null){
                fis.close();
                fis = null;
            }
        }
    }
}

模拟在响应请求的过程中执行java程序

这个项目有点复杂。

我们最终实现的效果是,浏览器输入一个url。服务器解析完请求后,如果是动态请求就会通过反射调用相应的servlet提供服务;如果是静态请求就给客户端发送静态页面。

我们要写一个Servlet接口,所有的提供服务的java类都要实现这个接口

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

然后我们需要编写AAServlet.java和BBServlet.java

AAServlet.java

public class AAServlet implements Servlet{
    @Override
    public void init() {
        System.out.println("aaServlet...init");
    }

    @Override
    public void service(InputStream is, OutputStream os) throws IOException {
        System.out.println("aaServlet...service");
        os.write("I am from AAServlet!".getBytes());
        os.flush();
    }

    @Override
    public void destroy() {
        System.out.println("aaServlet...destroy");
    }
}

BBServlet.java

public class BBServlet implements Servlet{
    @Override
    public void init() {
        System.out.println("bbServlet...init");
    }

    @Override
    public void service(InputStream is, OutputStream os) throws IOException {
        System.out.println("bbServlet...service");
        os.write("I am from BBServlet!".getBytes());
        os.flush();
    }

    @Override
    public void destroy() {
        System.out.println("bbServlet...destroy");
    }
}

之后我们需要创建一个配置文件conf.properities,在里面填写映射关系

conf.properities

aa=TOMCAT2.AAServlet
bb=TOMCAT2.BBServlet

TestServer.java

public class TestServer {

    // 定义一个变量,存放服务器WebContent的绝对路径
    public static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent\\";
    // 定义静态变量,用于存放本次请求的静态页面名称
    private static String url = "";
    // 定义一个静态的map,用来存储服务器中conf.properities中的信息
    private static HashMap<String, String> map  = new HashMap();

    static {
        Properties prop = new Properties();
        try {
            prop.load(new FileInputStream(WEB_ROOT + "//conf.properities"));
            Set set = prop.keySet();
            Iterator iterator = set.iterator();
            while(iterator.hasNext()){
                String key = (String)iterator.next();
                String value = prop.getProperty(key);
                map.put(key, value);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {
        //System.out.println(WEB_ROOT);
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream is = null;
        OutputStream os = null;
        // 创建一个ServerSocket,监听本机8080端口,等待客户端请求
        try {
            serverSocket = new ServerSocket(8080);
            while(true){
                socket = serverSocket.accept();
                is = socket.getInputStream();
                os = socket.getOutputStream();
                // 获取协议请求部分,截取客户端要访问的资源名称,将这个资源名称赋值给url
                parse(is);
                if(url!=null){
                    if(url.indexOf(".") != -1){
                        // 发送静态资源
                        sendStaaticResource(os);
                    } else {
                        // 发送动态资源
                        sendDynamicResource(is, os);
                    }
                }
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if(serverSocket != null){
                serverSocket.close();
                serverSocket = null;
            }
            if(socket != null){
                socket.close();
                socket = null;
            }
            if(is != null){
                is.close();
                is = null;
            }
            if(os != null){
                os.close();
                os = null;
            }
        }
    }

    // 发送动态资源
    private static void sendDynamicResource(InputStream is, OutputStream os) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        // 将HTTP协议的响应头和响应行发送给对方
        os.write("HTTP/1.1 OK\n".getBytes());
        os.write("Server:Apache\n".getBytes());
        os.write("Content-type:text/html;charset=utf-8\n".getBytes());
        os.write("\n".getBytes());
        // 判断map中是否存在一个key和本次待请求的资源路径一致
        if(map.containsKey(url)){
            // 如果包含指定的key,就可以获取到响应的value
            String value = map.get(url);
            // 通过反射将java程序加载到内存
            Class clazz = Class.forName(value);
            Servlet servlet = (Servlet)clazz.newInstance();
            // 执行init()方法
            servlet.init();
            // 执行service()方法
            servlet.service(is, os);
        }

    }

    // 获取协议请求部分,截取客户端要访问的资源名称,将这个资源名称赋值给url
    public static void parse(InputStream is) throws IOException {
        // 定义一个变量,存放HTTP协议请求部分数据
        StringBuffer content = new StringBuffer(2048);
        // 定义一个数组,存放HTTP协议请求部分数据
        byte[] buffer = new byte[2048];
        // 定义一个变量i,代表读取数据到数组中之后,数据量的带下
        int i;
        // 读取数据
        i = is.read(buffer);
        // 遍历字节数组,将数据追加到content变量中
        for(int j=0; j<i; j++){
            content.append((char)buffer[j]);
        }
        // 打印HTTP协议请求部分数据
        System.out.println(content);
        // 截取客户端请求的资源路径demo.html,赋值给url
        parseURL(content.toString());
    }

    // 截取客户端请求的资源路径demo.html,赋值给url
    private 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);
    }

    // 发送静态资源
    public static void sendStaaticResource(OutputStream os) throws IOException {
        byte[] bytes = new byte[2048];
        FileInputStream fis = null;
        try{
            File file = new File(WEB_ROOT, url);
            if(file.exists()){
                os.write("HTTP/1.1 200 OK\n".getBytes());
                os.write("Server:apache-Coyote/1.1\n".getBytes());
                os.write("Content-Type:text/html;charset=utf-8\n".getBytes());
                os.write("\n".getBytes());
                fis = new FileInputStream(file);
                // 读取静态资源的内容到数组中
                int ch = fis.read(bytes);
                while(ch!=-1){
                    os.write(bytes, 0, ch);
                    ch = fis.read(bytes);
                }
            } else {
                os.write("HTTP/1.1 404 OK\n".getBytes());
                os.write("Server:apache-Coyote/1.1\n".getBytes());
                os.write("Content-Type:text/html;charset=utf-8\n".getBytes());
                os.write("\n".getBytes());
                String errorMessage="file not found";
                os.write(errorMessage.getBytes());
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if(fis!=null){
                fis.close();
                fis = null;
            }
        }
    }
}

最后实现的效果如下

自制简易Tomcat服务器

 自制简易Tomcat服务器

自制简易Tomcat服务器

自制简易Tomcat服务器

总结

这段程序我是跟着视频敲的,只是一个超级简化版的Tomcat,有许多不足的地方,但是对于理解Tomcat的运行原理是足够了。希望你能有所收获!

上一篇:os.walk


下一篇:Python小技巧