手写tomcat

1.前言

对于JavaEE的初学者来说,大家学完JAVASE之后,马上进入了WEB阶段的学习。大家在JAVASE阶段写代码时,从头到尾都是自己写的,到了WEB阶段,尤其是进入Servlet的学习阶段,有人会感到困惑,怎么我写的代码看不到main函数了,服务端的Servlet是个什么东东呢?tomcat服务器到底底层做了哪些事情呢?为了帮助大家更好的理解tomcat服务器,也为了帮助大家更好的步入WEB的学习,我们特意安排了本次课程,动手实现Tomcat。我们自己手动的方式搭建一个Tomcat,让大家能够顺利的从JAVASE切换到JAVAWEB阶段的学习。

我们要实现一个Tomcat需要一些技术铺垫,这些技术分别是http协议和JavaSe的Socket编程。我们按照以下的顺序进行我们本次课程的学习。

1)HTTP协议

2)JavaSE的Socket编程

3)动手搭建Tomcat服务器

2.HTTP协议

2.1.HTTP协议概述

2.1.1.什么是协议

         协议就是一组约定规则

2.1.2.网络协议

         网络协议就是数据在网络上的传输规则,例如http,pop3,pop,imap,ftp,流媒体协议等。

2.1.3.HTTP协议

         htyper text transform protocol

         超文本传输协议:如何在互联网上传输超文本

HTML:超文本标记语言 htyper text markup language

2.1.4.HTTP协议格式

         由于我们平时访问互联网上的网页都是请求之后才响应。所以HTTP协议基于请求-响应模型。协议分为请求部分,响应部分

2.1.5.HTTP协议分为请求部分和响应部分

         请求部分格式:请求行 请求头 请求体

         响应部分格式:响应行 响应头 响应体

2.2.请求部分(请求行/请求头/请求体)

POST  /XXX/XXX/XXX.png  http/1.1

k1:v1

k2:v2

k3:v3

k4:v4

Username=tom&password=1234

2.2.1.请求行

         格式:请求方式  本次请求路径  协议/版本

         POST  /index.html  http/1.1(换行)**

         1)请求方式(POST/GET/PUT/DELETE/HEAD/OPTION/TRANCE, 一共设计7个方法)

         2)本次请求哪些内容

         3)本次请求采用的协议以及协议版本

2.2.2.请求头

         作用:1)告诉服务器对客户端描述

                     2)对本次请求描述

         格式:

         K1:v1(换行)

K2:v2(换行)

K3:v3(换行)

空行

2.2.3.观察HTTP协议请求头

         火狐浏览器下访问百度图片。

https://ss0.bdstatic.com/5aV1bjqh.Q23odCf/static/superman/img/logo/bd.logo1.31bdc765.png](https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png

         利用火狐===>工具===>web开发者===>网络 观察本次请求过程

Host:"ss0.bdstatic.com"

User-Agent:"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0"

Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"

Accept-Language:"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"

Accept-Encoding:"gzip, deflate, br"

Connection:"keep-alive"

1)Host: 本次请求的主机路径

2)User-Agent:告诉服务端本次请求客户端所在的平台以及本次请求采用的浏览器

3)Accept:用于指定客户端接受哪些类型的信息.eg:Accept:image/gif,表明客户端希望接受GIF图象格式的资源;Accept:text/html,表明客户端希望接受html文本.

4)Accept-Language:告诉服务端,浏览器可以识别的语言种类

5)Accept-Encoding:告诉服务端,浏览器可以哪些类型压缩格式数据 gzip,defalte

2.2.4.请求体(约定用户的表单数据向服务端传递格式)

  

  

  

         上面的表单中,当我们录入数据之后,点击提交按钮,数据以下述方式进行提交。

GET  /aaa?username=tom&pasword=123  http/1.1

请求头

请求头

请求头

请求头

