码迷,mamicode.com
首页 > 其他好文 > 详细

简单的Tomcat实现--1.7多应用支持

时间:2020-08-05 00:03:48      阅读:52      评论:0      收藏:0      [点我收藏+]

标签:之间   cti   getpath   单元   好的   specific   assert   ase   ann   

支持多应用

  • 使用多线程支持多个请求同时访问同一个页面,下面通过建立Context类来支持多个请求同时访问多个应用
  • 设计的思想大概是
    • 建立一个Context类,这个类具有两个字段,一个是应用的路径映射,例如ROOT路径映射到/,等等,在bootstrap中建立一个映射用于保存path和context。当服务启动时,就扫描webapp目录下的所有应用并将其保存。
    • 在Request类中添加Context字段,解析request中传来的uri,通过uri获取path,在通过path获取context,通过context获取真实路径。

Context类

package jerrymice.catalina;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.log.LogFactory;

/**
 * @author :xiaosong
 * @description:
 * 代表一个应用,它有两个属性,一个是path,表示访问的路径,docBase表示在文件系统中得分位置
 * @date :2020/8/4 16:09
 */
public class Context {
    private String path;
    private String docBase;

    public Context(String path, String docBase){
        TimeInterval timeInterval = DateUtil.timer();
        this.path = path;
        this.docBase = docBase;
        LogFactory.get().info("Deploying web application directory {}", this.docBase);
        LogFactory.get().info("Deployment of web application directory {} has finished in {} ms",
                this.docBase, timeInterval.intervalMs());

    }
    public String getPath() {
        return path;
    }

    public String getDocBase() {
        return docBase;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public void setDocBase(String docBase) {
        this.docBase = docBase;
    }
}

Request类添加Context

package jerrymice.http;

import jerrymice.Bootstrap;
import jerrymice.catalina.Context;
import cn.hutool.core.util.StrUtil;
import jerrymice.util.MiniBrowser;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * @author :xiaosong
 * @description:TODO
 * @date :2020/8/3 20:44
 */
public class Request {
    private String requestString;
    private String uri;
    private Socket socket;
    private Context context;

    public Context getContext() {
        return context;
    }

    /**
     * 根据request的uri来解析成Context
     */
    public void parseContext(){

        String path = StrUtil.subBetween(uri, "/", "/");
        if (null == path) {
            // 如果uri = /timeConsume.html,那么path = null, 经过此处之后path=/
            path = "/";
        }
        else {
            // uri = /dir1/1.html, 那么path= dir1, 经过此处之后path=/dir1
            path = "/" + path;
        }
        // 根据获取到的path去扫描得到的映射中去寻找这个文件夹
        context = Bootstrap.contextMap.get(path);
        if (context == null) {
            // 如果没有获取到这个context对象,那么说明目录中根本就没有这个应用,或者本身就在根目录下
            context = Bootstrap.contextMap.get("/");
        }
    }


    /**
     * 构造方法
     */
    public Request(Socket socket) throws IOException {
        this.socket = socket;
        parseHttpRequest();
        if (StrUtil.isEmpty(requestString)){
            return;
        }
        parseUri();
        parseContext();
        // 比如 uri 是 /a/index.html, 获取出来的 Context路径不是 "/”, 那么要修正 uri 为 /index.html
        if (!"/".equals(context.getPath())){
            uri = StrUtil.removePrefix(uri, context.getPath());
        }
    }
    private void parseHttpRequest() throws IOException {
        // 解析Request,服务器端获取浏览器端传过来的请求
        InputStream inputStream = this.socket.getInputStream();
        byte[] bytes = MiniBrowser.readBytes(inputStream);
        this.requestString = new String(bytes, StandardCharsets.UTF_8);
    }
    private void parseUri() {
        // 解析uri,定位服务器上的文件
        String temp;
        /*
        StrUtil.subBetween方法返回before和after之间的子串,不包含before和after
        此处就是获取两个空格之间的内容,如果地址是 http://127.0.0.1:18080/index.html?name=gareen
        那么http请求就会是
        GET /index.html?name=gareen HTTP/1.1
        Host: 127.0.0.1:18080
        Connection: keep-alive
        。。。。
        只需要获取两个空格之间的部分就可以获得请求的uri
         */
        temp = StrUtil.subBetween(requestString, " ", " ");
        if (!StrUtil.contains(temp, ‘?‘)){
            uri = temp;
            return;
        }
        this.uri = StrUtil.subBefore(temp, "?", false);
    }
    public String getUri(){
        return uri;
    }

