大飞带你深入理解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" +
              "<h1>File Not Found</h1>";
    
    //请求响应成功响应头
    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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,358评论 1 92
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 燕子去了,有再来的时候;杨柳枯了,有再青的时候;桃花谢了,有再开的时候。但是,聪明的,你告诉我,我们的日子为什么一...
    小女无敌阅读 274评论 0 2
  • 她真的怀孕了!!!一阵恶心感又上来了…… 顾惜玖漫步跑向卫生间处理完孕吐的恶心感,忙在水池边,用凉水冲...
    帝尊025阅读 300评论 0 1