空行

  

  

  

         上面的表单中,当我们录入数据之后,点击提交按钮,数据以下述方式进行提交。

POST /aaa http/1.1

请求头

请求头

请求头

请求头

username=tom&pasword=123

         请求体的作用:存放客户端向服务端传递的数据

2.3.响应部分(响应行/响应头/响应体)

         **[响应部分格式]**

HTTP/1.1  200  OK(换行)

K1:v1(换行)

K2:v2(换行)

K3:v3(换行)

空行

  XXXX.....

2.3.1.响应行

HTTP/1.1  200  OK(回车换行)

1)本次响应采用的协议

2)状态码:协议设计之初,用一些数字描述了本次响应

3)状态描述:用文字本次响应进行简短描述

状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值

11xx: 指示信息表示请求已接收,继续处理

22xx: 成功--表示请求已被成功接收,理解,接受

33xx: 重定向--要完成请求必须进行更进一步操作

44xx: 客户端错误请求有语法错误或请求无法实现

55xx: 服务器错误服务器未能实现合法的请求

常见状态代码,状态描述说明:

200   OK         //客户端请求成功

404   Not Found    //请求资源不存在,eg:输入了错误的URL"

2.3.2.响应头

         作用:

                   1)服务端告诉浏览器服务端信息

                   2)对本次响应描述

         格式:

K1:v1

K2:v2

K3:v3

空行

响应体

2.3.3.分析响应头

Date: Fri, 21 Apr 2017 02:04:54 GMT

Content-Type: text/html

Connection: keep-alive

Content-Encoding:gzip

Content-length

Server

1)Date:响应时间

2)content-Type:本次响应内容类型

3)content-Encoding:本次内容采用的压缩格式

4)content-length:本次内容长度

5)server:服务端采用的服务器类型

2.3.4.响应体

         服务端响应到客户端部分,可能是HTML页面,JS文件,CSS文件,图片。

3.Socket编程

关于Socket的基础知识我们不在赘述,Socket属于JAVASE的基础API。我们主要结合2段程序来验证HTTP协议传输规则。

3.1.案例1

3.1.1.案例需求

         模拟浏览器向服务器端发起HTTP请求,接受来自服务器端响应回客户端的数据。

3.1.2.图形分析

3.1.3.代码实现

package com.me.mytomcat.mytomcatv1;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.Socket;

public class TestClient {

    public static void main(String[] args) throws IOException {

        Socket s = null;

        OutputStream ops = null;

        InputStream is = null;

       

        try {

            s = new Socket("www.baidu.com", 80);

            ops = s.getOutputStream();

            ops.write("GET /subject/about/index.html HTTP/1.1\n".getBytes());

            ops.write("HOST:www.baidu.com\n".getBytes());

            ops.write("\n".getBytes());

            is = s.getInputStream();

            int i = is.read();

            while(i != -1) {

                System.out.print((char)i);

                i = is.read();

            }

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            if(null != is) {

                is.close();

                is = null;

            }

            if(null!=ops) {

                ops.close();

                ops=null;

            }          

            if(null!=s) {

                s.close();

                s=null;

            }      

        }

    }

   

}

