大飞带你深入理解Tomcat(二)

作者:叩丁狼教育王一飞,高级讲师。转载请注明出处。

接上一篇,通过HttpServer,Request,Response个类的配合勉强可以处理浏览器发起的请求跟响应请求。但功能有点寒酸,只可以处理静态网页和404,本篇加入对servlet的支持,注意,仅仅是对servlet的简单支持。

servlet回顾:
servlet是java web一个组件,是java动态网页的基石,使用相对简单,想深入学习的朋友可以腾讯课堂看任小龙老师传送门:java大神之路java大神之路,这里不累赘了,就回顾下servlet用法:

public class MyServlet implements Servlet{
    public MyServlet() {
        System.out.println("创建....");
    }
    public void init(ServletConfig config) throws ServletException {
        System.out.println("初始化....");
    }
    public void service(ServletRequest req, ServletResponse resp) 
                throws ServletException, IOException {
        System.out.println("服务....");
    }
    public void destroy() {
        System.out.println("销毁....");
    }
    public ServletConfig getServletConfig() {
        return null;
    }
    public String getServletInfo() {
        return null;
    }
}

tomcat启动后, 发起第一请求时,servlet执行顺序
创建(构造器)----初始化(init)---[服务(service)] 循环----销毁(destroy)
非第一次发起请求,直接调用serivce方法重复执行。
好,回顾到这,下面进入主题。
代码结构:

UML类图(借用书中类图):


本篇代码类图

相对上篇代码做改进:

0:创建一个常量类Consts, 持有项目中所有的静态常量
/**
 * 常量类
 */
public class Consts {
    // tomcat项目绝对路径, 所有web项目都丢在webapps目录下
    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webapps";
    