    public String getRequestString() {
        return requestString;
    }
}

修改Bootstrap

package jerrymice;

import jerrymice.catalina.Context;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.LogFactory;
import cn.hutool.system.SystemUtil;
import jerrymice.http.Request;
import jerrymice.http.Response;
import jerrymice.util.Constant;
import sun.awt.windows.WPrinterJob;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author :xiaosong
 * @description:项目的启动类
 * @date :2020/7/28 20:41
 */
public class Bootstrap {
    /**
     * 定义服务器的端口号
     */
    final static int PORT = 10086;
    public static Map<String, Context> contextMap = new HashMap<>();
    public static void main(String[] args) {
        System.out.println(SystemUtil.get("user.dir"));;
        try {
            // 打印jvm信息
            logJvm();
            LogFactory.get().info("Scanning webapps ...");
            // 扫描文件夹内的所有应用
            scanContextOnWebAppsFolder();
            // 在port端口上新建serverSocket
            ServerSocket serverSocket = new ServerSocket(PORT);
            // 外部使用一个while循环,当处理完一个Socket的链接请求之后,再处理下一个链接请求
            while (true) {
                Socket socket = serverSocket.accept();
                // 使用lambda表达式代替Runnable
                Runnable runnable = () -> {
                    try {
                        // 获取输入流,这个输入流表示的是收到一个浏览器客户端的请求
                        Request request = new Request(socket);

                        System.out.println("浏览器的输入信息: \r\n" + request.getRequestString());
                        Response response = new Response();
                        // 先将html信息写入到response的Writer的StringWriter中
                        String uri;
                        uri = request.getUri();
                        if (uri == null) {
                            return;
                        }
                        Context context = request.getContext();
                        if ("/".equals(uri)) {
                            String html = "Hello JerryMice";
                            response.getWriter().println(html);
                        } else {
                            // removePrefix()方法可以去掉字符串指定的前缀
                            String fileName = StrUtil.removePrefix(uri, "/");
                            File file = FileUtil.file(context.getDocBase(), fileName);
                            if (file.exists()) {
                                //如果文件存在,那就去试图访问
                                String fileContent = FileUtil.readUtf8String(file);
                                // 写入到response中
                                response.getWriter().println(fileContent);
                                // 判断是否是模拟的耗时任务
                                if ("timeConsume.html".equals(fileName)) {
                                    ThreadUtil.sleep(1000);
                                }
                            } else {
                                System.out.println("File not found!");
                            }
                        }
                        System.out.println(uri);
                        // 打开输出流,准备给客户端输出信息
                        handle200(socket, response);
                    } catch (IOException e) {
                        LogFactory.get().error(e);
                    }
                };
                jerrymice.util.ThreadUtil.run(runnable);
            }
        } catch (IOException e) {
            LogFactory.get().error(e);
        }
    }

    private static void logJvm() {
        // 创建一个Map用于保存各种信息
        Map<String, String> infoMap = new LinkedHashMap<>();
        infoMap.put("Server version", "JerryMice 1.0.0");
        infoMap.put("Server build", "2020-08-03");
        infoMap.put("OS:\t", SystemUtil.get("os.name"));
        infoMap.put("OS version", SystemUtil.get("os.version"));
        infoMap.put("Architecture", SystemUtil.get("os.arch"));
        infoMap.put("Java Home", SystemUtil.get("java.home"));
        infoMap.put("JSM Version", SystemUtil.get("java.runtime.version"));
        infoMap.put("JVM Vendor", SystemUtil.get("java.vm.specification.vendor"));
        Set<String> keys = infoMap.keySet();
        for (String key : keys) {
            // 调用hutool的LogFactory工厂函数获取logger,logger会自动根据log4j.properties来对Log4j的Logger进行配置
            LogFactory.get().info(key + ":\t\t" + infoMap.get(key));
        }
    }

    /**
     * @param socket:
     * @param response:Response对象,服务器对浏览器请求的响应,可以通过response的getBody()获取存储在其中的html文本
     * @throws IOException
     */
    private static void handle200(Socket socket, Response response) throws IOException {
        // 获取类型
        String contentType = response.getContentType();
        String headText = Constant.responseHead200;
        headText = StrUtil.format(headText, contentType);
        byte[] head = headText.getBytes();
        // 获取response中的html文本,这个html文本是通过writer写到stringWriter字符流上的
        byte[] body = response.getBody();
        byte[] responseBytes = new byte[head.length + body.length];
        ArrayUtil.copy(head, 0, responseBytes, 0, head.length);
        ArrayUtil.copy(body, 0, responseBytes, head.length, body.length);

        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(responseBytes);
        socket.close();
    }

    /**
     *
     * 扫描webapp的根目录,将所有的文件夹(应用)做成Context对象保存在Map中
     */
    private static void scanContextOnWebAppsFolder(){
        File[] files = Constant.webappsFolder.listFiles();
        if (files == null){
            // 如果应用目录下根本没有应用,那就直接再见报告错误日志
            LogFactory.get().error(new NoSuchFieldError());
            return;
        }
        for (File file : files){
            if (!file.isDirectory()) {
                continue;
            }
            loadContext(file);
        }
    }
    private static void loadContext(File folder) {
        // 对文件夹中的文件进行解析, 获取文件夹名
        String path = folder.getName();
        if ("ROOT".equals(path)) {
            // 如果是根目录的话
            path = "/";
        }
        else {
            path = "/" + path;
        }
        String docBase = folder.getAbsolutePath();
        // 建立Context对象用于保存path和docBase
        Context context = new Context(path, docBase);
        // 将创建好的context放在Map中留待使用
        contextMap.put(context.getPath(), context);
    }
}

单元测试

    @Test
    public void testContext(){
        String html = getContentString("/dir1/1.html");
        Assert.assertEquals(html, "this file in webapps/dir1/");
    }

简单的Tomcat实现--1.7多应用支持

标签:之间   cti   getpath   单元   好的   specific   assert   ase   ann   

原文地址:https://www.cnblogs.com/xsliu/p/13435732.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!