3.1.4.运行结果

         如果我们通过浏览器访问对应的网址,之后再页面中右击...>查看页面源代码,HTML页面源码中的内容和我们响应到客户端的响应体部分是一致的。(案例中使用的网址本身访问会是404,所以此代码运行后的结果也是404

3.2.案例2

3.2.1.案例需求

从浏览器的地址栏输入localhost:8080 ,向本机的8080端口发起请求,服务端向客户端响应回去HTTP协议的响应部分

3.2.2.图形分析

3.2.3.代码实现

package com.me.mytomcat.mytomcatv2;

import java.io.IOException;

import java.io.OutputStream;

import java.net.ServerSocket;

import java.net.Socket;

public class TestServer {

    // localhost:8080

    public static void main(String[] args) throws IOException {

        ServerSocket ss = null;

        Socket socket = null;

        OutputStream ops = null;

       

        try {

            ss = new ServerSocket(8080);

            while(true) {

                socket = ss.accept();

                ops = socket.getOutputStream();

                ops.write("HTTP/1.1 200 ok\n".getBytes());

                ops.write("Content-Type:text/html;charset-utf-8\n".getBytes());

                ops.write("Server:Apache-Coyote/1.1\n".getBytes());

                ops.write("\n\n".getBytes());

                StringBuffer buffer = new StringBuffer();

                buffer.append("");

                buffer.append("");

                buffer.append(""</span><span style="color:#000000;">);</span></p> <p style="margin-left:.0001pt;text-align:left;">                <span style="color:#6a3e3e;">buffer</span><span style="color:#000000;">.append(</span><span style="color:#2a00ff;">"</span><span style="color:#2a00ff;">我是标题</span><span style="color:#2a00ff;">"</span><span style="color:#000000;">);</span></p> <p style="margin-left:.0001pt;text-align:left;">                <span style="color:#6a3e3e;">buffer</span><span style="color:#000000;">.append(</span><span style="color:#2a00ff;">"");

                buffer.append("");

                buffer.append("");

                buffer.append("

I am header

");

                buffer.append("www.baidu.com");

                buffer.append("");

                buffer.append("");

                ops.write(buffer.toString().getBytes());

                ops.flush();

            }

        } catch (Exception e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } finally {

            if(null != ops) {

                ops.close();

                ops = null;

            }

            if(null != socket) {

                socket.close();

                socket = null;

            }

        }

    }

   

}

3.2.4.运行结果

4.实现Tomcat版本1

4.1.案例需求

         1)在WebContent下发布静态资源demo.html, demo02.html

         2)启动Tomcat服务器

         3)当客户端对服务端发起不同的请求:localhost:8080/demo.html

         4)服务端可以将对应的html页面响应到客户端

4.2.图形分析

4.3.代码实现

4.3.1.实现服务端的准备工作

         1)定义静态变量WEB_ROOT用于存放WebContent目录的绝对路径

         2)定义静态变量url,存放本次请求服务端的静态资源名称

package com.me.mytomcat.mytomcatv3;

public class TestServer {

    // 获取到项目下的WebContent目录

    public static final String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";

   

    // 代码客户端要请求的资源的路径

    public static String url = "";

   

    public static void main(String[] args) {

       

    }

}

4.3.2.实现启动服务端代码

         1)创建SercerSocket对象,监听本机8080端口

         2)等待来自客户端的请求

package com.me.mytomcat.mytomcatv3;

import java.net.ServerSocket;

import java.net.Socket;

public class TestServer {

    // 获取到项目下的WebContent目录

    public static final String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";

   

    // 代码客户端要请求的资源的路径

    public static String url = "";

   

    public static void main(String[] args) {

        try {

            // 建立本机器的ServerSocket,监听本机器的8080端口

            ServerSocket serverSocket = new ServerSocket(8080);

            while(true) {

                Socket socket = serverSocket.accept();

            }

        } catch(Exception e) {

            e.printStackTrace();

        }

    }

}

4.3.3.实现服务端解析HTTP请求部分

         1)读取HTTP协议请求部分数据

         2)解析请求行,获取本次请求的资源路径

package com.me.mytomcat.mytomcatv3;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.ServerSocket;

import java.net.Socket;

public class TestServer {

    // 获取到项目下的WebContent目录

    public static final String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";

   

    // 代码客户端要请求的资源的路径

    public static String url = "";

   

    public static void main(String[] args) {

        try {

            // 建立本机器的ServerSocket,监听本机器的8080端口

            ServerSocket serverSocket = new ServerSocket(8080);

            while(true) {

                Socket socket = serverSocket.accept();

               

                // 服务端的输入流

                InputStream inputStream = socket.getInputStream();

                // 服务端的输出流

                OutputStream outputStream = socket.getOutputStream();

               

                //获取HTTP协议的请求部分,截取客户端要访问的资源名称,

                //将这个资源名称赋值给url

                parse(inputStream);

            }

        } catch(Exception e) {

            e.printStackTrace();

        }

    }

   