    //请求404响应内容
    public static final String RESPONSE_404_CONTENT = "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

"; //请求响应成功响应头 public static final String RESPONSE_200_HEADER = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "Content-Length: #{count}\r\n" + "\r\n"; }
1:Request遵循serlvet规范,实现ServletRequest接口
/**
 * 请求信息封装对象
 */
public class Request implements ServletRequest{
    // 浏览器socket连接的读流
    private InputStream in;
    //请求行信息信息中的uri
    private String uri;
    public Request(InputStream in) {
        this.in = in;
    }
    // 解析浏览器发起的请求
    public void parseRequest() {
        // 暂时忽略文件上传的请求,假设都字符型请求
        byte[] buff = new byte[2048];
        StringBuffer sb = new StringBuffer(2048);
        int len = 0;
        //请求内容
        try {
            len = in.read(buff);
            sb.append(new String(buff, 0, len));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.print(sb.toString());
        //解析请求行中uri信息
        uri = this.parseUri(sb.toString());
    }
    public String parseUri(String httpContent) {
        //传入的内容解析第一行的请求行即可:
        //请求行格式:  请求方式   请求uri 协议版本     3个内容以空格隔开
        int beginIndex = httpContent.indexOf(" ");
        int endIndex;
        if(beginIndex > -1) {
            endIndex = httpContent.indexOf(" ", beginIndex + 1);
            if(endIndex > beginIndex) {
                return httpContent.substring(beginIndex, endIndex).trim();
            }
        }
        return null;
    }
    public String getUri() {
        return uri;
    }
    
    /**省略一堆目前暂时没用到的ServletRequest需要实现的方法*/
    public Object getAttribute(String name) {
        return null;
    }
    //-------------------------------------------------

实现ServletRequest接口,需要重写的方法一概不动,空实现。

2:Response遵循servlet规范,实现ServletResponse接口
/**
 * 处理响应请求对象
 */
public class Response implements ServletResponse{
    // 浏览器socket连接的写流
    private OutputStream out;
    
    public Response(OutputStream out) {
        this.out = out;
    }
    //跳转
    public void sendRedirect(String uri) {
        File webPage = new File(Consts.WEB_ROOT, uri);
        FileInputStream fis = null;
        StringBuffer sb = new StringBuffer();
        try {
            //找得到页面是
            if(webPage.exists()&& webPage.isFile()) {
                fis = new FileInputStream(webPage);
                byte[] buff = new byte[2048];
                int len = 0;
                while( (len = fis.read(buff))!= -1) {
                    sb.append(new String(buff, 0, len));
                }
                String respHeader=Consts.RESPONSE_200_HEADER.replace("#{count}", sb.length()+"");
                System.out.println(respHeader + sb);
                out.write((respHeader + sb).getBytes());
                
            }else {
                 //页面找不到时
                out.write(Consts.RESPONSE_404_CONTENT.getBytes());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    //重写getWriter方法
    public PrintWriter getWriter() throws IOException {
        PrintWriter writer = new PrintWriter(out, true);
                //设置响应头,后续还会修改
        writer.println(Consts.RESPONSE_200_HEADER);
        return writer;
    }
    //--------------------------

仅仅重写getWriter方法,其他方法空实现

3:响应处理分离:处理静态请求使用staticSourceProcessor类,处理servlet使用ServletProcessor类,判断依据uri中使用servlet字样
/**
 * 用于响应静态文件请求
 */
public class StaticSourceProcessor {

    public void process(Request request, Response response) {
        response.sendRedircet(request.getUri());
    }
    
}

servlet类的处理有点麻烦,原因:servlet摆放在约定好的webapp目录下,项目使用时,需要额外加载自定义的servlet的字节码到内存。

public class ServletProcessor {
    public void process(Request request, Response response) {
        String uri = request.getUri();
        //从uri中获取serlvet名称
        String servletName = uri.substring(uri.lastIndexOf("/")+1);
        try {
            //加载classpath路径,默认使用webapps
            //此处设一个限制,约束自定义的serlvet必须没有包名,没有为什么,demo就不要那么多要求
            File classPath = new File(Consts.WEB_ROOT);
            //转换成url能识别的路径, 简单讲加上file协议
            String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
            URL url = new URL(repository);
            //创建一个url类加载器,用于加载上面指定serlvetName的servlet类
            URLClassLoader loader = new URLClassLoader(new URL[] {url});
            //通过反射创建servlet类对象
            Class myClass = loader.loadClass(servletName);
            Servlet servlet= (Servlet) myClass.newInstance();
            //使用servlet调用service方法,servlet处理完成
            servlet.service(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:这里做2个约定, 1:自定义的serlvet必须没有包名,直接放在src目录里。 2:将编译后的自定义字节码拷贝到webapps中等价于部署。

4:HttpServer类改造,实现响应分离
/**
 * 模拟tomcat的核心类
 */
public class HttpServer {
    // 模拟tomcat关闭命令
    private static final String SHUTDOWN_CMD = "/SHUTDOWN";
    private boolean shutdown = false;
    //持续监听端口
    @SuppressWarnings("resource")
    public void accept() {
        ServerSocket serverSocket = null;
        try {
            // 启动socket服务, 监听8080端口,
            serverSocket =  new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("启动myTomcat服务器失败:" + e.getMessage(), e);
        }
        // 没接收到关闭命令前一直监听
        while (!shutdown) {
            Socket socket = null;
            InputStream in = null;
            OutputStream out = null;
            try {
                // 接收请求
                socket = serverSocket.accept();
                in = socket.getInputStream();
                out = socket.getOutputStream();
                // 将浏览器发送的请求信息封装成请求对象
                Request request = new Request(in);
                request.parseRequest();
                // 将相应信息封装相应对象
                Response response = new Response(out);
                
                //实现约定:servlet请求路径必须以/servlet开头,以servlet简单类名结束
                if(request.getUri().startsWith("/servlet")) {
                    ServletProcessor processor = new ServletProcessor();
                    processor.process(request, response);
                }else {
                    //此处简单响应一个静态资源文件
                    StaticSourceProcessor processor = new StaticSourceProcessor();
                    processor.process(request, response);
                }
                socket.close();
                //如果是使用关闭命令,停止监听退出
                shutdown = request.getUri().equals(SHUTDOWN_CMD);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }
    public static void main(String[] args) {
        new HttpServer().accept();
    }
}
5:添加自定义的servlet类

HelloServlet:
在MyTomcat项目中加入servlet-api.jar依赖包,自定义HelloServlet实现Servlet接口,同时实现service方法,用于响应浏览器发送的请求。

public class HelloServlet implements Servlet {
    public void init(ServletConfig arg0) throws ServletException {}
    //重写service方法,响应请求
    public void service(ServletRequest req, ServletResponse resp)
            throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("hello, servlet....");
    }
    public void destroy() {}
    public ServletConfig getServletConfig() {return null;   }
    public String getServletInfo() {return null;}
}

再次强调, 这个类没有包(package),编译之后,应该将字节码放到webapps目录下,否则报类找不到异常。

6:测试

运行HttpServer类,
在浏览器中输入:http://localhost:8080/hello/index.html 进入静态资源处理

静态资源

在浏览器中输入:http://localhost:8080/servlet/HelloServlet 进入静态资源处理

servlet

大飞带你深入理解Tomcat(二)_第1张图片
WechatIMG9.jpeg

你可能感兴趣的:(大飞带你深入理解Tomcat(二))