    public static void parse(InputStream input) throws IOException {

        StringBuffer content = new StringBuffer(2048);

        int i;

       

        byte[] buffer = new byte[2048];

        // 读取客户端发送过来的数据,将数据读取到字节数组buffer.

        //i代表读取数据量的大小 311字节

        i = input.read(buffer);

        System.out.println(buffer);

       

        for(int j = 0; j < i; j++) {

            content.append((char)buffer[j]);

        }

        // 打印读取到的内容

        System.out.print(content.toString());

       

        // 截取客户端要请求的资源路径,demo.html

        String uri = parseUri(content.toString());

       

        // 赋值给本类中静态成员

        url = uri;

        System.out.println("uri..............:"+uri);

    }

   

    /**

     * 截取客户端请求资源的路径 + 名称

     * @param requestString

     * @return

     */

    public static String parseUri(String requestString) {

        int index1, index2;

        index1 = requestString.indexOf(' ');

        if(index1 != -1) {

            index2 = requestString.indexOf(' ', index1 + 1);

            if (index2 > index1)

                return requestString.substring(index1+2, index2);

        }

        return null;

    }

}

4.3.4.实现服务端向客户端发送HTTP协议响应部分

         1)通过输入流读取静态资源到服务器内存

         2)将HTTP协议响应部分到客户端

package com.me.mytomcat.mytomcatv3;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.ServerSocket;

import java.net.Socket;

public class TestServer {

    // 获取到项目下的WebContent目录

    public static final String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";

   

    // 代码客户端要请求的资源的路径

    public static String url = "";

   

    public static void main(String[] args) {

        try {

            // 建立本机器的ServerSocket,监听本机器的8080端口

            ServerSocket serverSocket = new ServerSocket(8080);

            while(true) {

                Socket socket = serverSocket.accept();

               

                // 服务端的输入流

                InputStream inputStream = socket.getInputStream();

                // 服务端的输出流

                OutputStream outputStream = socket.getOutputStream();

               

                //获取HTTP协议的请求部分,截取客户端要访问的资源名称,

                //将这个资源名称赋值给url

                parse(inputStream);

               

                // 响应数据

                sendStaticResource(outputStream);

            }

        } catch(Exception e) {

            e.printStackTrace();

        }

    }

   

    public static void parse(InputStream input) throws IOException {

        StringBuffer content = new StringBuffer(2048);

        int i;

       

        byte[] buffer = new byte[2048];

        // 读取客户端发送过来的数据,将数据读取到字节数组buffer中.

        //i代表读取数据量的大小 311字节

        i = input.read(buffer);

        System.out.println(buffer);

       

        for(int j = 0; j < i; j++) {

            content.append((char)buffer[j]);

        }

        // 打印读取到的内容

        System.out.print(content.toString());

       

        // 截取客户端要请求的资源路径,demo.html

        String uri = parseUri(content.toString());

       

        // 赋值给本类中静态成员

        url = uri;

        System.out.println("uri..............:"+uri);

    }

   

    /**

     * 截取客户端请求资源的路径 + 名称

     * @param requestString

     * @return

     */

    public static String parseUri(String requestString) {

        int index1, index2;

        index1 = requestString.indexOf(' ');

        if(index1 != -1) {

            index2 = requestString.indexOf(' ', index1 + 1);

            if (index2 > index1)

                return requestString.substring(index1+2, index2);

        }

        return null;

    }

   

    public static void sendStaticResource(OutputStream output) throws IOException {

        byte[] bytes = new byte[2048];

        FileInputStream fis = null;

       

        try {

            File file = new File(TestServer.WEB_ROOT, url);

            if(file.exists()) {

                output.write("HTTP/1.1 200 OK\n".getBytes());

                output.write("Server: Apache-Coyote/1.1\n".getBytes());

                output.write("Content-Type: text/html;charset=UTF-8\n"

                        .getBytes());

                output.write("\n".getBytes());

               

                fis = new FileInputStream(file);

                int ch = fis.read(bytes);

                while(ch != -1) {

                    output.write(bytes, 0, ch);

                    ch = fis.read(bytes);

                }

            } else {

                // file not found

                String errorMessage = "HTTP/1.1 404 File Not Found\r\n"

                        + "Content-Type: text/html\r\n"

                        + "Content-Length: 23\r\n" + "\r\n"

                        + "

File Not Found

";

                output.write(errorMessage.getBytes());

            }

        } catch(Exception e) {

            // thrown if cannot instantiate a File object

            e.printStackTrace();

            System.out.println(e.toString());

        } finally {

            if(fis != null) {

                fis.close();

            }

        }

    }

}

5.实现Tomcat版本2

5.1.案例需求

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

5.2.图形分析

5.3.代码实现

5.3.1.定义一个Servlet

         该接口是所有在服务端运行的JAVA小程序都要遵循的接口。

package com.me.mytomcat.mytomcatv4;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

public interface Servlet {

    public void init();

    public void service(InputStream is,OutputStream ops)throws IOException;

    public void destroy();

   

}

5.3.2.实现服务端的Java小程序

         1)AAServlet.java

package com.me.mytomcat.mytomcatv4;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

public class AAServlet implements Servlet {

    @Override

    public void init() {

        System.out.println("aa...init.....");

    }

    @Override

    public void service(InputStream is, OutputStream ops) throws IOException {

        System.out.println("AA service...");

        ops.write("I am from Server...AA".getBytes());

        ops.flush();

    }

    @Override

    public void destroy() {

        System.out.println("aa...destroy.....");

    }

}

         2)BBServlet.java

package com.me.mytomcat.mytomcatv4;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

public class BBServlet implements Servlet {

    @Override

    public void init() {

        System.out.println("bb...init.....");

    }

    @Override

    public void service(InputStream is, OutputStream ops) throws IOException {

        System.out.println("BB service...");

        ops.write("I am from Server...BB".getBytes());

        ops.flush();

    }

    @Override

    public void destroy() {

        System.out.println("bb...destroy.....");

    }

}

5.3.4.为服务端的2端JAVA小程序配置参数

         conf.properties

aa=com.me.mytomcat.mytomcatv4.AAServlet

bb=com.me.mytomcat.mytomcatv4.BBServlet

5.3.5.服务器启动就读取配置参数

         在TestServer中定义一个静态类型的MAP,用于存放配置文件中的参数信息:

public static Map map = new HashMap();

………

public static void readConf() throws Exception {

        Properties properties = new Properties();

        properties.load(new FileInputStream(TestServer.WEB_ROOT+"\\conf.properties"));

       

        Set keySet = properties.keySet();

        Iterator iterator = keySet.iterator();

        while(iterator.hasNext()) {

            String key=(String)iterator.next();

            String value = properties.getProperty(key);

            map.put(key, value);

        }

    }

5.3.6.在版本一的基础上实现静态动态资源的响应

    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 ss = (Servlet)clazz.newInstance();

            ss.service(is, ops);

        }

    }

6.总结

通过手动实现一个Tomcat,我们可以发现运行在服务端的JAVA小程序AAServlet或者是BBServlet本质上还是一段JAVA小程序,只不过我们只需要按照约定实现Servlet这个接口,只需要做好对应的配置信息,那么我们就可以通过浏览器向服务端发送请求,让服务端的这个JAVA小程序也就是AAServlet或者BBServlet小程序进行执行。

你可能感兴趣的:(tomcat,java,服务器)